;; -*- lisp -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;**************************************************************************************************
;; FOMUS
;; beams.lisp
;;**************************************************************************************************

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; BEAMS

(declaim (type symbol *auto-beam-plugin* *auto-beam-module*))
(defparameter *auto-beam-plugin* nil)
(defparameter *auto-beam-module* t)
(declaim (inline auto-beam-fun))
(defun auto-beam-fun () (if (truep *auto-beam-module*) :beams1 *auto-beam-module*))

(declaim (type boolean *auto-beams*))
(defparameter *auto-beams* t)

(declaim (type (member nil t :always) *long-eighth-beams*)
	 (type (integer 1) *long-eighth-beam-count*)
	 (type (integer 1) *comp-long-eighth-beam-count*))
(defparameter *long-eighth-beams* t) ; set to t (half-note under certain conditions), nil (quarter-note) or :always
(defparameter *long-eighth-beam-count* #|3 5/9/06|# 3) ; number of 1/8 notes to activate large grouping, if 0, will ALWAYS beam a whole 1/2 note if possible
(defparameter *comp-long-eighth-beam-count* 5)

;; decision to group four or six eigth notes together
;; receives all events overlapping into area <= proper duration
;; events are separated into different voices
(defun beams-grouplarge (events timesig)
  (declare (type list events) (type timesig-repl timesig))
  (if (timesig-comp timesig)
      (or (eq *long-eighth-beams* :always) 
	  (and *long-eighth-beams* 
	       (>= (count-if (lambda (x) (declare (type (or noteex restex) x)) (and (notep x) (null (event-tupfrac x)) (= (event-writtendur* x timesig) 1/8))) events)
		   *comp-long-eighth-beam-count*)))
      (or (eq *long-eighth-beams* :always)
	  (and *long-eighth-beams* 
	       (>= (count-if (lambda (x) (declare (type (or noteex restex) x)) (and (notep x) (null (event-tupfrac x)) (= (event-writtendur* x timesig) 1/8))) events)
		   *long-eighth-beam-count*)))))

;; top level generic rules for dividing/beaming measures
;; input written duration
;; beamdur starts at 1/8, then 1/16, etc.
(defun beams-rules (events off writdur beamdur timesig)
  (declare (type list events) (type (real 0) off) (type (rational (0)) writdur beamdur) (type timesig-repl timesig))
  (flet ((fi (wd)
	   (declare (type (rational (0)) wd))
	   (loop with o = (+ off (/ wd (timesig-beat* timesig)))
		 for e in events
		 when (< (event-off e) o) collect e into r1
		 when (> (event-endoff e) o) collect e into r2
		 finally (return (values r1 r2 o)))))
    (if (timesig-comp timesig)
	(cond
	  ((and (<= writdur 6/8) (beams-grouplarge events timesig) (>= beamdur (/ (timesig-den timesig)))) (list writdur)) ; 6/8
	  ((<= writdur 3/8) (list writdur))
	  ((and (<= writdur 6/8) (>= (timesig-den timesig) 8)) (list 3/8 (- writdur 3/8))) ; 9/19/06 added (>= (timesig-den timesig) 8)
	  (t (multiple-value-bind (e1 e2 off2) (fi 6/8)
	       (nconc (beams-rules e1 off 6/8 beamdur timesig)
		      (when (> writdur 6/8) (beams-rules e2 off2 (- writdur 6/8) beamdur timesig))))))
	(cond
	  ((and (<= writdur 6/8) (beams-grouplarge events timesig)) (list writdur)) ; 4/8
	  ((<= writdur 2/8) (list writdur))
	  ((<= writdur 4/8) (list 2/8 (- writdur 2/8)))
	  ((<= writdur 5/8) (list 3/8 (- writdur 3/8)))
	  (t (multiple-value-bind (e1 e2 off2) (fi 4/8)
	       (nconc (beams-rules e1 off 4/8 beamdur timesig)
		      (when (> writdur 4/8) (beams-rules e2 off2 (- writdur 4/8) beamdur timesig)))))))))

;; must be before postproc adds tuplet marks???
(defun beams-standbydiv (meas)		; list of measures
  (declare (type list meas))
  (loop for m of-type meas in meas
	do (multiple-value-bind (grs evs) (split-list (meas-events m) #'event-grace)
	     (let ((ts (meas-timesig m)))
	       (labels ((spt (evs wd wl &optional dmu (tf 0)) ; return events unsorted in their groups
			  (declare (type cons evs) (type (or (rational (0)) null) wd) (type list wl) (type list dmu) (type (integer 0) tf))
			  (let ((wll (loop for e of-type (rational (0)) in wl ; is nil for tuplet areas
					   nconc (loop while (> e wd)
						       collect wd into re
						       do (decf e wd)
						       finally (return (nconc re (list e)))))))
			    (flet ((pwd (o)
				     (declare (type (rational 0) o))
				     (loop while (and wll (>= o (the (rational (0)) (car wll)))) do (decf o (pop wll)) finally (return o))))
			      (loop
			       with ee = evs and o of-type (rational (0)) = 0 and re and rr ; re and rr should be in reverse order, re is group-list, rr is list of group-lists
			       #-clisp while #-clisp ee
			       for e of-type (or noteex restex) = #-clisp (car ee) #+clisp (if ee (car ee) (loop-finish)) 
			       do (if (not (equal (event-tupdurmult e) dmu)) ; different tuplet region
				      (progn
					(prenconc
					 (let ((xa (when (and re (> o 0)) (copy-event (first re)))) ; oo = t if tuplet section doesn't start on boundary
					       (x (loop	; gather events in tuplet
						   with pr = 0
						   for ee0 on ee
						   for e0 = (car ee0)
						   do (incf pr (apply #'* (butlast (event-tupfrac e0) tf)))
						   while (<= pr 1)
						   collect e0
						   do (incf o (event-writtendur e0 ts dmu))
						   finally (setf ee ee0)))) ; x is in forward order
					   (when re (push re rr) (setf re nil))	; first of re is the largest offset
					   (let ((xr (spt x nil nil (event-tupdurmult e) (1+ tf))))
					     (when xa (nconc (last-element xr) (list xa))) ; "prepend" for continuous beaming
					     xr))
					 rr)
					(setf o (pwd o))
					(when (> o 0) (setf re (list (copy-event (first (first rr))))))) ; "append" copy for continuous beaming
				      (progn
					(push e re)
					(incf o (event-writtendur e ts))
					(when (and wll (>= o (car wll)))
					  (push re rr)
					  (setf re nil)
					  (setf o (pwd o)))
					(setf ee (rest ee))))
			       finally
			       (return (if re (cons re rr) rr))))))) 
		 (flet ((bm (evs dv wl)	; dv = number of beams, ad = written beam duration
			  (declare (type cons evs) (type (integer 1) dv) (type cons wl))
			  (let ((ad (if (= dv 1) 3/4 (expt 1/2 dv)))) ; ad is written division duration
			    (let ((spf (nreverse (mapcar #'nreverse (spt evs ad wl))))
				  (spb (nreverse (mapcar #'nreverse (spt (reverse evs) ad (nreverse wl))))))
			      #+debug (check-order spf "BEAMS-STANDBYDIV (1)" (lambda (x y) (<= (event-off (first x)) (event-off (first y)))))
			      #+debug (mapc (lambda (i) (check-order i "BEAMS-STANDBYDIV (2)" (lambda (x y) (<= (event-off x) (event-off y))))) spf)
			      #+debug (check-order spb "BEAMS-STANDBYDIV (3)" (lambda (x y) (>= (event-off (first x)) (event-off (first y)))))
			      #+debug (mapc (lambda (i) (check-order i "BEAMS-STANDBYDIV (4)" (lambda (x y) (>= (event-off x) (event-off y))))) spb)
			      (loop for ee of-type cons in spf
				    do (loop
					for (e0 e1) of-type ((or noteex restex) (or noteex restex null)) on ee while e1
					when (and (notep e0) (notep e1))
					do (setf (event-beamlt e1) (min dv (event-nbeams e0 ts) (event-nbeams e1 ts)))))
			      (loop for ee of-type cons in spb
				    do (loop
					for (e0 e1) of-type ((or noteex restex) (or noteex restex null)) on ee while e1
					when (and (notep e0) (notep e1))
					do (setf (event-beamrt e1) (min dv (event-nbeams e0 ts) (event-nbeams e1 ts)))))
			      (cons spf spb))))
			(fb (spf spb)
			  (declare (type cons spf spb))
			  (let ((ll nil) (lr nil)) ; fix beams that don't have enough
			    (loop for ee of-type cons in spf
				  do (loop
				      for (e0 e1) of-type ((or noteex restex) (or noteex restex null)) on ee #-clisp while #-clisp e1
				      for nb = #-clisp (event-nbeams e1 ts) #+clisp (if e1 (event-nbeams e1 ts) (loop-finish))
				      when (and (notep e0) (notep e1) (> (event-beamrt e0) 0)
						(and (< (event-beamlt e1) nb) (or (< (event-beamrt e1) nb) (= (event-beamrt e0) nb))))
				      do (push (cons (event-nbeams e1 ts) e1) ll)))
			    (loop for ee of-type cons in spb
				  do (loop for (e0 e1) of-type ((or noteex restex) (or noteex restex null)) on ee #-clisp while #-clisp e1
					   for nb = #-clisp (event-nbeams e1 ts) #+clisp (if e1 (event-nbeams e1 ts) (loop-finish))
					   when (and (notep e0) (notep e1) (> (event-beamlt e0) 0)
						     (and (or (< (event-beamlt e1) nb) (= (event-beamlt e0) nb)) (< (event-beamrt e1) nb)))
					   do (push (cons (event-nbeams e1 ts) e1) lr)))
			    (loop for (nb . e) of-type ((integer 0) . noteex) in ll do (setf (event-beamlt e) nb))
			    (loop for (nb . e) of-type ((integer 0) . noteex) in lr do (setf (event-beamrt e) nb)))))
		   (loop
		    with dv = (let ((nb (timesig-nbeats ts)))
				(or (mapcar (lambda (x) (declare (type (rational (0)) x)) (* x nb)) (meas-div m)) (list nb)))
		    for i from 1 to (mloop for e in evs maximize (event-nbeams e ts))
		    collect (bm evs i (loop
				       for x of-type (rational (0)) in dv 
				       and d0 = (meas-off m) then d1
				       for d1 = (+ (meas-off m) x) then (+ d1 x)
				       nconc (beams-rules (loop for e of-type (or noteex restex) in evs
								when (and (> (event-endoff e) d0)
									  (< (event-off e) d1))
								collect e)
							  d0 (* x (timesig-beat* ts)) (/ 1/4 (expt 2 i)) ts)))
		    into ag finally
		    (loop for (f . b) of-type (cons . cons) in (nreverse ag) do (fb f b))
		    (fb (list evs) (list (reverse evs))))))
	       (let ((gg (mapcar (lambda (x) (declare (type cons x)) (sort x #'sort-offdur)) (split-into-groups grs #'event-off))))
		 (loop for gr of-type cons in gg
		       do (loop for (e1 e2) of-type ((or noteex restex) (or noteex restex null)) on gr #-clisp while #-clisp e2
				for nb = #-clisp (event-nbeams e1 ts) #+clisp (if e2 (event-nbeams e1 ts) (loop-finish))
				when (and (notep e1) (notep e2)) do (let ((x (min (event-nbeams e2 ts) nb))) (setf (event-beamrt e1) x (event-beamlt e2) x))))
		 (let ((ll nil) (lr nil)) ; fix beams that don't have enough
		   (loop for ee of-type cons in gg
			 do (loop for (e0 e1) of-type ((or noteex restex) (or noteex restex null)) on ee #-clisp while #-clisp e1
				  for nb = #-clisp (event-nbeams e1 ts) #+clisp (if e1 (event-nbeams e1 ts) (loop-finish))
				  when (and (notep e0) (notep e1) (> (event-nbeams e0 ts) 0)
					    (and (< (event-beamlt e1) nb) (< (event-beamrt e1) nb)))
				  do (push (cons nb e1) ll)))
		   (loop for ee of-type cons in gg
			 do (loop for (e1 e2) of-type ((or noteex restex) (or noteex restex null)) on ee #-clisp while #-clisp e2
				  for nb = #-clisp (event-nbeams e1 ts) #+clisp (if e2 (event-nbeams e1 ts) (loop-finish))
				  when (and (notep e1) (notep e2) (> (event-nbeams e2 ts) 0)
					    (and (< (event-beamlt e1) nb) (< (event-beamrt e1) nb)))
				  do (push (cons nb e1) lr)))
		   (loop for (nb . e) of-type ((integer 0) . noteex) in ll do (setf (event-beamlt e) nb))
		   (loop for (nb . e) of-type ((integer 0) . noteex) in lr do (setf (event-beamrt e) nb)))))
	     (setf (meas-events m) (sort (nconc grs evs) #'sort-offdur))
	     #+debug (loop for e in (meas-events m)
			   when (and (notep e) (or (> (event-beamlt e) 0) (> (event-beamrt e) 0))
				     (/= (max (event-beamlt e) (event-beamrt e)) (event-nbeams e (meas-timesig m))))
			   do (error "Error in BEAMS-STANDBYDIV (5)"))) (print-dot)))

(defun beams (parts)
  (declare (type list parts))
  (loop for p of-type partex in parts
	do (case (auto-beam-fun)
	     (:beams1 (beams-standbydiv (part-meas p)))
	     (otherwise (error "Unknown auto-beam module ~S" *auto-beam-module*)))))

