From 386c1fb71fef99d9ac865ccd1449412c26cef5cc Mon Sep 17 00:00:00 2001 From: Dan Eble Date: Sun, 23 Nov 2014 23:55:03 -0500 Subject: [PATCH 1/1] Issue 4205: Improve part combiner's rest analysis Rests must begin and end simultaneously to be merged into the shared voice. Rests, skips, and multi-measure rests are kept apart even if they begin and end simultaneously. This does not produce ideal output in every case, but it avoids producing musical nonsense. --- ...part-combine-mmrest-after-apart-silence.ly | 18 ++++++ .../regression/part-combine-mmrest-shared.ly | 26 ++++++++ .../regression/part-combine-silence-mixed.ly | 18 ++++++ input/regression/part-combine-silence.ly | 31 +++++++++ scm/part-combiner.scm | 64 +++++++++++++++++-- 5 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 input/regression/part-combine-mmrest-after-apart-silence.ly create mode 100644 input/regression/part-combine-mmrest-shared.ly create mode 100644 input/regression/part-combine-silence-mixed.ly create mode 100644 input/regression/part-combine-silence.ly diff --git a/input/regression/part-combine-mmrest-after-apart-silence.ly b/input/regression/part-combine-mmrest-after-apart-silence.ly new file mode 100644 index 0000000000..0bea1fab53 --- /dev/null +++ b/input/regression/part-combine-mmrest-after-apart-silence.ly @@ -0,0 +1,18 @@ +\version "2.19.16" + +\header { + texidoc = "A multi-measure rest after apart-silence returns the state to unisilence." +} + +\score { << + \new Staff { + \partcombine + \relative f' { r2 r2 | R1^"!!!" } + \relative f' { R1*2 } + } + \new Staff { + \partcombine + \relative f' { R1*2 } + \relative f' { r2 r2 | R1_"!!!" } + } +>> } diff --git a/input/regression/part-combine-mmrest-shared.ly b/input/regression/part-combine-mmrest-shared.ly new file mode 100644 index 0000000000..62a866770b --- /dev/null +++ b/input/regression/part-combine-mmrest-shared.ly @@ -0,0 +1,26 @@ +\version "2.19.16" + +\header { + texidoc = "Multi-measure rests do not have to begin and end simultaneously to be combined." +} + +\score { << + \compressFullBarRests + \partcombine + \relative f' { R1*8 | R1*16 | R1*4 } + \relative f' { R1*16 | R1*8 | R1*4 } +>> } + +\score { << + \compressFullBarRests + \partcombine + \relative f' { R1*8 | r1^"r" | R1*15 | R1*4 } + \relative f' { R1*16 | R1*8 | R1*4 } +>> } + +\score { << + \compressFullBarRests + \partcombine + \relative f' { R1*16 | R1*8 | R1*4 } + \relative f' { R1*8 | r1_"r" | R1*15 | R1*4 } +>> } diff --git a/input/regression/part-combine-silence-mixed.ly b/input/regression/part-combine-silence-mixed.ly new file mode 100644 index 0000000000..7d59777775 --- /dev/null +++ b/input/regression/part-combine-silence-mixed.ly @@ -0,0 +1,18 @@ +\version "2.19.16" + +\header { + texidoc = "Different kinds of silence are not merged into the shared voice even if they begin and end simultaneously." +} + +\score { << + \new Staff { + \partcombine + \relative f' { R1^"R" | s1^"s" | r1^"r" } + \relative f' { r1_"r" | R1_"R" | s1_"s" } + } + \new Staff { + \partcombine + \relative f' { r1^"r" | R1^"R" | s1^"s" } + \relative f' { R1_"R" | s1_"s" | r1_"r" } + } +>> } diff --git a/input/regression/part-combine-silence.ly b/input/regression/part-combine-silence.ly new file mode 100644 index 0000000000..796242b80f --- /dev/null +++ b/input/regression/part-combine-silence.ly @@ -0,0 +1,31 @@ +\version "2.19.16" + +\header { + texidoc = "Rests must begin and end simultaneously to be merged into the shared voice." +} + +% rests of different durations beginning simultaneously, followed by +% unisilence + +\score { + \partcombine + \relative f' { r4 r2 r8 r8 | r1 } + \relative f' { r8 r8 r2 r4 | r1 } +} + +% rests of different durations beginning simultaneously, followed by +% solo then a2. + +\score { + \partcombine + \relative f' { r4 f2. | r8 f e2. } + \relative f' { r8 d f2. | r4 e2. } +} + +% mmrest and rest of different durations beginning simultaneously + +\score { + \partcombine + \relative f' { r4 f2. | R1 } + \relative f' { R1 | r4 d2. } +} diff --git a/scm/part-combiner.scm b/scm/part-combiner.scm index e4ca227e2d..83a5a2e330 100644 --- a/scm/part-combiner.scm +++ b/scm/part-combiner.scm @@ -44,6 +44,12 @@ (ly:in-event-class? x 'note-event)) (filter f? (events vs))) +(define-method (rest-and-skip-events (vs )) + (define (f? x) + (or (ly:in-event-class? x 'rest-event) + (ly:in-event-class? x 'skip-event))) + (filter f? (events vs))) + (define-method (previous-voice-state (vs )) (let ((i (slot-ref vs 'vector-index)) (v (slot-ref vs 'state-vector))) @@ -432,18 +438,64 @@ Only set if not set previously. (let* ((now-state (vector-ref result result-idx)) (vs1 (car (voice-states now-state))) (vs2 (cdr (voice-states now-state)))) - (if (and (equal? (configuration now-state) 'chords) - vs1 vs2) - (let ((notes1 (note-events vs1)) - (notes2 (note-events vs2))) - (cond ((and (= 1 (length notes1)) + + (define (analyse-silence) + (let ((rests1 (if vs1 (rest-and-skip-events vs1) '())) + (rests2 (if vs2 (rest-and-skip-events vs2) '()))) + (cond + + ;; multi-measure rests (probably), which the + ;; part-combine iterator handles well + ((and (synced? now-state) + (= 0 (length rests1)) + (= 0 (length rests2))) + (set! (configuration now-state) 'unisilence)) + + ;; equal rests or equal skips, but not one of each + ((and (synced? now-state) + (= 1 (length rests1)) + (= 1 (length rests2)) + (equal? (ly:event-property (car rests1) 'class) + (ly:event-property (car rests2) 'class)) + (equal? (ly:event-property (car rests1) 'duration) + (ly:event-property (car rests2) 'duration))) + (set! (configuration now-state) 'unisilence)) + + ;; rests of different durations or mixed with + ;; skips or multi-measure rests + ((synced? now-state) + ;; TODO When one part has a rest and the other has a + ;; multi-measure rest, tell the part-combine + ;; iterator to route the part with the rest to the + ;; shared voice. Until there is a way to do this, + ;; we print them both; it does not look very good, + ;; but failing to print the rest is misleading. + ;; + ;; Maybe do something similar for skips; route + ;; the rest to the shared voice and the skip to + ;; the voice for its part. + (set! (configuration now-state) 'apart-silence)) + + ;; TODO At a multi-measure rest, return to unisilence + ;; even after having been apart. The results are not + ;; good now because of the deficiency mentioned + ;; above. + ))) + + (if (or vs1 vs2) + (let ((notes1 (if vs1 (note-events vs1) '())) + (notes2 (if vs2 (note-events vs2) '()))) + ; Todo: What about a2 chords, e.g. string multi-stops? + ; Sort and compare notes1 and notes2? + (cond ((and (equal? (configuration now-state) 'chords) + (= 1 (length notes1)) (= 1 (length notes2)) (equal? (ly:event-property (car notes1) 'pitch) (ly:event-property (car notes2) 'pitch))) (set! (configuration now-state) 'unisono)) ((and (= 0 (length notes1)) (= 0 (length notes2))) - (set! (configuration now-state) 'unisilence))))) + (analyse-silence))))) (analyse-a2 (1+ result-idx))))) (define (analyse-solo12 result-idx) -- 2.39.2