view clojure/com/aurellem/run/music.clj @ 455:1c10fa8366a7

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