From 88d306d9c5666b5ade4a136df29cca19c5ff5ed7 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Sat, 6 Apr 2013 09:54:58 +0200 Subject: [PATCH] Break slurs between alternative endings in repeats; issue 1698. Create new event-types BreakPhrasingSlurEvent BreakSlurEvent, and a music-function \free so that users can create them. Create these event-types in the volta-repeat-iterator to break slurs between alternatives in a \repeat volta structure. --- input/regression/repeat-slur.ly | 162 ++++++++++++++++++++++++++++ lily/include/slur-proto-engraver.hh | 3 + lily/phrasing-slur-engraver.cc | 9 ++ lily/slur-engraver.cc | 9 ++ lily/slur-proto-engraver.cc | 89 ++++++++++++--- lily/slur.cc | 9 +- lily/spanner.cc | 1 + lily/volta-repeat-iterator.cc | 14 +++ ly/music-functions-init.ly | 20 ++++ ly/spanners-init.ly | 4 + scm/define-context-properties.scm | 1 + scm/define-event-classes.scm | 3 +- scm/define-grob-properties.scm | 2 + scm/define-music-types.scm | 10 ++ 14 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 input/regression/repeat-slur.ly diff --git a/input/regression/repeat-slur.ly b/input/regression/repeat-slur.ly new file mode 100644 index 0000000000..67420fc51a --- /dev/null +++ b/input/regression/repeat-slur.ly @@ -0,0 +1,162 @@ +\version "2.17.14" + +\header { + texidoc = "Slurs are automatically broken at repeats. +This behavior can be changed by setting @code{\\slurOverRepeat} +to @code{#t}. To manually break a slur at a bar line, use +@code{\\breakSlur}. To manually start a free slur at a bar, use +@code{\\free} with an opening parenthesis. To manually stop a +free slur at a bar, use @code{\\free} with a closing parenthesis. +" +} + +\new Staff { + a'4 ( b' c' d' + \repeat volta 3 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' ( d' } + { a' b' ) b'2 ( } + { a'4 b' ) b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + a'4 ( b' c' d' + \repeat volta 2 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' d' ( } + { a' b' ) b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + a'4 ( b' c' d' + \repeat volta 2 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' ( d' } + { a' ) b' b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + a'4 ( b' c' d' + \repeat volta 2 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' d' ( } + { a' ) b' b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + \set slurOverRepeat = ##t + a'4 ( b' c' d' + \repeat volta 2 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' ( d' } + { a' b' ) b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + \repeat volta 2 + { \free ( a'4 ) b' ( c' d' } + \alternative { { a' ) b' c' ( d' } + { a' b' ) b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + a'4 ( b' c' d' + \repeat volta 2 + { a' ) b' ( c' d' } + \alternative { { a' ) b' c' ( d' \free ) } + { a' b' b'2 ( } } + + a'1 ) \bar "|." +} + +\new Staff { + a'4 ( b' c' d' \breakSlur | a' b' c' d' ) | +} + +%% phrasing slurs + +\new Staff { + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' \( d' } + { a' b' \) b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' d' \( } + { a' b' \) b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' \( d' } + { a' \) b' b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' d' \( } + { a' \) b' b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + \set slurOverRepeat = ##t + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' \( d' } + { a' b' \) b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + \repeat volta 2 + { \free \( a'4 \) b' \( c' d' } + \alternative { { a' \) b' c' \( d' } + { a' b' \) b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + a'4 \( b' c' d' + \repeat volta 2 + { a' \) b' \( c' d' } + \alternative { { a' \) b' c' \( d' \free \) } + { a' b' b'2 \( } } + + a'1 \) \bar "|." +} + +\new Staff { + a'4 \( b' c' d' \breakPhrasingSlur | a' b' c' d' \) | +} diff --git a/lily/include/slur-proto-engraver.hh b/lily/include/slur-proto-engraver.hh index 0b0861775c..d0ae3c0875 100644 --- a/lily/include/slur-proto-engraver.hh +++ b/lily/include/slur-proto-engraver.hh @@ -38,6 +38,7 @@ protected: vector slurs_; vector end_slurs_; vector objects_to_acknowledge_; + Stream_event *break_slur_; const char* double_property_name_; const char* grob_name_; const char* object_name_; @@ -53,6 +54,7 @@ protected: DECLARE_ACKNOWLEDGER (tuplet_number); void internal_listen_slur (Stream_event *ev); + void internal_listen_break_slur (Stream_event *ev); void acknowledge_extra_object (Grob_info); void stop_translation_timestep (); void process_music (); @@ -60,6 +62,7 @@ protected: bool can_create_slur (string, vsize, vsize *, Stream_event *); void create_slur (string spanner_id, Stream_event *ev_cause, Grob *g_cause, Direction dir, bool left_broken); bool try_to_end (Stream_event *ev); + void break_slurs (); virtual void set_melisma (bool); virtual void finalize (); diff --git a/lily/phrasing-slur-engraver.cc b/lily/phrasing-slur-engraver.cc index 6d9aac7af6..34b8cb09ee 100644 --- a/lily/phrasing-slur-engraver.cc +++ b/lily/phrasing-slur-engraver.cc @@ -35,6 +35,7 @@ class Phrasing_slur_engraver : public Slur_proto_engraver { protected: DECLARE_TRANSLATOR_LISTENER (phrasing_slur); + DECLARE_TRANSLATOR_LISTENER (break_phrasing_slur); DECLARE_ACKNOWLEDGER (slur); public: @@ -44,6 +45,7 @@ public: Phrasing_slur_engraver::Phrasing_slur_engraver () : Slur_proto_engraver (0, "PhrasingSlur", "phrasing slur", "phrasing-slur-event") { + break_slur_ = 0; } IMPLEMENT_TRANSLATOR_LISTENER (Phrasing_slur_engraver, phrasing_slur); @@ -53,6 +55,13 @@ Phrasing_slur_engraver::listen_phrasing_slur (Stream_event *ev) internal_listen_slur (ev); } +IMPLEMENT_TRANSLATOR_LISTENER (Phrasing_slur_engraver, break_phrasing_slur); +void +Phrasing_slur_engraver::listen_break_phrasing_slur (Stream_event *ev) +{ + internal_listen_break_slur (ev); +} + void Phrasing_slur_engraver::acknowledge_slur (Grob_info info) { diff --git a/lily/slur-engraver.cc b/lily/slur-engraver.cc index a8a54f535a..3f17035544 100644 --- a/lily/slur-engraver.cc +++ b/lily/slur-engraver.cc @@ -37,6 +37,7 @@ class Slur_engraver : public Slur_proto_engraver protected: DECLARE_TRANSLATOR_LISTENER (slur); + DECLARE_TRANSLATOR_LISTENER (break_slur); public: TRANSLATOR_DECLARATIONS (Slur_engraver); @@ -45,6 +46,7 @@ public: Slur_engraver::Slur_engraver () : Slur_proto_engraver ("doubleSlurs", "Slur", "slur", "slur-event") { + break_slur_ = 0; } IMPLEMENT_TRANSLATOR_LISTENER (Slur_engraver, slur); @@ -54,6 +56,13 @@ Slur_engraver::listen_slur (Stream_event *ev) internal_listen_slur (ev); } +IMPLEMENT_TRANSLATOR_LISTENER (Slur_engraver, break_slur); +void +Slur_engraver::listen_break_slur (Stream_event *ev) +{ + internal_listen_break_slur (ev); +} + void Slur_engraver::set_melisma (bool m) { diff --git a/lily/slur-proto-engraver.cc b/lily/slur-proto-engraver.cc index fbc5f00b84..a3ced1c2c1 100644 --- a/lily/slur-proto-engraver.cc +++ b/lily/slur-proto-engraver.cc @@ -52,6 +52,22 @@ Slur_proto_engraver::internal_listen_slur (Stream_event *ev) event_name_, int (d))); } +void +Slur_proto_engraver::internal_listen_break_slur (Stream_event *ev) +{ + // if break_slur_ is set, we only keep events with direction + if (break_slur_ + && robust_scm2dir (ev->get_property ("span-direction"), CENTER)) + break_slur_ = ev; + else if (!break_slur_) + break_slur_ = ev; + else if (break_slur_ + && robust_scm2dir (break_slur_->get_property ("span-direction"), CENTER) + && robust_scm2dir (ev->get_property ("span-direction"), CENTER)) + ev->origin ()->warning (_f ("cannot set break slur with two directions")); +} + + void Slur_proto_engraver::acknowledge_note_column (Grob_info info) { @@ -136,16 +152,20 @@ Slur_proto_engraver::create_slur (string spanner_id, Stream_event *ev_cause, Gro slurs_.push_back (slur); if (double_property_name_ && to_boolean (get_property (double_property_name_))) - { - set_grob_direction (slur, DOWN); - slur = make_spanner (grob_name_, cause); - slur->set_property ("spanner-id", ly_string2scm (spanner_id)); - set_grob_direction (slur, UP); - if (left_broken) - slur->set_bound (LEFT, ccc); - slurs_.push_back (slur); - } - + { + set_grob_direction (slur, DOWN); + slur = make_spanner (grob_name_, cause); + slur->set_property ("spanner-id", ly_string2scm (spanner_id)); + set_grob_direction (slur, UP); + if (left_broken) + slur->set_bound (LEFT, ccc); + slurs_.push_back (slur); + } + else if (g_cause && Slur::has_interface (g_cause) && left_broken) + { + g_cause->set_object ("other-half", slur->self_scm ()); + slur->set_object ("other-half", g_cause->self_scm ()); + } } bool @@ -224,9 +244,44 @@ Slur_proto_engraver::try_to_end (Stream_event *ev) return ended; } +void +Slur_proto_engraver::break_slurs () +{ + for (vsize i = slurs_.size (); i--;) + { + Grob *ccc = unsmob_grob (get_property ("currentCommandColumn")); + Spanner *s = dynamic_cast (slurs_[i]); + s->set_bound (RIGHT, ccc); + announce_end_grob (s, SCM_EOL); + slurs_.erase (slurs_.begin () + i); + SCM maybe_dir = s->get_property_data ("direction"); + Direction dir = is_direction (maybe_dir) + ? robust_scm2dir (maybe_dir, CENTER) + : CENTER; + create_slur (robust_scm2string (s->get_property ("spanner-id"), ""), + 0, s, dir, true); + } +} + void Slur_proto_engraver::process_music () { + // break slurs that span over this column + // if break_slur_'s direction is center + if (break_slur_ + && robust_scm2dir (break_slur_->get_property ("span-direction"), CENTER) == CENTER + && unsmob_grob (get_property ("currentCommandColumn"))) + break_slurs (); + + // create broken slurs starting at this column if break_slur_ is left + vsize old_slurs = slurs_.size (); + if (break_slur_ + && robust_scm2dir (break_slur_->get_property ("span-direction"), CENTER) == LEFT + && can_create_slur ("", old_slurs, 0, break_slur_)) + create_slur ("", break_slur_, 0, + robust_scm2dir (break_slur_->get_property ("direction"), CENTER), + true); + for (vsize i = 0; i < stop_events_.size (); i++) { string id = robust_scm2string (stop_events_[i]->get_property ("spanner-id"), ""); @@ -243,18 +298,22 @@ Slur_proto_engraver::process_music () else stop_events_[i]->origin ()->warning (_f ("cannot end %s", object_name_)); } + old_slurs = slurs_.size (); - vsize old_slurs = slurs_.size (); for (vsize i = start_events_.size (); i--;) { Stream_event *ev = start_events_[i]; string id = robust_scm2string (ev->get_property ("spanner-id"), ""); Direction updown = to_dir (ev->get_property ("direction")); - if (can_create_slur (id, old_slurs, &i, ev)) create_slur (id, ev, 0, updown, false); } + // if BreakSlurEvent with span-dir right, we end here + if (break_slur_ + && robust_scm2dir (break_slur_->get_property ("span-direction"), CENTER) == RIGHT) + (void) try_to_end (break_slur_); + set_melisma (slurs_.size ()); } @@ -281,6 +340,11 @@ Slur_proto_engraver::stop_translation_timestep () Spanner *s = dynamic_cast (end_slurs_[i]); if (!s->get_bound (RIGHT)) s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))); + // if BreakSlurEvent with span-dir right, + // we set right bound to current command column + if (break_slur_ + && robust_scm2dir (break_slur_->get_property ("span-direction"), CENTER) == RIGHT) + s->set_bound (RIGHT, unsmob_grob (get_property ("currentCommandColumn"))); announce_end_grob (s, SCM_EOL); } @@ -291,6 +355,7 @@ Slur_proto_engraver::stop_translation_timestep () end_slurs_.clear (); start_events_.clear (); stop_events_.clear (); + break_slur_ = 0; } // no ADD_ACKNOWLEDGER / ADD_TRANSLATOR macro calls diff --git a/lily/slur.cc b/lily/slur.cc index 0aa96c787d..9f12df878c 100644 --- a/lily/slur.cc +++ b/lily/slur.cc @@ -48,7 +48,14 @@ SCM Slur::calc_direction (SCM smob) { Grob *me = unsmob_grob (smob); - extract_grob_set (me, "note-columns", encompasses); + vector encompasses; + extract_grob_set (me, "note-columns", ro_encompasses); + encompasses.insert (encompasses.end (), ro_encompasses.begin (), ro_encompasses.end ()); + if (Grob *other_half = unsmob_grob (me->get_object ("other-half"))) + { + extract_grob_set (other_half, "note-columns", oh_encompasses); + encompasses.insert (encompasses.end (), oh_encompasses.begin (), oh_encompasses.end ()); + } if (encompasses.empty ()) { diff --git a/lily/spanner.cc b/lily/spanner.cc index af2ea7137f..a6a2cb7e93 100644 --- a/lily/spanner.cc +++ b/lily/spanner.cc @@ -522,6 +522,7 @@ ADD_INTERFACE (Spanner, " point of the spanner.", /* properties */ + "other-half " "normalized-endpoints " "minimum-length " "spanner-broken " diff --git a/lily/volta-repeat-iterator.cc b/lily/volta-repeat-iterator.cc index 2e386e6fd1..ddcdcb4802 100644 --- a/lily/volta-repeat-iterator.cc +++ b/lily/volta-repeat-iterator.cc @@ -87,6 +87,20 @@ void Volta_repeat_iterator::next_element (bool side_effect) { done_count_++; + if (done_count_ > 1 + && done_count_ <= alt_count_ + && !to_boolean (get_outlet ()->get_property ("slurOverRepeat"))) + { + SCM ev_scm = scm_call_1 (ly_lily_module_constant ("make-music"), + ly_symbol2scm ("BreakSlurEvent")); + Music *ev = unsmob_music (ev_scm); + ev->send_to_context (get_outlet ()); + + ev_scm = scm_call_1 (ly_lily_module_constant ("make-music"), + ly_symbol2scm ("BreakPhrasingSlurEvent")); + ev = unsmob_music (ev_scm); + ev->send_to_context (get_outlet ()); + } Sequential_iterator::next_element (side_effect); diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly index a25198f3e0..86a81c3598 100644 --- a/ly/music-functions-init.ly +++ b/ly/music-functions-init.ly @@ -429,6 +429,26 @@ to the preceding note or rest as a post-event with @code{-}.") 'footnote-text footnote))) #{ \tweak footnote-music #mus #item #})) +free = +#(define-music-function (parser location music) (ly:music?) + (_i "@var{event} should start a free spanner.") + (let ((name (ly:music-property music 'name))) + (cond + ((eq? name 'SlurEvent) + (make-music 'BreakSlurEvent + 'span-direction (ly:music-property music 'span-direction) + 'direction (ly:music-property music 'direction) + 'spanner-id (ly:music-property music 'spanner-id))) + ((eq? name 'PhrasingSlurEvent) + (make-music 'BreakPhrasingSlurEvent + 'span-direction (ly:music-property music 'span-direction) + 'direction (ly:music-property music 'direction) + 'spanner-id (ly:music-property music 'spanner-id))) + (else + (begin + (ly:music-warning music (_ "not a breakable event")) + music))))) + grace = #(def-grace-function startGraceMusic stopGraceMusic (_i "Insert @var{music} as grace notes.")) diff --git a/ly/spanners-init.ly b/ly/spanners-init.ly index 44dcdb6633..ab56412734 100644 --- a/ly/spanners-init.ly +++ b/ly/spanners-init.ly @@ -110,3 +110,7 @@ sostenutoOff = #(make-span-event 'SostenutoEvent STOP) newSpacingSection = #(make-event-chord (list (make-music 'SpacingSectionEvent))) breakDynamicSpan = #(make-music 'BreakDynamicSpanEvent) + +breakSlur = #(make-music 'BreakSlurEvent) + +breakPhrasingSlur = #(make-music 'BreakPhrasingSlurEvent) diff --git a/scm/define-context-properties.scm b/scm/define-context-properties.scm index 98f52aaa09..5bd8f76a45 100644 --- a/scm/define-context-properties.scm +++ b/scm/define-context-properties.scm @@ -669,6 +669,7 @@ is not set") used by the @code{Script_engraver} for typesetting note-superscripts and subscripts. See @file{scm/@/script.scm} for more information.") (slurMelismaBusy ,boolean? "Signal if a slur is present.") + (slurOverRepeat ,boolean? "A slur should be drawn over a repeat.") (stavesFound ,grob-list? "A list of all staff-symbols found.") diff --git a/scm/define-event-classes.scm b/scm/define-event-classes.scm index c8ecbcf227..15aaab53df 100644 --- a/scm/define-event-classes.scm +++ b/scm/define-event-classes.scm @@ -50,7 +50,8 @@ trill-span-event tremolo-span-event tuplet-span-event)) (span-dynamic-event . (decrescendo-event crescendo-event)) - (break-span-event . (break-dynamic-span-event)) + (break-span-event . (break-dynamic-span-event break-slur-event + break-phrasing-slur-event )) (pedal-event . (sostenuto-event sustain-event una-corda-event)) (rhythmic-event . (lyric-event melodic-event multi-measure-rest-event double-percent-event percent-event diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index 4c5c404dc7..2371e5f9bd 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -1138,6 +1138,8 @@ pure-from-neighbor-interface to determine various grob heights.") (note-columns ,ly:grob-array? "An array of @code{NoteColumn} grobs.") (note-head ,ly:grob? "A single note head.") (note-heads ,ly:grob-array? "An array of note head grobs.") + (other-half ,ly:grob? "The other half a broken spanner. For example, +a slur that breaks at a repeat stores its other half here.") (pedal-text ,ly:grob? "A pointer to the text of a mixed-style piano pedal.") (potential-X-colliding-grobs ,ly:grob-array? "Grobs that can potentially diff --git a/scm/define-music-types.scm b/scm/define-music-types.scm index 1090d332b4..5b9e6f6174 100644 --- a/scm/define-music-types.scm +++ b/scm/define-music-types.scm @@ -118,6 +118,16 @@ Syntax for manual control: @code{c8-[ c c-] c8}") (types . (general-music post-event break-span-event break-dynamic-span-event event)) )) + (BreakPhrasingSlurEvent + . ((description . "End a phrasing slur here.") + (types . (general-music break-span-event break-phrasing-slur-event event)) + )) + + (BreakSlurEvent + . ((description . "End a slur here.") + (types . (general-music break-span-event break-slur-event event)) + )) + (BendAfterEvent . ((description . "A drop/@/fall/@/doit jazz articulation.") (types . (general-music post-event bend-after-event event)))) -- 2.39.5