+(define-public default-part-combine-mark-state-machine
+ ;; (current-state . ((split-state-event .
+ ;; (output-voice output-event next-state)) ...))
+ '((Initial . ((solo1 . (solo SoloOneEvent Solo1))
+ (solo2 . (solo SoloTwoEvent Solo2))
+ (unisono . (shared UnisonoEvent Unisono))))
+ (Solo1 . ((apart . (#f #f Initial))
+ (chords . (#f #f Initial))
+ (solo2 . (solo SoloTwoEvent Solo2))
+ (unisono . (shared UnisonoEvent Unisono))))
+ (Solo2 . ((apart . (#f #f Initial))
+ (chords . (#f #f Initial))
+ (solo1 . (solo SoloOneEvent Solo1))
+ (unisono . (shared UnisonoEvent Unisono))))
+ (Unisono . ((apart . (#f #f Initial))
+ (chords . (#f #f Initial))
+ (solo1 . (solo SoloOneEvent Solo1))
+ (solo2 . (solo SoloTwoEvent Solo2))))))
+
+(define-public (make-part-combine-marks state-machine split-list)
+ "Generate a sequence of part combiner events from a split list"
+
+ (define (get-state state-name)
+ (assq-ref state-machine state-name))
+
+ (let ((full-seq '()) ; sequence of { \context Voice = "x" {} ... }
+ (segment '()) ; sequence within \context Voice = "x" {...}
+ (prev-moment ZERO-MOMENT)
+ (prev-voice #f)
+ (state (get-state 'Initial)))
+
+ (define (commit-segment)
+ "Add the current segment to the full sequence and begin another."
+ (if (pair? segment)
+ (set! full-seq
+ (cons (make-music 'ContextSpeccedMusic
+ 'context-id (symbol->string prev-voice)
+ 'context-type 'Voice
+ 'element (make-sequential-music (reverse! segment)))
+ full-seq)))
+ (set! segment '()))
+
+ (define (handle-split split)
+ (let* ((moment (car split))
+ (action (assq-ref state (cdr split))))
+ (if action
+ (let ((voice (car action))
+ (part-combine-event (cadr action))
+ (next-state-name (caddr action)))
+ (if part-combine-event
+ (let ((dur (ly:moment-sub moment prev-moment)))
+ ;; start a new segment when the voice changes
+ (if (not (eq? voice prev-voice))
+ (begin
+ (commit-segment)
+ (set! prev-voice voice)))
+ (if (not (equal? dur ZERO-MOMENT))
+ (set! segment (cons (make-music 'SkipEvent
+ 'duration (make-duration-of-length dur)) segment)))
+ (set! segment (cons (make-music part-combine-event) segment))
+
+ (set! prev-moment moment)))
+ (set! state (get-state next-state-name))))))
+
+ (for-each handle-split split-list)
+ (commit-segment)
+ (make-sequential-music (reverse! full-seq))))
+
+(define-public default-part-combine-context-change-state-machine-one
+ ;; (current-state . ((split-state-event . (output-voice next-state)) ...))
+ '((Initial . ((apart . (one . Initial))
+ (apart-silence . (one . Initial))
+ (apart-spanner . (one . Initial))
+ (chords . (shared . Initial))
+ (silence1 . (shared . Initial))
+ (silence2 . (null . Demoted))
+ (solo1 . (solo . Initial))
+ (solo2 . (null . Demoted))
+ (unisono . (shared . Initial))
+ (unisilence . (shared . Initial))))
+
+ ;; After a part has been used as the exclusive input for a
+ ;; passage, we want to use it by default for unisono/unisilence
+ ;; passages because Part_combine_iterator might have killed
+ ;; multi-measure rests in the other part. Here we call such a
+ ;; part "promoted". Part one begins promoted.
+ (Demoted . ((apart . (one . Demoted))
+ (apart-silence . (one . Demoted))
+ (apart-spanner . (one . Demoted))
+ (chords . (shared . Demoted))
+ (silence1 . (shared . Initial))
+ (silence2 . (null . Demoted))
+ (solo1 . (solo . Initial))
+ (solo2 . (null . Demoted))
+ (unisono . (null . Demoted))
+ (unisilence . (null . Demoted))))))
+
+(define-public default-part-combine-context-change-state-machine-two
+ ;; (current-state . ((split-state-event . (output-voice next-state)) ...))
+ '((Initial . ((apart . (two . Initial))
+ (apart-silence . (two . Initial))
+ (apart-spanner . (two . Initial))
+ (chords . (shared . Initial))
+ (silence1 . (null . Initial))
+ (silence2 . (shared . Promoted))
+ (solo1 . (null . Initial))
+ (solo2 . (solo . Promoted))
+ (unisono . (null . Initial))
+ (unisilence . (null . Initial))))
+
+ ;; See the part-one state machine for the meaning of "promoted".
+ (Promoted . ((apart . (two . Promoted))
+ (apart-silence . (two . Promoted))
+ (apart-spanner . (two . Promoted))
+ (chords . (shared . Promoted))
+ (silence1 . (null . Initial))
+ (silence2 . (shared . Promoted))
+ (solo1 . (null . Initial))
+ (solo2 . (solo . Promoted))
+ (unisono . (shared . Promoted))
+ (unisilence . (shared . Promoted))))))
+
+(define-public (make-part-combine-context-changes state-machine split-list)
+ "Generate a sequence of part combiner context changes from a split list"
+
+ (define (get-state state-name)
+ (assq-ref state-machine state-name))
+
+ (let ((change-list '())
+ (prev-voice #f)
+ (state (get-state 'Initial)))
+
+ (define (handle-split split)
+ (let* ((moment (car split))
+ (action (assq-ref state (cdr split))))
+ (if action
+ (let ((voice (car action))
+ (next-state-name (cdr action)))
+ (if (not (eq? voice prev-voice))
+ (begin
+ (set! change-list (cons (cons moment voice) change-list))
+ (set! prev-voice voice)))
+ (set! state (get-state next-state-name))))))
+
+ (for-each handle-split split-list)
+ (reverse! change-list)))