# HG changeset patch # User Robert McIntyre # Date 1335187583 18000 # Node ID fbccf46cf34d14cd76dd6f02a25e1eb5b27da1b4 # Parent c03f28aa98d90c157de8925c509fdc02c748f484 sucessfully played third-kind. diff -r c03f28aa98d9 -r fbccf46cf34d clojure/com/aurellem/run/music.clj --- a/clojure/com/aurellem/run/music.clj Mon Apr 23 07:02:39 2012 -0500 +++ b/clojure/com/aurellem/run/music.clj Mon Apr 23 08:26:23 2012 -0500 @@ -8,12 +8,87 @@ (:import [com.aurellem.gb.gb_driver SaveState]) (:import java.io.File)) +(def third-kind + (File. "/home/r/proj/midi/third-kind.mid")) +(defn raw-midi-text [#^File midi-file] + (:out + (clojure.java.shell/sh + "midicsv" + (.getCanonicalPath midi-file) + "-"))) + +(def command-line #"^(\d+), (\d+), ([^,]+)(.*)$") + +(defmulti parse-command :command) + +(defn discard-args [command] (dissoc command :args)) + +(defmethod parse-command :Start_track + [command] (discard-args command)) + +(defmethod parse-command :End_track + [command] (discard-args command)) + +(defmethod parse-command :default + [command] command) + +(defn parse-number-list + [number-list-str] + (map #(Integer/parseInt %) + (clojure.string/split number-list-str #", "))) + +(defmethod parse-command :Tempo + [command] + (update-in command [:args] #(Integer/parseInt %))) + +(defn parse-midi-note-list + [midi-note-list-str] + (let [[channel note velocity] + (parse-number-list midi-note-list-str)] + {:channel channel :note note :velocity velocity})) + +(defmethod parse-command :Note_on_c + [command] + (update-in command [:args] parse-midi-note-list)) + +(defmethod parse-command :Note_off_c + [command] + (update-in command [:args] parse-midi-note-list)) + +(defmethod parse-command :Header + [command] + (let [args (:args command) + [format num-tracks division] (parse-number-list args)] + (assoc command :args + {:format format + :num-tracks num-tracks + :division division}))) + +(defmethod parse-command :Program_c + [command] + (let [args (:args command) + [channel program-num] (parse-number-list args)] + (assoc command :args + {:channel channel + :program-num program-num}))) + +(defn parse-midi [#^File midi-file] + (map + (comp parse-command + (fn [line] + (let [[[_ channel time command args]] + (re-seq command-line line)] + {:channel (Integer/parseInt channel) + :time (Integer/parseInt time) + :command (keyword command) + :args (apply str (drop 2 args))}))) + (drop-last + (clojure.string/split-lines + (raw-midi-text midi-file))))) + (def music-base new-kernel) - - - (defn store [n address] (flatten [0xF5 @@ -33,11 +108,8 @@ (defn infinite-loop [] [0x18 0xFE]) - - (def divider-register 0xFF04) - (defrecord Bit-Note [frequency volume duration duty]) (defn clear-music-registers [] @@ -92,7 +164,6 @@ (def note-code 0x00) (def change-duty-code 0x01) (def silence-code 0x02) - (defn do-message "Read the message which starts at the current value of HL and do @@ -250,6 +321,67 @@ low-frequency duration])) +(defn midi-code->frequency + [midi-code] + (* 8.1757989156 + (Math/pow 2 (* (float (/ 12)) midi-code)))) + +;; division == clock-pulses / quarter-note +;; tempo == microseconds / quarter-note + +;; have: clock-pulses +;; want: seconds + + + + +(defn midi->mini-midi [#^File midi-file] + (let [midi-events (parse-midi midi-file) + + note-on-events + (filter #(= :Note_on_c (:command %)) midi-events) + note-off-events + (filter #(= :Note_off_c (:command %)) midi-events) + + channel-1-on + (sort-by :time + (filter #(= 1 (:channel (:args %))) + note-on-events)) + channel-1-off + (sort-by :time + (filter #(= 1 (:channel (:args %))) + note-off-events)) + + + tempo (:args (first (filter #(= :Tempo (:command %)) midi-events))) + division (:division + (:args (first (filter #(= :Header (:command %)) midi-events)))) + ] + + (map + (fn [note-event] + (note-codes (:frequency note-event) + (:volume note-event) + (int (* (:duration note-event) 0x100)))) + + (map + (fn [note-on note-off] + {:frequency (midi-code->frequency (:note (:args note-on))) + :duration + (/ (* (/ tempo division) + (- (:time note-off) (:time note-on))) + 1e6) ;; convert clock-pulses into seconds + :volume (int (/ (:velocity (:args note-on)) 10)) + :time-stamp (/ (* (/ tempo division) + (:time note-on)) 1e6)}) + channel-1-on channel-1-off)))) + + + + + + + (def C4 (partial note-codes 261.63)) (def D4 (partial note-codes 293.66)) (def E4 (partial note-codes 329.63)) @@ -314,85 +446,4 @@ [0xF0 0x05])])) -(def third-kind - (File. "/home/r/proj/midi/third-kind.mid")) -(defn raw-midi-text [#^File midi-file] - (:out - (clojure.java.shell/sh - "midicsv" - (.getCanonicalPath midi-file) - "-"))) - -(def command-line #"^(\d+), (\d+), ([^,]+)(.*)$") - -(defmulti parse-command :command) - -(defn discard-args [command] (dissoc command :args)) - -(defmethod parse-command :Start_track - [command] (discard-args command)) - -(defmethod parse-command :End_track - [command] (discard-args command)) - -(defmethod parse-command :default - [command] command) - -(defn parse-number-list - [number-list-str] - (map #(Integer/parseInt %) - (clojure.string/split number-list-str #", "))) - -(defmethod parse-command :Tempo - [command] - (update-in command [:args] #(Integer/parseInt %))) - -(defn parse-midi-note-list - [midi-note-list-str] - (let [[channel note velocity] - (parse-number-list midi-note-list-str)] - {:channel channel :note note :velocity velocity})) - - -(defmethod parse-command :Note_on_c - [command] - (update-in command [:args] parse-midi-note-list)) - -(defmethod parse-command :Note_off_c - [command] - (update-in command [:args] parse-midi-note-list)) - -(defmethod parse-command :Header - [command] - (let [args (:args command) - [format num-tracks division] (parse-number-list args)] - (assoc command :args - {:format format - :num-tracks num-tracks - :division division}))) - -(defmethod parse-command :Program_c - [command] - (let [args (:args command) - [channel program-num] (parse-number-list args)] - (assoc command :args - {:channel channel - :program-num program-num}))) - - -(defn parse-midi [#^File midi-file] - (map - (comp parse-command - (fn [line] - (let [[[_ channel time command args]] - (re-seq command-line line)] - ;;(println (re-seq command-parse-1 line)) - {:channel (Integer/parseInt channel) - :time (Integer/parseInt time) - :command (keyword command) - :args (apply str (drop 2 args))}))) - (drop-last - (clojure.string/split-lines - (raw-midi-text midi-file))))) -