view clojure/com/aurellem/run/music.clj @ 466:b31cd6651375

working on noise.
author Robert McIntyre <rlm@mit.edu>
date Fri, 04 May 2012 04:13:13 -0500
parents 34bf4b64d9d1
children ac0ed5c1a1c4
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, 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
225 0x2A] ;; load duration into A
226 ))
229 ;; (defn play-note
230 ;; "Play the note referenced by HL in the appropiate channel.
231 ;; Leaves desired-duration in A."
233 ;; [0x2A ;; load volume/frequency-high info
234 ;; 0xF5 ;; push A
235 ;; 0xE6
236 ;; (Integer/parseInt "11110000" 2) ;; volume mask
237 ;; 0xE0
238 ;; 0x17 ;; set volume
239 ;; 0xF1 ;; pop A
240 ;; 0xE6
241 ;; (Integer/parseInt "00000111" 2) ;; frequency-high mask
242 ;; 0xE0
243 ;; 0x19 ;; set frequency-high
245 ;; 0x2A ;; load frequency low-bits
246 ;; 0xE0
247 ;; 0x18 ;; set frequency-low-bits
249 ;; 0x2A ;; load duration
250 ;; ])
252 (defn music-step [sound-base-address wave-duty noise?]
253 ;; C == current-ticks
254 ;; A == desired-ticks
256 (flatten
257 [;; restore variables from stack
258 0xE1 ;; pop HL
259 0xC1 ;; pop CB
260 0xF1 ;; pop AF
263 0xF5 ;; push A
264 0xF0
265 0x05 ;; load current ticks from 0xF005
266 0xB8 ;;
267 0x30 ;; increment C only if last result caused carry
268 0x01
269 0x0C
271 0x47 ;; update sub-ticks (A->B)
273 0xF1 ;; pop AF, now A contains desired-ticks
275 0xB9 ;; compare with current ticks
277 ;; if desired-ticks = current ticks
278 ;; go to next note ; set current set ticks to 0.
280 (if noise?
281 [0x20
282 (+ 2 (count (play-noise)))
283 (play-noise)]
285 [0x20
286 (+ (count (do-message 0 0)) 2)
287 (do-message sound-base-address wave-duty)])
289 0x0E
290 0x00 ;; 0->C (current-ticks)
292 ;; save variables to stack
293 0xF5 ;; push AF
294 0xC5 ;; push CB
295 0xE5 ;; push HL
298 ]))
300 (def music-1 0x11)
301 (def music-2 0x16)
303 (defn music-kernel [wave-duty-1 wave-duty-2]
304 (flatten
305 [;; global initilization section
306 (clear-music-registers)
308 0x3E
309 0x01
310 0xE0
311 0x06 ;; set TMA to 0
313 0x3E
314 (Integer/parseInt "00000110" 2)
315 0xE0
316 0x07 ;; set TAC to 65536 Hz and activate timer
318 ;; initialize frame 1
319 0x21
320 0x00
321 0xA0 ;; set HL to 0xA000 == music-start 1
322 0x0E
323 0x00 ;; 0->C
324 0x06
325 0x00 ;; 0->B
327 0xAF ;; 0->A
329 0xF5 ;; push AF
330 0xC5 ;; push CB
331 0xE5 ;; push HL
333 ;; initialize frame 2
334 0x21
335 0x00
336 0xB0 ;; set HL to 0xB000 == music-start 2
338 0xF5 ;; push AF
339 0xC5 ;; push CB
340 0xE5 ;; push HL
343 ;; initialize frame 3 (noise)
344 0x21
345 0x00
346 0xA9 ;; 0xA9OO -> HL
348 0xF5 ;; push AF
349 0xC5 ;; push CB
350 0xE5 ;; push HL
352 ;; main music loop
354 0xE8 ;; SP + 12; activate frame 1
355 12
356 (music-step music-1 wave-duty-1 false)
358 0xE8 ;; SP - 6; activate frame 2
359 (->signed-8-bit -6)
360 (music-step music-2 wave-duty-2 false)
362 0xE8 ;; SP - 6; activate frame 3
363 (->signed-8-bit -6)
364 (music-step nil nil true)
366 0x18
367 (->signed-8-bit (+
368 ;; two music-steps
369 (- (* 2 (count (music-step 0 0 false))))
370 (- (count (music-step nil nil true)))
371 -2 ;; this jump instruction
372 -2 ;; activate frame 1
373 -2 ;; activate frame 2
374 -2 ;; activate frame 3
375 ))]))
377 (defn frequency-code->frequency
378 [code]
379 (assert (<= 0 code 2047))
380 (/ 131072 (- 2048 code)))
382 (defn clamp [x low high]
383 (cond (> x high) high
384 (< x low) low
385 true x))
387 (defn frequency->frequency-code
388 [frequency]
389 (clamp
390 (Math/round
391 (float
392 (/ (- (* 2048 frequency) 131072) frequency)))
393 0x00 2048))
395 (defn note-codes [frequency volume duration]
396 (assert (<= 0 volume 0xF))
397 (if (<= duration 0xFF)
398 (let [frequency-code
399 (frequency->frequency-code frequency)
400 volume&high-frequency
401 (+ (bit-shift-left volume 4)
402 (bit-shift-right frequency-code 8))
403 low-frequency
404 (bit-and 0xFF frequency-code)]
405 [note-code
406 volume&high-frequency
407 low-frequency
408 duration])
409 (vec
410 (flatten
411 [(note-codes frequency volume 0xFF)
412 (note-codes frequency volume (- duration 0xFF))]))))
415 (defn midi-code->frequency
416 [midi-code]
417 (* 8.1757989156
418 (Math/pow 2 (* (float (/ 12)) midi-code))))
420 ;; division == clock-pulses / quarter-note
421 ;; tempo == microseconds / quarter-note
423 ;; have: clock-pulses
424 ;; want: seconds
427 (defn silence [length]
428 {:frequency 1
429 :duration length
430 :volume 0})
432 (defn commands
433 "return all events where #(= (:command %) command)"
434 [command s]
435 (filter #(= command (:command %)) s))
437 (defn track-info [#^File midi-file]
438 (let [events (parse-midi midi-file)
439 track-titles (commands :Title_t events)
440 track-info
441 (map #(read-string (read-string (:args %))) track-titles)
442 track-map
443 (zipmap track-info track-titles)]
444 track-map))
446 (defn target-tracks
447 "return the track-numbers in the form [voice-0 voice-1 noise]"
448 [#^File midi-file]
449 (let [track-data (track-info midi-file)
450 track-order
451 (zipmap (map :out (keys track-data))
452 (vals track-data))
453 channel-nums (map (comp :channel track-order) (range 3))]
454 channel-nums))
456 (defn midi-track->mini-midi [#^File midi-file track-num]
457 (let [midi-events (parse-midi midi-file)
459 note-on-events (commands :Note_on_c midi-events)
460 note-off-events (commands :Note_off_c midi-events)
462 select-channel
463 (fn [n s]
464 (sort-by :time (filter #(= n (:channel %)) s)))
466 channel-on (select-channel track-num note-on-events)
468 channel-off (select-channel track-num note-off-events)
471 tempo (:args (first (commands :Tempo midi-events)))
472 division
473 (:division (:args (first (commands :Header midi-events))))
475 notes
476 (map
477 (fn [note-on note-off]
478 {:frequency (midi-code->frequency (:note (:args note-on)))
479 :duration
480 (/ (* (/ tempo division)
481 (- (:time note-off) (:time note-on)))
482 1e6) ;; convert clock-pulses into seconds
483 :volume (int (/ (:velocity (:args note-on)) 10))
484 :time-stamp (/ (* (/ tempo division)
485 (:time note-on)) 1e6)})
486 channel-on channel-off)
488 silences
489 (map (fn [note-1 note-2]
490 (let [note-1-space (- (:time-stamp note-2)
491 (:time-stamp note-1))
492 note-1-length (:duration note-1)]
493 (silence (- note-1-space note-1-length))))
494 ;; to handle silence at the beginning.
495 (concat [(assoc (silence 0)
496 :time-stamp 0)] notes)
497 notes)
499 notes-with-silence
500 (concat
501 (filter (comp not zero? :duration)
502 (interleave silences notes))
503 [(silence 3)])]
505 (map
506 (fn [note-event]
507 (note-codes (:frequency note-event)
508 (:volume note-event)
509 (int (* (:duration note-event) 0x100))))
510 notes-with-silence)))
512 (defn midi->mini-midi [#^File midi-file]
513 (let [targets (target-tracks midi-file)
514 get-track (fn [n]
515 (if (not (nil? n))
516 (midi-track->mini-midi midi-file n)
517 []))
518 duty-info (keys (track-info midi-file))]
520 {:voice-1 (get-track (nth targets 0))
521 :voice-2 (get-track (nth targets 1))
522 :noise (get-track (nth targets 2))
523 :duty (zipmap (map :out duty-info)
524 (map #(get % :duty 0) duty-info))}))
526 (defn play-midi [#^File midi-file]
527 (let [track-1-target 0xA000
528 track-2-target 0xB000
529 program-target 0xC000
530 mini-midi (midi->mini-midi midi-file)
531 long-silence (flatten (note-codes 20 0 9001))
533 voice-1 (flatten (:voice-1 mini-midi))
534 wave-duty-1 ((:duty mini-midi) 0 0)
536 voice-2 (flatten (:voice-2 mini-midi))
537 wave-duty-2 ((:duty mini-midi) 1 0)
539 noise (flatten (:noise mini-midi))
540 ]
542 (-> (second (music-base))
543 (set-memory-range track-1-target long-silence)
544 (set-memory-range track-2-target long-silence)
545 (set-memory-range track-1-target voice-1)
546 (set-memory-range track-2-target voice-2)
547 (set-memory-range
548 program-target
549 (music-kernel wave-duty-1 wave-duty-2))
550 (PC! program-target))))
552 (def C4 (partial note-codes 261.63))
553 (def D4 (partial note-codes 293.66))
554 (def E4 (partial note-codes 329.63))
555 (def F4 (partial note-codes 349.23))
556 (def G4 (partial note-codes 392))
557 (def A4 (partial note-codes 440))
558 (def B4 (partial note-codes 493.88))
559 (def C5 (partial note-codes 523.3))
561 (def scale
562 (flatten
563 [(C4 0xF 0x40)
564 (D4 0xF 0x40)
565 (E4 0xF 0x40)
566 (F4 0xF 0x40)
567 (G4 0xF 0x40)
568 (A4 0xF 0x40)
569 (B4 0xF 0x40)
570 (C5 0xF 0x40)]))
572 (defn play-music [music-bytes]
573 (let [program-target 0xC000
574 music-target 0xA000]
575 (-> (set-memory-range (second (music-base))
576 program-target (music-kernel))
577 (set-memory-range music-target music-bytes)
578 (PC! program-target))))
580 (defn run-program
581 ([program]
582 (let [target 0xC000]
583 (-> (set-memory-range (second (music-base))
584 target program)
585 (PC! target)))))
587 (defn test-timer []
588 (flatten
589 [0x3E
590 0x01
591 0xE0
592 0x06 ;; set TMA to 0
594 0x3E
595 (Integer/parseInt "00000100" 2)
596 0xE0
597 0x07 ;; set TAC to 16384 Hz and activate timer
599 (repeat
600 500
601 [0xF0
602 0x05])]))