view clojure/com/aurellem/run/music.clj @ 454:bf87b87a4ad7

exporting to midi file.
author Robert McIntyre <rlm@mit.edu>
date Thu, 03 May 2012 07:25:06 -0500
parents 067ea3f0d951
children 1c10fa8366a7
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 (def pony "/home/r/proj/vba-clojure/music/pony-title.mid")
16 (defn raw-midi-text [#^File midi-file]
17 (:out
18 (clojure.java.shell/sh
19 "midicsv"
20 (.getCanonicalPath midi-file)
21 "-")))
23 (def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
25 (defmulti parse-command :command)
27 (defn discard-args [command] (dissoc command :args))
29 (defmethod parse-command :Start_track
30 [command] (discard-args command))
32 (defmethod parse-command :End_track
33 [command] (discard-args command))
35 (defmethod parse-command :default
36 [command] command)
38 (defn parse-number-list
39 [number-list-str]
40 (map #(Integer/parseInt %)
41 (clojure.string/split number-list-str #", ")))
43 (defmethod parse-command :Tempo
44 [command]
45 (update-in command [:args] #(Integer/parseInt %)))
47 (defn parse-midi-note-list
48 [midi-note-list-str]
49 (let [[channel note velocity]
50 (parse-number-list midi-note-list-str)]
51 {:channel channel :note note :velocity velocity}))
53 (defmethod parse-command :Note_on_c
54 [command]
55 (update-in command [:args] parse-midi-note-list))
57 (defmethod parse-command :Note_off_c
58 [command]
59 (update-in command [:args] parse-midi-note-list))
61 (defmethod parse-command :Header
62 [command]
63 (let [args (:args command)
64 [format num-tracks division] (parse-number-list args)]
65 (assoc command :args
66 {:format format
67 :num-tracks num-tracks
68 :division division})))
70 (defmethod parse-command :Program_c
71 [command]
72 (let [args (:args command)
73 [channel program-num] (parse-number-list args)]
74 (assoc command :args
75 {:channel channel
76 :program-num program-num})))
78 (defn parse-midi [#^File midi-file]
79 (map
80 (comp parse-command
81 (fn [line]
82 (let [[[_ channel time command args]]
83 (re-seq command-line line)]
84 {:channel (Integer/parseInt channel)
85 :time (Integer/parseInt time)
86 :command (keyword command)
87 :args (apply str (drop 2 args))})))
88 (drop-last
89 (clojure.string/split-lines
90 (raw-midi-text midi-file)))))
92 (def music-base new-kernel)
94 (defn store [n address]
95 (flatten
96 [0xF5
97 0xE5
99 0x3E
100 n
102 0x21
103 (reverse (disect-bytes-2 address))
105 0x77
107 0xE1
108 0xF1]))
110 (defn infinite-loop []
111 [0x18 0xFE])
113 (def divider-register 0xFF04)
115 (defrecord Bit-Note [frequency volume duration duty])
117 (defn clear-music-registers []
118 (flatten
119 [(store (Integer/parseInt "00000000" 2) 0xFF10) ;; sweep
120 (store (Integer/parseInt "00000000" 2) 0xFF11) ;; pattern duty
121 (store (Integer/parseInt "00000000" 2) 0xFF12) ;; volume
122 (store (Integer/parseInt "00000000" 2) 0xFF13) ;; frequency-low
123 (store (Integer/parseInt "00000000" 2) 0xFF14) ;; frequency-high
125 (store (Integer/parseInt "00000000" 2) 0xFF16) ;; pattern duty 000000
126 (store (Integer/parseInt "00000000" 2) 0xFF17) ;; volume 0000
127 (store (Integer/parseInt "00000000" 2) 0xFF18) ;; frequency-low
128 (store (Integer/parseInt "00000000" 2) 0xFF19) ;; 00000 frequency-high
130 (store (Integer/parseInt "00000000" 2) 0xFF1A)
131 (store (Integer/parseInt "00000000" 2) 0xFF1B)
132 (store (Integer/parseInt "00000000" 2) 0xFF1C)
133 (store (Integer/parseInt "00000000" 2) 0xFF1D)
134 (store (Integer/parseInt "00000000" 2) 0xFF1E)
136 (store (Integer/parseInt "00000000" 2) 0xFF20) ;; length
137 (store (Integer/parseInt "00000000" 2) 0xFF21) ;; volume
138 (store (Integer/parseInt "00000000" 2) 0xFF22) ;; noise-frequency
139 (store (Integer/parseInt "00000000" 2) 0xFF23) ;; control
140 ]))
143 ;; mini-midi syntax
145 ;; codes
146 ;; note-code == 0x00
147 ;; change-duty-code = 0x01
148 ;; silence-code = 0x02
150 ;; silence format
151 ;; 2 bytes
152 ;; [silence-code (0x02)]
153 ;; [duration-8-bits]
155 ;; note data format
156 ;; 4 bytes
157 ;; [note-code (0x00)]
158 ;; [volume-4-bits 0 frequency-high-3-bits]
159 ;; [frequengy-low-8-bits]
160 ;; [duration-8-bits]
162 ;; change-duty-format
163 ;; 2 bytes
164 ;; [change-duty-code (0x01)]
165 ;; [new-duty]
167 (def note-code 0x00)
168 (def change-duty-code 0x01)
169 (def silence-code 0x02)
171 (defn do-message
172 "Read the message which starts at the current value of HL and do
173 what it says. Duration is left in A, and HL is advanced
174 appropraitely."
175 ([] (do-message 0x16))
176 ([sound-base-address]
177 (let [switch
178 [0x2A ;; load message code into A, increment HL
180 ;; switch on message
181 0xFE
182 note-code
184 0x20
185 :note-length]
187 play-note
188 [0x2A ;; load volume/frequency-high info
189 0xF5 ;; push A
190 0xE6
191 (Integer/parseInt "11110000" 2) ;; volume mask
192 0xE0
193 (inc sound-base-address) ;;0x17 ;; set volume
194 0xF1 ;; pop A
195 0xE6
196 (Integer/parseInt "00000111" 2) ;; frequency-high mask
197 0xE0
198 (+ 3 sound-base-address) ;;0x19 ;; set frequency-high
200 0x2A ;; load frequency low-bits
201 0xE0
202 (+ 2 sound-base-address) ;;0x18 ;; set frequency-low-bits
203 0x2A]] ;; load duration
204 (replace
205 {:note-length (count play-note)}
206 (concat switch play-note)))))
208 ;; (defn play-note
209 ;; "Play the note referenced by HL in the appropiate channel.
210 ;; Leaves desired-duration in A."
212 ;; [0x2A ;; load volume/frequency-high info
213 ;; 0xF5 ;; push A
214 ;; 0xE6
215 ;; (Integer/parseInt "11110000" 2) ;; volume mask
216 ;; 0xE0
217 ;; 0x17 ;; set volume
218 ;; 0xF1 ;; pop A
219 ;; 0xE6
220 ;; (Integer/parseInt "00000111" 2) ;; frequency-high mask
221 ;; 0xE0
222 ;; 0x19 ;; set frequency-high
224 ;; 0x2A ;; load frequency low-bits
225 ;; 0xE0
226 ;; 0x18 ;; set frequency-low-bits
228 ;; 0x2A ;; load duration
229 ;; ])
231 (defn music-step [sound-base-address]
232 ;; C == current-ticks
233 ;; A == desired-ticks
235 (flatten
236 [;; restore variables from stack
237 0xE1 ;; pop HL
238 0xC1 ;; pop CB
239 0xF1 ;; pop AF
242 0xF5 ;; push A
243 0xF0
244 0x05 ;; load current ticks from 0xF005
245 0xB8 ;;
246 0x30 ;; increment C only if last result caused carry
247 0x01
248 0x0C
250 0x47 ;; update sub-ticks (A->B)
252 0xF1 ;; pop AF, now A contains desired-ticks
254 0xB9 ;; compare with current ticks
256 ;; if desired-ticks = current ticks
257 ;; go to next note ; set current set ticks to 0.
259 0x20
260 (+ (count (do-message 0)) 2)
262 (do-message sound-base-address)
264 0x0E
265 0x00 ;; 0->C (current-ticks)
267 ;; save variables to stack
268 0xF5 ;; push AF
269 0xC5 ;; push CB
270 0xE5 ;; push HL
273 ]))
275 (def music-1 0x11)
276 (def music-2 0x16)
278 (defn music-kernel []
279 (flatten
280 [;; global initilization section
281 (clear-music-registers)
283 0x3E
284 0x01
285 0xE0
286 0x06 ;; set TMA to 0
288 0x3E
289 (Integer/parseInt "00000110" 2)
290 0xE0
291 0x07 ;; set TAC to 65536 Hz and activate timer
293 ;; initialize frame 1
294 0x21
295 0x00
296 0xA0 ;; set HL to 0xA000 == music-start 1
297 0x0E
298 0x00 ;; 0->C
299 0x06
300 0x00 ;; 0->B
302 0xAF ;; 0->A
304 0xF5 ;; push AF
305 0xC5 ;; push CB
306 0xE5 ;; push HL
308 ;; initialize frame 2
309 0x21
310 0x00
311 0xB0 ;; set HL to 0xB000 == music-start 2
313 0xF5 ;; push AF
314 0xC5 ;; push CB
315 0xE5 ;; push HL
318 ;; main music loop
320 0xE8 ;; SP + 6; activate frame 1
321 6
322 (music-step music-1)
323 ;;(repeat (count (music-step music-1)) 0x00)
325 0xE8 ;; SP - 6; activate frame 2
326 (->signed-8-bit -6)
327 ;;(repeat (count (music-step music-2)) 0x00)
328 (music-step music-2)
331 0x18
332 (->signed-8-bit (+
333 ;; two music-steps
334 (- (* 2 (count (music-step 0))))
335 -2 ;; this jump instruction
336 -2 ;; activate frame 1
337 -2 ;; activate frame 2
338 ))]))
340 (defn frequency-code->frequency
341 [code]
342 (assert (<= 0 code 2047))
343 (/ 131072 (- 2048 code)))
345 (defn clamp [x low high]
346 (cond (> x high) high
347 (< x low) low
348 true x))
350 (defn frequency->frequency-code
351 [frequency]
352 (clamp
353 (Math/round
354 (float
355 (/ (- (* 2048 frequency) 131072) frequency)))
356 0x00 2048))
358 (defn note-codes [frequency volume duration]
359 (assert (<= 0 volume 0xF))
360 (if (<= duration 0xFF)
361 (let [frequency-code
362 (frequency->frequency-code frequency)
363 volume&high-frequency
364 (+ (bit-shift-left volume 4)
365 (bit-shift-right frequency-code 8))
366 low-frequency
367 (bit-and 0xFF frequency-code)]
368 [note-code
369 volume&high-frequency
370 low-frequency
371 duration])
372 (vec
373 (flatten
374 [(note-codes frequency volume 0xFF)
375 (note-codes frequency volume (- duration 0xFF))]))))
378 (defn midi-code->frequency
379 [midi-code]
380 (* 8.1757989156
381 (Math/pow 2 (* (float (/ 12)) midi-code))))
383 ;; division == clock-pulses / quarter-note
384 ;; tempo == microseconds / quarter-note
386 ;; have: clock-pulses
387 ;; want: seconds
390 (defn silence [length]
391 {:frequency 1
392 :duration length
393 :volume 0})
395 (defn commands
396 "return all events where #(= (:command %) command)"
397 [command s]
398 (filter #(= command (:command %)) s))
400 (defn midi-track->mini-midi [#^File midi-file track-num]
401 (let [midi-events (parse-midi midi-file)
403 note-on-events (commands :Note_on_c midi-events)
404 note-off-events (commands :Note_off_c midi-events)
406 select-channel
407 (fn [n s]
408 (sort-by :time (filter #(= n (:channel (:args %))) s)))
410 channel-on (select-channel track-num note-on-events)
412 channel-off (select-channel track-num note-off-events)
415 tempo (:args (first (commands :Tempo midi-events)))
416 division
417 (:division (:args (first (commands :Header midi-events))))
419 notes
420 (map
421 (fn [note-on note-off]
422 {:frequency (midi-code->frequency (:note (:args note-on)))
423 :duration
424 (/ (* (/ tempo division)
425 (- (:time note-off) (:time note-on)))
426 1e6) ;; convert clock-pulses into seconds
427 :volume (int (/ (:velocity (:args note-on)) 10))
428 :time-stamp (/ (* (/ tempo division)
429 (:time note-on)) 1e6)})
430 channel-on channel-off)
432 silences
433 (map (fn [note-1 note-2]
434 (let [note-1-space (- (:time-stamp note-2)
435 (:time-stamp note-1))
436 note-1-length (:duration note-1)]
437 (silence (- note-1-space note-1-length))))
438 ;; to handle silence at the beginning.
439 (concat [(assoc (silence 0)
440 :time-stamp 0)] notes)
441 notes)
443 notes-with-silence
444 (filter (comp not zero? :duration)
445 (interleave silences notes))]
446 (map
447 (fn [note-event]
448 (note-codes (:frequency note-event)
449 (:volume note-event)
450 (int (* (:duration note-event) 0x100))))
451 notes-with-silence)))
453 (defn midi->mini-midi [#^File midi-file]
454 {:track-1 (flatten (midi-track->mini-midi midi-file 1))
455 :track-2 (flatten (midi-track->mini-midi midi-file 2))})
457 (defn play-midi [#^File midi-file]
458 (let [track-1-target 0xA000
459 track-2-target 0xB000
460 program-target 0xC000
461 mini-midi (midi->mini-midi midi-file)
462 long-silence (flatten (note-codes 20 0 9001))]
464 (-> (second (music-base))
465 (set-memory-range track-1-target long-silence)
466 (set-memory-range track-2-target long-silence)
467 (set-memory-range track-1-target (:track-1 mini-midi))
468 (set-memory-range track-2-target (:track-2 mini-midi))
469 (set-memory-range program-target (music-kernel))
470 (PC! program-target))))
475 (def C4 (partial note-codes 261.63))
476 (def D4 (partial note-codes 293.66))
477 (def E4 (partial note-codes 329.63))
478 (def F4 (partial note-codes 349.23))
479 (def G4 (partial note-codes 392))
480 (def A4 (partial note-codes 440))
481 (def B4 (partial note-codes 493.88))
482 (def C5 (partial note-codes 523.3))
484 (def scale
485 (flatten
486 [(C4 0xF 0x40)
487 (D4 0xF 0x40)
488 (E4 0xF 0x40)
489 (F4 0xF 0x40)
490 (G4 0xF 0x40)
491 (A4 0xF 0x40)
492 (B4 0xF 0x40)
493 (C5 0xF 0x40)]))
495 (defn play-music [music-bytes]
496 (let [program-target 0xC000
497 music-target 0xA000]
498 (-> (set-memory-range (second (music-base))
499 program-target (music-kernel))
500 (set-memory-range music-target music-bytes)
501 (PC! program-target))))
505 ;; (defn test-note [music-bytes]
506 ;; (-> (set-memory-range (second (music-base))
507 ;; 0xC000 (concat (clear-music-registers)
508 ;; (play-note)
509 ;; (infinite-loop)))
510 ;; (set-memory-range 0xD000 music-bytes)
511 ;; (PC! 0xC000)
512 ;; (HL! 0xD000)
513 ;; ))
516 (defn run-program
517 ([program]
518 (let [target 0xC000]
519 (-> (set-memory-range (second (music-base))
520 target program)
521 (PC! target)))))
523 (defn test-timer []
524 (flatten
525 [0x3E
526 0x01
527 0xE0
528 0x06 ;; set TMA to 0
530 0x3E
531 (Integer/parseInt "00000100" 2)
532 0xE0
533 0x07 ;; set TAC to 16384 Hz and activate timer
535 (repeat
536 500
537 [0xF0
538 0x05])]))