diff clojure/com/aurellem/run/music.clj @ 427:fbccf46cf34d

sucessfully played third-kind.
author Robert McIntyre <rlm@mit.edu>
date Mon, 23 Apr 2012 08:26:23 -0500
parents c03f28aa98d9
children 476f7da175a4
line wrap: on
line diff
     1.1 --- a/clojure/com/aurellem/run/music.clj	Mon Apr 23 07:02:39 2012 -0500
     1.2 +++ b/clojure/com/aurellem/run/music.clj	Mon Apr 23 08:26:23 2012 -0500
     1.3 @@ -8,12 +8,87 @@
     1.4    (:import [com.aurellem.gb.gb_driver SaveState])
     1.5    (:import java.io.File))
     1.6  
     1.7 +(def third-kind
     1.8 +  (File. "/home/r/proj/midi/third-kind.mid"))
     1.9  
    1.10 +(defn raw-midi-text [#^File midi-file]
    1.11 +  (:out 
    1.12 +   (clojure.java.shell/sh
    1.13 +    "midicsv"
    1.14 +    (.getCanonicalPath midi-file)
    1.15 +    "-")))
    1.16 +
    1.17 +(def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
    1.18 +
    1.19 +(defmulti parse-command :command)
    1.20 +
    1.21 +(defn discard-args [command] (dissoc command :args))
    1.22 +
    1.23 +(defmethod parse-command :Start_track
    1.24 +  [command] (discard-args command))
    1.25 +
    1.26 +(defmethod parse-command :End_track
    1.27 +  [command] (discard-args command))
    1.28 +
    1.29 +(defmethod parse-command :default
    1.30 +  [command] command)
    1.31 +
    1.32 +(defn parse-number-list
    1.33 +  [number-list-str]
    1.34 +  (map #(Integer/parseInt %)
    1.35 +       (clojure.string/split number-list-str #", ")))
    1.36 +
    1.37 +(defmethod parse-command :Tempo
    1.38 +  [command]
    1.39 +  (update-in command [:args] #(Integer/parseInt %)))
    1.40 +
    1.41 +(defn parse-midi-note-list
    1.42 +  [midi-note-list-str]
    1.43 +  (let [[channel note velocity]
    1.44 +        (parse-number-list midi-note-list-str)]
    1.45 +    {:channel channel :note note :velocity velocity}))
    1.46 +
    1.47 +(defmethod parse-command :Note_on_c
    1.48 +  [command]
    1.49 +  (update-in command [:args] parse-midi-note-list))
    1.50 +
    1.51 +(defmethod parse-command :Note_off_c
    1.52 +  [command]
    1.53 +  (update-in command [:args] parse-midi-note-list))
    1.54 +
    1.55 +(defmethod parse-command :Header
    1.56 +  [command]
    1.57 +  (let [args (:args command)
    1.58 +        [format num-tracks division] (parse-number-list args)]
    1.59 +    (assoc command :args
    1.60 +           {:format format
    1.61 +            :num-tracks num-tracks
    1.62 +            :division division})))
    1.63 +        
    1.64 +(defmethod parse-command :Program_c
    1.65 +  [command]
    1.66 +  (let [args (:args command)
    1.67 +        [channel program-num] (parse-number-list args)]
    1.68 +    (assoc command :args
    1.69 +           {:channel channel
    1.70 +            :program-num program-num})))
    1.71 +
    1.72 +(defn parse-midi [#^File midi-file]
    1.73 +  (map
    1.74 +   (comp parse-command
    1.75 +         (fn [line]
    1.76 +           (let [[[_ channel time command args]]
    1.77 +                 (re-seq command-line line)]
    1.78 +             {:channel (Integer/parseInt channel)
    1.79 +              :time (Integer/parseInt time)
    1.80 +              :command (keyword command)
    1.81 +              :args (apply str (drop 2 args))})))
    1.82 +   (drop-last
    1.83 +    (clojure.string/split-lines
    1.84 +     (raw-midi-text midi-file)))))
    1.85 +  
    1.86  (def music-base new-kernel)
    1.87  
    1.88 -
    1.89 -
    1.90 -
    1.91  (defn store [n address]
    1.92    (flatten
    1.93     [0xF5
    1.94 @@ -33,11 +108,8 @@
    1.95  (defn infinite-loop []
    1.96    [0x18 0xFE])
    1.97  
    1.98 -
    1.99 -
   1.100  (def divider-register 0xFF04)
   1.101  
   1.102 -
   1.103  (defrecord Bit-Note [frequency volume duration duty])
   1.104  
   1.105  (defn clear-music-registers []
   1.106 @@ -92,7 +164,6 @@
   1.107  (def note-code 0x00)
   1.108  (def change-duty-code 0x01)
   1.109  (def silence-code 0x02)
   1.110 -  
   1.111  
   1.112  (defn do-message
   1.113    "Read the message which starts at the current value of HL and do
   1.114 @@ -250,6 +321,67 @@
   1.115       low-frequency
   1.116       duration]))
   1.117  
   1.118 +(defn midi-code->frequency
   1.119 +  [midi-code]
   1.120 +  (* 8.1757989156
   1.121 +     (Math/pow 2 (* (float (/ 12)) midi-code))))
   1.122 +
   1.123 +;; division == clock-pulses / quarter-note
   1.124 +;; tempo    == microseconds / quarter-note
   1.125 +
   1.126 +;; have: clock-pulses
   1.127 +;; want: seconds
   1.128 +
   1.129 +
   1.130 +
   1.131 +
   1.132 +(defn midi->mini-midi [#^File midi-file]
   1.133 +  (let [midi-events (parse-midi midi-file)
   1.134 +
   1.135 +        note-on-events
   1.136 +        (filter #(= :Note_on_c (:command %)) midi-events)
   1.137 +        note-off-events
   1.138 +        (filter #(= :Note_off_c (:command %)) midi-events)
   1.139 +
   1.140 +        channel-1-on
   1.141 +        (sort-by :time
   1.142 +                 (filter #(= 1 (:channel (:args %)))
   1.143 +                  note-on-events))
   1.144 +        channel-1-off
   1.145 +        (sort-by :time
   1.146 +                 (filter #(= 1 (:channel (:args %)))
   1.147 +                         note-off-events))
   1.148 +        
   1.149 +        
   1.150 +        tempo (:args (first (filter #(= :Tempo (:command %)) midi-events)))
   1.151 +        division (:division
   1.152 +                  (:args (first (filter #(= :Header (:command %)) midi-events))))
   1.153 +        ]
   1.154 +  
   1.155 +    (map
   1.156 +     (fn [note-event]
   1.157 +       (note-codes (:frequency note-event)
   1.158 +                   (:volume note-event)
   1.159 +                   (int (* (:duration note-event) 0x100))))
   1.160 +     
   1.161 +     (map
   1.162 +      (fn [note-on note-off]
   1.163 +        {:frequency (midi-code->frequency (:note (:args note-on)))
   1.164 +         :duration
   1.165 +         (/ (* (/ tempo division)
   1.166 +               (- (:time note-off) (:time note-on)))
   1.167 +            1e6) ;; convert clock-pulses into seconds
   1.168 +         :volume (int (/ (:velocity (:args note-on)) 10))
   1.169 +         :time-stamp (/ (* (/ tempo division)
   1.170 +                           (:time note-on)) 1e6)})
   1.171 +      channel-1-on channel-1-off))))
   1.172 +           
   1.173 +
   1.174 +        
   1.175 +  
   1.176 +  
   1.177 +
   1.178 +
   1.179  (def C4 (partial note-codes 261.63))
   1.180  (def D4 (partial note-codes 293.66))
   1.181  (def E4 (partial note-codes 329.63))
   1.182 @@ -314,85 +446,4 @@
   1.183       [0xF0
   1.184        0x05])]))
   1.185  
   1.186 -(def third-kind
   1.187 -  (File. "/home/r/proj/midi/third-kind.mid"))
   1.188  
   1.189 -(defn raw-midi-text [#^File midi-file]
   1.190 -  (:out 
   1.191 -   (clojure.java.shell/sh
   1.192 -    "midicsv"
   1.193 -    (.getCanonicalPath midi-file)
   1.194 -    "-")))
   1.195 -
   1.196 -(def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
   1.197 -
   1.198 -(defmulti parse-command :command)
   1.199 -
   1.200 -(defn discard-args [command] (dissoc command :args))
   1.201 -
   1.202 -(defmethod parse-command :Start_track
   1.203 -  [command] (discard-args command))
   1.204 -
   1.205 -(defmethod parse-command :End_track
   1.206 -  [command] (discard-args command))
   1.207 -
   1.208 -(defmethod parse-command :default
   1.209 -  [command] command)
   1.210 -
   1.211 -(defn parse-number-list
   1.212 -  [number-list-str]
   1.213 -  (map #(Integer/parseInt %)
   1.214 -       (clojure.string/split number-list-str #", ")))
   1.215 -
   1.216 -(defmethod parse-command :Tempo
   1.217 -  [command]
   1.218 -  (update-in command [:args] #(Integer/parseInt %)))
   1.219 -
   1.220 -(defn parse-midi-note-list
   1.221 -  [midi-note-list-str]
   1.222 -  (let [[channel note velocity]
   1.223 -        (parse-number-list midi-note-list-str)]
   1.224 -    {:channel channel :note note :velocity velocity}))
   1.225 -  
   1.226 -
   1.227 -(defmethod parse-command :Note_on_c
   1.228 -  [command]
   1.229 -  (update-in command [:args] parse-midi-note-list))
   1.230 -
   1.231 -(defmethod parse-command :Note_off_c
   1.232 -  [command]
   1.233 -  (update-in command [:args] parse-midi-note-list))
   1.234 -
   1.235 -(defmethod parse-command :Header
   1.236 -  [command]
   1.237 -  (let [args (:args command)
   1.238 -        [format num-tracks division] (parse-number-list args)]
   1.239 -    (assoc command :args
   1.240 -           {:format format
   1.241 -            :num-tracks num-tracks
   1.242 -            :division division})))
   1.243 -        
   1.244 -(defmethod parse-command :Program_c
   1.245 -  [command]
   1.246 -  (let [args (:args command)
   1.247 -        [channel program-num] (parse-number-list args)]
   1.248 -    (assoc command :args
   1.249 -           {:channel channel
   1.250 -            :program-num program-num})))
   1.251 -
   1.252 -
   1.253 -(defn parse-midi [#^File midi-file]
   1.254 -  (map
   1.255 -   (comp parse-command
   1.256 -         (fn [line]
   1.257 -           (let [[[_ channel time command args]]
   1.258 -                 (re-seq command-line line)]
   1.259 -             ;;(println (re-seq command-parse-1 line))
   1.260 -             {:channel (Integer/parseInt channel)
   1.261 -              :time (Integer/parseInt time)
   1.262 -              :command (keyword command)
   1.263 -              :args (apply str (drop 2 args))})))
   1.264 -   (drop-last
   1.265 -    (clojure.string/split-lines
   1.266 -     (raw-midi-text midi-file)))))
   1.267 -