view clojure/com/aurellem/run/music.clj @ 430:c709f4857fa9

correctly handles notes longer than 1 second.
author Robert McIntyre <rlm@mit.edu>
date Mon, 23 Apr 2012 09:49:24 -0500
parents a69c4d0c1a3b
children b73cb1b937d5
line wrap: on
line source
1 (ns com.aurellem.run.music
2 (:use (com.aurellem.gb saves gb-driver util constants
3 items vbm characters money
4 rlm-assembly))
5 (:use (com.aurellem.run util title save-corruption
6 bootstrap-0 bootstrap-1))
7 (:require clojure.string)
8 (:import [com.aurellem.gb.gb_driver SaveState])
9 (:import java.io.File))
11 (def third-kind
12 (File. "/home/r/proj/midi/third-kind.mid"))
14 (defn raw-midi-text [#^File midi-file]
15 (:out
16 (clojure.java.shell/sh
17 "midicsv"
18 (.getCanonicalPath midi-file)
19 "-")))
21 (def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
23 (defmulti parse-command :command)
25 (defn discard-args [command] (dissoc command :args))
27 (defmethod parse-command :Start_track
28 [command] (discard-args command))
30 (defmethod parse-command :End_track
31 [command] (discard-args command))
33 (defmethod parse-command :default
34 [command] command)
36 (defn parse-number-list
37 [number-list-str]
38 (map #(Integer/parseInt %)
39 (clojure.string/split number-list-str #", ")))
41 (defmethod parse-command :Tempo
42 [command]
43 (update-in command [:args] #(Integer/parseInt %)))
45 (defn parse-midi-note-list
46 [midi-note-list-str]
47 (let [[channel note velocity]
48 (parse-number-list midi-note-list-str)]
49 {:channel channel :note note :velocity velocity}))
51 (defmethod parse-command :Note_on_c
52 [command]
53 (update-in command [:args] parse-midi-note-list))
55 (defmethod parse-command :Note_off_c
56 [command]
57 (update-in command [:args] parse-midi-note-list))
59 (defmethod parse-command :Header
60 [command]
61 (let [args (:args command)
62 [format num-tracks division] (parse-number-list args)]
63 (assoc command :args
64 {:format format
65 :num-tracks num-tracks
66 :division division})))
68 (defmethod parse-command :Program_c
69 [command]
70 (let [args (:args command)
71 [channel program-num] (parse-number-list args)]
72 (assoc command :args
73 {:channel channel
74 :program-num program-num})))
76 (defn parse-midi [#^File midi-file]
77 (map
78 (comp parse-command
79 (fn [line]
80 (let [[[_ channel time command args]]
81 (re-seq command-line line)]
82 {:channel (Integer/parseInt channel)
83 :time (Integer/parseInt time)
84 :command (keyword command)
85 :args (apply str (drop 2 args))})))
86 (drop-last
87 (clojure.string/split-lines
88 (raw-midi-text midi-file)))))
90 (def music-base new-kernel)
92 (defn store [n address]
93 (flatten
94 [0xF5
95 0xE5
97 0x3E
98 n
100 0x21
101 (reverse (disect-bytes-2 address))
103 0x77
105 0xE1
106 0xF1]))
108 (defn infinite-loop []
109 [0x18 0xFE])
111 (def divider-register 0xFF04)
113 (defrecord Bit-Note [frequency volume duration duty])
115 (defn clear-music-registers []
116 (flatten
117 [(store (Integer/parseInt "00000000" 2) 0xFF10)
118 (store (Integer/parseInt "00000000" 2) 0xFF11)
119 (store (Integer/parseInt "00000000" 2) 0xFF12)
120 (store (Integer/parseInt "00000000" 2) 0xFF13)
121 (store (Integer/parseInt "00000000" 2) 0xFF14)
123 (store (Integer/parseInt "00000000" 2) 0xFF16) ;; pattern duty 000000
124 (store (Integer/parseInt "00000000" 2) 0xFF17) ;; volume 0000
125 (store (Integer/parseInt "00000000" 2) 0xFF18) ;; frequency-low
126 (store (Integer/parseInt "00000000" 2) 0xFF19) ;; 00000 frequency-high
128 (store (Integer/parseInt "00000000" 2) 0xFF1A)
129 (store (Integer/parseInt "00000000" 2) 0xFF1B)
130 (store (Integer/parseInt "00000000" 2) 0xFF1C)
131 (store (Integer/parseInt "00000000" 2) 0xFF1D)
132 (store (Integer/parseInt "00000000" 2) 0xFF1E)
134 (store (Integer/parseInt "00000000" 2) 0xFF20)
135 (store (Integer/parseInt "00000000" 2) 0xFF21)
136 (store (Integer/parseInt "00000000" 2) 0xFF22)
137 (store (Integer/parseInt "00000000" 2) 0xFF23)]))
140 ;; mini-midi syntax
142 ;; codes
143 ;; note-code == 0x00
144 ;; change-duty-code = 0x01
145 ;; silence-code = 0x02
147 ;; silence format
148 ;; 2 bytes
149 ;; [silence-code (0x02)]
150 ;; [duration-8-bits]
152 ;; note data format
153 ;; 4 bytes
154 ;; [note-code (0x00)]
155 ;; [volume-4-bits 0 frequency-high-3-bits]
156 ;; [frequengy-low-8-bits]
157 ;; [duration-8-bits]
159 ;; change-duty-format
160 ;; 2 bytes
161 ;; [change-duty-code (0x01)]
162 ;; [new-duty]
164 (def note-code 0x00)
165 (def change-duty-code 0x01)
166 (def silence-code 0x02)
168 (defn do-message
169 "Read the message which starts at the current value of HL and do
170 what it says. Duration is left in A, and HL is advanced
171 appropraitely."
172 []
173 (let [switch
174 [0x2A ;; load message code into A, increment HL
176 ;; switch on message
177 0xFE
178 note-code
180 0x20
181 :note-length]
183 play-note
184 [0x2A ;; load volume/frequency-high info
185 0xF5 ;; push A
186 0xE6
187 (Integer/parseInt "11110000" 2) ;; volume mask
188 0xE0
189 0x17 ;; set volume
190 0xF1 ;; pop A
191 0xE6
192 (Integer/parseInt "00000111" 2) ;; frequency-high mask
193 0xE0
194 0x19 ;; set frequency-high
196 0x2A ;; load frequency low-bits
197 0xE0
198 0x18 ;; set frequency-low-bits
200 0x2A]] ;; load duration
201 (replace
202 {:note-length (count play-note)}
203 (concat switch play-note))))
205 (defn play-note
206 "Play the note referenced by HL in the appropiate channel.
207 Leaves desired-duration in A."
208 []
209 [0x2A ;; load volume/frequency-high info
210 0xF5 ;; push A
211 0xE6
212 (Integer/parseInt "11110000" 2) ;; volume mask
213 0xE0
214 0x17 ;; set volume
215 0xF1 ;; pop A
216 0xE6
217 (Integer/parseInt "00000111" 2) ;; frequency-high mask
218 0xE0
219 0x19 ;; set frequency-high
221 0x2A ;; load frequency low-bits
222 0xE0
223 0x18 ;; set frequency-low-bits
225 0x2A ;; load duration
226 ])
228 (defn music-step []
229 (flatten
230 [
231 0xF5 ;; push A
232 0xF0
233 0x05 ;; load current ticks
234 0xB8 ;; B holds previous sub-ticks, subtract it from A
235 ;; if A-B caused a carry, then (B > A) is true, and
236 ;; A = current-sub-tics, B = previous-sub-ticks, so
237 ;; current-sub-ticks < previous-sub-ticks, which means that the
238 ;; timer counter HAS overflowed.
239 0x30 ;; increment C only if last result caused carry
240 0x01
241 0x0C
243 0x47 ;; update sub-ticks (A->B)
245 0xF1 ;; pop AF, now A contains desired-ticks
247 0xB9 ;; compare with current ticks
249 ;; if desired-ticks = current ticks
250 ;; go to next note ; set current set ticks to 0.
252 0x20
253 (+ (count (do-message)) 2)
255 (do-message)
257 0x0E
258 0x00])) ;; 0->C (current-ticks)
260 (defn music-kernel []
261 (flatten
262 [(clear-music-registers)
264 0x21
265 0x00
266 0xD0 ;; set HL to 0xD000 == music-start
267 0x0E
268 0x00 ;; 0->C
269 0x06
270 0x00 ;; 0->B
272 0x3E
273 0x01
274 0xE0
275 0x06 ;; set TMA to 0
277 0x3E
278 (Integer/parseInt "00000110" 2)
279 0xE0
280 0x07 ;; set TAC to 65536 Hz and activate timer
283 0xAF ;; initialiaze A to zero
286 (music-step)
287 0x18
288 (->signed-8-bit (+ (- (count (music-step)))
289 -2))]))
291 (defn frequency-code->frequency
292 [code]
293 (assert (<= 0 code 2047))
294 (/ 131072 (- 2048 code)))
296 (defn clamp [x low high]
297 (cond (> x high) high
298 (< x low) low
299 true x))
301 (defn frequency->frequency-code
302 [frequency]
303 (clamp
304 (Math/round
305 (float
306 (/ (- (* 2048 frequency) 131072) frequency)))
307 0x00 2048))
309 (defn note-codes [frequency volume duration]
310 (assert (<= 0 volume 0xF))
311 (if (<= duration 0xFF)
312 (let [frequency-code
313 (frequency->frequency-code frequency)
314 volume&high-frequency
315 (+ (bit-shift-left volume 4)
316 (bit-shift-right frequency-code 8))
317 low-frequency
318 (bit-and 0xFF frequency-code)]
319 [note-code
320 volume&high-frequency
321 low-frequency
322 duration])
323 (vec
324 (flatten
325 [(note-codes frequency volume 0xFF)
326 (note-codes frequency volume (- duration 0xFF))]))))
329 (defn midi-code->frequency
330 [midi-code]
331 (* 8.1757989156
332 (Math/pow 2 (* (float (/ 12)) midi-code))))
334 ;; division == clock-pulses / quarter-note
335 ;; tempo == microseconds / quarter-note
337 ;; have: clock-pulses
338 ;; want: seconds
341 (defn silence [length]
342 {:frequency 1
343 :duration length
344 :volume 0})
346 (defn midi->mini-midi [#^File midi-file]
347 (let [midi-events (parse-midi midi-file)
349 note-on-events
350 (filter #(= :Note_on_c (:command %)) midi-events)
351 note-off-events
352 (filter #(= :Note_off_c (:command %)) midi-events)
354 channel-1-on
355 (sort-by :time
356 (filter #(= 1 (:channel (:args %)))
357 note-on-events))
358 channel-1-off
359 (sort-by :time
360 (filter #(= 1 (:channel (:args %)))
361 note-off-events))
364 tempo (:args (first (filter #(= :Tempo (:command %)) midi-events)))
365 division (:division
366 (:args (first (filter #(= :Header (:command %)) midi-events))))
368 notes
369 (map
370 (fn [note-on note-off]
371 {:frequency (midi-code->frequency (:note (:args note-on)))
372 :duration
373 (/ (* (/ tempo division)
374 (- (:time note-off) (:time note-on)))
375 1e6) ;; convert clock-pulses into seconds
376 :volume (int (/ (:velocity (:args note-on)) 10))
377 :time-stamp (/ (* (/ tempo division)
378 (:time note-on)) 1e6)})
379 channel-1-on channel-1-off)
381 silences
382 (map (fn [note-1 note-2]
383 (let [note-1-space (- (:time-stamp note-2)
384 (:time-stamp note-1))
385 note-1-length (:duration note-1)]
386 (silence (- note-1-space note-1-length))))
387 ;; to handle silence at the beginning.
388 (concat [(assoc (silence 0)
389 :time-stamp 0)] notes)
390 notes)
392 notes-with-silence
393 (filter (comp not zero? :duration) (interleave silences notes))
394 ]
396 (map
397 (fn [note-event]
398 (note-codes (:frequency note-event)
399 (:volume note-event)
400 (int (* (:duration note-event) 0x100))))
401 notes-with-silence)))
404 (def C4 (partial note-codes 261.63))
405 (def D4 (partial note-codes 293.66))
406 (def E4 (partial note-codes 329.63))
407 (def F4 (partial note-codes 349.23))
408 (def G4 (partial note-codes 392))
409 (def A4 (partial note-codes 440))
410 (def B4 (partial note-codes 493.88))
411 (def C5 (partial note-codes 523.3))
413 (def scale
414 (flatten
415 [(C4 0xF 0x40)
416 (D4 0xF 0x40)
417 (E4 0xF 0x40)
418 (F4 0xF 0x40)
419 (G4 0xF 0x40)
420 (A4 0xF 0x40)
421 (B4 0xF 0x40)
422 (C5 0xF 0x40)]))
424 (defn play-music [music-bytes]
425 (let [program-target 0xC000
426 music-target 0xD000]
427 (-> (set-memory-range (second (music-base))
428 program-target (music-kernel))
429 (set-memory-range music-target music-bytes)
430 (PC! program-target))))
433 (defn test-note [music-bytes]
434 (-> (set-memory-range (second (music-base))
435 0xC000 (concat (clear-music-registers)
436 (play-note)
437 (infinite-loop)))
438 (set-memory-range 0xD000 music-bytes)
439 (PC! 0xC000)
440 (HL! 0xD000)
441 ))
444 (defn run-program
445 ([program]
446 (let [target 0xC000]
447 (-> (set-memory-range (second (music-base))
448 target program)
449 (PC! target)))))
451 (defn test-timer []
452 (flatten
453 [0x3E
454 0x01
455 0xE0
456 0x06 ;; set TMA to 0
458 0x3E
459 (Integer/parseInt "00000100" 2)
460 0xE0
461 0x07 ;; set TAC to 16384 Hz and activate timer
463 (repeat
464 500
465 [0xF0
466 0x05])]))