2 ;;; Several of these functions are very flaky WRT EOF, and that should
3 ;;; eventually be fixed. This is all just a quick hack. Most of this
4 ;;; could be converted to a very data-driven style of programming.
6 ;;; Other things that should be checked/fixed:
7 ;;; - durations should get tweaked (say, by parse-music-section) if
8 ;;; we're inside a triplet or tuplet figure.
9 ;;; - haven't figured out yet who should deal with specifying an
10 ;;; initial tempo if we don't find one before the first note. I
11 ;;; have a feeling I should just have this code insert a tempo
12 ;;; set event on any channel where we get a duration-dependant
13 ;;; event before any tempo is set.
15 ;;; (an abashed) Julian Squires <tek@wiw.org> / 2004
20 ;;;; CONSTANTS AND PARAMETERS.
22 (defparameter *channel-select-characters
* "ABCDEFGHIJ")
23 (defparameter *duration-digits
* "0123456789")
24 (defparameter *note-characters
* "c_d_ef_g_a_b")
25 (defparameter *whitespace-characters
* #(#\Space
#\Newline
#\Tab
))
26 (defparameter *ws-and-barline-characters
* #(#\Space
#\Newline
#\Tab
#\|
))
28 (defconstant +octave-size
+ 12)
30 (defparameter *staccato-base-division
* 1/8)
31 (defparameter *default-duration
* (make-duration 4))
32 (defparameter *default-octave
* 4)
33 (defparameter *default-staccato
* 1)
34 (defparameter *default-tempo
* 120)
37 ;;;; LOW-LEVEL PARSE/LEX ROUTINES.
39 (defun digit-to-int (char)
40 (- (char-code char
) (char-code #\
0)))
42 (defun expect-int (stream)
43 ;; if the next character is a digit, read digits until the next
44 ;; character is not a digit.
45 (do ((next-char #1=(peek-char nil stream
) #1#)
47 ((not (find next-char
*duration-digits
*)) int
)
48 (let ((digit (digit-to-int (read-char stream
))))
50 (setf int
(+ (* int
10) digit
))
53 (defun expect-duration (stream)
54 (let ((duration (make-duration (expect-int stream
)))
55 ;; if the next character is a dot, read dots until the next
56 ;; character is not a dot.
57 (dots (do ((next-char #1=(peek-char nil stream
) #1#)
58 (number-of-dots 0 (1+ number-of-dots
)))
59 ((char/= next-char
#\.
) number-of-dots
)
62 (when (and (plusp dots
) (null duration
))
63 (error "Bad duration (relative dots are not allowed)."))
65 (orig duration
(/ orig
2)))
67 (incf duration
(/ orig
2)))
70 (unless (null duration
)
71 (do ((next-char #2=(peek-char nil stream
) #2#))
72 ((char/= next-char
#\^
))
74 (incf duration
(make-duration (expect-int stream
)))))
78 (defun read-accidentals (stream)
79 (do ((next-char #1=(peek-char nil stream
) #1#)
81 ((char/= next-char
#\
+ #\-
) accidentals
)
82 (if (char= (read-char stream
) #\
+)
86 (defun expect-note (stream)
87 (let* ((note-char (read-char stream
))
88 (accidentals (read-accidentals stream
))
89 (duration (expect-duration stream
)))
91 ;; this function should always be called when we know there's a
92 ;; note character next.
93 (assert (find note-char
*note-characters
*))
95 (values note-char accidentals duration
)))
97 (defun expect-rest (stream)
98 (let ((rest-char (read-char stream
))
99 (duration (expect-duration stream
)))
101 (if (char= rest-char
#\r)
102 (values :rest duration
)
103 (values :wait duration
))))
105 (defun expect-channels (stream)
106 (do ((next-char #1=(peek-char nil stream
) #1#)
108 ((not (find next-char
*channel-select-characters
*)) channels
)
110 (push (- (char-code (read-char stream
))
111 (char-code (char *channel-select-characters
* 0)))
114 (defun eat-whitespace (stream &optional
(characters *whitespace-characters
*))
115 (do ((next-char #1=(peek-char nil stream
) #1#))
116 ((not (find next-char characters
)))
119 (defun expect-= (stream)
120 (eat-whitespace stream
)
121 (assert (char= (read-char stream
) #\
=))
122 (eat-whitespace stream
))
124 (defun read-numbers-and-loop-macro (stream)
125 (assert (char= (read-char stream
) #\
{))
126 (eat-whitespace stream
)
127 (do ((next-char #1=(peek-char nil stream
) #1#)
129 ((char= next-char
#\
}) (progn (read-char stream
)
131 (cond ((char= next-char
#\|
)
134 ((find next-char
"0123456789-")
135 (push (read stream
) list
))
138 (format t
"~&Warning: ignored ~A in macro definition."
140 (eat-whitespace stream
)))
142 (defun read-symbols-macro (stream)
143 (assert (char= (read-char stream
) #\
{))
144 (eat-whitespace stream
)
145 (do ((symbol (read stream
) (read stream
))
147 ((eql symbol
'}) (reverse list
))
150 (defparameter *macro-table-mapping
*
151 '((#\a :arpeggio read-numbers-and-loop-macro
)
152 (#\v :volume-envelope read-numbers-and-loop-macro
)
153 (#\i
:instrument read-symbols-macro
)
154 (#\~
:vibrato read-symbols-macro
)))
156 (defun read-macro-definition (stream)
157 (assert (char= (read-char stream
) #\
@))
158 (let* ((dispatch (read-char stream
))
159 (index (expect-int stream
))
160 (mapping (find dispatch
*macro-table-mapping
* :test
#'equal
163 (values (second mapping
) index
(funcall (third mapping
) stream
))))
166 ;;;; HIGH-LEVEL PARSE ROUTINES.
168 ;;; We should really just create a readtable for the use of all the
169 ;;; following routines. Basically, what's in parse-header-section,
170 ;;; but with the other CL standard macro characters disabled (parens,
171 ;;; single/back quote, comma).
172 (defun parse-mumble-file (stream)
173 (let ((*read-eval
* nil
)
175 ;; Any preamble that occurs before the first section is ignored.
176 (parse-comment-section stream
)
178 (do ((section (read stream
) (read stream
)))
180 ;; Note that the section handler is always responsible for
181 ;; eating the # sign so we don't see it.
183 (COMMENT (parse-comment-section stream
))
184 (MACROS (parse-macro-section stream tune
))
185 (HEADER (parse-header-section stream tune
))
186 (MUSIC (parse-music-section stream
(tune-channels tune
)))))
187 (end-of-file () tune
))))
190 (defun parse-comment-section (stream)
191 (do () ((char= (read-char stream
) #\
#))))
193 (defun parse-header-section (stream tune
)
194 (let ((*readtable
* (copy-readtable))
196 (set-macro-character #\
#
197 (lambda (stream char
)
198 (declare (ignore stream char
))
200 (do ((header (read stream
) (read stream
)))
202 (let ((argument (read stream
)))
205 ;; XXX genericize replay stuff
206 (assert (string= argument
"YMamoto"))
207 (setf (tune-channels tune
) (make-ymamoto-channels))
208 (setf (tune-replay tune
) argument
))
209 ((TITLE COMPOSER COPYRIGHT
)
210 (push (list header argument
) (tune-metadata tune
))))))))
213 (defun parse-macro-section (stream tune
)
214 (do ((next-char #1=(peek-char nil stream
) #1#))
216 (cond ((char= next-char
#\
@)
217 (multiple-value-bind (table index entry
)
218 (read-macro-definition stream
)
219 (assert (plusp index
) ()
220 "Bad index ~A (tables index from 1 -- 0 is the ~
221 \"effect off\" index)." index
)
222 (format t
"~&got macro ~A ~A ~A" table index entry
)
223 (unless (tune-get-table tune table
)
224 (tune-add-table tune table
))
225 (tune-add-to-table tune table index entry
)))
228 ((char= next-char
#\
#)
233 ((char= next-char
#\
;)
237 (t (format t
"~&Ignored character in macro section: ~A (~:*~S)"
238 (read-char stream
))))
239 (eat-whitespace stream
)))
241 ;; possible ``dispatch table'' format for routine below?
243 ((octave (progn (read-char stream
) (expect-int stream
))))
244 (setf (channel-octave channel
) octave
))
247 (decf (channel-octave c
)))
249 ((note-char accidentals duration
) (expect-note stream
))
250 (push (make-note (calculate-tone note-char
252 (channel-octave channel
))
253 (clarify-duration duration channel
))
254 (channel-data-stream channel
))))
258 (defun parse-music-section (stream channels
259 &optional loop-channels in-loop-p
)
260 "Reads a music section from stream; returns at EOF or if a section
261 change is detected. Writes data and property changes to channels.
262 Highly intolerant of malformed inputs."
263 (do ((current-channels (and in-loop-p loop-channels
))
264 (next-char #1=(peek-char nil stream
) #1#))
266 ;; Channel selection characters.
267 (cond ((find next-char
*channel-select-characters
*)
268 (setf current-channels nil
)
269 (dolist (c (expect-channels stream
))
270 (assert (< c
(length channels
))
271 () "Invalid channel for this replay.")
272 (push (nth c channels
) current-channels
)))
274 ;; Repeats (unrolled loops).
275 ((char= next-char
#\
[)
276 (assert current-channels
() "Command outside channels.")
278 (dolist (c current-channels
)
279 (push (channel-current-position c
)
280 (channel-repeats c
)))
281 (parse-music-section stream channels current-channels t
))
283 ((char= next-char
#\
])
284 (assert (and in-loop-p
287 (let ((count (expect-int stream
)))
288 (dolist (c current-channels
)
289 (let ((begin (pop (channel-repeats c
)))
290 (end (1- (channel-current-position c
))))
291 (dotimes (i (1- count
))
292 (copy-and-append-channel-data c begin end
)))))
296 ((char= next-char
#\o
)
297 (assert current-channels
() "Command outside channels.")
299 (let ((octave (expect-int stream
)))
300 (dolist (c current-channels
)
301 (setf (channel-octave c
) octave
))))
303 ((char= next-char
#\
<)
304 (assert current-channels
() "Command outside channels.")
306 (dolist (c current-channels
)
307 (decf (channel-octave c
))))
309 ((char= next-char
#\
>)
310 (assert current-channels
() "Command outside channels.")
312 (dolist (c current-channels
)
313 (incf (channel-octave c
))))
316 ((find next-char
*note-characters
*)
317 (assert current-channels
() "Command outside channels.")
318 (multiple-value-bind (note-char accidentals duration
)
320 (dolist (c current-channels
)
321 (vector-push-extend (make-note
322 (calculate-tone note-char
325 (clarify-duration duration c
))
326 (channel-data-stream c
)))))
328 ((or (char= next-char
#\r) (char= next-char
#\w
))
329 (assert current-channels
() "Command outside channels.")
330 (multiple-value-bind (note-type duration
)
332 (dolist (c current-channels
)
333 (vector-push-extend (make-note note-type
334 (clarify-duration duration c
))
335 (channel-data-stream c
)))))
338 ((char= next-char
#\t)
339 (assert current-channels
() "Command outside channels.")
341 (let ((tempo (expect-int stream
)))
342 (dolist (c current-channels
)
343 (vector-push-extend (make-tempo-command tempo
)
344 (channel-data-stream c
))
345 (setf (channel-tempo c
) tempo
))))
348 ;; XXX: add something to complain about unfinished loops.
349 ((char= next-char
#\
#)
352 (format t
"WARNING: changing sections during a [] repeat. ~
353 This probably won't work."))
357 ((char= next-char
#\q
)
358 (assert current-channels
() "Command outside channels.")
360 (let ((staccato (* *staccato-base-division
* (expect-int stream
))))
361 (dolist (c current-channels
)
362 (vector-push-extend (make-staccato-command staccato
)
363 (channel-data-stream c
))
364 (setf (channel-staccato c
) staccato
))))
367 ((char= next-char
#\
@)
368 (assert current-channels
() "Command outside channels.")
369 (parse-macro-invocation stream current-channels
))
371 ;; Structural dispatch character.
372 ((char= next-char
#\
!)
373 (assert current-channels
() "Command outside channels.")
374 (parse-bang-invocation stream current-channels
))
376 ;; Replay-special invocation.
377 ((char= next-char
#\%
)
378 (assert current-channels
() "Command outside channels.")
380 ;; XXX genericize replay stuff
381 (ymamoto-special-handler stream channels
))
384 ((char= next-char
#\
;)
388 (t (format t
"~&Ignored character in music section: ~A (~:*~S)"
389 (read-char stream
))))
390 (eat-whitespace stream
*ws-and-barline-characters
*)))
393 (defun parse-macro-invocation (stream channels
)
395 (let ((next-char (peek-char nil stream
)))
397 (cond ((char= next-char
#\a)
399 (let ((arp-num (expect-int stream
)))
401 (vector-push-extend (make-arpeggio-command arp-num
)
402 (channel-data-stream c
)))))
404 ((char= next-char
#\v)
406 (let ((venv-num (expect-int stream
)))
408 (vector-push-extend (make-volume-envelope-command venv-num
)
409 (channel-data-stream c
)))))
412 ((char= next-char
#\~
)
414 (let ((vibrato-num (expect-int stream
)))
419 (t (format t
"~&Ignored macro invocator: @~A (~:*~S)"
420 (read-char stream
))))))
423 (defun parse-bang-invocation (stream channels
)
424 (let ((symbol (read stream
)))
428 (setf (channel-loop-point c
) (channel-current-position c
))))
430 ;;; XXX how to handle this nicely?
431 #+nil
(dolist (c channels
)
432 (vector-push-extend (make-track-end-command)
433 (channel-data-stream c
)))))))