;; -*- lisp -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;**************************************************************************************************
;; FOMUS
;; split.lisp
;;**************************************************************************************************

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ENGINE

;; each node is a subset of its parent node's problem
;; all functions are relative to ea. node (solutfun returns t if subsolution is correct, complete solution is correct if all subsolutions are correct)
;; score for each node must be optimistic
;; expandfun returns or-list of and-lists (if returns values, then second value = t if it's an and-combine-only node (with only one choice)), if an and-list is nil, it's considered empty and skipped
;; assemfun reassembles subsolutions
(declaim (type (integer -1) *itdepfirst*-id*))
(declaim (special *itdepfirst*-id*))
(defstruct (itdepfirst*-andnode (:copier nil) (:predicate itdepfirst*-andnode-p))
  sol dat/hp) ; dat/hp is data if either depth is 0 or sol is t
(defstruct (itdepfirst*-ornode (:copier nil) (:predicate itdepfirst*-ornode-p))
  (id (incf *itdepfirst*-id*) :type (integer 0) :read-only t)
  (ands nil :type list)
  assem score
  (depth 0 :type (integer 0)))
(defun itdepfirst*-engine (init-node scorefun expandfun assemfun solutfun &key scoregreaterfun)
  (declare (type (function (t) list) expandfun))
  (let* ((*itdepfirst*-id* -1))
    (labels ((proc (nd de) ; nd is and-node--shouldn't be called if it's a solution
	       (when (<= de 0) ; if first time, replace data with heap of ors
		 (setf (itdepfirst*-andnode-dat/hp nd)
		       (make-heap (lambda (x y)
				    (or (funcall scoregreaterfun (itdepfirst*-ornode-score x) (itdepfirst*-ornode-score y))
					(unless (funcall scoregreaterfun (itdepfirst*-ornode-score y) (itdepfirst*-ornode-score x))
					  (< (itdepfirst*-ornode-id x) (itdepfirst*-ornode-id y)))))
				  :initial-contents (loop
						     for e in (funcall expandfun (itdepfirst*-andnode-dat/hp nd)) ; e = and-list, heap temporarily contains data to expand
						     when e ; at least something in and-list
						     collect (let ((a (funcall assemfun e)))
							       (make-itdepfirst*-ornode
								:ands (mapcar (lambda (i) (make-itdepfirst*-andnode :dat/hp i :sol (funcall solutfun i))) e)
								:assem a :score (funcall scorefun a)))))))
	       (loop
		for n = (or (heap-peek (itdepfirst*-andnode-dat/hp nd)) (return)) ; n is or-node
		for so = (every #'itdepfirst*-andnode-sol (itdepfirst*-ornode-ands n)) ; so = or-node is complete (all and-nodes inside it are complete)
		until (or so (>= (itdepfirst*-ornode-depth n) de))
		do
		(heap-rem (itdepfirst*-andnode-dat/hp nd))
		(loop
		 for e in (itdepfirst*-ornode-ands n) ; e is and-node
		 if (itdepfirst*-andnode-sol e) collect (itdepfirst*-andnode-dat/hp e) into d
		 else collect (or (proc e (itdepfirst*-ornode-depth n)) (return)) into d ; if dead-end, don't put back into heap
		 finally
		 (incf (itdepfirst*-ornode-depth n))
		 (let ((a (funcall assemfun d)))
		   (setf (itdepfirst*-ornode-assem n) a
			 (itdepfirst*-ornode-score n) (funcall scorefun a)))
		 (heap-ins n (itdepfirst*-andnode-dat/hp nd)))
		finally
		(when so (setf (itdepfirst*-andnode-sol nd) t ; this node is a solution--save it
			       (itdepfirst*-andnode-dat/hp nd) (itdepfirst*-ornode-assem n)))
		(return (itdepfirst*-ornode-assem n))))) ; return assembled data or nil if dead-end
      (loop
       with tn = (make-itdepfirst*-andnode :dat/hp init-node :sol (funcall solutfun init-node))
       #-clisp until #-clisp (itdepfirst*-andnode-sol tn)
       for de from 0
       #+clisp until #+clisp (itdepfirst*-andnode-sol tn)
       do (or (proc tn de) (return))
       finally (return (itdepfirst*-andnode-dat/hp tn))))))

;; SAVE THIS!--old version w/ extra and-nodes, wouldn't want to rewrite this
;; (defun itdepfirst*-engine (init-node scorefun expandfun assemfun solutfun &key scoregreaterfun)
;;   (let* ((*itdepfirst*-id* -1))
;;     (labels ((proc (nd de) ; nd is and-node--shouldn't be called if it's a solution
;; 	       (when (<= de 0) ; if first time, replace data with heap of ors
;; 		 (setf (itdepfirst*-andnode-dat/hp nd)
;; 		       (multiple-value-bind (ii dd) (funcall expandfun (itdepfirst*-andnode-dat/hp nd))
;; 			 (if dd
;; 			     (mapcar (lambda (i) (make-itdepfirst*-andnode :dat/hp i :sol (funcall solutfun i))) ii)
;; 			     (make-heap (lambda (x y)
;; 					  (or (funcall scoregreaterfun (itdepfirst*-ornode-score x) (itdepfirst*-ornode-score y))
;; 					      (unless (funcall scoregreaterfun (itdepfirst*-ornode-score y) (itdepfirst*-ornode-score x))
;; 						(< (itdepfirst*-ornode-id x) (itdepfirst*-ornode-id y)))))
;; 					:initial-contents (loop
;; 							   for e in ii ; e = and-list, heap temporarily contains data to expand
;; 							   when e ; at least something in and-list
;; 							   collect (let ((a (funcall assemfun e)))
;; 								     (make-itdepfirst*-ornode
;; 								      :ands (mapcar (lambda (i) (make-itdepfirst*-andnode :dat/hp i :sol (funcall solutfun i))) e)
;; 								      :assem a :score (funcall scorefun a)))))))))
;; 	       (if (heapp (itdepfirst*-andnode-dat/hp nd))
;; 		   (loop
;; 		    for n = (or (heap-peek (itdepfirst*-andnode-dat/hp nd)) (return)) ; n is or-node
;; 		    for so = (every #'itdepfirst*-andnode-sol (itdepfirst*-ornode-ands n)) ; so = or-node is complete (all and-nodes inside it are complete)
;; 		    until (or so (>= (itdepfirst*-ornode-depth n) de))
;; 		    do
;; 		    (heap-rem (itdepfirst*-andnode-dat/hp nd))
;; 		    (loop
;; 		     for e in (itdepfirst*-ornode-ands n) ; e is and-node
;; 		     if (itdepfirst*-andnode-sol e) collect (itdepfirst*-andnode-dat/hp e) into d
;; 		     else collect (or (proc e (itdepfirst*-ornode-depth n)) (return)) into d ; if dead-end, don't put back into heap
;; 		     finally
;; 		     (incf (itdepfirst*-ornode-depth n))
;; 		     (let ((a (funcall assemfun d)))
;; 		       (setf (itdepfirst*-ornode-assem n) a
;; 			     (itdepfirst*-ornode-score n) (funcall scorefun a)))
;; 		     (heap-ins n (itdepfirst*-andnode-dat/hp nd)))
;; 		    finally
;; 		    (when so (setf (itdepfirst*-andnode-sol nd) t ; this node is a solution--save it
;; 				   (itdepfirst*-andnode-dat/hp nd) (itdepfirst*-ornode-assem n)))
;; 		    (return (itdepfirst*-ornode-assem n)))
;; 		   (loop ; and-node of ands (pass on processing to lower depths without incrementing depth)
;; 		    for e in (or (itdepfirst*-andnode-dat/hp nd) (return))
;; 		    if (itdepfirst*-andnode-sol e) collect (itdepfirst*-andnode-dat/hp e) into d
;; 		    else collect (or (proc e de) (return)) into d ; if one and fails, entire node fails
;; 		    finally
;; 		    (let ((so (every #'itdepfirst*-andnode-sol (itdepfirst*-andnode-dat/hp nd)))
;; 			  (a (funcall assemfun d)))
;; 		      (when so (setf (itdepfirst*-andnode-sol nd) t
;; 				     (itdepfirst*-andnode-dat/hp nd) a))
;; 		      (return a)))))) ; return assembled data or nil if dead-end
;;       (loop
;;        with tn = (make-itdepfirst*-andnode :dat/hp init-node :sol (funcall solutfun init-node))
;;        until (itdepfirst*-andnode-sol tn)
;;        for de from 0
;;        do (or (proc tn de) (return))
;;        finally (return (itdepfirst*-andnode-dat/hp tn))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PREPROCESS

;; adds rests, ties overlapping notes of different durs
;; returns values: notes in measure, notes outside measure
;; expects voices separated into parts, input is sorted, output is sorted
(defun split-preproc (evs off endoff voc)
  (declare (type list evs) (type (rational 0) off endoff) (type (integer 1) voc))
  (multiple-value-bind (gs ns) (split-list evs #'event-grace)
    (loop				; get rid of unison overlaps
     for el on ns do
     (loop
      with e1 of-type (or noteex restex) = (first el)
      for e2 in (rest el) until (>= (event-off e2) (event-endoff e1)) ; e1 and e2 overlap
      do (cond ((and (notep e1) (notep e2) (= (event-note* e1) (event-note* e2)))
		(setf (event-dur e2) (- (max (event-endoff e1) (event-endoff e2)) (event-off e2))
		      (event-dur e1) (- (event-off e2) (event-off e1)))
		(when (<= (event-dur* e1) 0) (setf (event-marks e2) (combmarks (list e1 e2)))))
	       ((and (notep e1) (restp e2)) (setf (event-dur e2) 0))
	       ((and (restp e1) (notep e2)) (setf (event-dur e1) 0) (return)))))
    (setf ns (delete-if (lambda (x) (declare (type (or noteex restex) x)) (<= (event-dur* x) 0)) ns))
    (setf gs (delete-duplicates gs :test
				(lambda (x y)
				  (declare (type (or noteex restex) x y))
				  (and (= (event-note* x) (event-note* y))
				       (= (event-off x) (event-off y))
				       (= (event-grace x) (event-grace y))))))
    (setf ns (nconc (mapcar (lambda (x) (declare (type (cons (rational 0) (rational 0)) x)) (make-restex nil :off (car x) :dur (- (cdr x) (car x)) :voice voc))
			    (get-holes (merge-linear (mapcar (lambda (x) (declare (type (or noteex restex))) (cons (event-off x) (event-endoff x))) ns)
						     (lambda (x y) (declare (type (cons (rational 0) (rational 0)) x y)) (when (<= (car y) (cdr x)) (cons (car x) (cdr y)))))
				       off endoff)) 
		    ns))
    (loop
     for x of-type (or noteex restex) in ns ; split overlapping events
     collect (event-off x) into s
     collect (event-endoff x) into s
     finally
     (loop
      for i of-type (rational 0) in (delete-duplicates (cons endoff s))	; include endoff
      do (setf ns (loop
		   for e of-type (or noteex restex) in ns
		   for (j . k) = (split-event e i)
		   when j collect j
		   when k collect k))))
    (setf ns (loop
	      for e of-type cons in (split-into-groups ns #'event-off) ; put vertical notes into chords (note = list of notes, combine all attributes)
	      if (list>1p e) collect (make-chord e) else collect (first e)))
    (setf gs (loop
	      for e of-type cons in (split-into-groups gs (lambda (x) (declare (type (or noteex restex) x)) (cons (event-off x) (event-grace x))) :test 'equal)	; put vertical notes into chords (note = list of notes, combine all attributes)
	      if (list>1p e) collect (make-chord e) else collect (first e)))
    (loop			  ; split places at grace note offsets
     for g of-type (or noteex restex) in gs
     for i = (event-off g)
     do (setf ns (loop
		  for e of-type (or noteex restex) in ns
		  for (j . k) = (split-event e i)
		  when j collect j
		  when k collect k)))
    (loop
     for e of-type (or noteex restex) in (nconc gs ns) ; separate notes belonging to next measure--notes after endoff already split
     if (< (event-off e) endoff) collect e into v1
     else collect e into v2
     finally (print-dot) (return (values (sort v1 #'sort-offdur) v2)))))

(defun preproc (parts)
  (loop for p of-type partex in parts do		; tie notes across measures
	(loop
	 with r	of-type list 			; leftover tied notes
	 for m of-type meas in (part-meas p) do
	 (multiple-value-bind (e n) (split-preproc (nconc r (meas-events m)) (meas-off m) (meas-endoff m)
						   (let ((i (find-if #'meas-events (part-meas p))))
						     (if i (event-voice* (first (meas-events i))) 1)))
	   (setf (meas-events m) e
		 r (loop for x of-type (or noteex restex) in n if (chordp x)
			 nconc (mapcar (lambda (y t1 t2) (declare (type (or rational list) y) (type (or boolean cons) t1 t2)) (copy-event x :note y :tielt t1 :tiert t2))
				       (event-note x) (event-tielt x) (event-tiert x)) else collect x))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SPLITTER

(declaim (type (real (0)) *min-split-all-parts-dur*))
(defparameter *min-split-all-parts-dur* 3/2)

;; return t if all parts should split together
(defun split-allparts (rule)
  (declare (type baserule rule))
  (when (or (initdivp rule) (sigp rule))
    (let ((n (/ (rule-num rule) (* (rule-den rule) (rule-beat rule)))))
      (and (> n 3/2) (not (expof2 n))))))

;; note/rest is valid duration
;; off, endoff are boundaries for entire measure--for special full-meaure rest cases
(defun split-valid (events off endoff rule)
  (declare (type cons events) (type (rational 0) off endoff) (type baserule rule))
  (when (list1p events)			; must be 1 event only
    (let ((ev (first events))) ; shouldn't be dealing with grace notes here
      (declare (type (or noteex restex) ev))
      (flet ((ti (x)
	       (declare (type (or boolean cons) x))
	       (not (if (consp x) (and-list x) x)))
	     (no (di)
	       (declare (type (rational (0) 1) di))
	       (expof2 (* (if (rule-comp rule) (* (event-effectdur ev) 3/2) (event-effectdur ev)) di)))) ; something wrong here
	(etypecase rule
	  (initdiv
	   (etypecase ev
	     (rest t)
	     (note (if (rule-comp rule) (no 2/3) (no 1)))))
	  (sig
	   (etypecase ev
	     (rest (or (and (= (event-off ev) off) (= (event-endoff ev) endoff))
		       (if (or (rule-comp rule) (rule-irr rule)) ; irr = irregular, like 5/8 meter
			   (or (no 1) (no 2/3))
			   (no 1))))
	     (note (if (or (rule-comp rule) (rule-irr rule))
		       (or (no 1) (no 2/3))
		       (no 1)))))
	  (unit
	   (cond ((= (rule-div rule) 2) (no 1))
		 ((and (= (rule-div rule) 3) (rule-irr rule)) (no 2/3))))
	  (sig-nodiv ; tlt/trt: nil = ties not allowed, t = tie is possible 
	   (etypecase ev
	     (rest nil #|(if (sig-nodiv-comp rule) (no 2/3) (no 1))|#)
	     (note (let ((aa (or (ti (event-tielt ev)) (ti (event-tiert ev)))))
		     (and ; these are special, so duration is assumed to be valid
		      (or (rule-tlt rule) aa) ; at least one note not tied
		      (or (rule-trt rule) aa)
		      (if (rule-comp rule) (no 2/3) (or (no 1) (no 2/3) (no 4/7) #|(and (no 4/7) (not (event-noddot ev)))|#))))))) ;; lilypond fix
	  (unit-nodiv ; tlt/trt: nil = ties not allowed, t = tie is possible 
	   (etypecase ev
	     (rest	      #|nil|# (and (rule-rst rule) (no 1))) ; 
	     (note (let ((aa (or (ti (event-tielt ev)) (ti (event-tiert ev)))))
		     (and ; these are special, so duration is assumed to be valid
		      (or (rule-tlt rule) aa)
		      (or (rule-trt rule) aa)
		      (or (no 1) (no 2/3) (no 4/7) #|(and (no 4/7) (not (event-noddot ev)))|#))))))))))) ;; lilypond fix

(declaim (type (real (0)) +event-score+ +tuplet-score+ +tupsmalldur-score+ +tupsmalldur-thresh+))
(defparameter +event-score+ 1)
(defparameter +tuplet-score+ 3/2)
(defparameter +smalltupnote-score+ 2) ; should be slightly higher than tuplet-score

;; returns list: lower number is better
;; first value MUST be optimistic (increasing only (worse) with each descent (note splitting) in search)
;; remaining values are heuristics for resolving ties
(defun split-score (events)
  (declare (type list events))
  (loop
   with ntu
   for (e en) of-type ((or noteex restex) (or noteex restex null)) on (sort (copy-list events) #'< :key #'event-off) ; no overlapping offsets should exist here
   for d = (event-dur* e)
   sum +event-score+ into su
   when (event-tupfrac e)
   sum (loop with tf = (event-tupfrac e) with le = (length tf)
	     for i from 1 and x of-type (rational (0)) in tf
	     for m = x then (* m x)
	     sum m into s
	     when (> i 1) do (push (cons (cons (- le i) (cons (event-off e) (event-endoff e))) (cons m x)) ntu)
	     finally (return (* s le +tuplet-score+))) into su
   and when (< (* (numerator (first (event-tupdurmult e))) (first (event-tupfrac e))) 1)
   sum (* (first (event-tupfrac e)) +smalltupnote-score+) into su
   maximize d into ad
   minimize d into id
   when en sum (diff d (event-dur* en)) into ce
   collect (event-off e) into fs
   collect (event-endoff e) into fs
   finally
   (loop with c = 0 and va and f0 = 0 ; look for contiguous nested tuplets of same duration, give them a boost
	 for l0 = 0 then l and o02 = -1 then o2
	 and ((l . (o1 . o2)) . (m . f)) of-type (((integer 0) . ((rational 0) . (rational 0))) . ((rational (0)) . (rational (0))))
	 in (sort ntu (lambda (x y) (declare (type (cons (cons (integer 0) (cons (rational 0) (rational 0)))) x y))
			      (if (= (caar x) (caar y)) (< (cadar x) (cadar y)) (> (caar x) (caar y)))))
	 #+debug do #+debug (or (<= c 1) (error "Error in SPLIT-SCORE"))
	 if (or (/= l0 l) (/= o02 o1) (>= c 1)) do (when va (decf su (* (1- (/ f)) c +tuplet-score+))) (setf c m f0 0) else do (incf c m) ; new group, reset everything
	 if (= f0 0) do (setf f0 f va t) else when (/= f0 f) do (setf va nil)
	 finally (when va (decf su (* (1- (/ f)) c +tuplet-score+))))
   (return (list su ; number of notes/tuplets (can only increase with splitting)
		 #-clisp (/ ad id) #+clisp (/ (or ad 0) id) ; difference in durations--might not always increase, but still functions well as a heuristic
		 ce (ave-list (delete-duplicates fs)))))) ; average offset location

;; = and < function for score tuplet
(defun splsc< (x y)
  (declare (type cons x y))
  (loop
   for x0 of-type real in x and y0 of-type real in y
   if (< x0 y0) do (return t)
   if (> x0 y0) do (return nil)))

;; maximum duration span of tuplets in number of beats (or nil)
;; (declaim (type (real (0)) *min-simple-tuplet-dur*))
;; (defparameter *min-simple-tuplet-dur* #|2 4/6/06|# nil)

;; events = list of parallel event-lists
;; expects voices separated into parts
;; DESTRUCTIVE
(defstruct (splitnode (:copier nil) (:predicate splitnodep))
  (rl (make-initdiv) :type baserule)
  (pts t :type boolean)
  (par nil :type (or splitnode null))
  (evs nil :type (or list boolean))
  (of1 0 :type (rational 0))
  (of2 0 :type (rational 0))
  (div nil :type list)) ; msc = missing score points, pts = if evs is list of part-event-lists

(defun split-engine-byscore (events off endoff timesig)
  (declare (type cons events) (type (rational 0) off endoff) (type timesig-repl timesig))
  (flet ((er () (error "Rhythm too difficult to notate at offsets ~S through ~S" (float off) (float endoff)))
	 (drst (li rl)			; move the rest over
	   (declare (type cons li) (type (or initdiv sig unit sig-nodiv unit-nodiv) rl))
	   (flet ((ex (e1 e2 es)     ; es is copied, so can destroy it
		    (declare (type (or noteex restex null) e1 e2) (type cons es))
		    (if (and (restp e1) (restp e2)
			     (not (find (event-off e2) (event-nomerge e1)))
			     (equal (list (event-dur* e1) (sort-marks (important-marks (event-marks e1))) (event-tup e1))
				    (list (event-dur* e2) (sort-marks (important-marks (event-marks e2))) (event-tup e2))))
			(cons (copy-event e1
					  :dur (* (event-dur* e1) 2)
					  :tup (cons (when (car (event-tup e1))
						       (cons (* (caar (event-tup e1)) 2)
							     (cdar (event-tup e1))))
						     (cdr (event-tup e1))))
			      (delete e1 (delete e2 es)))
			es)))
	     (when (or (initdivp rl) (basesplitp rl))
	       (when (or (initdivp rl) (rule-alt rl))
		 (let ((x (sort (copy-list li) #'sort-offdur)))
		   (setf li (ex (first x) (second x) x))))
	       (when (or (initdivp rl) (rule-art rl))
		 (let ((x (sort (copy-list li) (complement #'sort-offdur))))
		   (setf li (ex (second x) (first x) x))))))
	   li))
    (let ((lm (/ (* (beat-division timesig) 8))))
      (flet ((scorefun (nd)		; score relative to ea. level
	       (declare (type splitnode nd))
	       (if (splitnode-pts nd)
		   (loop
		    for e in (remove-if #'truep (splitnode-evs nd)) ; if t then part is already complete in a higher branch
		    for ts = (split-score e) then (mapcar #'+ ts (split-score e))
		    finally (return (cons 0 ts)))
		   (cons 1 (split-score (splitnode-evs nd)))))
	     (expandfun (nd)		; expand (into or-list)
	       (declare (type splitnode nd))
	       ;; (when (<= (- (splitnode-of2 nd) (splitnode-of1 nd)) lm) (er))
	       (unless (or (basenodivp (splitnode-rl nd)) (<= (- (splitnode-of2 nd) (splitnode-of1 nd)) lm))
		 (let ((rt (labels ((of (o) (declare (type (rational 0) o)) (+ (splitnode-of1 nd) (* o (- (splitnode-of2 nd) (splitnode-of1 nd)))))
				    (spl (evs sp rr) ; return list of split event-lists
				      (declare (type cons sp rr))
				      (loop
				       with nx = evs and td0 = (- (splitnode-of2 nd) (splitnode-of1 nd))
				       for o0 = 0 then o
				       for o of-type (rational (0) 1) in sp and r in rr	; o = split offset, r = rule
				       collect (loop
						with u = (when (baseunitp r) (rule-tup r)) ; u = tuplet list--rule should have all tuplet information for note
						and m = (when (baseunitp r) (rule-dmu r))
						and td = (* (- o o0) td0)
						for e of-type (or noteex restex) in nx
						for (l . x) = (split-event e (of o) (when u (cons (* (first u) (/ (event-dur e) td)) (rest u))) m)
						when l collect l into ll
						when x collect x into xx
						finally
						(setf nx xx)
						(return ll)))))
			     (loop
			      for ru of-type (cons (or (rational (0) (1)) cons) list)
			      in (loop for e #|of-type baserule|#
				       in (split-rules-bylevel
					   (splitnode-rl nd)
					   (let ((du (- (splitnode-of2 nd) (splitnode-of1 nd))))
					     (and (or (null *min-tuplet-dur*) (>= du *min-tuplet-dur*))
						  (or (null *max-tuplet-dur*) (<= du *max-tuplet-dur*)))))
				       collect e) ; ors, ru = new rule
			      for div = (or (when (basesplitp (splitnode-rl nd)) (rule-init (splitnode-rl nd)))
					    (splitnode-div nd))
			      for sp = (append (force-list (first ru)) '(1)) and rr = (rest ru)	; sp =(unless (and tv (eq (first sp) :grandstaff)) (first sp)) split points (last one is a), rr = replacement rules
			      collect (if (splitnode-pts nd)
					  (loop	; iterate through parts
					   with al = (make-list (length rr) :initial-element t)
					   for p of-type (or (member t) cons) in (splitnode-evs nd)
					   for xx = (if (or (truep p) (split-valid p off endoff (splitnode-rl nd))) al (spl p sp rr))
					   for li = (mapcar #'list xx) then (cons-list xx li)
					   finally (return (loop 
							    for e of-type cons in li and r #|of-type baserule|# in rr
							    and (o1 o2) of-type ((rational 0 1) (rational 0 1)) on (cons 0 sp) ; ands
							    collect (make-splitnode :rl r :pts t :par nd :evs (nreverse e) :of1 (of o1) :of2 (of o2) :div div)))) ; evs might contain t
					  (loop
					   for e of-type cons in (spl (splitnode-evs nd) sp rr) and r #|of-type baserule|# in rr
					   and (o1 o2) of-type ((rational 0 1) (rational 0 1)) on (cons 0 sp) ; ands
					   collect (make-splitnode :rl r :pts nil :par nd :evs e :of1 (of o1) :of2 (of o2) :div div)))))))
		   (if (and (splitnode-pts nd) (not (split-allparts (splitnode-rl nd))))
		       (cons (mapcar (lambda (p)
				       (make-splitnode :rl (splitnode-rl nd) :pts nil :par nd
						       :evs (if (or (truep p) (split-valid p off endoff (splitnode-rl nd))) t p)
						       :of1 (splitnode-of1 nd) :of2 (splitnode-of2 nd) :div (splitnode-div nd)))
				     (splitnode-evs nd))
			     rt)
		       rt))))
	     (assemfun (nds) ; assemble and-list of splitnodes (some might = t, some might have parts that = t, there should be at least 1 node struct somewhere)
	       (declare (type list nds))
	       (let* ((f (first nds)) ; find a node struct--par and pts should be same for all
		      (pa (splitnode-par f))
		      (rl (splitnode-rl pa)))
		 (declare (type splitnode f))
		 (flet ((mn (vs)
			  (make-splitnode :rl rl :pts (splitnode-pts pa) :par (splitnode-par pa)
					  :evs vs :of1 (splitnode-of1 pa) :of2 (splitnode-of2 pa)
					  :div (first (stable-sort (loop for i of-type splitnode in nds when (splitnode-div i) collect (splitnode-div i))
								   #'> :key (lambda (x) (declare (type list x)) (count x nds :key #'splitnode-div :test #'equal)))))))
		   (if (splitnode-pts f)
		       (loop
			with li
			for s in nds	; all s's should be structs
			and fl = t then nil
			do (let ((xx (mapcar (lambda (x y)
					       (declare (type (or (member t) cons) x y))
					       (if (truep x)
						   (when fl (unless (truep y) y))
						   x))
					     (splitnode-evs s) (splitnode-evs pa))))
			     (if li (prepend-lists xx li) (setf li xx)))
			finally (return (mn (mapcar (lambda (x) (declare (type list x)) (if x (drst x rl) t)) li))))
		       (if (splitnode-pts pa)
			   (mn (mapcar (lambda (x0 y)
					 (declare (type splitnode x0) (type (or (member t) cons) y))
					 (let ((x (splitnode-evs x0))) (if (truep x) (if (truep y) y (drst y rl)) (drst x rl))))
				       nds (splitnode-evs pa))) 
			   (mn (drst (loop for e of-type splitnode in nds append (splitnode-evs e)) rl)))))))
	     (solutfun (nd)		; complete/valid?
	       (declare (type splitnode nd))
	       (if (splitnode-pts nd)
		   (let ((x (splitnode-rl nd)))
		     (every (lambda (n) (declare (type (or (member t) cons) n)) (or (truep n) (split-valid n off endoff x))) (splitnode-evs nd)))
		   (or (truep (splitnode-evs nd))
		       (split-valid (splitnode-evs nd) off endoff (splitnode-rl nd))))))
	(multiple-value-bind (evs grs)
	    (loop
	     for p of-type cons in events
	     for (gr . ev) = (multiple-value-bind (a b) (split-list p #'event-grace) (cons a b))
	     collect ev into evs
	     collect gr into grs
	     finally (return (values evs grs)))
	  (loop for li of-type cons in evs and gr of-type list in grs do
		(loop with g = (delete-duplicates (mapcar #'event-off gr))
		      for e of-type (or noteex restex) in li when (restp e) do (setf (event-nomerge e) g)))
	  (let ((re (or (itdepfirst*-engine
			 (make-splitnode :rl (first-splitrule timesig)
					 :evs evs
					 :of1 off :of2 endoff)
			 #'scorefun #'expandfun #'assemfun #'solutfun
			 :scoregreaterfun #'splsc<)
			(er))))
	    (print-dot)
	    (values (let ((rl (splitnode-rl re)))
		      (mapcar (lambda (ev gr) (declare (type cons ev) (type list gr)) (sort (nconc gr (drst ev rl)) #'sort-offdur)) (splitnode-evs re) grs))
		    (splitnode-div re))))))))

(declaim (type symbol *split-plugin* *split-module*))
(defparameter *split-plugin* nil)
(defparameter *split-module* t)
(declaim (inline split-fun))
(defun split-fun () (if (truep *split-module*) :split1 *split-module*))

(declaim (inline load-split-modules))
(defun load-split-modules ()
  (unless (eq (split-fun) :split1) (load-fomus-module (split-fun))))

;; the main function--events must be organized into measures (by offsets) first
(defun split (parts)
  (declare (type list parts))
  (apply #'mapc
	 (lambda (&rest ms)		; list of parallel measures
	   (loop for ml of-type cons
		 in (split-into-groups ms ; m is list of measures with matching time signature
				       (lambda (x)
					 (declare (type meas x))
					 (let ((s (meas-timesig x)))
					   (list (timesig-num s) (timesig-den s) (timesig-div* s) (timesig-comp s) (timesig-beat* s)
						 (meas-off x) (meas-endoff x))))
				       :test 'equal)
		 do (multiple-value-bind (sp di) (let ((f (first ml)))
						   (declare (type meas f))
						   (if (eq (split-fun) :split1)
						       (split-engine-byscore (mapcar #'meas-events ml) (meas-off f) (meas-endoff f) (meas-timesig f))
						       (call-module (split-fun) (list "Unknown split module ~S" *split-module*)
								    (mapcar #'meas-events ml) (meas-off f) (meas-endoff f) (meas-timesig f))))
		      (mapc
		       (lambda (re m)
			 (declare (type list re) (type meas m))
			 (setf (meas-events m) re (meas-div m) di))
		       sp ml))))
	 (mapcar #'part-meas parts)))

