view clojure/com/aurellem/run/music.clj @ 467:ac0ed5c1a1c4

working on drums.
author Robert McIntyre <rlm@mit.edu>
date Fri, 04 May 2012 05:17:18 -0500
parents b31cd6651375
children 85d9fa354f0b
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 1))
181 ([sound-base-address wave-duty]
182 (assert (<= 0 wave-duty 3))
183 (let [switch
184 [0x2A ;; load message code into A, increment HL
186 ;; switch on message
187 0xFE
188 note-code
190 0x20
191 :note-length]
193 play-note
194 [0x3E ;; set wave-duty
195 (bit-shift-left wave-duty 6)
196 0xE0
197 sound-base-address
198 0x2A ;; load volume/frequency-high info
199 0xF5 ;; push A
200 0xE6
201 (Integer/parseInt "11110000" 2) ;; volume mask
202 0xE0
203 (inc sound-base-address) ;;0x17 ;; set volume
204 0xF1 ;; pop A
205 0xE6
206 (Integer/parseInt "00000111" 2) ;; frequency-high mask
207 0xE0
208 (+ 3 sound-base-address) ;;0x19 ;; set frequency-high
210 0x2A ;; load frequency low-bits
211 0xE0
212 (+ 2 sound-base-address) ;;0x18 ;; set frequency-low-bits
213 0x2A]] ;; load duration
214 (replace
215 {:note-length (count play-note)}
216 (concat switch play-note)))))
218 (defn play-noise
219 "read [noise-code, volume, duration] and play the noise. Duration is left in
220 A, and HL is advanced appropraitely."
221 ([]
222 [0x2A ;; load noise-code into A
223 0xE0
224 0x22 ;; write noise-code
226 0x2A ;; load volume
227 0xE0
228 0x21 ;; write volume
230 0x2A] ;; load duration into A
231 ))
234 ;; (defn play-note
235 ;; "Play the note referenced by HL in the appropiate channel.
236 ;; Leaves desired-duration in A."
238 ;; [0x2A ;; load volume/frequency-high info
239 ;; 0xF5 ;; push A
240 ;; 0xE6
241 ;; (Integer/parseInt "11110000" 2) ;; volume mask
242 ;; 0xE0
243 ;; 0x17 ;; set volume
244 ;; 0xF1 ;; pop A
245 ;; 0xE6
246 ;; (Integer/parseInt "00000111" 2) ;; frequency-high mask
247 ;; 0xE0
248 ;; 0x19 ;; set frequency-high
250 ;; 0x2A ;; load frequency low-bits
251 ;; 0xE0
252 ;; 0x18 ;; set frequency-low-bits
254 ;; 0x2A ;; load duration
255 ;; ])
257 (defn music-step [sound-base-address wave-duty noise?]
258 ;; C == current-ticks
259 ;; A == desired-ticks
261 (flatten
262 [;; restore variables from stack
263 0xE1 ;; pop HL
264 0xC1 ;; pop CB
265 0xF1 ;; pop AF
268 0xF5 ;; push A
269 0xF0
270 0x05 ;; load current ticks from 0xF005
271 0xB8 ;;
272 0x30 ;; increment C only if last result caused carry
273 0x01
274 0x0C
276 0x47 ;; update sub-ticks (A->B)
278 0xF1 ;; pop AF, now A contains desired-ticks
280 0xB9 ;; compare with current ticks
282 ;; if desired-ticks = current ticks
283 ;; go to next note ; set current set ticks to 0.
285 (if noise?
286 [0x20
287 (+ 2 (count (play-noise)))
288 (play-noise)]
290 [0x20
291 (+ (count (do-message 0 0)) 2)
292 (do-message sound-base-address wave-duty)])
294 0x0E
295 0x00 ;; 0->C (current-ticks)
297 ;; save variables to stack
298 0xF5 ;; push AF
299 0xC5 ;; push CB
300 0xE5 ;; push HL
303 ]))
305 (def music-1 0x11)
306 (def music-2 0x16)
308 (defn music-kernel [wave-duty-1 wave-duty-2]
309 (flatten
310 [;; global initilization section
311 (clear-music-registers)
313 0x3E
314 0x01
315 0xE0
316 0x06 ;; set TMA to 0
318 0x3E
319 (Integer/parseInt "00000110" 2)
320 0xE0
321 0x07 ;; set TAC to 65536 Hz and activate timer
323 ;; initialize frame 1
324 0x21
325 0x00
326 0xA0 ;; set HL to 0xA000 == music-start 1
327 0x0E
328 0x00 ;; 0->C
329 0x06
330 0x00 ;; 0->B
332 0xAF ;; 0->A
334 0xF5 ;; push AF
335 0xC5 ;; push CB
336 0xE5 ;; push HL
338 ;; initialize frame 2
339 0x21
340 0x00
341 0xB0 ;; set HL to 0xB000 == music-start 2
343 0xF5 ;; push AF
344 0xC5 ;; push CB
345 0xE5 ;; push HL
348 ;; initialize frame 3 (noise)
349 0x21
350 0x00
351 0xA9 ;; 0xA9OO -> HL
353 0xF5 ;; push AF
354 0xC5 ;; push CB
355 0xE5 ;; push HL
357 ;; main music loop
359 0xE8 ;; SP + 12; activate frame 1
360 12
361 (music-step music-1 wave-duty-1 false)
363 0xE8 ;; SP - 6; activate frame 2
364 (->signed-8-bit -6)
365 (music-step music-2 wave-duty-2 false)
367 0xE8 ;; SP - 6; activate frame 3
368 (->signed-8-bit -6)
369 (music-step nil nil true)
371 0x18
372 (->signed-8-bit (+
373 ;; two music-steps
374 (- (* 2 (count (music-step 0 0 false))))
375 (- (count (music-step nil nil true)))
376 -2 ;; this jump instruction
377 -2 ;; activate frame 1
378 -2 ;; activate frame 2
379 -2 ;; activate frame 3
380 ))]))
382 (defn frequency-code->frequency
383 [code]
384 (assert (<= 0 code 2047))
385 (/ 131072 (- 2048 code)))
387 (defn clamp [x low high]
388 (cond (> x high) high
389 (< x low) low
390 true x))
392 (defn frequency->frequency-code
393 [frequency]
394 (clamp
395 (Math/round
396 (float
397 (/ (- (* 2048 frequency) 131072) frequency)))
398 0x00 2048))
400 (defn note-codes [frequency volume duration]
401 (assert (<= 0 volume 0xF))
402 (if (<= duration 0xFF)
403 (let [frequency-code
404 (frequency->frequency-code frequency)
405 volume&high-frequency
406 (+ (bit-shift-left volume 4)
407 (bit-shift-right frequency-code 8))
408 low-frequency
409 (bit-and 0xFF frequency-code)]
410 [note-code
411 volume&high-frequency
412 low-frequency
413 duration])
414 (vec
415 (flatten
416 [(note-codes frequency volume 0xFF)
417 (note-codes frequency volume (- duration 0xFF))]))))
420 (defn midi-code->frequency
421 [midi-code]
422 (* 8.1757989156
423 (Math/pow 2 (* (float (/ 12)) midi-code))))
425 ;; division == clock-pulses / quarter-note
426 ;; tempo == microseconds / quarter-note
428 ;; have: clock-pulses
429 ;; want: seconds
432 (defn silence [length]
433 {:frequency 1
434 :duration length
435 :volume 0})
437 (defn commands
438 "return all events where #(= (:command %) command)"
439 [command s]
440 (filter #(= command (:command %)) s))
442 (defn track-info [#^File midi-file]
443 (let [events (parse-midi midi-file)
444 track-titles (commands :Title_t events)
445 track-info
446 (map #(read-string (read-string (:args %))) track-titles)
447 track-map
448 (zipmap track-info track-titles)]
449 track-map))
451 (defn target-tracks
452 "return the track-numbers in the form [voice-0 voice-1 noise]"
453 [#^File midi-file]
454 (let [track-data (track-info midi-file)
455 track-order
456 (zipmap (map :out (keys track-data))
457 (vals track-data))
458 channel-nums (map (comp :channel track-order) (range 3))]
459 channel-nums))
461 (defn midi-track->abstract-mini-midi
462 [#^File midi-file track-num]
463 (let [midi-events (parse-midi midi-file)
465 note-on-events (commands :Note_on_c midi-events)
466 note-off-events (commands :Note_off_c midi-events)
468 select-channel
469 (fn [n s]
470 (sort-by :time (filter #(= n (:channel %)) s)))
472 channel-on (select-channel track-num note-on-events)
474 channel-off (select-channel track-num note-off-events)
477 tempo (:args (first (commands :Tempo midi-events)))
478 division
479 (:division (:args (first (commands :Header midi-events))))
481 notes
482 (map
483 (fn [note-on note-off]
484 {:frequency (midi-code->frequency (:note (:args note-on)))
485 :midi-code (:note (:args note-on))
486 :duration
487 (/ (* (/ tempo division)
488 (- (:time note-off) (:time note-on)))
489 1e6) ;; convert clock-pulses into seconds
490 :volume (int (/ (:velocity (:args note-on)) 10))
491 :time-stamp (/ (* (/ tempo division)
492 (:time note-on)) 1e6)})
493 channel-on channel-off)
495 silences
496 (map (fn [note-1 note-2]
497 (let [note-1-space (- (:time-stamp note-2)
498 (:time-stamp note-1))
499 note-1-length (:duration note-1)]
500 (silence (- note-1-space note-1-length))))
501 ;; to handle silence at the beginning.
502 (concat [(assoc (silence 0)
503 :time-stamp 0)] notes)
504 notes)
506 notes-with-silence
507 (concat
508 (filter (comp not zero? :duration)
509 (interleave silences notes))
510 [(silence 3)])]
511 notes-with-silence))
513 (defn midi-track->mini-midi-voice [#^File midi-file track-num]
514 (let [abstract-mini-midi
515 (midi-track->abstract-mini-midi midi-file track-num)]
516 (map
517 (fn [note-event]
518 (note-codes (:frequency note-event)
519 (:volume note-event)
520 (int (* (:duration note-event) 0x100))))
521 abstract-mini-midi)))
523 (defn noise-codes [code volume duration]
524 (assert (<= 0 volume 0xF))
525 (if (<= duration 0xFF)
526 [(if (nil? code) 0xFF code)
527 (bit-shift-left volume 4)
528 duration]
529 (vec
530 (flatten
531 [(noise-codes code volume 0xFF)
532 (noise-codes code volume (- duration 0xFF))]))))
534 (defn midi-track->mini-midi-noise [#^File midi-file track-num]
535 (let [abstract-mini-midi
536 (midi-track->abstract-mini-midi midi-file track-num)]
537 (map
538 (fn [noise-event]
539 (noise-codes (:midi-code noise-event)
540 (:volume noise-event)
541 (int (* (:duration noise-event) 0x100))))
542 abstract-mini-midi)))
545 (defn midi->mini-midi [#^File midi-file]
546 (let [targets (target-tracks midi-file)
547 duty-info (keys (track-info midi-file))]
549 {:voice-1 (midi-track->mini-midi-voice midi-file (nth targets 0))
550 :voice-2 (midi-track->mini-midi-voice midi-file (nth targets 1))
551 :noise (midi-track->mini-midi-noise midi-file (nth targets 2))
552 :duty (zipmap (map :out duty-info)
553 (map #(get % :duty 0) duty-info))}))
555 (defn play-midi [#^File midi-file]
556 (let [voice-1-target 0xA000
557 voice-2-target 0xB000
558 noise-target 0xA900
559 program-target 0xC000
560 mini-midi (midi->mini-midi midi-file)
561 long-silence (flatten (note-codes 20 0 20001))
562 long-noise-silence
563 (interleave (range 500) (repeat 0x00) (repeat 255))
565 voice-1 (flatten (:voice-1 mini-midi))
566 wave-duty-1 ((:duty mini-midi) 0 0)
568 voice-2 (flatten (:voice-2 mini-midi))
569 wave-duty-2 ((:duty mini-midi) 1 0)
571 noise (flatten (:noise mini-midi))
572 ]
574 (-> (second (music-base))
575 (set-memory-range voice-1-target long-silence)
576 (set-memory-range voice-2-target long-silence)
577 (set-memory-range noise-target long-noise-silence)
578 (set-memory-range voice-1-target voice-1)
579 (set-memory-range voice-2-target voice-2)
580 (set-memory-range noise-target noise)
581 (set-memory-range
582 program-target
583 (music-kernel wave-duty-1 wave-duty-2))
584 (PC! program-target))))
587 (defn test-noise []
588 (let [noise-pattern
589 (concat (interleave (range 0x100) (repeat 0xF0) (repeat 255))
590 (interleave (range 10) (repeat 0x00) (repeat 255)))]
592 (-> (second (music-base))
593 (set-memory-range 0xA900 (flatten noise-pattern))
594 (set-memory-range 0xC000 (music-kernel 0 0))
595 (PC! 0xC000))))
597 (defn test-play-noise [noise-code]
598 (println "playing-noise" noise-code)
599 (run-moves
600 (let [noise-pattern
601 (interleave (repeat 10 noise-code) (repeat 0xF0) (repeat 255))]
602 (-> (second (music-base))
603 (set-memory-range 0xA900 (flatten noise-pattern))
604 (set-memory-range 0xC000 (music-kernel 0 0))
605 (PC! 0xC000)))
606 (repeat 128 [])))
608 (defn test-all-noises []
609 (dorun (map test-play-noise (range 0x100))))
613 (def C4 (partial note-codes 261.63))
614 (def D4 (partial note-codes 293.66))
615 (def E4 (partial note-codes 329.63))
616 (def F4 (partial note-codes 349.23))
617 (def G4 (partial note-codes 392))
618 (def A4 (partial note-codes 440))
619 (def B4 (partial note-codes 493.88))
620 (def C5 (partial note-codes 523.3))
622 (def scale
623 (flatten
624 [(C4 0xF 0x40)
625 (D4 0xF 0x40)
626 (E4 0xF 0x40)
627 (F4 0xF 0x40)
628 (G4 0xF 0x40)
629 (A4 0xF 0x40)
630 (B4 0xF 0x40)
631 (C5 0xF 0x40)]))
633 (defn play-music [music-bytes]
634 (let [program-target 0xC000
635 music-target 0xA000]
636 (-> (set-memory-range (second (music-base))
637 program-target (music-kernel))
638 (set-memory-range music-target music-bytes)
639 (PC! program-target))))
641 (defn run-program
642 ([program]
643 (let [target 0xC000]
644 (-> (set-memory-range (second (music-base))
645 target program)
646 (PC! target)))))
648 (defn test-timer []
649 (flatten
650 [0x3E
651 0x01
652 0xE0
653 0x06 ;; set TMA to 0
655 0x3E
656 (Integer/parseInt "00000100" 2)
657 0xE0
658 0x07 ;; set TAC to 16384 Hz and activate timer
660 (repeat
661 500
662 [0xF0
663 0x05])]))