view clojure/com/aurellem/run/music.clj @ 436:3171cbe077f3

created basic frame system for multiple voices.
author Robert McIntyre <rlm@mit.edu>
date Wed, 25 Apr 2012 12:41:30 -0500
parents 3939ad680681
children 20a9d5faf47c
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 (defn raw-midi-text [#^File midi-file]
15 (:out
16 (clojure.java.shell/sh
17 "midicsv"
18 (.getCanonicalPath midi-file)
19 "-")))
21 (def command-line #"^(\d+), (\d+), ([^,]+)(.*)$")
23 (defmulti parse-command :command)
25 (defn discard-args [command] (dissoc command :args))
27 (defmethod parse-command :Start_track
28 [command] (discard-args command))
30 (defmethod parse-command :End_track
31 [command] (discard-args command))
33 (defmethod parse-command :default
34 [command] command)
36 (defn parse-number-list
37 [number-list-str]
38 (map #(Integer/parseInt %)
39 (clojure.string/split number-list-str #", ")))
41 (defmethod parse-command :Tempo
42 [command]
43 (update-in command [:args] #(Integer/parseInt %)))
45 (defn parse-midi-note-list
46 [midi-note-list-str]
47 (let [[channel note velocity]
48 (parse-number-list midi-note-list-str)]
49 {:channel channel :note note :velocity velocity}))
51 (defmethod parse-command :Note_on_c
52 [command]
53 (update-in command [:args] parse-midi-note-list))
55 (defmethod parse-command :Note_off_c
56 [command]
57 (update-in command [:args] parse-midi-note-list))
59 (defmethod parse-command :Header
60 [command]
61 (let [args (:args command)
62 [format num-tracks division] (parse-number-list args)]
63 (assoc command :args
64 {:format format
65 :num-tracks num-tracks
66 :division division})))
68 (defmethod parse-command :Program_c
69 [command]
70 (let [args (:args command)
71 [channel program-num] (parse-number-list args)]
72 (assoc command :args
73 {:channel channel
74 :program-num program-num})))
76 (defn parse-midi [#^File midi-file]
77 (map
78 (comp parse-command
79 (fn [line]
80 (let [[[_ channel time command args]]
81 (re-seq command-line line)]
82 {:channel (Integer/parseInt channel)
83 :time (Integer/parseInt time)
84 :command (keyword command)
85 :args (apply str (drop 2 args))})))
86 (drop-last
87 (clojure.string/split-lines
88 (raw-midi-text midi-file)))))
90 (def music-base new-kernel)
92 (defn store [n address]
93 (flatten
94 [0xF5
95 0xE5
97 0x3E
98 n
100 0x21
101 (reverse (disect-bytes-2 address))
103 0x77
105 0xE1
106 0xF1]))
108 (defn infinite-loop []
109 [0x18 0xFE])
111 (def divider-register 0xFF04)
113 (defrecord Bit-Note [frequency volume duration duty])
115 (defn clear-music-registers []
116 (flatten
117 [(store (Integer/parseInt "00000000" 2) 0xFF10) ;; sweep
118 (store (Integer/parseInt "00000000" 2) 0xFF11) ;; pattern duty
119 (store (Integer/parseInt "00000000" 2) 0xFF12) ;; volume
120 (store (Integer/parseInt "00000000" 2) 0xFF13) ;; frequency-low
121 (store (Integer/parseInt "00000000" 2) 0xFF14) ;; frequency-high
123 (store (Integer/parseInt "00000000" 2) 0xFF16) ;; pattern duty 000000
124 (store (Integer/parseInt "00000000" 2) 0xFF17) ;; volume 0000
125 (store (Integer/parseInt "00000000" 2) 0xFF18) ;; frequency-low
126 (store (Integer/parseInt "00000000" 2) 0xFF19) ;; 00000 frequency-high
128 (store (Integer/parseInt "00000000" 2) 0xFF1A)
129 (store (Integer/parseInt "00000000" 2) 0xFF1B)
130 (store (Integer/parseInt "00000000" 2) 0xFF1C)
131 (store (Integer/parseInt "00000000" 2) 0xFF1D)
132 (store (Integer/parseInt "00000000" 2) 0xFF1E)
134 (store (Integer/parseInt "00000000" 2) 0xFF20) ;; length
135 (store (Integer/parseInt "00000000" 2) 0xFF21) ;; volume
136 (store (Integer/parseInt "00000000" 2) 0xFF22) ;; noise-frequency
137 (store (Integer/parseInt "00000000" 2) 0xFF23) ;; control
138 ]))
141 ;; mini-midi syntax
143 ;; codes
144 ;; note-code == 0x00
145 ;; change-duty-code = 0x01
146 ;; silence-code = 0x02
148 ;; silence format
149 ;; 2 bytes
150 ;; [silence-code (0x02)]
151 ;; [duration-8-bits]
153 ;; note data format
154 ;; 4 bytes
155 ;; [note-code (0x00)]
156 ;; [volume-4-bits 0 frequency-high-3-bits]
157 ;; [frequengy-low-8-bits]
158 ;; [duration-8-bits]
160 ;; change-duty-format
161 ;; 2 bytes
162 ;; [change-duty-code (0x01)]
163 ;; [new-duty]
165 (def note-code 0x00)
166 (def change-duty-code 0x01)
167 (def silence-code 0x02)
169 (defn do-message
170 "Read the message which starts at the current value of HL and do
171 what it says. Duration is left in A, and HL is advanced
172 appropraitely."
173 ([] (do-message 0x16))
174 ([sound-base-address]
175 (let [switch
176 [0x2A ;; load message code into A, increment HL
178 ;; switch on message
179 0xFE
180 note-code
182 0x20
183 :note-length]
185 play-note
186 [0x2A ;; load volume/frequency-high info
187 0xF5 ;; push A
188 0xE6
189 (Integer/parseInt "11110000" 2) ;; volume mask
190 0xE0
191 (inc sound-base-address) ;;0x17 ;; set volume
192 0xF1 ;; pop A
193 0xE6
194 (Integer/parseInt "00000111" 2) ;; frequency-high mask
195 0xE0
196 (+ 3 sound-base-address) ;;0x19 ;; set frequency-high
198 0x2A ;; load frequency low-bits
199 0xE0
200 (+ 2 sound-base-address) ;;0x18 ;; set frequency-low-bits
201 0x2A]] ;; load duration
202 (replace
203 {:note-length (count play-note)}
204 (concat switch play-note)))))
206 ;; (defn play-note
207 ;; "Play the note referenced by HL in the appropiate channel.
208 ;; Leaves desired-duration in A."
210 ;; [0x2A ;; load volume/frequency-high info
211 ;; 0xF5 ;; push A
212 ;; 0xE6
213 ;; (Integer/parseInt "11110000" 2) ;; volume mask
214 ;; 0xE0
215 ;; 0x17 ;; set volume
216 ;; 0xF1 ;; pop A
217 ;; 0xE6
218 ;; (Integer/parseInt "00000111" 2) ;; frequency-high mask
219 ;; 0xE0
220 ;; 0x19 ;; set frequency-high
222 ;; 0x2A ;; load frequency low-bits
223 ;; 0xE0
224 ;; 0x18 ;; set frequency-low-bits
226 ;; 0x2A ;; load duration
227 ;; ])
229 (def storage-start 0xC999)
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 0xD0 ;; set HL to 0xD000 == 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 0xC0 ;; set HL to 0xC000 == music-start 2
313 0xF5 ;; push AF
314 0xC5 ;; push CB
315 0xE5 ;; push HL
319 ;; init-2 (0->A,B,C), 0xC000 -> HL
321 ;; push to stack
323 0xE8 ;; SP + 8
324 6
325 ;; pop from stack
326 (music-step music-1)
327 ;; save to stack
330 ;; SP + 5
331 ;; pop from stack
332 ;;(music-step music-2)
333 ;; save to stack
335 0x18
336 (->signed-8-bit (+ (- (count (music-step 0)))
337 -2))]))
339 (defn frequency-code->frequency
340 [code]
341 (assert (<= 0 code 2047))
342 (/ 131072 (- 2048 code)))
344 (defn clamp [x low high]
345 (cond (> x high) high
346 (< x low) low
347 true x))
349 (defn frequency->frequency-code
350 [frequency]
351 (clamp
352 (Math/round
353 (float
354 (/ (- (* 2048 frequency) 131072) frequency)))
355 0x00 2048))
357 (defn note-codes [frequency volume duration]
358 (assert (<= 0 volume 0xF))
359 (if (<= duration 0xFF)
360 (let [frequency-code
361 (frequency->frequency-code frequency)
362 volume&high-frequency
363 (+ (bit-shift-left volume 4)
364 (bit-shift-right frequency-code 8))
365 low-frequency
366 (bit-and 0xFF frequency-code)]
367 [note-code
368 volume&high-frequency
369 low-frequency
370 duration])
371 (vec
372 (flatten
373 [(note-codes frequency volume 0xFF)
374 (note-codes frequency volume (- duration 0xFF))]))))
377 (defn midi-code->frequency
378 [midi-code]
379 (* 8.1757989156
380 (Math/pow 2 (* (float (/ 12)) midi-code))))
382 ;; division == clock-pulses / quarter-note
383 ;; tempo == microseconds / quarter-note
385 ;; have: clock-pulses
386 ;; want: seconds
389 (defn silence [length]
390 {:frequency 1
391 :duration length
392 :volume 0})
394 (defn midi->mini-midi [#^File midi-file]
395 (let [midi-events (parse-midi midi-file)
397 note-on-events
398 (filter #(= :Note_on_c (:command %)) midi-events)
399 note-off-events
400 (filter #(= :Note_off_c (:command %)) midi-events)
402 channel-1-on
403 (sort-by :time
404 (filter #(= 1 (:channel (:args %)))
405 note-on-events))
406 channel-1-off
407 (sort-by :time
408 (filter #(= 1 (:channel (:args %)))
409 note-off-events))
412 tempo (:args (first (filter #(= :Tempo (:command %)) midi-events)))
413 division (:division
414 (:args (first (filter #(= :Header (:command %)) midi-events))))
416 notes
417 (map
418 (fn [note-on note-off]
419 {:frequency (midi-code->frequency (:note (:args note-on)))
420 :duration
421 (/ (* (/ tempo division)
422 (- (:time note-off) (:time note-on)))
423 1e6) ;; convert clock-pulses into seconds
424 :volume (int (/ (:velocity (:args note-on)) 10))
425 :time-stamp (/ (* (/ tempo division)
426 (:time note-on)) 1e6)})
427 channel-1-on channel-1-off)
429 silences
430 (map (fn [note-1 note-2]
431 (let [note-1-space (- (:time-stamp note-2)
432 (:time-stamp note-1))
433 note-1-length (:duration note-1)]
434 (silence (- note-1-space note-1-length))))
435 ;; to handle silence at the beginning.
436 (concat [(assoc (silence 0)
437 :time-stamp 0)] notes)
438 notes)
440 notes-with-silence
441 (filter (comp not zero? :duration) (interleave silences notes))
442 ]
444 (map
445 (fn [note-event]
446 (note-codes (:frequency note-event)
447 (:volume note-event)
448 (int (* (:duration note-event) 0x100))))
449 notes-with-silence)))
452 (def C4 (partial note-codes 261.63))
453 (def D4 (partial note-codes 293.66))
454 (def E4 (partial note-codes 329.63))
455 (def F4 (partial note-codes 349.23))
456 (def G4 (partial note-codes 392))
457 (def A4 (partial note-codes 440))
458 (def B4 (partial note-codes 493.88))
459 (def C5 (partial note-codes 523.3))
461 (def scale
462 (flatten
463 [(C4 0xF 0x40)
464 (D4 0xF 0x40)
465 (E4 0xF 0x40)
466 (F4 0xF 0x40)
467 (G4 0xF 0x40)
468 (A4 0xF 0x40)
469 (B4 0xF 0x40)
470 (C5 0xF 0x40)]))
472 (defn play-music [music-bytes]
473 (let [program-target 0xC000
474 music-target 0xD000]
475 (-> (set-memory-range (second (music-base))
476 program-target (music-kernel))
477 (set-memory-range music-target music-bytes)
478 (PC! program-target))))
481 (defn test-note [music-bytes]
482 (-> (set-memory-range (second (music-base))
483 0xC000 (concat (clear-music-registers)
484 (play-note)
485 (infinite-loop)))
486 (set-memory-range 0xD000 music-bytes)
487 (PC! 0xC000)
488 (HL! 0xD000)
489 ))
492 (defn run-program
493 ([program]
494 (let [target 0xC000]
495 (-> (set-memory-range (second (music-base))
496 target program)
497 (PC! target)))))
499 (defn test-timer []
500 (flatten
501 [0x3E
502 0x01
503 0xE0
504 0x06 ;; set TMA to 0
506 0x3E
507 (Integer/parseInt "00000100" 2)
508 0xE0
509 0x07 ;; set TAC to 16384 Hz and activate timer
511 (repeat
512 500
513 [0xF0
514 0x05])]))