]> git.donarmstrong.com Git - lilypond.git/blobdiff - scm/part-combiner.scm
Imported Upstream version 2.14.2
[lilypond.git] / scm / part-combiner.scm
index 81beaf6ae7d83c6dab583c1297dcd061fe775f3c..0e72ebe2a057eef1f4284e9c4d69fa3c4e20ee94 100644 (file)
@@ -1,8 +1,19 @@
-;;;; part-combiner.scm -- Part combining, staff changes.
+;;;; This file is part of LilyPond, the GNU music typesetter.
 ;;;;
 ;;;;
-;;;;  source file of the GNU LilyPond music typesetter
-;;;; 
-;;;; (c) 2004--2009    Han-Wen Nienhuys <hanwen@xs4all.nl>
+;;;; Copyright (C) 2004--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
+;;;;
+;;;; LilyPond is free software: you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation, either version 3 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; LilyPond is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
 
 ;; todo: figure out how to make module,
 ;; without breaking nested ly scopes
 
 ;; todo: figure out how to make module,
 ;; without breaking nested ly scopes
@@ -19,7 +30,7 @@
   ;; of (SYMBOL . RESULT-INDEX), which indicates where
   ;; said spanner was started.
   (spanner-state #:init-value '() #:accessor span-state))
   ;; of (SYMBOL . RESULT-INDEX), which indicates where
   ;; said spanner was started.
   (spanner-state #:init-value '() #:accessor span-state))
-  
+
 (define-method (write (x <Voice-state> ) file)
   (display (when x) file)
   (display " evs = " file)
 (define-method (write (x <Voice-state> ) file)
   (display (when x) file)
   (display " evs = " file)
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define-class <Split-state> ()
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (define-class <Split-state> ()
+  ;; The automatically determined split configuration
   (configuration #:init-value '() #:accessor configuration)
   (configuration #:init-value '() #:accessor configuration)
+  ;; Allow overriding split configuration, takes precedence over configuration
+  (forced-configuration #:init-value #f #:accessor forced-configuration)
   (when-moment #:accessor when #:init-keyword #:when)
   ;; voice-states are states starting with the Split-state or later
   ;;
   (is #:init-keyword #:voice-states #:accessor voice-states)
   (synced  #:init-keyword #:synced #:init-value         #f #:getter synced?))
   (when-moment #:accessor when #:init-keyword #:when)
   ;; voice-states are states starting with the Split-state or later
   ;;
   (is #:init-keyword #:voice-states #:accessor voice-states)
   (synced  #:init-keyword #:synced #:init-value         #f #:getter synced?))
-                            
+
 
 (define-method (write (x <Split-state> ) f)
   (display (when x) f)
 
 (define-method (write (x <Split-state> ) f)
   (display (when x) f)
@@ -82,7 +96,7 @@
   "Merge lists VS1 and VS2, containing Voice-state objects into vector
 of Split-state objects, crosslinking the Split-state vector and
 Voice-state objects
   "Merge lists VS1 and VS2, containing Voice-state objects into vector
 of Split-state objects, crosslinking the Split-state vector and
 Voice-state objects
-"  
+"
   (define (helper ss-idx ss-list idx1 idx2)
     (let* ((state1 (if (< idx1 (vector-length vs1)) (vector-ref vs1 idx1) #f))
           (state2 (if (< idx2 (vector-length vs2)) (vector-ref vs2 idx2) #f))
   (define (helper ss-idx ss-list idx1 idx2)
     (let* ((state1 (if (< idx1 (vector-length vs1)) (vector-ref vs1 idx1) #f))
           (state2 (if (< idx2 (vector-length vs2)) (vector-ref vs2 idx2) #f))
@@ -114,13 +128,13 @@ Voice-state objects
 
   (define (helper index active)
     "Analyse EVS at INDEX, given state ACTIVE."
 
   (define (helper index active)
     "Analyse EVS at INDEX, given state ACTIVE."
-    
+
     (define (analyse-tie-start active ev)
       (if (equal? (ly:event-property ev 'class) 'tie-event)
          (acons 'tie (split-index (vector-ref voice-state-vec index))
                 active)
          active))
     (define (analyse-tie-start active ev)
       (if (equal? (ly:event-property ev 'class) 'tie-event)
          (acons 'tie (split-index (vector-ref voice-state-vec index))
                 active)
          active))
-    
+
     (define (analyse-tie-end active ev)
       (if (equal? (ly:event-property ev 'class) 'note-event)
          (assoc-remove! active 'tie)
     (define (analyse-tie-end active ev)
       (if (equal? (ly:event-property ev 'class) 'note-event)
          (assoc-remove! active 'tie)
@@ -132,12 +146,12 @@ Voice-state objects
                   (equal? STOP (ly:event-property ev 'span-direction))))
          (assoc-remove! (assoc-remove! active 'cresc) 'decr)
          active))
                   (equal? STOP (ly:event-property ev 'span-direction))))
          (assoc-remove! (assoc-remove! active 'cresc) 'decr)
          active))
-    
+
     (define (active<? a b)
       (cond ((symbol<? (car a) (car b)) #t)
            ((symbol<? (car b) (car b)) #f)
            (else (< (cdr a) (cdr b)))))
     (define (active<? a b)
       (cond ((symbol<? (car a) (car b)) #t)
            ((symbol<? (car b) (car b)) #f)
            (else (< (cdr a) (cdr b)))))
-    
+
     (define (analyse-span-event active ev)
       (let* ((name (ly:event-property ev 'class))
             (key (cond ((equal? name 'slur-event) 'slur)
     (define (analyse-span-event active ev)
       (let* ((name (ly:event-property ev 'class))
             (key (cond ((equal? name 'slur-event) 'slur)
@@ -181,41 +195,50 @@ Voice-state objects
          (set! (span-state (vector-ref voice-state-vec index))
                (list-copy active))
          (helper (1+ index) active))))
          (set! (span-state (vector-ref voice-state-vec index))
                (list-copy active))
          (helper (1+ index) active))))
-  
+
   (helper 0 '()))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   (helper 0 '()))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define-public (recording-group-emulate music odef) 
-  "Interprets music according to odef, but stores all events in a chronological list, similar to the Recording_group_engraver in 2.8 and earlier"
+(define-public (recording-group-emulate music odef)
+  "Interpret @var{music} according to @var{odef}, but store all events
+in a chronological list, similar to the @code{Recording_group_engraver} in
+LilyPond version 2.8 and earlier."
   (let*
   (let*
-      ((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)))))
-       (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))
-                                                                                     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) 'music-event)
-              (ly:add-listener save-acc-listener (ly:context-event-source global) 'OneTimeStep))))))
-    (ly:add-listener new-context-listener (ly:context-events-below global) 'AnnounceNewContext)
+     ((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)))))
+      (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))))))
+    (ly:add-listener new-context-listener
+        (ly:context-events-below global) 'AnnounceNewContext)
     (ly:add-listener mom-listener (ly:context-event-source global) 'Prepare)
     (ly:interpret-music-expression (make-non-relative-music music) global)
     context-list))
     (ly:add-listener mom-listener (ly:context-event-source global) 'Prepare)
     (ly:interpret-music-expression (make-non-relative-music music) global)
     context-list))
@@ -227,23 +250,73 @@ Voice-state objects
         (listener (ly:parser-lookup parser 'partCombineListener))
         (evs2 (recording-group-emulate m2 listener))
         (evs1 (recording-group-emulate m1 listener)))
         (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 'split-list)
     (set! (ly:music-property m 'elements) (list m1 m2))
     (set! (ly:music-property m 'split-list)
-      (if (and (assoc "one" evs1) (assoc "two" evs2))
-         (determine-split-list (reverse! (cdr (assoc "one" evs1)) '())
-                               (reverse! (cdr (assoc "two" evs2)) '()))
-         '() ))
+         (if (and (assoc "one" evs1) (assoc "two" evs2))
+             (determine-split-list (reverse! (assoc-get "one" evs1) '())
+                                   (reverse! (assoc-get "two" evs2) '()))
+             '()))
     m))
 
 (define-public (determine-split-list evl1 evl2)
     m))
 
 (define-public (determine-split-list evl1 evl2)
-  "EVL1 and EVL2 should be ascending"
+  "@var{evl1} and @var{evl2} should be ascending."
   (let* ((pc-debug #f)
         (chord-threshold 8)
         (voice-state-vec1 (make-voice-states evl1))
         (voice-state-vec2 (make-voice-states evl2))
         (result (make-split-state voice-state-vec1 voice-state-vec2)))
   (let* ((pc-debug #f)
         (chord-threshold 8)
         (voice-state-vec1 (make-voice-states evl1))
         (voice-state-vec2 (make-voice-states evl2))
         (result (make-split-state voice-state-vec1 voice-state-vec2)))
-    
+
+    ;; Go through all moments recursively and check if the events of that
+    ;; moment contain a part-combine-force-event override. If so, store its
+    ;; value in the forced-configuration field, which will override. The
+    ;; previous configuration is used to determine non-terminated settings.
+    (define (analyse-forced-combine result-idx prev-res)
+
+      (define (get-forced-event x)
+       (if (ly:in-event-class? x 'part-combine-force-event)
+           (cons (ly:event-property x 'forced-type) (ly:event-property x 'once))
+           #f))
+      (define (part-combine-events vs)
+       (if (not vs)
+           '()
+           (filter-map get-forced-event (events vs))))
+      ;; end part-combine-events
+
+      ;; forced-result: Take the previous config and analyse whether
+      ;; any change happened.... Return new once and permanent config
+      (define (forced-result evt state)
+       ;; sanity check, evt should always be (new-state . once)
+       (if (not (and (pair? evt) (pair? state)))
+           state
+           (if (cdr evt)
+               ;; Once-event, leave permanent state unchanged
+               (cons (car evt) (cdr state))
+               ;; permanent change, leave once state unchanged
+               (cons (car state) (car evt)))))
+      ;; end forced-combine-result
+
+      ;; body of analyse-forced-combine:
+      (if (< result-idx (vector-length result))
+         (let* ((now-state (vector-ref result result-idx)) ; current result
+                ;; Extract all part-combine force events
+                (ev1 (part-combine-events (car (voice-states now-state))))
+                (ev2 (part-combine-events (cdr (voice-states now-state))))
+                (evts (append ev1 ev2))
+                ;; result is (once-state permament-state):
+                (state (fold forced-result (cons 'automatic prev-res) evts))
+                ;; Now let once override permanent changes:
+                (force-state (if (equal? (car state) 'automatic)
+                                 (cdr state)
+                                 (car state))))
+           (set! (forced-configuration (vector-ref result result-idx))
+                 force-state)
+           ;; For the next moment, ignore the once override (car stat)
+           ;; and pass on the permanent override, stored as (cdr state)
+           (analyse-forced-combine (1+ result-idx) (cdr state)))))
+    ;; end analyse-forced-combine
+
+
     (define (analyse-time-step result-idx)
       (define (put x . index)
        "Put the result to X, starting from INDEX backwards.
     (define (analyse-time-step result-idx)
       (define (put x . index)
        "Put the result to X, starting from INDEX backwards.
@@ -256,7 +329,7 @@ Only set if not set previously.
              (begin
                (set! (configuration (vector-ref result i)) x)
                (put x (1- i))))))
              (begin
                (set! (configuration (vector-ref result i)) x)
                (put x (1- i))))))
-      
+
       (define (copy-state-from state-vec vs)
        (define (copy-one-state key-idx)
          (let* ((idx (cdr key-idx))
       (define (copy-state-from state-vec vs)
        (define (copy-one-state key-idx)
          (let* ((idx (cdr key-idx))
@@ -314,12 +387,12 @@ Only set if not set previously.
                                                (previous-voice-state vs2)))
                           (if (and (null? (span-state vs1)) (null? (span-state vs2)))
                               (put 'chords)))))))))
                                                (previous-voice-state vs2)))
                           (if (and (null? (span-state vs1)) (null? (span-state vs2)))
                               (put 'chords)))))))))
-      
+
       (if (< result-idx (vector-length result))
          (let* ((now-state (vector-ref result result-idx))
                 (vs1 (car (voice-states now-state)))
                 (vs2 (cdr (voice-states now-state))))
       (if (< result-idx (vector-length result))
          (let* ((now-state (vector-ref result result-idx))
                 (vs1 (car (voice-states now-state)))
                 (vs2 (cdr (voice-states now-state))))
-           
+
            (cond ((not vs1) (put 'apart))
                  ((not vs2) (put 'apart))
                  (else
            (cond ((not vs1) (put 'apart))
                  ((not vs2) (put 'apart))
                  (else
@@ -336,13 +409,13 @@ Only set if not set previously.
                              (equal? active1 active2)
                              (equal? new-active1 new-active2))
                         (analyse-notes now-state)
                              (equal? active1 active2)
                              (equal? new-active1 new-active2))
                         (analyse-notes now-state)
-                        
+
                         ;; active states different:
                         (put 'apart)))
                         ;; active states different:
                         (put 'apart)))
-                  
+
                   ;; go to the next one, if it exists.
                   (analyse-time-step (1+ result-idx)))))))
                   ;; go to the next one, if it exists.
                   (analyse-time-step (1+ result-idx)))))))
-    
+
     (define (analyse-a2 result-idx)
       (if (< result-idx (vector-length result))
          (let* ((now-state (vector-ref result result-idx))
     (define (analyse-a2 result-idx)
       (if (< result-idx (vector-length result))
          (let* ((now-state (vector-ref result result-idx))
@@ -350,7 +423,7 @@ Only set if not set previously.
                 (vs2 (cdr (voice-states now-state))))
            (if (and (equal? (configuration now-state) 'chords)
                     vs1 vs2)
                 (vs2 (cdr (voice-states now-state))))
            (if (and (equal? (configuration now-state) 'chords)
                     vs1 vs2)
-               (let ((notes1 (note-events vs1)) 
+               (let ((notes1 (note-events vs1))
                      (notes2 (note-events vs2)))
                  (cond ((and (= 1 (length notes1))
                              (= 1 (length notes2))
                      (notes2 (note-events vs2)))
                  (cond ((and (= 1 (length notes1))
                              (= 1 (length notes2))
@@ -361,9 +434,9 @@ Only set if not set previously.
                              (= 0 (length notes2)))
                         (set! (configuration now-state) 'unisilence)))))
            (analyse-a2 (1+ result-idx)))))
                              (= 0 (length notes2)))
                         (set! (configuration now-state) 'unisilence)))))
            (analyse-a2 (1+ result-idx)))))
-    
+
     (define (analyse-solo12 result-idx)
     (define (analyse-solo12 result-idx)
-      
+
       (define (previous-config vs)
        (let* ((pvs (previous-voice-state vs))
               (spi (if pvs (split-index pvs) #f))
       (define (previous-config vs)
        (let* ((pvs (previous-voice-state vs))
               (spi (if pvs (split-index pvs) #f))
@@ -371,26 +444,26 @@ Only set if not set previously.
          (if prev-split
              (configuration prev-split)
              'apart)))
          (if prev-split
              (configuration prev-split)
              'apart)))
-      
+
       (define (put-range x a b)
        ;; (display (list "put range "  x a b "\n"))
        (do ((i a (1+ i)))
            ((> i b) b)
          (set! (configuration (vector-ref result i)) x)))
       (define (put-range x a b)
        ;; (display (list "put range "  x a b "\n"))
        (do ((i a (1+ i)))
            ((> i b) b)
          (set! (configuration (vector-ref result i)) x)))
-      
+
       (define (put x)
        ;; (display (list "putting "  x "\n"))
        (set! (configuration (vector-ref result result-idx)) x))
       (define (put x)
        ;; (display (list "putting "  x "\n"))
        (set! (configuration (vector-ref result result-idx)) x))
-      
+
       (define (current-voice-state now-state voice-num)
        (define vs ((if (= 1 voice-num) car cdr)
                    (voice-states now-state)))
        (if (or (not vs) (equal? (when now-state) (when vs)))
            vs
            (previous-voice-state vs)))
       (define (current-voice-state now-state voice-num)
        (define vs ((if (= 1 voice-num) car cdr)
                    (voice-states now-state)))
        (if (or (not vs) (equal? (when now-state) (when vs)))
            vs
            (previous-voice-state vs)))
-      
+
       (define (try-solo type start-idx current-idx)
       (define (try-solo type start-idx current-idx)
-       "Find a maximum stretch that can be marked as solo. Only set
+       "Find a maximum stretch that can be marked as solo.  Only set
 the mark when there are no spanners active.
 
       return next idx to analyse.
 the mark when there are no spanners active.
 
       return next idx to analyse.
@@ -414,7 +487,7 @@ the mark when there are no spanners active.
                     ;;
                     ;; This includes rests. This isn't a problem: long rests
                     ;; will be shared with the silent voice, and be marked
                     ;;
                     ;; This includes rests. This isn't a problem: long rests
                     ;; will be shared with the silent voice, and be marked
-                    ;; as unisilence. Therefore, long rests won't 
+                    ;; as unisilence. Therefore, long rests won't
                     ;;  accidentally be part of a solo.
                     ;;
                     (put-range type start-idx current-idx)
                     ;;  accidentally be part of a solo.
                     ;;
                     (put-range type start-idx current-idx)
@@ -423,9 +496,9 @@ the mark when there are no spanners active.
                     (try-solo type start-idx (1+ current-idx)))))
            ;; try-solo
            start-idx))
                     (try-solo type start-idx (1+ current-idx)))))
            ;; try-solo
            start-idx))
-      
+
       (define (analyse-moment result-idx)
       (define (analyse-moment result-idx)
-       "Analyse 'apart starting at RESULT-IDX. Return next index. "
+       "Analyse 'apart starting at RESULT-IDX.  Return next index."
        (let* ((now-state (vector-ref result result-idx))
               (vs1 (current-voice-state now-state 1))
               (vs2 (current-voice-state now-state 2))
        (let* ((now-state (vector-ref result result-idx))
               (vs1 (current-voice-state now-state 1))
               (vs2 (current-voice-state now-state 2))
@@ -449,11 +522,11 @@ the mark when there are no spanners active.
                       (equal? (when vs2) (when now-state))
                       (null? (previous-span-state vs2)))
                  (try-solo 'solo2 result-idx result-idx))
                       (equal? (when vs2) (when now-state))
                       (null? (previous-span-state vs2)))
                  (try-solo 'solo2 result-idx result-idx))
-                
+
                 (else (1+ result-idx)))
           ;; analyse-moment
           (1+ result-idx))))
                 (else (1+ result-idx)))
           ;; analyse-moment
           (1+ result-idx))))
-      
+
       (if (< result-idx (vector-length result))
          (if (equal? (configuration (vector-ref result result-idx)) 'apart)
              (analyse-solo12 (analyse-moment result-idx))
       (if (< result-idx (vector-length result))
          (if (equal? (configuration (vector-ref result result-idx)) 'apart)
              (analyse-solo12 (analyse-moment result-idx))
@@ -469,14 +542,25 @@ the mark when there are no spanners active.
          (display "***\n")
          (display result)
          (display "***\n")))
          (display "***\n")
          (display result)
          (display "***\n")))
+
+    ;; Extract all forced combine strategies, i.e. events inserted by
+    ;; \partcombine(Apart|Automatic|SoloI|SoloII|Chords)[Once]
+    ;; They will in the end override the automaically determined ones.
+    ;; Initial state for both voices is no override
+    (analyse-forced-combine 0 #f)
+    ;; Now go through all time steps in a loop and find a combination strategy
+    ;; based only on the events of that one moment (i.e. neglecting longer
+    ;; periods of solo/apart, etc.)
     (analyse-time-step 0)
     ;; (display result)
     (analyse-time-step 0)
     ;; (display result)
+    ;; Check for unisono or unisilence moments
     (analyse-a2 0)
     ;;(display result)
     (analyse-solo12 0)
     ;; (display result)
     (set! result (map
     (analyse-a2 0)
     ;;(display result)
     (analyse-solo12 0)
     ;; (display result)
     (set! result (map
-                 (lambda (x) (cons (when x) (configuration x)))
+                 ;; forced-configuration overrides, if it is set
+                 (lambda (x) (cons (when x) (or (forced-configuration x) (configuration x))))
                  (vector->list result)))
     (if #f ;; pc-debug
         (display result))
                  (vector->list result)))
     (if #f ;; pc-debug
         (display result))