;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;

;; MUSICXML

;; 

;; Plays back a MUSICXML file.  make sure you have evaluated

;; the xml library (example 35) before running this example 

;;

;; This is a quick example to demonstrate the xml library

;; not a full musicxml parser. expect the unexpected! 

;;

;; Don't forget to change the files path to a valid musicxml file! 

;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(au:clear-graph)


(define piano (au:make-node "aumu" "dls " "appl"))

(au:connect-node piano 0 *au:output-node* 0)

(au:update-graph)


(define *str-pitch-map*

   `(("C" . 0)

     ("D" . 2)

     ("E" . 4)

     ("F" . 5)

     ("G" . 7)

     ("A" . 9)

     ("B" . 11)))


(define parse-measure

   (lambda (notes div transpose)

      (list (map (lambda (note)

                    (if (string=? "backup" (xml:get-node-name note))

                        'backup

                        (let* ((step (objc->list (xml:xpath note "pitch/step/text()")))

                               (rest (objc->list (xml:xpath note "rest")))

                               (octave (objc->list (xml:xpath note "pitch/octave/text()")))

                               (alter (objc->list (xml:xpath note "pitch/alter/text()")))

                               (adjust (if (null? alter) 0 

                                           (string->number (xml:get-node-value (car alter))))))

                           (if (null? rest)

                               (+ 12 adjust transpose 

                                  (* 12 (string->number (xml:get-node-value (car octave))))

                                  (cdr (assoc (xml:get-node-value (car step)) *str-pitch-map*)))

                               -1))))

                 notes)

            (map (lambda (note)

                    (let* ((dur (objc->list (xml:xpath note "duration/text()")))

                           (chord (objc->list (xml:xpath note "chord"))))

                       (if (null? dur) 

                           (set! dur 0) 

                           (set! dur (/ 1.0 (/ div (string->number (xml:get-node-value (car dur)))))))

                       (if (null? chord) dur (* -1 dur))))

                 notes))))


(define parse-part

   (lambda (part) 

      (let ((div-g 1)

            (transpose-g 0))

         (map (lambda (measure)

                 (let ((div (objc->list (xml:xpath measure "attributes/divisions/text()")))

                       (num (objc->list (xml:xpath measure "attributes/time/beats/text()")))

                       (den (objc->list (xml:xpath measure "attributes/time/beat-type/text()")))

                       (transpose (objc->list (xml:xpath measure "attributes/transpose/chromatic/text()")))

                       (notes (objc->list (xml:xpath measure "note|backup"))))   

                    (if (not (null? div))

                        (set! div-g (string->number (xml:get-node-value (car div)))))

                    (if (not (null? transpose))

                        (set! transpose-g (string->number (xml:get-node-value (car transpose)))))                 

                    (if (not (null? notes))

                        (parse-measure notes div-g transpose-g))))

              (objc->list (xml:xpath part "measure"))))))


(define parse-score

   (lambda (xmlscore)

      (map parse-part (objc->list (xml:xpath xmlscore "/score-partwise/part")))))


(define player

   (lambda (time part)

      (let loop ((measures part))

         (cond ((null? measures) 'done)

               (else (for-each (lambda (p d)

                                  (if (symbol? p) 

                                      (set! time (- time (* *second* d)))

                                      (begin (play-note time piano p 80 (* *second* (abs d)))

                                             (if (positive? d) (set! time (+ time (* *second* d)))))))

                               (caar  measures)

                               (cadar measures))

                     (callback (- time *second*) loop (cdr measures)))))))


(define start

   (lambda (time path)

      (for-each (lambda (part)

                   (player time part))

                (parse-score (xml:load-document path)))))


(callback (+ (now) (* 2 *second*)) start 

          (+ (now) (* *second* 4)) "/tmp/score.xml")