(display (span-state x) file)
(display "\n" file))
+;; Return the duration of the longest event in the Voice-state.
+(define-method (duration (vs <Voice-state>))
+ (define (duration-max event d1)
+ (let ((d2 (ly:event-property event 'duration #f)))
+ (if d2
+ (if (ly:duration<? d1 d2) d2 d1)
+ d1)))
+
+ (fold duration-max (ly:make-duration 0 0 0) (events vs)))
+
+;; Return the moment that the longest event in the Voice-state ends.
+(define-method (end-moment (vs <Voice-state>))
+ (ly:moment-add (moment vs) (ly:duration-length (duration vs))))
+
(define-method (note-events (vs <Voice-state>))
(define (f? x)
(ly:in-event-class? x 'note-event))
(note-events vs))
note<?))
-(define-method (rest-and-skip-events (vs <Voice-state>))
- (define (f? x)
- (or (ly:in-event-class? x 'rest-event)
- (ly:in-event-class? x 'skip-event)))
- (filter f? (events vs)))
+(define-method (rest-or-skip-events (vs <Voice-state>))
+ (define (filtered-events event-class)
+ (filter (lambda(x) (ly:in-event-class? x event-class))
+ (events vs)))
+ (let ((result (filtered-events 'rest-event)))
+ ;; There may be skips in the same part with rests for various
+ ;; reasons. Regard the skips only if there are no rests.
+ (if (and (not (pair? result)) (not (any-mmrest-events vs)))
+ (set! result (filtered-events 'skip-event)))
+ result))
(define-method (any-mmrest-events (vs <Voice-state>))
(define (f? x)
(if p (span-state p) '())))
(define (make-voice-states evl)
- (let ((vec (list->vector (map (lambda (v)
- (make <Voice-state>
- #:moment (caar v)
- #:tuning (cdar v)
- #:events (map car (cdr v))))
- evl))))
- (do ((i 0 (1+ i)))
- ((= i (vector-length vec)) vec)
- (slot-set! (vector-ref vec i) 'vector-index i)
- (slot-set! (vector-ref vec i) 'state-vector vec))))
+ (let* ((states (map (lambda (v)
+ (make <Voice-state>
+ #:moment (caar v)
+ #:tuning (cdar v)
+ #:events (map car (cdr v))))
+ (reverse evl))))
+
+ ;; add an entry with no events at the moment the last event ends
+ (if (pair? states)
+ (let ((last-real-event (car states)))
+ (set! states
+ (cons (make <Voice-state>
+ #:moment (end-moment last-real-event)
+ #:tuning (tuning last-real-event)
+ #:events '())
+ states))))
+
+ ;; TODO: Add an entry at +inf.0 and see if it allows us to remove
+ ;; the many instances of conditional code handling the case that
+ ;; there is no voice state at a given moment.
+
+ (let ((vec (list->vector (reverse! states))))
+ (do ((i 0 (1+ i)))
+ ((= i (vector-length vec)) vec)
+ (slot-set! (vector-ref vec i) 'vector-index i)
+ (slot-set! (vector-ref vec i) 'state-vector vec)))))
(define (make-split-state vs1 vs2)
"Merge lists VS1 and VS2, containing Voice-state objects into vector
((context-list '())
(now-mom (ly:make-moment 0 0))
(global (ly:make-global-context odef))
- (mom-listener (ly:make-listener
- (lambda (tev) (set! now-mom (ly:event-property tev 'moment)))))
+ (mom-listener (lambda (tev) (set! now-mom (ly:event-property tev 'moment))))
(new-context-listener
- (ly:make-listener
- (lambda (sev)
- (let*
- ((child (ly:event-property sev 'context))
- (this-moment-list (cons (ly:context-id child) '()))
- (dummy (set! context-list (cons this-moment-list context-list)))
- (acc '())
- (accumulate-event-listener
- (ly:make-listener (lambda (ev)
- (set! acc (cons (cons ev #t) acc)))))
- (save-acc-listener
- (ly:make-listener (lambda (tev)
- (if (pair? acc)
- (let ((this-moment
- (cons (cons now-mom
- (ly:context-property child 'instrumentTransposition))
- ;; The accumulate-event-listener above creates
- ;; the list of events in reverse order, so we
- ;; have to revert it to the original order again
- (reverse acc))))
- (set-cdr! this-moment-list
- (cons this-moment (cdr this-moment-list)))
- (set! acc '())))))))
- (ly:add-listener accumulate-event-listener
- (ly:context-event-source child) 'StreamEvent)
- (ly:add-listener save-acc-listener
- (ly:context-event-source global) 'OneTimeStep))))))
+ (lambda (sev)
+ (let*
+ ((child (ly:event-property sev 'context))
+ (this-moment-list (cons (ly:context-id child) '()))
+ (dummy (set! context-list (cons this-moment-list context-list)))
+ (acc '())
+ (accumulate-event-listener
+ (lambda (ev)
+ (set! acc (cons (cons ev #t) acc))))
+ (save-acc-listener
+ (lambda (tev)
+ (if (pair? acc)
+ (let ((this-moment
+ (cons (cons now-mom
+ (ly:context-property child 'instrumentTransposition))
+ ;; The accumulate-event-listener above creates
+ ;; the list of events in reverse order, so we
+ ;; have to revert it to the original order again
+ (reverse acc))))
+ (set-cdr! this-moment-list
+ (cons this-moment (cdr this-moment-list)))
+ (set! acc '()))))))
+ (ly:add-listener accumulate-event-listener
+ (ly:context-event-source child) 'StreamEvent)
+ (ly:add-listener save-acc-listener
+ (ly:context-event-source global) 'OneTimeStep)))))
(ly:add-listener new-context-listener
(ly:context-events-below global) 'AnnounceNewContext)
(ly:add-listener mom-listener (ly:context-event-source global) 'Prepare)
global)
context-list))
-(define-public (make-part-combine-music parser music-list direction chord-range)
- (let* ((m (make-music 'PartCombineMusic))
- (m1 (make-non-relative-music (context-spec-music (first music-list) 'Voice "one")))
- (m2 (make-non-relative-music (context-spec-music (second music-list) 'Voice "two")))
- (listener (ly:parser-lookup parser 'partCombineListener))
- (evs2 (recording-group-emulate m2 listener))
- (evs1 (recording-group-emulate m1 listener)))
-
- (set! (ly:music-property m 'elements) (list m1 m2))
- (set! (ly:music-property m 'direction) direction)
- (set! (ly:music-property m 'split-list)
- (if (and (assoc "one" evs1) (assoc "two" evs2))
- (determine-split-list (reverse! (assoc-get "one" evs1) '())
- (reverse! (assoc-get "two" evs2) '())
- chord-range)
- '()))
- m))
-
(define-public (determine-split-list evl1 evl2 chord-range)
"@var{evl1} and @var{evl2} should be ascending. @var{chord-range} is a pair of numbers (min . max) defining the distance in steps between notes that may be combined into a chord or unison."
(let* ((pc-debug #f)
(define (analyse-forced-combine result-idx prev-res)
(define (get-forced-event x)
- (and (ly:in-event-class? x 'part-combine-force-event)
- (cons (ly:event-property x 'forced-type)
- (ly:event-property x 'once))))
+ (cond
+ ((and (ly:in-event-class? x 'SetProperty)
+ (eq? (ly:event-property x 'symbol) 'partCombineForced))
+ (cons (ly:event-property x 'value #f)
+ (ly:event-property x 'once #f)))
+ ((and (ly:in-event-class? x 'UnsetProperty)
+ (eq? (ly:event-property x 'symbol) 'partCombineForced))
+ (cons #f (ly:event-property x 'once #f)))
+ (else #f)))
+
(define (part-combine-events vs)
(if (not vs)
'()
(vs2 (cdr (voice-states now-state))))
(define (analyse-synced-silence)
- (let ((rests1 (if vs1 (rest-and-skip-events vs1) '()))
- (rests2 (if vs2 (rest-and-skip-events vs2) '())))
+ (let ((rests1 (if vs1 (rest-or-skip-events vs1) '()))
+ (rests2 (if vs2 (rest-or-skip-events vs2) '())))
(cond
;; multi-measure rests (probably), which the
(let* ((now-state (vector-ref result result-idx))
(vs1 (current-voice-state now-state 1))
(vs2 (current-voice-state now-state 2))
- (rests1 (if vs1 (rest-and-skip-events vs1) '()))
- (rests2 (if vs2 (rest-and-skip-events vs2) '()))
+ (rests1 (if vs1 (rest-or-skip-events vs1) '()))
+ (rests2 (if vs2 (rest-or-skip-events vs2) '()))
(prev-state (if (> result-idx 0)
(vector-ref result (- result-idx 1))
#f))
(display result))
result))
+(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)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define-public (add-quotable parser name mus)
+(define-public (add-quotable name mus)
(let* ((tab (eval 'musicQuotes (current-module)))
(voicename (get-next-unique-voice-name))
;; recording-group-emulate returns an assoc list (reversed!), so
;; hand it a proper unique context name and extract that key:
(ctx-spec (context-spec-music mus 'Voice voicename))
- (listener (ly:parser-lookup parser 'partCombineListener))
+ (listener (ly:parser-lookup 'partCombineListener))
(context-list (reverse (recording-group-emulate ctx-spec listener)))
(raw-voice (assoc voicename context-list))
(quote-contents (if (pair? raw-voice) (cdr raw-voice) '())))