added simple arpeggio support; improved staccato/frame tracking.
[mumble.git] / music-parser.lisp
blob0adc1deb5c4da0facb4690bb3c96faf87ac562c8
1 ;;;
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.
5 ;;;
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.
14 ;;;
15 ;;; (an abashed) Julian Squires <tek@wiw.org> / 2004
16 ;;;
18 (in-package :mumble)
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 #\|))
27 (defconstant +octave-size+ 12)
29 (defparameter *staccato-base-division* 1/8)
30 (defparameter *default-duration* (make-duration 4))
31 (defparameter *default-octave* 4)
32 (defparameter *default-staccato* 1)
33 (defparameter *default-tempo* 120)
36 ;;;; CLASSES AND DATA STRUCTURES.
38 (defclass duration ()
39 ((denominator :reader duration-denominator)
40 ;; other modifiers here
41 (dots :reader duration-dots)))
43 (defun make-duration (denominator &optional (dots 0))
44 (when denominator
45 (let ((duration (make-instance 'duration)))
46 (setf (slot-value duration 'denominator) denominator)
47 (setf (slot-value duration 'dots) dots)
48 duration)))
50 (defmethod print-object ((obj duration) stream)
51 (print-unreadable-object (obj stream :type t)
52 (princ (duration-denominator obj) stream)
53 (dotimes (i (duration-dots obj))
54 (princ #\. stream))))
57 (defclass music-command ()
58 ((type :reader music-command-type)
59 (value :reader music-command-value)))
61 (defun make-tempo-command (tempo)
62 (let ((cmd (make-instance 'music-command)))
63 (setf (slot-value cmd 'type) :tempo)
64 (setf (slot-value cmd 'value) tempo)
65 cmd))
67 (defun make-staccato-command (staccato)
68 (let ((cmd (make-instance 'music-command)))
69 (setf (slot-value cmd 'type) :staccato)
70 (setf (slot-value cmd 'value) staccato)
71 cmd))
73 ;; This might become a special macro-command later.
74 (defun make-arpeggio-command (n)
75 (let ((cmd (make-instance 'music-command)))
76 (setf (slot-value cmd 'type) :arpeggio)
77 (setf (slot-value cmd 'value) n)
78 cmd))
81 (defclass note (music-command)
82 ((tone :reader note-tone)
83 (duration :reader note-duration))
84 (:documentation "Notes encapsulate an absolute pitch (the TONE slot)
85 and a relative length (the DURATION slot). DURATION is relative to
86 the current channel tempo."))
88 (defun make-note (tone duration)
89 (let ((note (make-instance 'note)))
90 (setf (slot-value note 'type) :note)
91 (setf (slot-value note 'tone) tone)
92 (setf (slot-value note 'duration) duration)
93 note))
95 (defmethod print-object ((obj note) stream)
96 (print-unreadable-object (obj stream :type t)
97 (princ (note-tone obj) stream)
98 (princ #\Space stream)
99 (princ (note-duration obj) stream)))
102 (defclass channel ()
103 ((octave :accessor channel-octave)
104 (tempo :accessor channel-tempo)
105 (staccato :accessor channel-staccato)
106 (duration :accessor channel-default-duration)
107 (loop-point :accessor channel-loop-point)
108 (data-stream :accessor channel-data-stream)))
110 (defun make-channel ()
111 (let ((channel (make-instance 'channel)))
112 (setf (channel-octave channel) *default-octave*)
113 (setf (channel-tempo channel) *default-tempo*)
114 (setf (channel-staccato channel) *default-staccato*)
115 (setf (channel-default-duration channel) *default-duration*)
116 (setf (channel-data-stream channel) nil)
117 channel))
121 ;;;; LOW-LEVEL PARSE/LEX ROUTINES.
123 (defun digit-to-int (char)
124 (- (char-code char) (char-code #\0)))
126 (defun clarify-duration (duration channel)
127 (if duration
128 (setf (channel-default-duration channel) duration)
129 (channel-default-duration channel)))
131 (defun expect-int (stream)
132 ;; if the next character is a digit, read digits until the next
133 ;; character is not a digit.
134 (do ((next-char #1=(peek-char nil stream) #1#)
135 (int nil))
136 ((not (find next-char *duration-digits*)) int)
137 (let ((digit (digit-to-int (read-char stream))))
138 (if int
139 (setf int (+ (* int 10) digit))
140 (setf int digit)))))
142 (defun expect-duration (stream)
143 (let ((duration (make-duration (expect-int stream)))
144 ;; if the next character is a dot, read dots until the next
145 ;; character is not a dot.
146 (dots (do ((next-char #2=(peek-char nil stream) #2#)
147 (number-of-dots 0 (1+ number-of-dots)))
148 ((char/= next-char #\.) number-of-dots)
149 (read-char stream))))
151 (when (plusp dots)
152 (setf (slot-value duration 'dots) dots))
153 duration))
155 (defun calculate-tone (char accidentals octave)
156 (let ((tone-value (* +octave-size+ octave)))
157 (incf tone-value
158 (do ((i 0 (1+ i)))
159 ((char= char (schar *note-characters* i)) i)
160 (assert (< i (length *note-characters*)))))
161 (incf tone-value accidentals)
162 tone-value))
164 (defun read-accidentals (stream)
165 (do ((next-char #1=(peek-char nil stream) #1#)
166 (accidentals 0))
167 ((char/= next-char #\+ #\-) accidentals)
168 (if (char= (read-char stream) #\+)
169 (incf accidentals)
170 (decf accidentals))))
172 (defun expect-note (stream)
173 (let* ((note-char (read-char stream))
174 (accidentals (read-accidentals stream))
175 (duration (expect-duration stream)))
177 ;; this function should always be called when we know there's a
178 ;; note character next.
179 (assert (find note-char *note-characters*))
181 (values note-char accidentals duration)))
183 (defun expect-rest (stream)
184 (let ((rest-char (read-char stream))
185 (duration (expect-duration stream)))
187 (if (char= rest-char #\r)
188 (values :rest duration)
189 (values :wait duration))))
191 (defun expect-channels (stream)
192 (do ((next-char #1=(peek-char nil stream) #1#)
193 (channels))
194 ((not (find next-char *channel-select-characters*)) channels)
195 ;; XXX dumb hack
196 (push (- (char-code (read-char stream))
197 (char-code (char *channel-select-characters* 0)))
198 channels)))
200 (defun eat-whitespace-and-barlines (stream)
201 (do ((next-char #1=(peek-char nil stream) #1#))
202 ((not (find next-char *whitespace-characters*)))
203 (read-char stream)))
206 (defmacro mv-push (source destination key)
207 `(do ((d ,destination (cdr d))
208 (s ,source (cdr s)))
209 ((not d))
210 (push (car s) (,key (car d)))))
213 ;;;; HIGH-LEVEL PARSE ROUTINES.
215 (defun parse-music-section (stream channels)
216 "Reads a music section from stream; returns at EOF or if a section
217 change is detected. Writes data and property changes to channels.
218 Highly intolerant of malformed inputs."
219 (handler-case
220 (music-parse-internal stream channels)
221 (end-of-file ()))
222 (dolist (c channels)
223 (setf (channel-data-stream c) (reverse (channel-data-stream c)))))
225 (defun music-parse-internal (stream channels)
226 (do ((current-channels nil)
227 (next-char #1=(peek-char nil stream) #1#))
228 (nil)
229 ;; Channel selection characters.
230 (cond ((find next-char *channel-select-characters*)
231 (setf current-channels nil)
232 (dolist (c (expect-channels stream))
233 (push (nth c channels) current-channels)))
235 ;; Octave changes.
236 ((char= next-char #\o)
237 (assert current-channels)
238 (read-char stream)
239 (let ((octave (expect-int stream)))
240 (dolist (c current-channels)
241 (setf (channel-octave c) octave))))
243 ((char= next-char #\<)
244 (assert current-channels)
245 (read-char stream)
246 (dolist (c current-channels)
247 (decf (channel-octave c))))
249 ((char= next-char #\>)
250 (assert current-channels)
251 (read-char stream)
252 (dolist (c current-channels)
253 (incf (channel-octave c))))
255 ;; Notes and rests.
256 ((find next-char *note-characters*)
257 (assert current-channels)
258 (multiple-value-bind (note-char accidentals duration)
259 (expect-note stream)
260 (dolist (c current-channels)
261 (push (make-note (calculate-tone note-char
262 accidentals
263 (channel-octave c))
264 (clarify-duration duration c))
265 (channel-data-stream c)))))
267 ((or (char= next-char #\r) (char= next-char #\w))
268 (assert current-channels)
269 (multiple-value-bind (note-type duration)
270 (expect-rest stream)
271 (dolist (c current-channels)
272 (push (make-note note-type
273 (clarify-duration duration c))
274 (channel-data-stream c)))))
276 ;; Tempo change.
277 ((char= next-char #\t)
278 (assert current-channels)
279 (read-char stream)
280 (let ((tempo (expect-int stream)))
281 (dolist (c current-channels)
282 (push (make-tempo-command tempo)
283 (channel-data-stream c))
284 (setf (channel-tempo c) tempo))))
285 ((char= next-char #\#)
286 (return))
288 ;; Staccato.
289 ((char= next-char #\q)
290 (assert current-channels)
291 (read-char stream)
292 (let ((staccato (* *staccato-base-division* (expect-int stream))))
293 (dolist (c current-channels)
294 (push (make-staccato-command staccato)
295 (channel-data-stream c))
296 (setf (channel-staccato c) staccato))))
298 ;; Macro invocation.
299 ((char= next-char #\@)
300 (assert current-channels)
301 (parse-macro-invocation stream current-channels))
303 ;; Comment.
304 ((char= next-char #\;)
305 (read-line stream))
307 ;; Something else?
308 (t (format nil "~&Ignored character: ~A"
309 (read-char stream))))
310 (eat-whitespace-and-barlines stream)))
313 (defun parse-macro-invocation (stream channels)
314 (read-char stream)
315 (let ((next-char (peek-char nil stream)))
316 ;; Arpeggio.
317 (cond ((char= next-char #\a)
318 (read-char stream)
319 (let ((arp-num (expect-int stream)))
320 (dolist (c channels)
321 (push (make-arpeggio-command arp-num)
322 (channel-data-stream c)))))
323 ;; Something else?
324 (t (format nil "~&Ignored macro invocator: @~A"
325 (read-char stream))))))