view clojure/com/aurellem/run/music.clj @ 477:ee000791ab4e

formatted ship-of-regret-and-sleep.mid for smooth mini-midi conversion.
author Robert McIntyre <rlm@mit.edu>
date Fri, 04 May 2012 07:23:07 -0500
parents f28a3baa4c56
children 91db9d1ce213
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"))
20 (def drum-test
21 (File. "/home/r/proj/vba-clojure/music/drum-test.mid"))
23 (def regret
24 (File. "/home/r/proj/vba-clojure/music/ship-of-regret-and-sleep.mid"))
26 (defn raw-midi-text [#^File midi-file]
27 (:out
28 (clojure.java.shell/sh
29 "midicsv"
30 (.getCanonicalPath midi-file)
31 "-")))
33 (def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
35 (defmulti parse-command :command)
37 (defn discard-args [command] (dissoc command :args))
39 (defmethod parse-command :Start_track
40 [command] (discard-args command))
42 (defmethod parse-command :End_track
43 [command] (discard-args command))
45 (defmethod parse-command :default
46 [command] command)
48 (defn parse-number-list
49 [number-list-str]
50 (map #(Integer/parseInt %)
51 (clojure.string/split number-list-str #", ")))
53 (defmethod parse-command :Tempo
54 [command]
55 (update-in command [:args] #(Integer/parseInt %)))
57 (defn parse-midi-note-list
58 [midi-note-list-str]
59 (let [[channel note velocity]
60 (parse-number-list midi-note-list-str)]
61 {:channel channel :note note :velocity velocity}))
63 (defmethod parse-command :Note_on_c
64 [command]
65 (update-in command [:args] parse-midi-note-list))
67 (defmethod parse-command :Note_off_c
68 [command]
69 (update-in command [:args] parse-midi-note-list))
71 (defmethod parse-command :Header
72 [command]
73 (let [args (:args command)
74 [format num-tracks division] (parse-number-list args)]
75 (assoc command :args
76 {:format format
77 :num-tracks num-tracks
78 :division division})))
80 (defmethod parse-command :Program_c
81 [command]
82 (let [args (:args command)
83 [channel program-num] (parse-number-list args)]
84 (assoc command :args
85 {:channel channel
86 :program-num program-num})))
88 (defn parse-midi [#^File midi-file]
89 (map
90 (comp parse-command
91 (fn [line]
92 (let [[[_ channel time command args]]
93 (re-seq command-line line)]
94 {:channel (Integer/parseInt channel)
95 :time (Integer/parseInt time)
96 :command (keyword command)
97 :args (apply str (drop 2 args))})))
98 (drop-last
99 (clojure.string/split-lines
100 (raw-midi-text midi-file)))))
102 (def music-base new-kernel)
104 (defn store [n address]
105 (flatten
106 [0xF5
107 0xE5
109 0x3E
110 n
112 0x21
113 (reverse (disect-bytes-2 address))
115 0x77
117 0xE1
118 0xF1]))
120 (defn infinite-loop []
121 [0x18 0xFE])
123 (def divider-register 0xFF04)
125 (defrecord Bit-Note [frequency volume duration duty])
127 (defn clear-music-registers []
128 (flatten
129 [(store (Integer/parseInt "00000000" 2) 0xFF10) ;; sweep
130 (store (Integer/parseInt "00000000" 2) 0xFF11) ;; pattern duty
131 (store (Integer/parseInt "00000000" 2) 0xFF12) ;; volume
132 (store (Integer/parseInt "00000000" 2) 0xFF13) ;; frequency-low
133 (store (Integer/parseInt "00000000" 2) 0xFF14) ;; frequency-high
135 (store (Integer/parseInt "00000000" 2) 0xFF16) ;; pattern duty 000000
136 (store (Integer/parseInt "00000000" 2) 0xFF17) ;; volume 0000
137 (store (Integer/parseInt "00000000" 2) 0xFF18) ;; frequency-low
138 (store (Integer/parseInt "00000000" 2) 0xFF19) ;; 00000 frequency-high
140 (store (Integer/parseInt "00000000" 2) 0xFF1A)
141 (store (Integer/parseInt "00000000" 2) 0xFF1B)
142 (store (Integer/parseInt "00000000" 2) 0xFF1C)
143 (store (Integer/parseInt "00000000" 2) 0xFF1D)
144 (store (Integer/parseInt "00000000" 2) 0xFF1E)
146 (store (Integer/parseInt "00000000" 2) 0xFF20) ;; length
147 (store (Integer/parseInt "00000000" 2) 0xFF21) ;; volume
148 (store (Integer/parseInt "00000000" 2) 0xFF22) ;; noise-frequency
149 (store (Integer/parseInt "00000000" 2) 0xFF23) ;; control
150 ]))
153 ;; mini-midi syntax
155 ;; codes
156 ;; note-code == 0x00
157 ;; change-duty-code = 0x01
158 ;; silence-code = 0x02
160 ;; silence format
161 ;; 2 bytes
162 ;; [silence-code (0x02)]
163 ;; [duration-8-bits]
165 ;; note data format
166 ;; 4 bytes
167 ;; [note-code (0x00)]
168 ;; [volume-4-bits 0 frequency-high-3-bits]
169 ;; [frequengy-low-8-bits]
170 ;; [duration-8-bits]
172 ;; change-duty-format
173 ;; 2 bytes
174 ;; [change-duty-code (0x01)]
175 ;; [new-duty]
177 (def note-code 0x00)
178 (def change-duty-code 0x01)
179 (def silence-code 0x02)
181 (defn do-message
182 "Read the message which starts at the current value of HL and do
183 what it says. Duration is left in A, and HL is advanced
184 appropraitely."
185 ([] (do-message 0x16 1))
186 ([sound-base-address wave-duty]
187 (assert (<= 0 wave-duty 3))
188 (let [switch
189 [0x2A ;; load message code into A, increment HL
191 ;; switch on message
192 0xFE
193 note-code
195 0x20
196 :note-length]
198 play-note
199 [0x3E ;; set wave-duty
200 (bit-shift-left wave-duty 6)
201 0xE0
202 sound-base-address
203 0x2A ;; load volume/frequency-high info
204 0xF5 ;; push A
205 0xE6
206 (Integer/parseInt "11110000" 2) ;; volume mask
207 0xE0
208 (inc sound-base-address) ;;0x17 ;; set volume
209 0xF1 ;; pop A
210 0xE6
211 (Integer/parseInt "00000111" 2) ;; frequency-high mask
212 0xE0
213 (+ 3 sound-base-address) ;;0x19 ;; set frequency-high
215 0x2A ;; load frequency low-bits
216 0xE0
217 (+ 2 sound-base-address) ;;0x18 ;; set frequency-low-bits
218 0x2A]] ;; load duration
219 (replace
220 {:note-length (count play-note)}
221 (concat switch play-note)))))
223 (defn play-noise
224 "read [noise-code, volume, duration] and play the noise. Duration is left in
225 A, and HL is advanced appropraitely."
226 ([]
227 [0x2A ;; load noise-code into A
228 0xE0
229 0x22 ;; write noise-code
231 0x2A ;; load volume
232 0xE0
233 0x21 ;; write volume
235 0x2A] ;; load duration into A
236 ))
239 ;; (defn play-note
240 ;; "Play the note referenced by HL in the appropiate channel.
241 ;; Leaves desired-duration in A."
243 ;; [0x2A ;; load volume/frequency-high info
244 ;; 0xF5 ;; push A
245 ;; 0xE6
246 ;; (Integer/parseInt "11110000" 2) ;; volume mask
247 ;; 0xE0
248 ;; 0x17 ;; set volume
249 ;; 0xF1 ;; pop A
250 ;; 0xE6
251 ;; (Integer/parseInt "00000111" 2) ;; frequency-high mask
252 ;; 0xE0
253 ;; 0x19 ;; set frequency-high
255 ;; 0x2A ;; load frequency low-bits
256 ;; 0xE0
257 ;; 0x18 ;; set frequency-low-bits
259 ;; 0x2A ;; load duration
260 ;; ])
262 (defn music-step [sound-base-address wave-duty noise?]
263 ;; C == current-ticks
264 ;; A == desired-ticks
266 (flatten
267 [;; restore variables from stack
268 0xE1 ;; pop HL
269 0xC1 ;; pop CB
270 0xF1 ;; pop AF
273 0xF5 ;; push A
274 0xF0
275 0x05 ;; load current ticks from 0xF005
276 0xB8 ;;
277 0x30 ;; increment C only if last result caused carry
278 0x01
279 0x0C
281 0x47 ;; update sub-ticks (A->B)
283 0xF1 ;; pop AF, now A contains desired-ticks
285 0xB9 ;; compare with current ticks
287 ;; if desired-ticks = current ticks
288 ;; go to next note ; set current set ticks to 0.
290 (if noise?
291 [0x20
292 (+ 2 (count (play-noise)))
293 (play-noise)]
295 [0x20
296 (+ (count (do-message 0 0)) 2)
297 (do-message sound-base-address wave-duty)])
299 0x0E
300 0x00 ;; 0->C (current-ticks)
302 ;; save variables to stack
303 0xF5 ;; push AF
304 0xC5 ;; push CB
305 0xE5 ;; push HL
308 ]))
310 (def music-1 0x11)
311 (def music-2 0x16)
313 (defn music-kernel [wave-duty-1 wave-duty-2]
314 (flatten
315 [;; global initilization section
316 (clear-music-registers)
318 0x3E
319 0x01
320 0xE0
321 0x06 ;; set TMA to 0
323 0x3E
324 (Integer/parseInt "00000110" 2)
325 0xE0
326 0x07 ;; set TAC to 65536 Hz and activate timer
328 ;; initialize frame 1
329 0x21
330 0x00
331 0xA0 ;; set HL to 0xA000 == music-start 1
332 0x0E
333 0x00 ;; 0->C
334 0x06
335 0x00 ;; 0->B
337 0xAF ;; 0->A
339 0xF5 ;; push AF
340 0xC5 ;; push CB
341 0xE5 ;; push HL
343 ;; initialize frame 2
344 0x21
345 0x00
346 0xB0 ;; set HL to 0xB000 == music-start 2
348 0xF5 ;; push AF
349 0xC5 ;; push CB
350 0xE5 ;; push HL
353 ;; initialize frame 3 (noise)
354 0x21
355 0x00
356 0xA9 ;; 0xA9OO -> HL
358 0xF5 ;; push AF
359 0xC5 ;; push CB
360 0xE5 ;; push HL
362 ;; main music loop
364 0xE8 ;; SP + 12; activate frame 1
365 12
366 (music-step music-1 wave-duty-1 false)
368 0xE8 ;; SP - 6; activate frame 2
369 (->signed-8-bit -6)
370 (music-step music-2 wave-duty-2 false)
372 0xE8 ;; SP - 6; activate frame 3
373 (->signed-8-bit -6)
374 (music-step nil nil true)
376 0x18
377 (->signed-8-bit (+
378 ;; two music-steps
379 (- (* 2 (count (music-step 0 0 false))))
380 (- (count (music-step nil nil true)))
381 -2 ;; this jump instruction
382 -2 ;; activate frame 1
383 -2 ;; activate frame 2
384 -2 ;; activate frame 3
385 ))]))
387 (defn frequency-code->frequency
388 [code]
389 (assert (<= 0 code 2047))
390 (/ 131072 (- 2048 code)))
392 (defn clamp [x low high]
393 (cond (> x high) high
394 (< x low) low
395 true x))
397 (defn frequency->frequency-code
398 [frequency]
399 (clamp
400 (Math/round
401 (float
402 (/ (- (* 2048 frequency) 131072) frequency)))
403 0x00 2048))
405 (defn note-codes [frequency volume duration]
406 (assert (<= 0 volume 0xF))
407 (if (<= duration 0xFF)
408 (let [frequency-code
409 (frequency->frequency-code frequency)
410 volume&high-frequency
411 (+ (bit-shift-left volume 4)
412 (bit-shift-right frequency-code 8))
413 low-frequency
414 (bit-and 0xFF frequency-code)]
415 [note-code
416 volume&high-frequency
417 low-frequency
418 duration])
419 (vec
420 (flatten
421 [(note-codes frequency volume 0xFF)
422 (note-codes frequency volume (- duration 0xFF))]))))
425 (defn midi-code->frequency
426 [midi-code]
427 (* 8.1757989156
428 (Math/pow 2 (* (float (/ 12)) midi-code))))
430 ;; division == clock-pulses / quarter-note
431 ;; tempo == microseconds / quarter-note
433 ;; have: clock-pulses
434 ;; want: seconds
437 (defn silence [length]
438 {:frequency 1
439 :duration length
440 :volume 0})
442 (defn commands
443 "return all events where #(= (:command %) command)"
444 [command s]
445 (filter #(= command (:command %)) s))
447 (defn track-info [#^File midi-file]
448 (let [events (parse-midi midi-file)
449 track-titles (commands :Title_t events)
450 track-info
451 (map #(read-string (read-string (:args %))) track-titles)
452 track-map
453 (zipmap track-info track-titles)]
454 track-map))
456 (defn target-tracks
457 "return the track-numbers in the form [voice-0 voice-1 noise]"
458 [#^File midi-file]
459 (let [track-data (track-info midi-file)
460 track-order
461 (zipmap (map :out (keys track-data))
462 (vals track-data))
463 channel-nums (map (comp :channel track-order) (range 3))]
464 channel-nums))
466 (defn midi-track->abstract-mini-midi
467 [#^File midi-file track-num]
468 (let [midi-events (parse-midi midi-file)
470 note-on-events (commands :Note_on_c midi-events)
471 note-off-events (commands :Note_off_c midi-events)
473 select-channel
474 (fn [n s]
475 (sort-by :time (filter #(= n (:channel %)) s)))
477 channel-on (select-channel track-num note-on-events)
479 channel-off (select-channel track-num note-off-events)
482 tempo (:args (first (commands :Tempo midi-events)))
483 division
484 (:division (:args (first (commands :Header midi-events))))
486 notes
487 (map
488 (fn [note-on note-off]
489 {:frequency (midi-code->frequency (:note (:args note-on)))
490 :midi-code (:note (:args note-on))
491 :duration
492 (/ (* (/ tempo division)
493 (- (:time note-off) (:time note-on)))
494 1e6) ;; convert clock-pulses into seconds
495 :volume (int (/ (:velocity (:args note-on)) 10))
496 :time-stamp (/ (* (/ tempo division)
497 (:time note-on)) 1e6)})
498 channel-on channel-off)
500 silences
501 (map (fn [note-1 note-2]
502 (let [note-1-space (- (:time-stamp note-2)
503 (:time-stamp note-1))
504 note-1-length (:duration note-1)]
505 (silence (- note-1-space note-1-length))))
506 ;; to handle silence at the beginning.
507 (concat [(assoc (silence 0)
508 :time-stamp 0)] notes)
509 notes)
511 notes-with-silence
512 (concat
513 (filter (comp not zero? :duration)
514 (interleave silences notes))
515 [(silence 3)])]
516 notes-with-silence))
518 (defn midi-track->mini-midi-voice [#^File midi-file track-num]
519 (let [abstract-mini-midi
520 (midi-track->abstract-mini-midi midi-file track-num)]
521 (map
522 (fn [note-event]
523 (note-codes (:frequency note-event)
524 (:volume note-event)
525 (int (* (:duration note-event) 0x100))))
526 abstract-mini-midi)))
528 (def midi-code->gb-noise-code
529 {nil 0xFF
530 35 87
531 38 20
532 39 0
533 })
535 (defn noise-codes [code volume duration]
536 (assert (<= 0 volume 0xF))
537 (if (<= duration 0xFF)
538 [(midi-code->gb-noise-code code code)
539 (bit-shift-left volume 4)
540 duration]
541 (vec
542 (flatten
543 [(noise-codes code volume 0xFF)
544 (noise-codes code volume (- duration 0xFF))]))))
546 (defn midi-track->mini-midi-noise [#^File midi-file track-num]
547 (let [abstract-mini-midi
548 (midi-track->abstract-mini-midi midi-file track-num)]
549 (map
550 (fn [noise-event]
551 (noise-codes (:midi-code noise-event)
552 (:volume noise-event)
553 (int (* (:duration noise-event) 0x100))))
554 abstract-mini-midi)))
557 (defn midi->mini-midi [#^File midi-file]
558 (let [targets (target-tracks midi-file)
559 duty-info (keys (track-info midi-file))]
561 {:voice-1 (midi-track->mini-midi-voice midi-file (nth targets 0))
562 :voice-2 (midi-track->mini-midi-voice midi-file (nth targets 1))
563 :noise (midi-track->mini-midi-noise midi-file (nth targets 2))
564 :duty (zipmap (map :out duty-info)
565 (map #(get % :duty 0) duty-info))}))
567 (defn play-midi [#^File midi-file]
568 (let [voice-1-target 0xA000
569 voice-2-target 0xB000
570 noise-target 0xA900
571 program-target 0xC000
572 mini-midi (midi->mini-midi midi-file)
573 long-silence (flatten (note-codes 20 0 20001))
574 long-noise-silence
575 (interleave (range 500) (repeat 0x00) (repeat 255))
577 voice-1 (flatten (:voice-1 mini-midi))
578 wave-duty-1 ((:duty mini-midi) 0 0)
580 voice-2 (flatten (:voice-2 mini-midi))
581 wave-duty-2 ((:duty mini-midi) 1 0)
583 noise (flatten (:noise mini-midi))
584 ]
586 (-> (second (music-base))
587 (set-memory-range voice-1-target long-silence)
588 (set-memory-range voice-2-target long-silence)
589 (set-memory-range noise-target long-noise-silence)
590 (set-memory-range voice-1-target voice-1)
591 (set-memory-range voice-2-target voice-2)
592 (set-memory-range noise-target noise)
593 (set-memory-range
594 program-target
595 (music-kernel wave-duty-1 wave-duty-2))
596 (PC! program-target))))
598 (defn test-noise []
599 (let [noise-pattern
600 (concat (interleave (range 0x100) (repeat 0xF0) (repeat 255))
601 (interleave (range 10) (repeat 0x00) (repeat 255)))]
603 (-> (second (music-base))
604 (set-memory-range 0xA900 (flatten noise-pattern))
605 (set-memory-range 0xC000 (music-kernel 0 0))
606 (PC! 0xC000))))
608 (defn test-play-noise [noise-code]
609 (Thread/sleep 300)
610 (println "playing noise:" noise-code)
611 (run-moves
612 (let [noise-pattern
613 (interleave (repeat 10 noise-code) (repeat 0xF0) (repeat 255))]
614 (-> (second (music-base))
615 (set-memory-range 0xA900 (flatten noise-pattern))
616 (set-memory-range 0xC000 (music-kernel 0 0))
617 (PC! 0xC000)))
618 (repeat 20 [])))
620 (defn test-all-noises []
621 (dorun (map test-play-noise (range 0x100))))
623 (def C4 (partial note-codes 261.63))
624 (def D4 (partial note-codes 293.66))
625 (def E4 (partial note-codes 329.63))
626 (def F4 (partial note-codes 349.23))
627 (def G4 (partial note-codes 392))
628 (def A4 (partial note-codes 440))
629 (def B4 (partial note-codes 493.88))
630 (def C5 (partial note-codes 523.3))
632 (def scale
633 (flatten
634 [(C4 0xF 0x40)
635 (D4 0xF 0x40)
636 (E4 0xF 0x40)
637 (F4 0xF 0x40)
638 (G4 0xF 0x40)
639 (A4 0xF 0x40)
640 (B4 0xF 0x40)
641 (C5 0xF 0x40)]))
643 (defn play-music [music-bytes]
644 (let [program-target 0xC000
645 music-target 0xA000]
646 (-> (set-memory-range (second (music-base))
647 program-target (music-kernel))
648 (set-memory-range music-target music-bytes)
649 (PC! program-target))))
651 (defn run-program
652 ([program]
653 (let [target 0xC000]
654 (-> (set-memory-range (second (music-base))
655 target program)
656 (PC! target)))))
658 (defn test-timer []
659 (flatten
660 [0x3E
661 0x01
662 0xE0
663 0x06 ;; set TMA to 0
665 0x3E
666 (Integer/parseInt "00000100" 2)
667 0xE0
668 0x07 ;; set TAC to 16384 Hz and activate timer
670 (repeat
671 500
672 [0xF0
673 0x05])]))