view clojure/com/aurellem/run/music.clj @ 432:8e88366a81b9

reordered assembly in music-kernel.
author Robert McIntyre <rlm@mit.edu>
date Tue, 24 Apr 2012 23:51:30 -0500
parents b73cb1b937d5
children 985c90ffa1fe
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)
118 (store (Integer/parseInt "00000000" 2) 0xFF11)
119 (store (Integer/parseInt "00000000" 2) 0xFF12)
120 (store (Integer/parseInt "00000000" 2) 0xFF13)
121 (store (Integer/parseInt "00000000" 2) 0xFF14)
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)
135 (store (Integer/parseInt "00000000" 2) 0xFF21)
136 (store (Integer/parseInt "00000000" 2) 0xFF22)
137 (store (Integer/parseInt "00000000" 2) 0xFF23)]))
140 ;; mini-midi syntax
142 ;; codes
143 ;; note-code == 0x00
144 ;; change-duty-code = 0x01
145 ;; silence-code = 0x02
147 ;; silence format
148 ;; 2 bytes
149 ;; [silence-code (0x02)]
150 ;; [duration-8-bits]
152 ;; note data format
153 ;; 4 bytes
154 ;; [note-code (0x00)]
155 ;; [volume-4-bits 0 frequency-high-3-bits]
156 ;; [frequengy-low-8-bits]
157 ;; [duration-8-bits]
159 ;; change-duty-format
160 ;; 2 bytes
161 ;; [change-duty-code (0x01)]
162 ;; [new-duty]
164 (def note-code 0x00)
165 (def change-duty-code 0x01)
166 (def silence-code 0x02)
168 (defn do-message
169 "Read the message which starts at the current value of HL and do
170 what it says. Duration is left in A, and HL is advanced
171 appropraitely."
172 []
173 (let [switch
174 [0x2A ;; load message code into A, increment HL
176 ;; switch on message
177 0xFE
178 note-code
180 0x20
181 :note-length]
183 play-note
184 [0x2A ;; load volume/frequency-high info
185 0xF5 ;; push A
186 0xE6
187 (Integer/parseInt "11110000" 2) ;; volume mask
188 0xE0
189 0x17 ;; set volume
190 0xF1 ;; pop A
191 0xE6
192 (Integer/parseInt "00000111" 2) ;; frequency-high mask
193 0xE0
194 0x19 ;; set frequency-high
196 0x2A ;; load frequency low-bits
197 0xE0
198 0x18 ;; set frequency-low-bits
200 0x2A]] ;; load duration
201 (replace
202 {:note-length (count play-note)}
203 (concat switch play-note))))
205 (defn play-note
206 "Play the note referenced by HL in the appropiate channel.
207 Leaves desired-duration in A."
208 []
209 [0x2A ;; load volume/frequency-high info
210 0xF5 ;; push A
211 0xE6
212 (Integer/parseInt "11110000" 2) ;; volume mask
213 0xE0
214 0x17 ;; set volume
215 0xF1 ;; pop A
216 0xE6
217 (Integer/parseInt "00000111" 2) ;; frequency-high mask
218 0xE0
219 0x19 ;; set frequency-high
221 0x2A ;; load frequency low-bits
222 0xE0
223 0x18 ;; set frequency-low-bits
225 0x2A ;; load duration
226 ])
228 (defn music-step []
229 ;; C == current-ticks
230 ;; A == desired-ticks
232 (flatten
233 [
234 0xF5 ;; push A
235 0xF0
236 0x05 ;; load current ticks from 0xF005
237 0xB8 ;;
238 0x30 ;; increment C only if last result caused carry
239 0x01
240 0x0C
242 0x47 ;; update sub-ticks (A->B)
244 0xF1 ;; pop AF, now A contains desired-ticks
246 0xB9 ;; compare with current ticks
248 ;; if desired-ticks = current ticks
249 ;; go to next note ; set current set ticks to 0.
251 0x20
252 (+ (count (do-message)) 2)
254 (do-message)
256 0x0E
257 0x00])) ;; 0->C (current-ticks)
259 (defn music-kernel []
260 (flatten
261 [;; global initilization section
262 (clear-music-registers)
264 0x3E
265 0x01
266 0xE0
267 0x06 ;; set TMA to 0
269 0x3E
270 (Integer/parseInt "00000110" 2)
271 0xE0
272 0x07 ;; set TAC to 65536 Hz and activate timer
274 ;; local initilization section
275 0x21
276 0x00
277 0xD0 ;; set HL to 0xD000 == music-start
278 0x0E
279 0x00 ;; 0->C
280 0x06
281 0x00 ;; 0->B
285 0xAF ;; initialiaze A to zero
288 (music-step)
289 0x18
290 (->signed-8-bit (+ (- (count (music-step)))
291 -2))]))
293 (defn frequency-code->frequency
294 [code]
295 (assert (<= 0 code 2047))
296 (/ 131072 (- 2048 code)))
298 (defn clamp [x low high]
299 (cond (> x high) high
300 (< x low) low
301 true x))
303 (defn frequency->frequency-code
304 [frequency]
305 (clamp
306 (Math/round
307 (float
308 (/ (- (* 2048 frequency) 131072) frequency)))
309 0x00 2048))
311 (defn note-codes [frequency volume duration]
312 (assert (<= 0 volume 0xF))
313 (if (<= duration 0xFF)
314 (let [frequency-code
315 (frequency->frequency-code frequency)
316 volume&high-frequency
317 (+ (bit-shift-left volume 4)
318 (bit-shift-right frequency-code 8))
319 low-frequency
320 (bit-and 0xFF frequency-code)]
321 [note-code
322 volume&high-frequency
323 low-frequency
324 duration])
325 (vec
326 (flatten
327 [(note-codes frequency volume 0xFF)
328 (note-codes frequency volume (- duration 0xFF))]))))
331 (defn midi-code->frequency
332 [midi-code]
333 (* 8.1757989156
334 (Math/pow 2 (* (float (/ 12)) midi-code))))
336 ;; division == clock-pulses / quarter-note
337 ;; tempo == microseconds / quarter-note
339 ;; have: clock-pulses
340 ;; want: seconds
343 (defn silence [length]
344 {:frequency 1
345 :duration length
346 :volume 0})
348 (defn midi->mini-midi [#^File midi-file]
349 (let [midi-events (parse-midi midi-file)
351 note-on-events
352 (filter #(= :Note_on_c (:command %)) midi-events)
353 note-off-events
354 (filter #(= :Note_off_c (:command %)) midi-events)
356 channel-1-on
357 (sort-by :time
358 (filter #(= 1 (:channel (:args %)))
359 note-on-events))
360 channel-1-off
361 (sort-by :time
362 (filter #(= 1 (:channel (:args %)))
363 note-off-events))
366 tempo (:args (first (filter #(= :Tempo (:command %)) midi-events)))
367 division (:division
368 (:args (first (filter #(= :Header (:command %)) midi-events))))
370 notes
371 (map
372 (fn [note-on note-off]
373 {:frequency (midi-code->frequency (:note (:args note-on)))
374 :duration
375 (/ (* (/ tempo division)
376 (- (:time note-off) (:time note-on)))
377 1e6) ;; convert clock-pulses into seconds
378 :volume (int (/ (:velocity (:args note-on)) 10))
379 :time-stamp (/ (* (/ tempo division)
380 (:time note-on)) 1e6)})
381 channel-1-on channel-1-off)
383 silences
384 (map (fn [note-1 note-2]
385 (let [note-1-space (- (:time-stamp note-2)
386 (:time-stamp note-1))
387 note-1-length (:duration note-1)]
388 (silence (- note-1-space note-1-length))))
389 ;; to handle silence at the beginning.
390 (concat [(assoc (silence 0)
391 :time-stamp 0)] notes)
392 notes)
394 notes-with-silence
395 (filter (comp not zero? :duration) (interleave silences notes))
396 ]
398 (map
399 (fn [note-event]
400 (note-codes (:frequency note-event)
401 (:volume note-event)
402 (int (* (:duration note-event) 0x100))))
403 notes-with-silence)))
406 (def C4 (partial note-codes 261.63))
407 (def D4 (partial note-codes 293.66))
408 (def E4 (partial note-codes 329.63))
409 (def F4 (partial note-codes 349.23))
410 (def G4 (partial note-codes 392))
411 (def A4 (partial note-codes 440))
412 (def B4 (partial note-codes 493.88))
413 (def C5 (partial note-codes 523.3))
415 (def scale
416 (flatten
417 [(C4 0xF 0x40)
418 (D4 0xF 0x40)
419 (E4 0xF 0x40)
420 (F4 0xF 0x40)
421 (G4 0xF 0x40)
422 (A4 0xF 0x40)
423 (B4 0xF 0x40)
424 (C5 0xF 0x40)]))
426 (defn play-music [music-bytes]
427 (let [program-target 0xC000
428 music-target 0xD000]
429 (-> (set-memory-range (second (music-base))
430 program-target (music-kernel))
431 (set-memory-range music-target music-bytes)
432 (PC! program-target))))
435 (defn test-note [music-bytes]
436 (-> (set-memory-range (second (music-base))
437 0xC000 (concat (clear-music-registers)
438 (play-note)
439 (infinite-loop)))
440 (set-memory-range 0xD000 music-bytes)
441 (PC! 0xC000)
442 (HL! 0xD000)
443 ))
446 (defn run-program
447 ([program]
448 (let [target 0xC000]
449 (-> (set-memory-range (second (music-base))
450 target program)
451 (PC! target)))))
453 (defn test-timer []
454 (flatten
455 [0x3E
456 0x01
457 0xE0
458 0x06 ;; set TMA to 0
460 0x3E
461 (Integer/parseInt "00000100" 2)
462 0xE0
463 0x07 ;; set TAC to 16384 Hz and activate timer
465 (repeat
466 500
467 [0xF0
468 0x05])]))