;; -*- lisp -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;**************************************************************************************************
;; FOMUS
;; backend_mid.lisp
;;**************************************************************************************************

(in-package :fomus)
(compile-settings)

(declaim (inline midi-off midi-dur midi-note midi-ch midi-vel midi-endoff))
(defun midi-off (x) (funcall *cm-midioff* x))
(defun midi-dur (x) (funcall *cm-mididur* x))
(defun midi-note (x) (funcall *cm-midinote* x))
(defun midi-ch (x) (slot-value x *cm-midichslot*))
(defun midi-vel (x) (funcall *cm-midivel* x))
(defun midi-endoff (x) (+ (midi-off x) (midi-dur x)))

(defmacro midi-ch* (x) `(slot-value ,x *cm-midichslot*))
(defmacro midi-off* (x) `(slot-value ,x *cm-midioffslot*))
(defmacro midi-dur* (x) `(slot-value ,x *cm-mididurslot*))
(defmacro midi-vel* (x) `(slot-value ,x *cm-midivelslot*))
(defmacro midi-note* (x) `(slot-value ,x *cm-midinoteslot*))

(defun midi-sort (x y)
  (if (= (midi-off x) (midi-off y))
      (if (= (midi-ch x) (midi-ch y))
	  (cond ((and (typep x *cm-midi*) (typep y *cm-midi*))
		 (if (= (midi-dur x) (midi-dur y))
		     (cond ((and (midi-note x) (midi-note y))
			    (if (= (midi-note x) (midi-note y))
				(< (midi-vel x) (midi-vel y))
				(< (midi-note x) (midi-note y))))
			   ((midi-note x) t))
		     (< (midi-dur x) (midi-dur y))))
		((typep y *cm-midi*) t))
	  (< (midi-ch x) (midi-ch y)))
      (< (midi-off x) (midi-off y))))

;; in order of processing
(defparameter +midi-marks+
  '((:ffffff :per :d) (:fffff :per :d) (:ffff :per :d) (:fff :per :d) (:ff :per :d)
    (:f :per :d) (:mf :per :d) (:mp :per :d) (:p :per :d) (:pp :per :d) (:ppp :per :d) (:pppp :per :d) (:ppppp :per :d) (:pppppp :per :d)
    (:ffffff* :repl :ffffff :per :d) (:fffff* :repl :fffff :per :d) (:ffff* :repl :ffff :per :d) (:fff* :repl :fff :per :d) (:ff* :repl :ff :per :d)
    (:f* :repl :f :per :d) (:mf* :repl :mf :per :d) (:mp* :repl :mp :per :d) (:p* :repl :p :per :d) (:pp* :repl :pp :per :d) (:ppp* :repl :ppp :per :d)
    (:pppp* :repl :pppp :per :d) (:ppppp* :repl :ppppp :per :d) (:pppppp* :repl :pppppp :per :d)
    
    (:tremolo :a1 1 :a2 2)			; arg1 = number of divs
    (:starttremolo :bot t :repl :span-tremfirst :a1 1 :a2 2)
    (:starttremolo :top t :repl :endtremfirst :a1 1 :a2 2)
    (:endtremolo :bot t :repl :span-tremsecond :a1 1 :a2 2)
    (:endtremolo :top t :repl :endtremsecond :a1 1 :a2 2)
    (:mordent :a1 :m) (:prall :a1 :n) (:trill :a1 :n) (:longtrill :a1 :n)
    (:arpeggio :bot t :repl :span-arp :a1 1 :a2 :e)
    (:arpeggio :top t :repl :endarp :a1 1 :a2 :e)
    ((:portamento :after) :top t :repl :span-port :addnx :endport)
    ((:glissando :after) :top t :repl :span-gliss :addnx :endgliss) 
    
    (:startgraceslur- :bot t :repl :span-slur :al 1)
    (:endgraceslur- :top t :repl :endslur- :al 1)
    (:startslur- :bot t :repl :span-slur :al 1)
    (:endslur- :top t :a1 1)

    ((:harmonic :sounding) :a1 1 :a2 2) ((:harmonic :touched) :a1 1 :a2 2) ; touched/sounding notenum
    :arco :pizz    
    :stopped :open :staccato :staccatissimo
    :portato :tenuto :marcato :accent
    :rfz :sfz :spp :sp :sff :sf :fp
    (:rfz* :repl :rfz) (:sfz* :repl :sfz) (:spp* :repl :spp) (:sp* :repl :sp) (:sff* :repl :sff) (:sf* :repl :sf) (:fp* :repl :fp)
    
    (:fermata :a1 1) ((:breath :after))
;;  (:lineprall :a1 1) (:prallup :a1 1) (:pralldown :a1 1) (:downmordent :a1 1)
;;  (:upmordent :a1 1) (:downprall :a1 1) (:upprall :a1 1) (:prallmordent :a1 1)
;;  (:prallprall :a1 1) (:reverseturn :a1 1)
;;  (:turn :a1 1)

    (:startwedge> :bot t :repl :span-wedge> :al :w)
    (:endwedge> :top t :a1 :w)
    (:startwedge< :bot t :repl :span-wedge< :al :w)
    (:endwedge< :top t :a1 :w)
    (:startwedge>* :bot t :repl :span-wedge> :al :w)
    (:endwedge>* :top t :a1 :w)
    (:startwedge<* :bot t :repl :span-wedge< :al :w)
    (:endwedge<* :top t :a1 :w)))

(defparameter +midi-allmarks+
  (delete-duplicates (mapcar (lambda (x) (destructuring-bind (ma &key repl &allow-other-keys) (force-list x) (or repl (first (force-list ma))))) +midi-marks+)))
(defparameter +midi-spannermarks+
  '((:span-wedge> . :endwedge>) (:span-wedge< . :endwedge<) (:span-slur . :endslur-)
    (:span-tremfirst . :endtremfirst) (:span-tremsecond . :endtremsecond) (:span-arp . :endarp) (:span-port . :endport) (:span-gliss . :endgliss)))
(defparameter +midi-firstmarks+
  '(:portato :tenuto :marcato :accent))
(defparameter +midi-endmarks+ ; marks that should get stuck at end of sequence of notes if necessary
  '(:staccato :staccatissimo :fermata :breath :portamento :glissando))
(declaim (special *midi-persistmarks* *midi-nm* *midi-lo*)) ; init: ((:d . :mf))   nil

(defun midi-marks (ev ebot etop pmn)	; pmn = perc. pitch
  (loop for e in +midi-marks+
	for (m . n) = (destructuring-bind (ma &key al a1 a2 top bot repl addnx per) (force-list e)
			(when (and (or (null top) etop) (or (null bot) ebot))
			  (let ((g (getmark ev ma)))
			    (flet ((arg (a)
				     (when a (case a
					       (:e *midi-lo*) ; last offset
					       (:n (+ (if (chordp ev) (last-element (event-notes* ev)) (event-note* ev)) (or (second (force-list g)) (if pmn 0 1))))
					       (:m (+ (if (chordp ev) (last-element (event-notes* ev)) (event-note* ev)) (or (second (force-list g)) (if pmn 0 -1))))
					       (:w :w)
					       (otherwise (nth a (force-list g)))))))
			      (when g 
				(let ((x (list (or repl (first (force-list ma))) al (arg a1) (arg a2))))
				  (if per (progn (setf (cdr (assoc per *midi-persistmarks*)) x) nil)
				      (if addnx (cons nil x) (cons x nil)))))))))
	when m collect m into re
	when n collect n into ne
	finally (return (prog1
			    (nconc (mapcar #'cdr *midi-persistmarks*)
				   (if etop (prog1 (nconc *midi-nm* re) (setf *midi-nm* nil)) re))
			  (prenconc ne *midi-nm*)
			  (when etop (setf *midi-lo* (event-off ev)))))))

(defparameter *grace-dur-secs* 1/12)
(declaim (special *gracedur*))
(defparameter *min-amp* 1/10)
(defparameter *trdur-secs* 1/16) ; trill notes per sec. (and unmeasured tremolos)
(declaim (special *trdur*))
(defparameter *tramp* 3/4)
(defparameter *fermata-mults* '(3/2 2 3))
(defparameter *breath-dur* 1/6)
(defparameter *tempo* 60)
(defparameter *staccato-mult* 1/3)
(defparameter *staccatissimo-mult* 1/5)
(defparameter *portato-mult* 2/3)
(defparameter *tenuto-adddur* 1/8)
(defparameter *slur-adddur* 1/8)
(defparameter *trovlp-adddur* 1/8)
(defparameter *mindur-secs* 1/12)
(defparameter *arpatt* 1/3)
(defparameter *harmatt* 1/3)

(defparameter +midi-touchedharms+ (vector nil nil 34 31 28 24 nil 19 12))

;; return values: replacement note(s), offset increment for remaining notes
;; how to handle dynamics, arco, pizz??? (make them "persistant" marks?)
(defun midi-default-events-fun (ev mark arg1 arg2)
  (labels ((amp (n) (+ *min-amp* (* (/ (1+ n) 11) (- 1 *min-amp*))))
	   (trem (s)
	     (loop for v in ev nconc
		   (loop with db = (/ (midi-dur v) (max (if (<= arg2 1/32) (/ (midi-dur v) *trdur*) (min (/ (midi-dur v) *trdur*) arg1)) 1))
			 with du = (min (* db 3/2) (+ db *trovlp-adddur*))
			 for o from (midi-off v) below (midi-endoff v) by db and sk = s then (not sk)
			 when sk collect
			 (make-instance *cm-midi* :channel (midi-ch v) :time o :duration du :keynum (midi-note v) :amplitude (* (midi-vel v) *tramp*))))))
    (ecase mark
      (:ffffff (setf (midi-vel* ev) (amp (+ 8 2/3))) ev)
      (:fffff (setf (midi-vel* ev) (amp (+ 8 1/3))) ev)
      (:ffff (setf (midi-vel* ev) (amp 8)) ev)
      (:fff (setf (midi-vel* ev) (amp (+ 7 1/2))) ev)
      (:ff (setf (midi-vel* ev) (amp 7)) ev)
      (:f (setf (midi-vel* ev) (amp 6)) ev)
      (:mf (setf (midi-vel* ev) (amp 5)) ev)
      (:mp (setf (midi-vel* ev) (amp 4)) ev)
      (:p (setf (midi-vel* ev) (amp 3)) ev)
      (:pp (setf (midi-vel* ev) (amp 2)) ev)
      (:ppp (setf (midi-vel* ev) (amp (+ 1 1/2))) ev)
      (:pppp (setf (midi-vel* ev) (amp 1)) ev)
      (:ppppp (setf (midi-vel* ev) (amp 2/3)) ev)
      (:pppppp (setf (midi-vel* ev) (amp 1/3)) ev)
      (:rfz (setf (midi-vel* ev) (/ (+ (midi-vel ev) 1) 2)) ev)
      (:sfz (setf (midi-vel* ev) (/ (+ (midi-vel ev) 1) 2)) ev)
      (:spp (setf (midi-vel* ev) (/ (+ (midi-vel ev) *min-amp* *min-amp*) 3)) ev)
      (:sp (setf (midi-vel* ev) (/ (+ (midi-vel ev) *min-amp*) 2)) ev)
      (:sff (setf (midi-vel* ev) (/ (+ (midi-vel ev) 2) 3)) ev)
      (:sf (setf (midi-vel* ev) (/ (+ (midi-vel ev) 1) 2)) ev)
      (:fp ev)				; ?
      (:span-wedge<
       (let ((le (midi-vel (last-element ev))))
	 (when (> arg1 le)
	   (loop with o0 = (midi-off (first ev)) and m2 = (log (/ arg1 le))
		 for e in ev do (setf (midi-vel* e) (* (midi-vel e) (exp (/ (* (- (event-off e) o0) m2) (- arg2 o0))))))))
       ev)
      (:span-wedge>
       (let ((le (midi-vel (last-element ev))))
	 (when (< arg1 le)
	   (loop with o0 = (midi-off (first ev)) and m2 = (log (/ le arg1))
		 for e in ev do (setf (midi-vel* e) (/ (midi-vel e) (exp (/ (* (- (event-off e) o0) m2) (- arg2 o0))))))))
       ev)
      (:longtrill (loop with db = (/ (midi-dur ev) (max (* (floor (/ (midi-dur ev) *trdur*) 2) 2) 2)) 
			with du = (min (* db 3/2) (+ db *trovlp-adddur*))
			for o from (midi-off ev) below (midi-endoff ev) by db
			and pt = t then nil
			collect (make-instance *cm-midi* :channel (midi-ch ev) :time o :duration du :keynum (if pt (midi-note ev) arg1) :amplitude (* (midi-vel ev) *tramp*))))
      (:pizz ev)
      (:arco ev)	; arg1 = program num. of instr.
      (:fermata (case arg1
		  (:short (let ((i (* (midi-dur ev) (1- (first *fermata-mults*))))) (setf (midi-dur* ev) (+ (midi-dur ev) i)) (values ev i)))
		  (:long (let ((i (* (midi-dur ev) (1- (second *fermata-mults*))))) (setf (midi-dur* ev) (+ (midi-dur ev) i)) (values ev i)))
		  (:verylong (let ((i (* (midi-dur ev) (1- (third *fermata-mults*))))) (setf (midi-dur* ev) (+ (midi-dur ev) i)) (values ev i)))))
      (:span-arp (let* ((e (first ev))
			(le (1- (length ev)))
			(o1 (if arg2 (max (+ arg2 *gracedur*) (- (midi-off e) (* *gracedur* le))) (- (midi-off e) (* *gracedur* le))))
			(du (/ (- (midi-off e) o1) (max le 1))))
		   (case arg1
		     ((:up nil)
		      (loop for o from o1 by du for v in ev and i from 0 for va = (+ (- 1 *arpatt*) (* *arpatt* (/ i le))) do
			    (setf (midi-dur* v) (- (midi-endoff v) o) (midi-off* v) o (midi-vel* v) (* (midi-vel v) va))))
		     (:down (loop for o from o1 by du for v in (reverse ev) and i from 0 for va = (+ (- 1 *arpatt*) (* *arpatt* (/ i le))) do
				  (setf (midi-dur* v) (- (midi-endoff v) o) (midi-off* v) o (midi-vel* v) (* (midi-vel v) va))))))
		 ev)
      (:tremolo (loop with db = (/ (midi-dur ev) (max (floor (if (<= arg2 1/32) (/ (midi-dur ev) *trdur*) (min (/ (midi-dur ev) *trdur*) arg1))) 2))
		      with du = (min (* db 3/2) (+ db *trovlp-adddur*))
		      for o from (midi-off ev) below (midi-endoff ev) by db
		      collect (make-instance *cm-midi* :channel (midi-ch ev) :time o :duration du :keynum (midi-note ev) :amplitude (* (midi-vel ev) *tramp*))))
      (:span-tremfirst (trem t))
      (:span-tremsecond (trem nil))
      (:span-port ev)			; ?
      (:span-gliss ev)			; ?
      (:breath (values ev *breath-dur*))
      (:harmonic (case arg1
		   (:sounding (setf (midi-note* ev) arg2 (midi-vel* ev) (* (midi-vel ev) (- 1 *harmatt*))))
		   (:touched (setf (midi-note* ev)
				   (+ (midi-note ev)
				      (or (let ((x (- arg2 (midi-note ev))))
					    (if (and (>= x 0) (< x (length +midi-touchedharms+)))
						(svref +midi-touchedharms+ x)))
					  0))
				   (midi-vel* ev) (* (midi-vel ev) (- 1 *harmatt*)))))
		 ev)
      (:stopped ev)
      (:open ev)
      (:staccato (setf (midi-dur* ev) (* (midi-dur ev) *staccato-mult*)) ev)
      (:staccatissimo (setf (midi-dur* ev) (* (midi-dur ev) *staccatissimo-mult*)) ev)
      ((:prall :trill :mordent)
       (let ((md (/ (midi-dur ev) 2)))
	 (cons
	  (make-instance *cm-midi* :channel (midi-ch ev) :time (+ (midi-off ev) md) :duration md :keynum (midi-note ev) :amplitude (midi-vel ev))
	  (loop with db = (/ md (max (* (floor (/ md *trdur*) 2) 2) 2))
		with du = (min (* db 3/2) (+ db *trovlp-adddur*))
		for o from (midi-off ev) below (+ (midi-off ev) md) by db
		and pt = t then nil
		collect (make-instance *cm-midi* :channel (midi-ch ev) :time o :duration du :keynum (if pt (midi-note ev) arg1)
				       :amplitude (* (midi-vel ev) *tramp*))))))
      (:portato (setf (midi-dur* ev) (* (midi-dur ev) *portato-mult*)) ev)
      (:tenuto (setf (midi-dur* ev) (min (+ (midi-dur ev) *tenuto-adddur*) (* (midi-dur ev) 3/2))) ev)
      (:marcato (setf (midi-vel* ev) (/ (+ (midi-vel ev) 2) 3)) ev)
      (:accent (setf (midi-vel* ev) (/ (+ (midi-vel ev) 1) 2)) ev)
      (:span-slur (loop for (e n) on ev
			when (and n (<= (midi-off n) (midi-endoff e)))
			do (setf (midi-dur* e) (min (+ (midi-dur e) *slur-adddur*) (* (midi-dur n) 3/2))))
		  ev))))

(defun save-midi-aux (parts filename options play) ; if play is open stream, then uses rts realtime (ignores filename)
  (unless *cm-exists*
    (format t ";; ERROR: Common Music required for MIDI output~%")
    (return-from save-midi-aux))
  (when (>= *verbose* 1)
    (typecase play
      (boolean (out ";; Saving MIDI file ~S...~%" filename))
      ((or function symbol) (out ";; Compiling MIDI data...~%"))
      (t (out ";; Scheduling MIDI playback...~%" filename))))
  (destructuring-bind (&key (nports 1) instr-per-ch events-fun (pbend-width 2) cm-args
			    (grace-dur-secs *grace-dur-secs*) (min-amp *min-amp*) (trdur-secs *trdur-secs*) (tramp *tramp*)
			    (fermata-mults *fermata-mults*) (breath-dur *breath-dur*) (tempo *tempo*)
			    (staccato-mult *staccato-mult*) (staccatissimo-mult *staccatissimo-mult*) (tenuto-adddur *tenuto-adddur*)
			    (trovlp-adddur *trovlp-adddur*) (mindur-secs *mindur-secs*) (slur-adddur *slur-adddur*)
			    (portato-mult *portato-mult*) (arpatt *arpatt*) (harmatt *harmatt*) (delay 0) &allow-other-keys) options
    (when (typep play 'boolean) (setf nports 1))
    (let* ((*gracedur* (/ (* grace-dur-secs tempo) 60))
	   (*min-amp* min-amp)
	   (*trdur* (/ (* trdur-secs tempo) 60))
	   (*tramp* tramp)
	   (*fermata-mults* fermata-mults)
	   (*breath-dur* breath-dur)
	   (*tempo* tempo)
	   (*staccato-mult* staccato-mult)
	   (*staccatissimo-mult* staccatissimo-mult)
	   (*tenuto-adddur* tenuto-adddur)
	   (*trovlp-adddur* trovlp-adddur)
	   (*mindur-secs* mindur-secs)
	   (*slur-adddur* slur-adddur)
	   (*portato-mult* portato-mult)
	   (*arpatt* arpatt)
	   (*harmatt* harmatt)
	   (adj nil)
	   (xta nil)
	   (evs (flet ((chs (ba) (if (and *quartertones* (/= (mod ba 16) 9)) (list ba (if (= ba 8) 10 (1+ ba))) (list ba)))) 
		  (loop with aps and is and el and ps = (loop repeat nports collect (let ((x (make-array 16 :initial-element nil))) ; list of 16-ch arrays
										      (setf (svref x 9) t) (when *quartertones* (setf (svref x 8) t)) x))
			for p in (loop for p in parts
				       for ex = (or (instr-midiprgch-ex (part-instr p)) 0)
				       when (consp ex) nconc (destructuring-bind (pp &key pizz stopped open harmonic) ex
							       (declare (ignore pp))
							       (loop for e in (list pizz stopped open harmonic)
								     and s in '(:pizz :stopped :open :harmonic)
								     while (some
									    (lambda (m) (some
											 (lambda (v) (some (lambda (e) (getmark e s)) v))
											 (meas-voices m)))
									    (part-meas p))
								     collect (let ((r (make-partex nil :instr (copy-instr (part-instr p) :midiprgch-ex e))))
									       (push (cons e r) aps)
									       r)))
				       collect p
				       finally (setf aps (nreverse aps)))
			for in = (part-instr p)
			for ex = (first (force-list (or (instr-midiprgch-ex in) 0)))
			and (midi-ch . midi-vel) = (destructuring-bind (&key midi-ch (midi-vel 1) &allow-other-keys) (part-opts p) (cons midi-ch midi-vel))
			for (po . ch) =
			(let ((c (or midi-ch ; c = (port . chan)
				     (if (is-percussion p)
					 (cons (or po 0) 9)
					 (loop for o from 0 and e in ps
					       for s = (position ex e)
					       when s do (return (cons o s))))
				     (loop for o from 0 and e in ps
					   for s = (position nil e)
					   when s do (return (cons o s)))
				     (progn
				       (format t ";; ERROR: Too many parts/instruments for ~S port(s)/~S channels (use NPORTS option, MIDI-CH option in parts or MIDIPRGCH-EX slot in instruments to fix)~%"
					       nports (* nports 16))
				       (return-from save-midi-aux)))))
			  (unless (is-percussion p)
			    (loop for i in (chs (cdr c))
				  do (setf (svref (nth (car c) ps) i)
					   (or ex
					       (progn
						 (push (format nil ";; WARNING: Missing MIDI program change number in MIDIPRGCH-EX slot of instrument ~S, part ~S~%"
							       (instr-sym in) (part-name p))
						       el)
						 0))))
			    (when instr-per-ch
			      (push ex is)
			      (let ((x (count ex is)))
				(when (>= x instr-per-ch)
				  (setf is (delete x is))
				  (mapc (lambda (e) (nsubstitute t ex e)) ps)))))
			  (cons (car c) (+ (* (car c) 16) (cdr c))))
			and pmn = (when (is-percussion p) (mapcar (lambda (x) (cons (perc-sym x) (perc-midinote-ex x))) (instr-percs in)))
			do
			(prenconc (unless (is-percussion p) (loop for i in (chs ch) collect (make-instance *cm-progch* :time 0 :channel i :program ex))) xta)
			(let ((ap (rassoc p aps))) (when ap (setf aps (delete-if (lambda (x) (and (= (car x) ex) (numberp (cdr x)))) aps) (cdr ap) ch)))
			when (or (not (is-percussion p)) pmn) nconc
			(let ((es (delete nil
					  (delete-duplicates
					   (labels ((gr (y) (loop for e in y if (numberp (midi-ch e)) collect e else nconc (gr (midi-ch e)))))
					     (gr
					      (loop for (m . es) in
						    (sort
						     (loop for r on
							   (sort
							    (loop for v from 0 below (mloop for m in (part-meas p) maximize (length (meas-voices m))) nconc
								  (let ((*midi-persistmarks* (list (cons :d (list :mf nil nil nil))))
									(*midi-nm* nil)
									(*midi-lo* nil))
								    (loop with ts ; ties
									  and evs = (loop for m in (part-meas p) append (nth v (meas-voices m)))
									  for ev in evs
									  for pizz = (cond ((getmark ev :pizz) t)
											   ((getmark ev :arco) nil)
											   (t pizz))
									  and (pizzch stoppedch opench harmonicch) =
									  (unless pmn (destructuring-bind (x &key pizz stopped open harmonic)
											  (force-list (or (instr-midiprgch-ex in) 0))
											(declare (ignore x)) (list pizz stopped open harmonic)))
									  and (of . du)
									  in (flet ((gem (di pr) ; grace notes, di = direction, pr = offsets so far
										      (nreverse
										       (loop with cd and co and oo and gr ; current dur & off
											     for (e . r) on (if di evs (reverse evs)) and p in pr
											     unless (eql oo (event-off e)) do (setf oo (event-off e) cd nil)
											     if (and (event-grace e)
												     (if di (>= (event-grace e) 0) (< (event-grace e) 0)))
											     collect (if cd (cons (if di (incf co cd) (decf co cd)) cd)
													 (progn
													   (setf cd (loop for x in r
															  for su from 1
															  while (and (event-grace x)
																     (= (event-off x) (event-off e)))
															  finally (return
																    (let ((xx (if di
																		  (- (event-endoff x)
																		     (event-off e))
																		  (- (event-off e)
																		     (event-off x)))))
																      (if (> xx 0)
																	  (min (/ xx (+ su 2))
																	       *gracedur*)
																	  *gracedur*)))))
													   (cons (setf co (if di (event-off e) (- (event-off e) cd)))
														 cd)))
											     and do (setf gr t)
											     else collect (or p (when (and gr (not (event-grace e)))
														  (if di
														      (cons (max (event-off e) (+ co cd)) nil)
														      (cons nil (- (min (event-endoff e) co)
																   (event-off e))))))
											     and do (setf gr nil)))))
									       (mapcar (lambda (x y) (cons (or (car x) (event-off y))
													   (or (cdr x) (event-dur* y))))
										       (gem nil (gem t (make-list (length evs) :initial-element nil))) evs))
									  for mi = (progn
										     (setf ts (delete-if (lambda (x) (< (midi-endoff (cdr x)) of)) ts))
										     (if (notep ev)
											 (loop with n0 = (let ((z (force-list (if (chordp ev) (event-notes* ev) (event-note* ev)))))
													   (if pmn (mapcar (lambda (x)
															     (let ((m (getmark ev (list :percsym x))))
															       (if m
																   (lookup (third m) pmn)
																   (lookup x pmn))))
															   z)
													       z))
											       with ln = (length n0)
											       and cch = (or (when pizz (lookup pizzch aps))
													     (loop for v in '(:stopped :open :harmonic)
														   and c in (list stoppedch opench harmonicch)
														   for m = (getmark ev v)
														   when m do (return (lookup c aps)))
													     ch)
											       for n in n0 and x from 1
											       and tr in (force-list (or (event-tiert ev) '(nil)))
											       and tl in (force-list (or (event-tielt ev) '(nil)))
											       for bot = t then nil and top = (= x ln)
											       for i = (find-if (lambda (y) (= (midi-note (cdr y)) n)) ts) ; i = (marks . tiedobj)
											       unless (getmark ev (list :harmonic :touched n))
											       if (and i tl)
											       do (setf (midi-dur* (cdr i)) (- (event-endoff ev) (midi-off (cdr i)))
													(car i) (delete-duplicates (nconc (midi-marks ev bot top pmn)
																	  (car i))
																   :test #'equal))
											       else collect
											       (let ((i (cons (midi-marks ev bot top pmn)
													      (make-instance *cm-midi*
															     :channel cch
															     :time of :duration du
															     :keynum (if (and *transpose*
																	      (instr-tpose in))
																	 (+ (instr-tpose in) n) n)
															     :amplitude midi-vel))))
												 (when tr (push i ts))
												 i) end end)
											 (list (cons (midi-marks ev t t pmn)
												     (make-instance *cm-midi* :channel ch :time of :duration du
														    :keynum nil :amplitude 0)))))
									  when mi nconc mi))) ; list is (marks . objs)
							    (lambda (x y) (midi-sort (cdr x) (cdr y))))
							   for (ms . e) = (first r)
							   nconc (delete-if
								  (lambda (x) (find (first x) +midi-spannermarks+ :key #'cdr))
								  (mapcar (lambda (m) ; m = 1 mark for midiobj e
									    (let ((nm (cons (first m) (cddr m))))
									      (cons nm
										    (let ((nd (lookup (first m) +midi-spannermarks+)))
										      (if nd ; nd = :endmark
											  (loop for ((mx . ex) . nex) on r collect ex until
												(find (cons nd (second m)) mx
												      :key (lambda (x) (cons (first x) (second x)))
												      :test #'equal)
												finally (when (eq (second nm) :w)
													  (setf (second nm)
														(if nex
														    (midi-vel (cdr nex))
														    (midi-vel ex))
														(third nm)
														(if nex
														    (midi-off (cdr nex))
														    (midi-endoff ex)))))
											  (list e))))))
									  ms)
								  :key #'car)) ; list is (mark . obj)
						     (lambda (x y) (< (position (caar x) +midi-allmarks+) (position (caar y) +midi-allmarks+))))
						    for (ev0 . mu) = (let ((z (gr es)))
								       (if (find (first m) +midi-spannermarks+ :key #'car) (cons (sort z #'midi-sort) nil)
									   (cond ((find (first m) +midi-endmarks+) (cons (last-element z) nil))
										 ((find (first m) +midi-firstmarks+) (cons (first z) nil))
										 (t (cons (sort z #'midi-sort) t)))))
						    when ev0 nconc
						    (loop for evs in (if mu ev0 (list ev0))
							  when (eq (first m) :span-tremfirst) do (mapc (lambda (x) (setf (midi-dur* x) (* (midi-dur x) 2))) evs)
							  when (eq (first m) :span-tremsecond) do (mapc (lambda (x) (setf (midi-off* x) (- (midi-off x) (midi-dur x))
															  (midi-dur* x) (* (midi-dur x) 2))) evs)
							  nconc
							  (multiple-value-bind (x1 a1) (if events-fun (apply events-fun evs m) (values nil nil))
							    (multiple-value-bind (xx a) (if x1 (values x1 a1) (apply #'midi-default-events-fun evs m))
							      (when a (push (cons (mloop for e in xx maximize (midi-endoff e)) a) adj))
							      (let ((x (if (listp xx) (copy-list xx) (list xx))))
								(unless (equal x '(nil))
								  (let ((xx (sort (nset-difference (force-list evs) x) #'midi-sort))) ; ones eliminated/to replace
								    (mapcar (lambda (y) (setf (midi-ch* y) nil)) xx)
								    (mapcar
								     (lambda (y)
								       (let ((z (loop-return-argmax
										 (- (min (midi-endoff i) (midi-endoff y))
										    (max (midi-off i) (midi-off y)))
										 for i in xx)))
									 (when (and z (< (midi-off z) (midi-endoff y)) (< (midi-off y) (midi-endoff z))) (push y (midi-ch* z)))))
								     x))
								  x)))))))))
					  :key #'midi-note)))
			  (let ((cs (chs ch)))
			    (when (list>1p cs)
			      (let ((ll (remove-if (lambda (e) (integerp (midi-note e))) es)))
				(mapc (lambda (x) (setf (midi-note* x) (floor (midi-note x)) (midi-ch* x) (second (chs (midi-ch x))))) ll)
				(push (make-instance *cm-midipbend* :time 0 :channel (second cs) :bend (roundint (/ 4096 pbend-width))) xta))))
			  es)
			finally (loop for e in (nreverse (delete-duplicates el :test #'string=)) do (format t e))))))
      (let ((o (floor (mloop with de = (/ (* delay tempo) 60) for e in evs do (incf (midi-off* e) de) minimize (midi-off e)))))
	(when (minusp o) (push (cons o (- o)) adj)))
      (loop for (o . a) in (merge-linear (sort adj #'> :key #'car) (lambda (x y) (when (= (car x) (car y)) (cons (car x) (max (cdr x) (cdr y))))))
	    do (mapc (lambda (x) (when (if (typep x *cm-midi*) (> (midi-endoff x) o) (>= (midi-off x) o))
				   (if (>= (midi-off x) o) (incf (midi-off* x) a) (incf (midi-dur* x) a))))
		     evs))
      (setf evs (delete-if-not #'midi-note evs))
      (let ((md (/ (* mindur-secs tempo) 60)))
	(mapc (lambda (x)
		(when (typep x *cm-midi*)
		  (setf (midi-vel* x) (min (max (coerce (midi-vel x) 'single-float) 0.0) 1.0)
			(midi-dur* x) (max (midi-dur x) md))))
	      evs))
      (setf xta (loop for e in (split-into-groups xta #'type-of) nconc (delete-duplicates e :key #'midi-ch)))
      (if evs
	  (let ((so (sort (nconc xta evs) #'midi-sort)))
	    (typecase play
	      (boolean (apply *cm-events* so filename :tempo tempo :play play cm-args))
	      ((or function symbol) (when
					(nth-value 1 (funcall play so filename))
				      (when (funcall play so)
					(format t ";; WARNING: Can't call user function~%"))))
	      (otherwise (apply *cm-rts* (sort (nconc xta evs) #'midi-sort) play :tempo tempo cm-args))))
	  (progn
	    (when (typep play 'boolean) (ignore-errors (delete-file filename))) ; probably better than leaving possible "incorrect" output
	    (format t ";; WARNING: No MIDI events~%"))))))

(defun save-midi (parts filename options play)
  (flet ((ms (x y) (< (position x parts) (position y parts)))
	 (me (p) (destructuring-bind (&key midi-filename &allow-other-keys) (part-opts p)
		   (namestring (merge-pathnames midi-filename filename)))))
    (loop for ps in (sort (mapcar (lambda (x) (sort x #'ms))
				  (split-into-groups (remove-if-not (lambda (p)
								      (destructuring-bind (&key midi-filename &allow-other-keys) (part-opts p)
									midi-filename))
								    parts)
						     #'me :test 'equal))
			  #'ms :key #'first)
	  do (save-midi-aux ps (me (first ps)) options (when (typep play '(or function symbol)) play))))
  (save-midi-aux parts filename options play))