Time signatures
[gsharp.git] / Mxml / mxml.lisp
blob42282912439a257c396016aa94c26738a9342c0c
1 (in-package :gsharp-mxml)
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;; Utility functions, macros
5 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7 (defmacro test-make-xml (obj id)
8 `(cxml:with-xml-output (cxml:make-rod-sink :indentation 2 :canonical nil)
9 (make-xml ,obj ,id)))
10 (defun write-buffer-to-xml-file (buffer filename)
11 (with-open-file (s filename :direction :output)
12 (write-string (write-mxml buffer) s)))
14 (defun pcdata (thing)
15 (let ((content (dom:first-child thing)))
16 ;; Could be empty
17 (if content
18 (string-trim '(#\Space #\Tab #\Newline)
19 (dom:node-value content))
20 "")))
21 (defun named-pcdata (node tag-name)
22 (if (has-element-type node tag-name)
23 (pcdata (elt (dom:get-elements-by-tag-name node tag-name) 0))
24 nil))
25 (eval-when (:compile-toplevel :load-toplevel :execute)
26 (defun expander-for-stringcase (keyform cases exhaustivep)
27 (let ((nkey (gensym "KEY")))
28 (flet ((expand-case (case)
29 (destructuring-bind (keys &rest forms) case
30 (cond
31 ((member keys '(t otherwise))
32 (when exhaustivep
33 (warn "~S found in ~S" keys 'estringcase))
34 `(t ,@forms))
35 ((stringp keys)
36 `((string= ,keys ,nkey) ,@forms))
37 ((and (consp keys) (every #'stringp keys))
38 `((or ,@(loop for k in keys collect `(string= ,k ,nkey)))
39 ,@forms))
41 (warn "Unrecognized keys: ~S" keys))))))
42 `(let ((,nkey ,keyform))
43 (cond
44 ,@(loop for case in cases collect (expand-case case))
45 ,@(when exhaustivep
46 `((t (error "~S failed to match any key in ~S"
47 ,nkey 'estringcase))))))))))
49 (defmacro stringcase (keyform &body cases)
50 (expander-for-stringcase keyform cases nil))
51 (defmacro estringcase (keyform &body cases)
52 (expander-for-stringcase keyform cases t))
54 (defun has-element-type (node type-name)
55 (> (length (dom:get-elements-by-tag-name node type-name)) 0))
57 (defmacro for-named-elements ((name varname node) &body body)
58 (let ((elements (gensym)))
59 `(let ((,elements (dom:get-elements-by-tag-name ,node ,name)))
60 (sequence:dosequence (,varname ,elements)
61 ,@body))))
62 (defmacro for-children ((varname node) &body body)
63 (let ((children (gensym)))
64 `(let ((,children (dom:child-nodes ,node)))
65 (sequence:dosequence (,varname ,children)
66 ,@body))))
68 (defun map-all-lists-maximally (fn id-base &rest all-lists)
69 (loop with lists = (copy-list all-lists)
70 for i from id-base
71 until (every #'null lists)
72 collecting (apply fn i (mapcar #'car lists))
73 do (map-into lists #'cdr lists)))
75 (defun split-if (predicate list)
76 (loop for x in list
77 if (funcall predicate x)
78 collect x into a
79 else
80 collect x into b
81 end
82 finally (return (values a b))))
84 (defun find-if-nthcdr (predicate n sequence)
85 "Finds the nth element that satisfies the predicate, and returns the
86 cdr with that element as the head"
87 (let ((i 0))
88 (do ((e sequence (cdr sequence)))
89 ((= i n) e)
90 (when (funcall predicate (car e))
91 (incf i)))))
93 ;; perhaps these should go in utilities.lisp
94 (defun unicode-to-string (unicode)
95 (map 'string #'gsharp-utilities:unicode-to-char unicode))
96 (defun string-to-unicode (string)
97 (map 'vector #'gsharp-utilities:char-to-unicode string))
100 ;;;;;;;;;;;;;;;
101 ;; Notes on mapping
103 ;; gsh maps to mxml pretty well:
104 ;; staff == staff
105 ;; voice == layer
106 ;; cluster == chord
108 ;; Gsharp allows staffs to be in more than one layer, which isn't
109 ;; explicit in mxml but is there: a note has to be in one staff, but
110 ;; the notes in a chord can be in different ones while in the same
111 ;; voice.
113 ;; the mapping seems to break down in that while mxml allows notes in
114 ;; the same chord to be in different voices (though i'm not sure what
115 ;; that would mean), a cluster in gsharp belongs to one layer. this
116 ;; isn't a problem though, because the mapping of chord to cluster is
117 ;; not really one-to-one.
118 ;;;;;;;;;;;;;;;;;;
120 ;;;;;;;;;;;;
121 ;; Import
122 ;;;;;;;;;;;;
124 (defun parse-mxml-note-duration (note-element)
125 "Given a MusicXML note element, return the appropriate Gsharp
126 notehead, dots and beams values."
127 ;; valid types: 256th, 128th, 64th, 32nd, 16th,
128 ;; eighth, quarter, half, whole, breve, and long
129 (let ((notehead
130 (if (has-element-type note-element "type")
131 (estringcase (named-pcdata note-element "type")
132 (("256th" "128th" "64th" "32nd" "16th" "eighth" "quarter")
133 :filled)
134 ("half" :half)
135 ("whole" :whole)
136 ;; KLUDGE: "full" here (and for beams) I think is a
137 ;; feature of catering for Nightingale's MusicXML
138 ;; export, which is wrong in this respect.
139 (("breve" "full") :breve)
140 ("long" :long))
141 :filled))
142 (beams
143 (if (has-element-type note-element "type")
144 (estringcase (named-pcdata note-element "type")
145 ("256th" 6)
146 ("128th" 5)
147 ("64th" 4)
148 ("32nd" 3)
149 ("16th" 2)
150 ("eighth" 1)
151 (("quarter" "half" "whole" "breve" "full" "long") 0))
153 (dots (length (dom:get-elements-by-tag-name note-element "dot")))
154 (stem (if (has-element-type note-element "stem")
155 (cond
156 ((string= (named-pcdata note-element "stem") "up") :up)
157 ((string= (named-pcdata note-element "stem") "down"):down)
158 (t :auto))
159 :auto)))
160 (values notehead beams dots stem)))
162 (defparameter *step-to-basenote* '((#\C . 0)
163 (#\D . 1)
164 (#\E . 2)
165 (#\F . 3)
166 (#\G . 4)
167 (#\A . 5)
168 (#\B . 6)))
170 (defun xmlnote-to-gsh (step octave)
171 ;; C4 is middle C is 28
172 (let ((basenum (cdr (assoc (char-upcase (character step)) *step-to-basenote*))))
173 (+ basenum (* 7 octave))))
175 (defun parse-mxml-accidental (note)
176 ;; I (presumably Brian Gruber -- CSR) wrote it fairly early on and
177 ;; it doesn't use things like has-element which it should.
178 (let ((alters (dom:get-elements-by-tag-name note "alter")))
179 (if (= 0 (length alters))
180 :natural
181 (let ((alter (pcdata (elt alters 0))))
182 (stringcase alter
183 ("1" :sharp)
184 ("0" :natural)
185 ("-1" :flat)
186 ("2" :double-sharp)
187 ("1.5" :sesquisharp)
188 ("0.5" :semisharp)
189 ("-0.5" :semiflat)
190 ("-1.5" :sesquiflat)
191 ("-2" :double-flat)
192 (t :natural))))))
194 (defun parse-mxml-note-staff-number (note)
195 (if (has-element-type note "staff")
196 (1- (parse-integer
197 (named-pcdata note "staff")))
200 (defun parse-mxml-note-staff (note staves)
201 "Given an xml note element and a list of all the staff objects, return
202 the staff object the note is supposed to be assigned to. If none is
203 specified, returns the first (hopefully default) staff."
204 (let ((melody-staves
205 (remove-if #'(lambda (s) (not (typep s 'fiveline-staff))) staves)))
206 (elt melody-staves (parse-mxml-note-staff-number note))))
208 (defvar *parsing-in-cluster*)
210 (defun parse-mxml-pitched-note (note staves)
211 (let* ((staff (parse-mxml-note-staff note staves))
212 (step (named-pcdata note "step"))
213 (octave (parse-integer (named-pcdata note "octave")))
214 (pitch (xmlnote-to-gsh step octave))
215 (accidentals (parse-mxml-accidental note))
216 (tie-left nil)
217 (tie-right nil))
218 (for-named-elements ("tied" tie note)
219 (estringcase (dom:get-attribute tie "type")
220 ("start" (setf tie-right t))
221 ("stop" (setf tie-left t))))
222 (for-named-elements ("staccato" stacc note)
223 (declare (ignore stacc))
224 (pushnew :staccato (annotations *parsing-in-cluster*)))
225 (for-named-elements ("tenuto" ten note)
226 (declare (ignore ten))
227 (pushnew :tenuto (annotations *parsing-in-cluster*)))
228 (make-instance 'note :pitch pitch :staff staff :accidentals accidentals
229 :tie-left tie-left :tie-right tie-right)))
231 (defvar *parsing-duration-gmeasure-position*)
232 (defvar *mxml-divisions*)
233 (defun parse-mxml-note (xnote bars staves lyrics-layer-hash)
234 ;; TODO: There is nothing in MusicXML that stops you from having
235 ;; multiple notes in a chord that have different durations, types,
236 ;; and dots, something which Gsharp does not support in any way.
237 ;; However, this is not something often run into: if 2 notes struck
238 ;; simultaneously have different rhythmic properties, they are
239 ;; almost always to be notated in separate voices. Supporting the
240 ;; rare case here is quite complicated, as it requires the
241 ;; spontaneous creation of another layer to accommodate it, so for
242 ;; now, this code will assume that all notes in a chord have the
243 ;; same type and dots as the first one mentioned in the MusicXML
244 ;; file. Suggested revision: throw a condition asking the user if
245 ;; they want to omit the note or make it the same duration as the
246 ;; others.
248 ;; Also, this breaks if you have a rest in a chord, which you can
249 ;; have in MusicXML, but I'm not really sure what that would be.
250 (let ((bar (elt bars (if (has-element-type xnote "voice")
251 (1- (parse-integer (named-pcdata xnote "voice")))
252 0)))
253 (advance 0))
254 (multiple-value-bind (notehead beams dots)
255 (parse-mxml-note-duration xnote)
257 (when (has-element-type xnote "lyric")
258 (let* ((xlyric (elt (dom:get-elements-by-tag-name xnote "lyric") 0))
259 (lyrics-staff
260 (cadr (find-if-nthcdr #'(lambda (s) (not (typep s 'lyrics-staff)))
261 (parse-mxml-note-staff-number xnote)
262 staves)))
263 (lyrics-layer (gethash lyrics-staff lyrics-layer-hash))
264 (lyrics-bar (car (last (bars (body lyrics-layer)))))
265 (lyrics-element (make-lyrics-element lyrics-staff
266 :notehead notehead
267 :lbeams beams
268 :rbeams beams
269 :dots dots)))
270 ;; TODO there can be multiple lyrics on a given xml-note,
271 ;; presumably for verses or something. Right now this just
272 ;; ignores all but the first one, but this should be addressed.
273 (loop for c across (string-to-unicode (named-pcdata xlyric "text"))
274 do (append-char lyrics-element c))
275 (add-element-at-duration lyrics-element
276 lyrics-bar
277 *parsing-duration-gmeasure-position*)))
279 (when (has-element-type xnote "rest")
280 (let ((new-rest (make-rest (parse-mxml-note-staff xnote staves)
281 :notehead notehead
282 :lbeams beams
283 :rbeams beams
284 :dots dots)))
285 (add-element-at-duration new-rest
287 *parsing-duration-gmeasure-position*)
288 (setf advance (duration new-rest))))
290 (when (has-element-type xnote "pitch")
291 (progn
292 (unless (has-element-type xnote "chord")
293 (multiple-value-bind (notehead beams dots stem)
294 (parse-mxml-note-duration xnote)
295 (setf *parsing-in-cluster* (make-cluster :notehead notehead
296 :lbeams beams
297 :rbeams beams
298 :dots dots
299 :stem-direction stem)))
301 (add-element-at-duration *parsing-in-cluster* bar *parsing-duration-gmeasure-position*)
302 (setf advance (duration *parsing-in-cluster*)))
303 (add-note *parsing-in-cluster* (parse-mxml-pitched-note xnote staves))))
305 (incf *parsing-duration-gmeasure-position* advance))))
307 (defun add-element-at-duration (element bar duration-position)
308 ;; go through the bar, adding up the 'duration' value of each element.
309 ;; if the total is less than the desired duration-position,
310 ;; add an empty cluster of the appropriate length, and then add the new element.
311 ;; when the sum is greater than the duration where the element should be placed, look at what the last element was
312 ;; if it's not an empty element
313 ;; throw some kind of error
314 ;; else
315 ;; concatenate empty elements together
316 ;; if there's not enough room, (this is a fairly complicated calculation), error
317 ;; else split up the empty cluster and insert the new element
318 (loop for ecdr = (elements bar) then (cdr ecdr)
319 for e = (car ecdr)
320 for position from 0
321 until (null ecdr)
322 for edur = (duration e)
323 summing edur into total-duration
324 until (> total-duration duration-position)
325 finally
326 (if (<= total-duration duration-position) ;;(this is going at the end of the bar)
327 (progn
328 (dolist (empty-cluster
329 (generate-empty-clusters (- duration-position total-duration)))
330 (add-element empty-cluster bar position)
331 (incf position))
332 (add-element element bar position))
333 (if (is-empty e)
334 (let ((empty-duration
335 (loop for ee in ecdr
336 until (not (is-empty ee))
337 summing (duration ee))))
338 ;; make sure there is enough empty space
339 (if (> (duration element) empty-duration)
340 (error "There is not enough empty space to put this element")
341 (progn
342 ;; remove all the empty space
343 (loop for ee in ecdr
344 until (not (is-empty ee))
345 do (remove-element ee bar))
347 ;; add back the needed empty preceding space
348 (dolist (empty-cluster
349 (generate-empty-clusters (- duration-position (- total-duration edur))))
350 (add-element empty-cluster bar position)
351 (incf position))
353 ;; add the element
354 (add-element element bar position)
355 (incf position)
357 ;; add the trailing empty space
358 (dolist (empty-cluster
359 (generate-empty-clusters
360 (- empty-duration (- duration-position (- total-duration edur)) (duration element))))
361 (add-element empty-cluster bar position)
362 (incf position)))))
363 ;; FIXME: this restart isn't actually good enough; it
364 ;; is legitimate to have a new element at the same
365 ;; offset from the start of the bar as a previous
366 ;; element, as long as that previous element had zero
367 ;; duration (e.g. key signature)
368 (restart-case
369 (error "There is already a non-empty element here")
370 (add-anyway ()
371 (add-element element bar position)
372 (incf position)))))))
374 (defgeneric is-empty (element))
375 (defmethod is-empty ((element cons))
377 (defmethod is-empty ((element element))
378 nil)
379 (defmethod is-empty ((lyrics-element lyrics-element))
380 (= 0 (length (text lyrics-element))))
381 (defmethod is-empty ((cluster cluster))
382 (null (notes cluster)))
385 (defun generate-empty-clusters (duration)
386 (let ((whole-divisions 1)
387 (half-divisions 1/2)
388 (quarter-divisions 1/4))
389 (nconc
390 (loop until (> whole-divisions duration)
391 do (decf duration whole-divisions)
392 collect (make-cluster :notehead :whole))
393 (loop until (> half-divisions duration)
394 do (decf duration half-divisions)
395 collect (make-cluster :notehead :half))
396 (loop until (> quarter-divisions duration)
397 do (decf duration quarter-divisions)
398 collect (make-cluster :notehead :filled))
399 (loop for beams from 1
400 for divisions = (/ quarter-divisions 2) then (/ divisions 2)
401 nconc
402 (loop until (> divisions duration)
403 do (decf duration divisions)
404 collect (make-cluster :notehead :filled :lbeams beams :rbeams beams))
405 until (= duration 0)))))
407 (defun parse-mxml-clef (clef)
408 "Takes dom element for clef and returns a clef object"
409 (let ((name (stringcase (named-pcdata clef "sign")
410 ("G" (if (string= (named-pcdata clef "clef-octave-change")
411 "-1")
412 :treble8
413 :treble))
414 ("F" :bass)
415 ("C" :c)
416 ("percussion" :percussion)
417 ;; "TAB" and "none" are the other, unsupported choices,
418 ;; along with other octave shifts.
419 (t :c)))
420 (lineno (if (has-element-type clef "line")
421 (* 2 (1- (parse-integer (named-pcdata clef "line"))))
422 nil))
423 (staff-number (if (dom:has-attribute clef "number")
424 (1- (parse-integer (dom:get-attribute clef "number")))
425 0)))
426 (values (make-clef name :lineno lineno) staff-number)))
428 (defun parse-mxml-time (time staves)
429 "Takes a dom element 'time' and returns a time-signature object"
430 ;; FIXME: More complex examples (e.g. additive ts) are missing as is
431 ;; the dreaded "symbol"
432 (let* ((numerators (dom:get-elements-by-tag-name time "beats"))
433 (denominators (dom:get-elements-by-tag-name time "beat-type"))
434 (symbol (dom:get-attribute time "symbol"))
435 (components))
436 (declare (ignore symbol)) ;; FIXME:
437 (setf components
438 (loop for numerator being the elements of numerators
439 for denominator being the elements of denominators
440 collect (mxml-ts-component numerator denominator)))
441 (loop for staff in staves
442 collect (make-time-signature staff :components components))))
444 (defun mxml-ts-component (numerator denominator)
445 (let ((numerator-string (pcdata numerator))
446 (denominator-string (pcdata denominator))
447 (num) (n-end) (den) (d-end))
448 (multiple-value-setq (num n-end)
449 (parse-integer numerator-string :junk-allowed t))
450 (multiple-value-setq (den d-end)
451 (parse-integer denominator-string :junk-allowed t))
452 (cons (if (and num (= n-end (length numerator-string)))
453 num numerator-string)
454 (if (and den (= d-end (length denominator-string)))
455 den denominator-string))))
457 (defun parse-mxml-key (key staves)
458 "Takes a dom element 'key' and returns a key-signature object"
459 (let ((alterations (fill (make-array 7) :natural))
460 (fifths-element (dom:get-elements-by-tag-name key "fifths"))
462 (staff (if (dom:has-attribute key "number")
463 (nth (1- (parse-integer (dom:get-attribute key "number")))
464 (remove-if #'(lambda (s) (typep s 'lyrics-staff)) staves))
465 ;; TODO: this next line is wrong... it's
466 ;; supposed to apply to all staves if the
467 ;; staff isn't specified
468 ;; DONE:?
469 nil)))
470 (if (eql 1 (length fifths-element))
471 (let ((fifths (parse-integer (pcdata (elt fifths-element 0))))
472 (order-of-sharps #(3 0 4 1 5 2 6))
473 (order-of-flats #(6 2 5 1 4 0 3)))
474 ;; deal with the basic fifths
475 (if (< fifths 0)
476 ;; well, this would have been a nice way to do it, but
477 ;; it doesn't work:
478 ;; (more-flats key-signature (abs fifths))
479 ;; (more-sharps key-signature fifths))
481 (dotimes (index (abs fifths))
482 (setf (elt alterations (elt order-of-flats index)) :flat))
483 (dotimes (index fifths)
484 (setf (elt alterations (elt order-of-sharps index)) :sharp))))
486 ;; Deal with weird key signatures
487 ;; The DTD specifies that it goes step, alter, step, alter. If
488 ;; it doesn't, the parser should have barfed when the file was
489 ;; loaded, so I'm not checking it here.
490 (let ((steps (dom:get-elements-by-tag-name key "key-step"))
491 (alters (dom:get-elements-by-tag-name key "key-alter")))
492 (loop for step across steps
493 for alter across (map 'vector #'pcdata alters)
495 (let ((index
496 (cdr (assoc
497 (char-upcase (character (pcdata step)))
498 *step-to-basenote*))))
499 (setf (elt alterations index)
500 (stringcase alter
501 ("1" :sharp)
502 ("0" :natural)
503 ("-1" :flat)
504 ("2" :double-sharp)
505 ("-2" :double-flat)
506 (t :natural)))))))
507 (if staff
508 (gsharp-buffer::make-key-signature staff :alterations alterations)
509 (loop for staff in staves
510 collect (gsharp-buffer::make-key-signature staff :alterations alterations)))))
512 (defun xmlstaff-has-lyrics (part staff-number)
513 "Given a MusicXML part and a staff number, determine if any of the
514 note elements in that staff have associated lyrics."
515 (for-named-elements ("note" note part)
516 (let ((staff (if (has-element-type note "staff")
517 (parse-integer (named-pcdata note "staff"))
518 1)))
519 (when (eql staff staff-number)
520 (when (has-element-type note "lyric")
521 (return-from xmlstaff-has-lyrics t)))))
522 nil)
524 (defun copy-keysignature (ks)
525 (make-key-signature
526 (staff ks) :alterations (copy-seq (alterations ks))))
527 (defun copy-timesignature (ts)
528 (make-time-signature
529 (staff ts) :components (copy-seq (time-signature-components ts))))
530 (defun copy-clef (clef)
531 (make-clef (name clef) :lineno (lineno clef)))
533 (defun gduration-from-xduration (xduration)
534 (/ xduration (* 4 *mxml-divisions*)))
536 (defun parse-mxml-part (part part-name)
537 (let ((staves nil)
538 (layers nil)
539 (lyrics-layer-hash (make-hash-table))
540 (attrib-time-sigs))
541 ;; Create all of the staves, along with their initial
542 ;; keysignatures and clefs.
543 ;; TODO change this to do look in the current part, not the
544 ;; current doc
546 ;; handling lyric staves: for every new stave i make, look to see
547 ;; if any of the note elements assigned to this staff have a
548 ;; lyrics element. if such a beast exists, create a new lyric
549 ;; staff and a new layer, which should come immediately after the
550 ;; melody staff in question in the staves list.
552 (let* ((number-of-staves
553 (max 1
554 (loop for x across
555 (dom:get-elements-by-tag-name part "staves")
556 maximizing (parse-integer (pcdata x)))))
557 (clefs (make-array number-of-staves))
558 (measures (dom:get-elements-by-tag-name part "measure"))
559 (attributes))
560 ;; The attributes don't appear to need to be the first things in
561 ;; a bar (see the Dichterliebe example on recordare
562 ;; website---it's a v2.0 example, but I can't see where what it
563 ;; does would be illegal in v1). I don't know what can precede
564 ;; it in the wild, but the dtd is very permissive. This approach
565 ;; allows the element to occur anywhere---not even limiting it
566 ;; to the first bar. This may be stupid, but I can't tell.
567 (do ((i 0 (1+ i)))
568 ((= i (length measures)))
569 (do ((thing (dom:first-child (elt measures i))
570 (dom:next-sibling thing)))
571 ((not thing))
572 (when (string= (dom:tag-name thing) "attributes")
573 (setf attributes thing)
574 (return)))
575 (when attributes
576 (return)))
577 (when attributes
579 ;; clefs need to be made before i make the staves, keysigs
580 ;; after. don't ask.
581 ;; clefs
582 (for-named-elements ("clef" clef attributes)
583 (multiple-value-bind (new-clef staff-number)
584 (parse-mxml-clef clef)
585 (setf (elt clefs staff-number) new-clef)))
586 ;; every fiveline staff must have a clef, even if the xml file did not specify one
587 (loop for clef across clefs
588 for i from 0
589 do (when (eql 0 clef)
590 (setf (elt clefs i) (make-clef :treble))))
591 ;; staves
592 ;; remember that the order of the staves matters, and the
593 ;; order that they are put in here is the order they will be
594 ;; in when added to the buffer.
595 (setf staves
596 (loop for i below number-of-staves
597 for melody-staff = (make-fiveline-staff :name (if (= number-of-staves 1)
598 part-name
599 (format nil "~A staff ~D" part-name (1+ i)))
600 :clef (elt clefs i))
601 for lyric-staff = (if (xmlstaff-has-lyrics part (1+ i))
602 (list (make-lyrics-staff :name (if (= number-of-staves 1)
603 part-name
604 (format nil "~A lyricstaff ~D" part-name (1+ i)))))
605 nil)
606 nconc (cons melody-staff lyric-staff)))
607 ;; keysignatures
608 (for-named-elements ("key" key attributes)
609 (let ((keysig (parse-mxml-key key staves)))
610 (if (listp keysig)
611 (dolist (sig keysig)
612 (setf (keysig (staff sig)) sig))
613 (setf (keysig (staff keysig)) keysig))))
614 (for-named-elements ("time" time attributes)
615 (push (parse-mxml-time time staves) attrib-time-sigs))))
617 ;; make the layers
618 (multiple-value-bind (lyrics-staves fiveline-staves)
619 (split-if #'(lambda (s) (typep s 'lyrics-staff)) staves)
620 ;; first figure out which staves go in each layer
621 (let ((staves-for-layers (make-array
622 (max 1
623 (loop for x across
624 (dom:get-elements-by-tag-name part "voice")
625 maximizing (parse-integer (pcdata x))))
626 :initial-element nil)))
627 (for-named-elements ("note" note part)
628 (let ((staff-number (if (has-element-type note "staff")
629 (1- (parse-integer (named-pcdata note "staff")))
631 (voice-number (if (has-element-type note "voice")
632 (1- (parse-integer (named-pcdata note "voice")))
633 0)))
634 (pushnew (nth staff-number fiveline-staves) (elt staves-for-layers voice-number))))
635 (setf layers (nconc
636 (loop for staves across staves-for-layers
637 for i from 1
638 collect (make-layer staves
639 :body (make-slice :bars nil)
640 :name (if (= (length staves-for-layers) 1)
641 part-name
642 (format nil "~A layer ~D" part-name i))))
643 (loop for lyrics-staff in lyrics-staves
644 for i from 1
645 for new-layer = (make-layer (list lyrics-staff)
646 :body (make-slice :bars nil)
647 :name (if (= (length staves-for-layers) 1)
648 part-name
649 (format nil "~A lyrics-layer ~D" part-name i)))
650 do (setf (gethash lyrics-staff lyrics-layer-hash) new-layer)
651 collecting new-layer)))))
653 ;; return the layers and the staves
654 (values layers
655 staves
656 lyrics-layer-hash
657 attrib-time-sigs)))
659 (defun parse-make-segment (part layers staves lyrics-layer-hash &key (attrib-time-sigs))
660 ;;look at each element
661 (loop for measure across (dom:get-elements-by-tag-name part "measure")
662 for measure-position from 0
664 (let ((bars (loop for layer in layers
665 for new-bar = (if (typep layer 'melody-layer)
666 (make-melody-bar)
667 (make-lyrics-bar))
668 do (add-bar new-bar (body layer) measure-position)
669 collect new-bar))
670 (*parsing-duration-gmeasure-position* 0)
671 (*parsing-in-cluster* nil))
672 (when (= measure-position 0)
673 (dolist (ts attrib-time-sigs)
674 (unless (listp ts)
675 (setf ts (list ts)))
676 (dolist (new-sig ts)
677 (loop for bar in bars
678 do (when (find (staff new-sig) (staves (layer (slice bar))))
679 (add-element-at-duration
680 (copy-timesignature new-sig)
681 bar 0))))))
683 (format t "~{~a ~}~%" (loop for staff in staves
684 collect (string (aref (alterations (keysig staff)) 2))))
685 (for-children (child measure)
686 (let ((element-type (dom:tag-name child)))
687 (stringcase element-type
688 ("note"
689 (parse-mxml-note child bars staves lyrics-layer-hash))
691 ("attributes"
692 ;; Divisions:
693 (when (has-element-type child "divisions")
694 (setf *mxml-divisions* (parse-integer (named-pcdata child "divisions"))))
696 ;; Keysigs:
698 ;; if we haven't written anything yet, this
699 ;; keysignature got added to the staff itself
700 (unless (= 0 measure-position *parsing-duration-gmeasure-position*)
701 ;; (unless (= 0 *parsing-duration-gmeasure-position*)
702 (when (has-element-type child "key")
703 (format t "~A ~A!!!~%" measure-position *parsing-duration-gmeasure-position*)
704 (let ((new-keysignature (parse-mxml-key
705 (elt (dom:get-elements-by-tag-name child "key") 0)
706 staves)))
707 (unless (listp new-keysignature)
708 (setf new-keysignature (list new-keysignature)))
709 (dolist (new-sig new-keysignature)
710 (loop for bar in bars
711 do (when (find (staff new-sig) (staves (layer (slice bar))))
712 (add-element-at-duration
713 (copy-keysignature new-sig)
714 bar *parsing-duration-gmeasure-position*))))))
715 (when (has-element-type child "time")
716 (format t "~A ~A!!!~%" measure-position *parsing-duration-gmeasure-position*)
717 (let ((new-timesignature (parse-mxml-time
718 (elt (dom:get-elements-by-tag-name child "time") 0)
719 staves)))
720 (unless (listp new-timesignature)
721 (setf new-timesignature (list new-timesignature)))
722 (dolist (new-sig new-timesignature)
723 (loop for bar in bars
724 do (when (find (staff new-sig) (staves (layer (slice bar))))
725 (add-element-at-duration
726 (copy-timesignature new-sig)
727 bar *parsing-duration-gmeasure-position*))))))
728 (when (has-element-type child "clef")
729 ;; spacer till this is available in gsharp
730 #+nil (multiple-value-bind (new-clef staff-number)
731 (parse-mxml-clef (elt (dom:get-elements-by-tag-name child "clef") 0))
732 (loop for bar in bars
733 do (when (find (nth staff-number staves) (staves (layer (slice bar))))
734 (add-element-at-duration
735 (copy-clef new-clef) bar
736 *parsing-duration-gmeasure-position*)))))))
738 ("backup" (setf *parsing-duration-gmeasure-position*
739 (max (- *parsing-duration-gmeasure-position*
740 (gduration-from-xduration
741 (parse-integer (named-pcdata child "duration"))))
742 0)))
743 ("forward" (incf *parsing-duration-gmeasure-position*
744 (gduration-from-xduration
745 (parse-integer (named-pcdata child "duration")))))))))))
747 (defun parse-mxml (document)
748 (let ((layerss nil)
749 (lyrics-layer-hashes nil)
750 (stavess nil)
751 (parts (dom:get-elements-by-tag-name document "part"))
752 (parts-alist nil) (attrib-time-sigss))
753 (sequence:dosequence (part (dom:child-nodes
754 (aref (dom:get-elements-by-tag-name document "part-list")
755 0)))
756 (setf parts-alist
757 (if (has-element-type part "part-name")
758 (acons (dom:get-attribute part "id")
759 (named-pcdata part "part-name")
760 parts-alist)
761 (acons (dom:get-attribute part "id")
762 (dom:get-attribute part "id")
763 parts-alist))))
764 (sequence:dosequence (part parts)
765 (multiple-value-bind (layers staves lyrics-layer-hash attrib-time-sigs)
766 (parse-mxml-part part (cdr (assoc (dom:get-attribute part "id")
767 parts-alist :test #'string=)))
768 (setf layerss
769 (append layerss (list layers))
770 lyrics-layer-hashes
771 (append lyrics-layer-hashes (list lyrics-layer-hash))
772 stavess (append stavess (list staves))
773 attrib-time-sigss (append attrib-time-sigss (list attrib-time-sigs)))))
774 ;; And finally make the buffer and start parsing notes.
775 ;; Previous operations result in staves and layers in opposite
776 ;; orders (don't know why) - hence the reverse for segment layers
777 (let* ((segment (make-instance 'segment
778 :layers (reverse (apply #'concatenate 'list layerss))))
779 (buffer (make-instance 'buffer
780 :segments (list segment)
781 :staves (apply #'concatenate 'list stavess))))
782 (loop for part across parts
783 for lyrics-layer-hash in lyrics-layer-hashes
784 for layers in layerss
785 for staves in stavess
786 for attrib-time-sigs in attrib-time-sigss
787 with *mxml-divisions* = nil
788 do (parse-make-segment part layers staves lyrics-layer-hash
789 :attrib-time-sigs attrib-time-sigs))
790 buffer)))
792 (defvar *mxml-dtds-dir*
793 (merge-pathnames "Mxml/mxml-dtds/"
794 ;; (make-pathname
795 ;; :directory
796 (asdf:component-pathname (asdf:find-system :gsharp))
797 #+nil (pathname-directory
798 (load-time-value *load-pathname*))))
800 (defun musicxml-document (pathname)
801 (flet ((resolver (pubid sysid)
802 (declare (ignore pubid))
803 (when (equal (puri:uri-host sysid) "www.musicxml.org")
804 (open (merge-pathnames
805 (get-dtd-path (puri:uri-parsed-path sysid))
806 *mxml-dtds-dir*)
807 :element-type '(unsigned-byte 8)))))
808 (cxml:parse-file pathname (cxml:make-whitespace-normalizer
809 (cxml-dom:make-dom-builder))
810 :entity-resolver #'resolver :validate t)))
811 (defun get-dtd-path (uri-path)
812 (let* ((parsed-path uri-path)
813 (parent-dir (nth (- (list-length parsed-path) 2)
814 parsed-path))
815 (filename (car (last parsed-path))))
816 (cond
817 ((member parent-dir '("1.0" "1.1" "2.0") :test #'string=)
818 (format nil "~D/~D" parent-dir filename))
819 (t (format nil "2.0/~D" filename)))))
823 (defun musicxml-document-from-string (string)
824 (flet ((resolver (pubid sysid)
825 (declare (ignore pubid))
826 (when (equal (puri:uri-host sysid) "www.musicxml.org")
827 (open (merge-pathnames
828 (file-namestring (puri:uri-path sysid))
829 *mxml-dtds-dir*)
830 :element-type '(unsigned-byte 8)))))
831 (cxml:parse-rod string (cxml:make-whitespace-normalizer
832 (cxml-dom:make-dom-builder))
833 :entity-resolver #'resolver :validate t)))
835 ;;;;;;;;;;;
836 ;; Export
837 ;;;;;;;;;;;
838 (defvar *staff-hash*)
840 (defun guess-parts (layers)
841 ;; Looks for the way of dividing layers into as many mxml-parts as
842 ;; possible without ending up with a single staff in two
843 ;; parts. Returns two parallel lists - one of lists of layers, the
844 ;; other of staves.
845 (let ((parts))
846 (dolist (layer layers (values (mapcar #'second parts)
847 (mapcar #'first parts)))
848 (dolist (part parts (setf parts (cons (list (staves layer)
849 (list layer))
850 parts)))
851 (when (not (every #'(lambda (x) (not (member x (first part))))
852 (staves layer)))
853 (setf (first part) (union (staves layer)
854 (first part))
855 (second part) (cons layer (second part)))
856 (return))))))
858 (defun ordered-parts (segment buffer)
859 ;; sort parts that can have multiple layers and staves. Sort by
860 ;; stave order and then by layers order.
861 (multiple-value-bind (part-layers part-staves)
862 (guess-parts (layers segment))
863 (let* ((s-positions (mapcar #'(lambda (x)
864 (loop for stave in x
865 minimize (position stave (staves buffer))))
866 part-staves))
867 (l-positions (mapcar #'(lambda (x)
868 (loop for layer in x
869 minimize (position layer (layers segment))))
870 part-layers))
871 (parts (mapcar #'list part-layers s-positions l-positions)))
872 (mapcar #'car
873 (sort parts #'(lambda (x y) (or (< (second x) (second y))
874 (and (= (second x) (second y))
875 (< (third x) (third y))))))))))
877 (defun write-mxml (buffer)
878 ;; Create mxml for buffer. Previously took part = segment, now takes
879 ;; part = layer.
880 (let ((sink (cxml:make-rod-sink :indentation 2 :canonical nil))
881 (ordered-parts))
882 (cxml:with-xml-output sink
883 (sax:start-dtd sink
884 "score-partwise"
885 "-//Recordare//DTD MusicXML 1.1 Partwise//EN"
886 "http://www.musicxml.org/dtds/partwise.dtd")
887 (sax:end-dtd sink)
888 (cxml:with-element "score-partwise"
889 (cxml:attribute "version" "1.1")
890 (loop for segment in (segments buffer)
891 with measure-number = 1
893 (setf ordered-parts (ordered-parts segment buffer))
894 (make-xml-partlist ordered-parts)
895 (make-xml-segment segment measure-number ordered-parts)
896 (setf measure-number
897 (+ measure-number
898 (loop for layer in (layers segment)
899 maximizing (length (bars (body layer)))))))))))
901 (defun make-xml-partlist (part-list)
902 ;; Generates the part-list element based on sublists of layers. Part ID's are
903 ;; numbered P1, P2, etc., part names are taken from the layer names.
904 (cxml:with-element "part-list"
905 (do ((part-list part-list (cdr part-list))
906 (i 1 (1+ i)))
907 ((null part-list))
908 (cxml:with-element "score-part"
909 (cxml:attribute "id" (format nil "P~D" i))
910 (cxml:with-element "part-name"
911 (cxml:text (name-for-part (car part-list))))))))
913 (defun name-for-part (layers)
914 (apply #'concatenate 'string (name (car layers))
915 (loop for layer in (cdr layers)
916 collect (format nil ", ~A" (name layer)))))
918 ;;;;;;;;;;;;;;;;;;;;;;;;;;
919 ;; Dealing with durations
920 ;;;;;;;;;;;;;;;;;;;;;;;;;;
921 (defun extract-all-elements (segment)
922 (loop for layer in (layers segment)
923 append (loop for bar in (bars (body layer))
924 appending (elements bar))))
926 (defun calculate-required-divisions (element)
927 "Determines what fraction of a quarter note is required to represent
928 the duration of this note. For example, passing a quarter-note will
929 return 1. Passing a double-dotted half-note will return 2. Passing a
930 dotted 16th note will return 8."
931 (when (not (typep element 'rhythmic-element))
932 (return-from calculate-required-divisions 1))
933 ;; so gsharp allows you to have half- and whole- notes w/
934 ;; flags/beams. i'm just gonna pretend that said flags/beams make
935 ;; any note half of it's normal value.
936 (let ((base-value
937 (ecase (notehead element)
938 (:filled 1)
939 (:whole 1/4)
940 (:half 1/2)
941 (:breve 1/8)
942 (:long 1/16)))
943 (dots (dots element))
944 (beams (max (rbeams element) (lbeams element))))
945 (ceiling (* base-value (expt 2 (+ beams dots))))))
947 (defun calculate-duration (element)
948 ;; If not all of these calculations result in integers, then
949 ;; calculate-required-divisions did not work properly.
950 (let ((base-value
951 (ecase (notehead element)
952 (:filled 1)
953 (:long 16)
954 (:breve 8)
955 (:whole 4)
956 (:half 2)))
957 (dots (dots element))
958 (beams (max (rbeams element) (lbeams element))))
959 (let ((b (* *mxml-divisions* base-value (expt 2 (* -1 beams)))))
960 (loop for i upto dots
961 summing (* b (expt 2 (* -1 i)))))))
963 ;;;;;;;;;;;;;;;;;;;;;;
964 ;; Back to exporting
965 ;;;;;;;;;;;;;;;;;;;;;;
967 (defun make-xml-segment (segment first-bar-number ordered-parts)
969 ;; Evaluate the appropriate mxml divisions.
970 ;; i think the beginning of a segment is a good place to do this. i
971 ;; have no real reason for doing it at this level, it just seems
972 ;; right.
973 (let ((*mxml-divisions*
974 (loop for element in (extract-all-elements segment)
975 maximizing (calculate-required-divisions element))))
976 (do* ((parts ordered-parts (cdr parts))
977 (part (car parts) (car parts))
978 (i 1 (1+ i)))
979 ((null parts))
980 (let ((*staff-hash*
981 (make-staff-hash (remove-duplicates
982 (apply #'concatenate 'list
983 (mapcar #'staves part))))))
984 (cxml:with-element "part"
985 (cxml:attribute "id" (format nil "P~D" i))
986 (do ((part-bars (mapcar #'(lambda (x) (bars (body x)))
987 part)
988 (mapcar #'cdr part-bars))
989 (bar-no first-bar-number (1+ bar-no)))
990 ((null (car part-bars)))
991 (apply #'make-xml-bars bar-no part (mapcar #'car part-bars))))))))
993 ;;(defun make-xml-layer (layer)
994 ;; (let ((body (body layer)))
995 ;; (loop for bar in (bars body)
996 ;; for measurenum from 1
997 ;; do (make-xml-bar bar measurenum))))
999 (defun make-staff-hash (staves)
1000 (let ((new-staff-hash (make-hash-table :size (length staves))))
1001 (loop for staff in staves
1002 and i from 1
1004 (multiple-value-bind (v p) (gethash staff new-staff-hash)
1005 (declare (ignore v))
1006 (unless p
1007 (setf (gethash staff new-staff-hash) i))))
1008 new-staff-hash))
1010 (defun make-xml-bars (id layers &rest bars)
1011 (cxml:with-element "measure"
1012 (cxml:attribute "number" (write-to-string id))
1014 ;; There are some things that can change mid-measure, mid-segment,
1015 ;; whatever, in mxml that can't change in gsharp (number of
1016 ;; staves, clef). Other things don't really have any meaning in
1017 ;; gsharp and so can't change: notably "divisions" and
1018 ;; "beats". These things will get written in the first measure of
1019 ;; the layer. Keysignatures CAN change in gsharp, but each staff
1020 ;; also has a keysignature assigned to it, so that will also go
1021 ;; into the first measure.
1023 ;; This is sort of an abuse of the measure number, since it is
1024 ;; really intended for printed measure numbers that would get
1025 ;; printed on a score, and are intended to be arbitrary rather
1026 ;; than purely sequential, starting with 1. But since gsharp
1027 ;; doesn't really have measure numbers of this sort, the numbers
1028 ;; resulting from an export operation will always be sequential
1029 ;; and start from one.
1030 (if (eql 1 id)
1031 (cxml:with-element "attributes"
1032 (cxml:with-element "divisions"
1033 (cxml:text (write-to-string *mxml-divisions*)))
1035 (let* ((staves (reduce #'union (mapcar #'staves layers)))
1036 (melody-staves
1037 (remove-if
1038 #'(lambda (staff) (typep staff 'lyrics-staff)) staves))
1039 (staves-length (length staves)))
1041 ;; what i would consider a bug in musicxml 1.1: only one
1042 ;; key allowed per attribute element, despite the fact
1043 ;; that you can specify which staff the key goes on. This
1044 ;; is fixed in MusicXML 2.0.
1045 ;; TODO: put a bunch more attribute elements after this
1046 ;; one if the other staves have different key signatures.
1047 ;; N.B. These comments are largely based on the
1048 ;; parts/segments/layers issue. Should be a very rare issue
1049 ;; with the new code.
1050 (let ((staff (car melody-staves)))
1051 (cxml:with-element "key"
1052 (alterations-to-fifths
1053 (alterations (keysig staff)))))
1055 (when (> staves-length 1)
1056 (cxml:with-element "staves"
1057 (cxml:text (write-to-string staves-length))))
1058 (loop for staff in melody-staves
1059 for clef = (clef staff)
1060 ;; possibilities for MusicXML:
1061 ;; G, F, C, percussion, TAB, and none
1062 for clef-sign = (case (name clef)
1063 (:treble "G")
1064 (:treble8 "G")
1065 (:bass "F")
1066 (:c "C")
1067 (:percussion "percussion"))
1068 for clef-line = (1+ (/ (lineno clef) 2))
1069 for staff-num = (gethash staff *staff-hash*)
1071 (cxml:with-element "clef"
1072 (when (> staves-length 1)
1073 (cxml:attribute "number" (write-to-string staff-num)))
1074 (cxml:with-element "sign"
1075 (cxml:text clef-sign))
1076 (cxml:with-element "line"
1077 (cxml:text (write-to-string clef-line)))
1078 (when (eq (name clef) :treble8)
1079 (cxml:with-element "clef-octave-change"
1080 (cxml:text "-1"))))))))
1082 ;; process each bar, backing up only if there's a "next" bar
1083 (loop for voice from 1
1084 and bar in bars
1085 do (unless (null bar)
1086 (make-xml-bar bar (unless (= voice 1) voice))
1087 (unless (= voice (length bars))
1088 ;; TODO: if spaces are the first thing in the next bar,
1089 ;; don't output backwards followed by a forwards.
1090 (cxml:with-element "backup"
1091 (cxml:with-element "duration"
1092 (cxml:text (write-to-string (bar-duration bar))))))))))
1094 (defun bar-duration (bar)
1095 (loop for element in (elements bar)
1096 when (typep element 'rhythmic-element)
1097 sum (calculate-duration element)))
1099 (defun make-xml-bar (bar voice)
1100 ;; and now do whatever elements are in there
1101 (loop for element in (elements bar)
1102 do (make-xml-element element voice)))
1104 (defgeneric make-xml-element (gharp-element voice))
1106 (defun rhythmic-element-type (element)
1107 (ecase (notehead element)
1108 (:long "long")
1109 (:breve "breve")
1110 (:whole "whole")
1111 (:half "half")
1112 (:filled
1113 (ecase (max (rbeams element) (lbeams element))
1114 (0 "quarter")
1115 (1 "eighth")
1116 (2 "16th")
1117 (3 "32nd")
1118 (4 "64th")
1119 (5 "128th")
1120 (6 "256th")))))
1122 (defmethod make-xml-element ((rest rest) voice)
1123 (let ((duration (calculate-duration rest))
1124 (type (rhythmic-element-type rest))
1125 (dots (dots rest)))
1126 (cxml:with-element "note"
1127 (cxml:with-element "rest")
1128 (cxml:with-element "duration" (cxml:text (write-to-string duration)))
1129 (unless (null voice)
1130 (cxml:with-element "voice" (cxml:text (write-to-string voice))))
1131 (cxml:with-element "type" (cxml:text type))
1132 (loop repeat dots
1133 do (cxml:with-element "dot"))
1134 (when (> (hash-table-count *staff-hash*) 1)
1135 (cxml:with-element "staff"
1136 (cxml:text (write-to-string (gethash (staff rest) *staff-hash*))))))))
1138 (defmethod make-xml-element ((cluster cluster) voice)
1139 ;; this maybe should get called earlier. or later. i don't know.
1140 (gsharp-measure::compute-final-accidentals (notes cluster))
1141 (let ((duration (calculate-duration cluster)))
1142 (loop for note in (notes cluster)
1143 for x from 0
1144 do (make-xml-note note (> x 0) duration voice cluster))
1145 (when (null (notes cluster))
1146 ;; it's an empty cluster, a "space"
1147 (cxml:with-element "forward"
1148 (cxml:text (write-to-string duration))))))
1150 (defmethod make-xml-element ((lyric lyrics-element) voice)
1151 (let ((duration (calculate-duration lyric))
1152 (syllabic "single")
1153 (text (unicode-to-string (text lyric))))
1154 (cxml:with-element "note"
1155 (cxml:with-element "unpitched")
1156 (cxml:with-element "duration" (cxml:text (write-to-string duration)))
1157 (unless (null voice)
1158 (cxml:with-element "voice" (cxml:text (write-to-string voice))))
1159 ;; TODO: make this use the first melody staff above the lyrics staff
1160 (when (> (hash-table-count *staff-hash*) 1)
1161 (cxml:with-element "staff"
1162 (cxml:text (write-to-string (gethash (staff lyric) *staff-hash*)))))
1163 (cxml:with-element "lyric"
1164 (cxml:with-element "syllabic" (cxml:text syllabic))
1165 (cxml:with-element "text" (cxml:text text))))))
1168 (defmethod make-xml-element ((key-signature key-signature) voice)
1169 ;; TODO: right now this only does "normal" keysignatures, which is
1170 ;; fine because that's the only kind a user can create in gsharp.
1171 ;; also, i'm not sure how to deal w/ canceling.
1172 (declare (ignore voice))
1173 (cxml:with-element "attributes"
1174 (cxml:with-element "key"
1175 (alterations-to-fifths (alterations key-signature)))))
1177 (defun alterations-to-fifths (alterations)
1178 (cxml:with-element "fifths"
1179 (let ((fifths 0))
1180 ;; the magic list on the next line is the order of fifths,
1181 ;; where C is 0, D is 1, etc.
1182 (dolist (index '(3 0 4 1 5 2 6))
1183 (let ((fifth (elt alterations index)))
1184 (case fifth
1185 (:sharp (incf fifths))
1186 (:flat (decf fifths)))))
1187 (cxml:text (write-to-string fifths)))))
1189 (defun gshnote-to-xml (pitch)
1190 (let ((step (mod pitch 7)))
1191 (list (car (rassoc step *step-to-basenote*)) (/ (- pitch step) 7))))
1193 (defun note-accidental (note)
1194 (ecase (final-accidental note)
1195 ((nil))
1196 (:sharp "sharp")
1197 (:natural "natural")
1198 (:flat "flat")
1199 (:double-sharp "double-sharp")
1200 (:sesquisharp "three-quarters-sharp")
1201 (:semisharp "quarter-sharp")
1202 (:semiflat "quarter-flat")
1203 (:sesquiflat "three-quarters-flat")
1204 (:double-flat "flat-flat")))
1206 (defun note-alter (note)
1207 (ecase (accidentals note)
1208 (:sharp "1")
1209 (:natural nil)
1210 (:flat "-1")
1211 (:double-sharp "2")
1212 (:sesquisharp "1.5")
1213 (:semisharp "0.5")
1214 (:semiflat "-0.5")
1215 (:sesquiflat "-1.5")
1216 (:double-flat "-2")))
1218 (defun note-notations-p (note cluster)
1219 (or (tie-left note)
1220 (tie-right note)
1221 (note-articulations-p note cluster)))
1223 (defun note-articulations-p (note cluster)
1224 (let ((annotations (annotations cluster)))
1225 (or (member :staccato annotations)
1226 (member :tenuto annotations))))
1227 (defparameter *foo* nil)
1228 (defun make-xml-note (note in-chord duration voice cluster)
1229 (let ((type (rhythmic-element-type cluster))
1230 (dots (dots cluster))
1231 (pitch (gshnote-to-xml (pitch note)))
1232 (accidental (note-accidental note))
1233 (alter (note-alter note)))
1234 (cxml:with-element "note"
1235 (when in-chord
1236 (cxml:with-element "chord"))
1237 (cxml:with-element "pitch"
1238 (cxml:with-element "step" (cxml:text (car pitch)))
1239 (when alter
1240 (cxml:with-element "alter" (cxml:text alter)))
1241 (cxml:with-element "octave" (cxml:text (write-to-string (cadr pitch)))))
1242 (cxml:with-element "duration" (cxml:text (write-to-string duration)))
1243 (unless (null voice)
1244 (cxml:with-element "voice" (cxml:text (write-to-string voice))))
1245 (cxml:with-element "type" (cxml:text type))
1246 (loop repeat dots
1247 do (cxml:with-element "dot"))
1248 (when accidental
1249 (cxml:with-element "accidental" (cxml:text accidental)))
1250 (unless (eq (final-stem-direction (cluster note)) :auto)
1251 (cxml:with-element "stem"
1252 (cxml:text (string-downcase
1253 (string (final-stem-direction (cluster note)))))))
1254 (when (> (hash-table-count *staff-hash*) 1)
1255 (cxml:with-element "staff"
1256 (cxml:text (write-to-string (gethash (staff note) *staff-hash*)))))
1257 (when (note-notations-p note cluster)
1258 (cxml:with-element "notations"
1259 (when (tie-left note)
1260 (cxml:with-element "tied" (cxml:attribute "type" "stop")))
1261 (when (tie-right note)
1262 (cxml:with-element "tied" (cxml:attribute "type" "start")))
1263 (when (note-articulations-p note cluster)
1264 (cxml:with-element "articulations"
1265 (when (member :staccato (annotations cluster))
1266 (cxml:with-element "staccato"))
1267 (when (member :tenuto (annotations cluster))
1268 (cxml:with-element "tenuto")))))))))