X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fmensural-ligature-engraver.cc;h=ead5f1f0ad7d8242f89e69672a3216530930a8bc;hb=e90f0536f9be39ada0bef0aeb0d275dec3b2fb5b;hp=bf1c2cd0c58abbd9ddf0c5a60504a9abe2f2bb3e;hpb=e8936ee66fdd3e7ebe30556590e8dc913908a533;p=lilypond.git diff --git a/lily/mensural-ligature-engraver.cc b/lily/mensural-ligature-engraver.cc index bf1c2cd0c5..ead5f1f0ad 100644 --- a/lily/mensural-ligature-engraver.cc +++ b/lily/mensural-ligature-engraver.cc @@ -1,336 +1,323 @@ /* - mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver - - source file of the GNU LilyPond music typesetter - - (c) 2002--2003 Juergen Reuter - */ + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2002--2011 Juergen Reuter , + Pal Benko + + 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 . +*/ -#include "mensural-ligature.hh" #include "coherent-ligature-engraver.hh" -#include "event.hh" -#include "warn.hh" -#include "item.hh" -#include "spanner.hh" -#include "rod.hh" -#include "paper-column.hh" +#include "font-interface.hh" +#include "international.hh" +#include "mensural-ligature.hh" #include "note-column.hh" -#include "rhythmic-head.hh" #include "note-head.hh" +#include "output-def.hh" +#include "paper-column.hh" +#include "pitch.hh" +#include "rhythmic-head.hh" +#include "spanner.hh" #include "staff-symbol-referencer.hh" -#include "paper-def.hh" -#include "font-interface.hh" +#include "stream-event.hh" +#include "warn.hh" + +#include "translator.icc" /* - * TODO: My resources on Franco of Cologne's rules claim that his - * rules map ligature<->mensural timing in a non-ambigous way, but in - * fact, as presented in these resources, the rules become ambigous as - * soon as there appear durations other than breves within a ligature - * with more than two heads (ligatura ternaria etc.). Hence, the - * below implementation is an approximation of what I think the rules - * could look like if forced to be non-ambigous. This should be - * further investigated. - * - * TODO: The automat is quite complicated, and its design is error - * prone (and most probably, it behaves wrong for some very special - * cases). Maybe we can find a better paradigm for modelling Franco - * of Cologne's rules? + * TODO: accidentals are aligned with the first note; + * they must appear ahead. * - * TODO: dotted heads: when applying Franco of Cologne's mapping, put - * dots *above* (rather than after) affected ligature heads. + * TODO: prohibit ligatures having notes differing only in accidentals + * (like \[ a\breve g as \]) * - * TODO: prohibit multiple voices within a ligature. + * TODO: do something with multiple voices within a ligature. See + * for example: + * Ockeghem: Missa Ecce ancilla domini, bassus part, end of Christe. * - * TODO: enhance robustness: in case of an illegal ligature (e.g. the - * user events for a ligature that contains a minima or STATE_ERROR - * is reached), automatically break the ligature into smaller, valid - * pieces. + * TODO: enhance robustness: in case of an invalid ligature (e.g. the + * input specifies a ligature that contains a minima), automatically + * break the ligature into smaller, valid pieces. Such a piece may be + * a single note. */ + class Mensural_ligature_engraver : public Coherent_ligature_engraver { protected: virtual Spanner *create_ligature_spanner (); - virtual void build_ligature (Spanner *ligature, Array primitives); + virtual void build_ligature (Spanner *ligature, vector primitives); + DECLARE_TRANSLATOR_LISTENER (ligature); public: - TRANSLATOR_DECLARATIONS(Mensural_ligature_engraver); + TRANSLATOR_DECLARATIONS (Mensural_ligature_engraver); private: - int apply_transition (Array primitives, - int state, int input, int i); - void transform_heads (Array primitives); - void propagate_properties (Spanner *ligature, Array primitives); - void fold_up_primitives (Array primitives); - void join_primitives (Array primitives); + void transform_heads (vector primitives); + void propagate_properties (Spanner *ligature, vector primitives); + void fold_up_primitives (vector primitives); }; +IMPLEMENT_TRANSLATOR_LISTENER (Mensural_ligature_engraver, ligature); +void +Mensural_ligature_engraver::listen_ligature (Stream_event *ev) +{ + Ligature_engraver::listen_ligature (ev); +} Mensural_ligature_engraver::Mensural_ligature_engraver () { + brew_ligature_primitive_proc = + Mensural_ligature::brew_ligature_primitive_proc; } Spanner * Mensural_ligature_engraver::create_ligature_spanner () { - return new Spanner (get_property ("MensuralLigature")); -} - -/* - * The following lines implement a finite state automat. Given a - * sequence of durations (Longa, Brevis, Semibrevis) or - * end-of-ligature-event as input, the automat outputs a sequence of - * events for grobs that form a proper ligature. - */ - -/* - * This enumeration represents the set of possible input values to the - * automat. There may (potentially) be any sequence of Longa, Brevis, - * and Semibrevis duration symbols fed into the automat, with a final - * EndOfLigature symbol to terminate the ligature. Other durations - * are explicitly prohibited. Depending on the note's pitch of the - * preceding and the current input, the melodic line may be ascending - * or descending. Per definition, the melodic line must either ascend - * or descend, because if the pitches were twice the same, the two - * notes would be merged into a single one (as long as not resulting - * in a prohibited duration). In the case of the EndOfLigature - * symbol, the melodic line is undefined (but we still have ascending - * and descending case for the sake of consistency, making the automat - * simpler). - */ -enum Ligature_input -{ - // Ascending/Descending Longa/Brevis/Semibrevis/EndOfLigature - INPUT_AL = 0, - INPUT_DL, - INPUT_AB, - INPUT_DB, - INPUT_AS, - INPUT_DS, - INPUT_AE, - INPUT_DE, -}; - -/* - * This enumeration represents all possible internal states of the - * automat. Besides the generic states START, ERROR, and END, the - * remaining states L, B, S, and SS describe pending values from the - * sequence of input values that have not yet been transformed to - * proper output values, including the melodic direction - * (ascending/descending) for state L. - */ -enum Ligature_state -{ - // aL = ascending Longa, dL descending Longa, B = Brevis, S = - // Semibrevis, SS = 2 Semibreves - STATE_START = 0, - STATE_aL, - STATE_dL, - STATE_B, - STATE_S, - STATE_SS, - STATE_ERROR, - STATE_END, -}; - -/* - * The following array represents the transitions of the automat: - * given some state and input, it maps to a new state, according (with - * the limitations as described above) to the rules of Franco of - * Cologne. - */ -const int/*new state*/ transition_state[/*old state*/][8/*input*/] = -{ - {STATE_aL, STATE_dL, STATE_B, STATE_B, - STATE_S, STATE_S, STATE_ERROR, STATE_ERROR}, // was: STATE_START - {STATE_aL, STATE_dL, STATE_B, STATE_START, - STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_aL - {STATE_aL, STATE_dL, STATE_B, STATE_START, - STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_dL - {STATE_aL, STATE_dL, STATE_B, STATE_START, - STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_B - {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR, - STATE_SS, STATE_SS, STATE_ERROR, STATE_ERROR}, // was: STATE_S - {STATE_aL, STATE_dL, STATE_B, STATE_B, - STATE_S, STATE_S, STATE_END, STATE_END}, // was: STATE_SS - {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR, - STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_ERROR - {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR, - STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_END -}; - -/* - * The following array represents the output of the automat while - * switching from one state to another: given some state and input, it - * maps to the output produced when switching to the next state, - * according (with the limitations as described above) to the rules of - * Franco of Cologne. - */ -const int/*output*/ transition_output[/*old state*/][8/*input*/] = -{ - {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE, - MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_START - {MLP_sc, MLP_ss, MLP_sc, MLP_LB, - MLP_NONE, MLP_NONE, MLP_sc, MLP_sc}, // was: STATE_aL - {MLP_sc, MLP_ss, MLP_sc, MLP_LB, - MLP_NONE, MLP_NONE, MLP_ss, MLP_ss}, // was: STATE_dL - {MLP_ss, MLP_cs, MLP_ss, MLP_BB, - MLP_NONE, MLP_NONE, MLP_ss, MLP_ss} , // was: STATE_B - {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE, - MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_S - {MLP_SS, MLP_SS, MLP_SS, MLP_SS, - MLP_SS, MLP_SS, MLP_SS, MLP_SS} , // was: STATE_SS - {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE, - MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_ERROR - {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE, - MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_END -}; - -int -Mensural_ligature_engraver::apply_transition (Array primitives, - int state, int input, int i) -{ - int output = transition_output[state][input]; - Item *last_last_primitive = (i > 1) ? - dynamic_cast (primitives[i-2].grob_) : 0; - Item *last_primitive = (i > 0) ? - dynamic_cast (primitives[i-1].grob_) : 0; - Item *primitive = (i < primitives.size ()) ? - dynamic_cast (primitives[i].grob_) : 0; - switch (output) - { - case MLP_NONE: - // skip note head, expecting a primitive with two note heads - break; - case MLP_sc: - case MLP_ss: - case MLP_cs: - // primitive with single note head - if (!last_primitive) - { - programming_error ("last_primitive undefined"); - break; - } - last_primitive->set_grob_property ("primitive", gh_int2scm (output)); - break; - case MLP_BB: - case MLP_LB: - // primitive with two note heads - if (!last_primitive) - { - programming_error ("last_primitive undefined"); - break; - } - if (!primitive) - { - programming_error ("primitive undefined"); - break; - } - last_primitive->set_grob_property ("primitive", gh_int2scm (output)); - primitive->set_grob_property ("primitive", gh_int2scm (MLP_NONE)); - break; - case MLP_SS: - // delayed primitive with two note heads - if (!last_last_primitive) - { - programming_error ("last_last_primitive undefined"); - break; - } - if (!last_primitive) - { - programming_error ("last_primitive undefined"); - break; - } - last_last_primitive->set_grob_property ("primitive", gh_int2scm (output)); - last_primitive->set_grob_property ("primitive", gh_int2scm (MLP_NONE)); - break; - default: - programming_error (_f ("unexpected case fall-through")); - break; - } - return transition_state[state][input]; + return make_spanner ("MensuralLigature", SCM_EOL); } void -Mensural_ligature_engraver::transform_heads (Array primitives) +Mensural_ligature_engraver::transform_heads (vector primitives) { if (primitives.size () < 2) { - warning (_f ("ligature with less than 2 heads -> skipping")); + warning (_ ("ligature with less than 2 heads -> skipping")); return; } - int state = STATE_START; - Pitch last_pitch, pitch; - bool have_last_pitch = 0, have_pitch = 0; - for (int i = 0; i < primitives.size (); i++) { - last_pitch = pitch; - have_last_pitch = have_pitch; - Grob_info info = primitives[i]; - int duration_log = - Note_head::get_balltype (dynamic_cast (info.grob_)); - - Music *nr = info.music_cause (); - - /* - ugh. why not simply check for pitch? - */ - if (!nr->is_mus_type ("note-event")) - { - info.music_cause ()->origin ()->warning (_f ("can not determine pitch of ligature primitive -> skipping")); - i++; - state = STATE_START; - have_pitch = 0; - continue; - } - else - { - pitch = *unsmob_pitch (nr->get_mus_property ("pitch")); - have_pitch = 1; - } + int prev_pitch = 0; + bool at_beginning = true; - int delta_pitch; + // needed so that we can check whether + // the previous note can be turned into a flexa + bool prev_brevis_shape = false; - if (!have_last_pitch) - { - delta_pitch = 0; // first pitch; delta undefined - } - else - { - delta_pitch = (pitch.steps () - last_pitch.steps ()); - if (Pitch::compare (last_pitch, pitch) == 0) - { - info.music_cause ()->origin ()->warning (_f ("prime interval within ligature -> skipping")); - i++; - state = STATE_START; - have_pitch = 0; - continue; - } - } + bool prev_semibrevis = false; + Item *prev_primitive = NULL; - if ((duration_log < -2) || (duration_log > 0)) - { - info.music_cause ()->origin ()->warning (_f ("mensural ligature: duration none of L, B, S -> skipping")); - i++; - state = STATE_START; - have_pitch = 0; - continue; - } + for (vsize i = 0, s = primitives.size (); i < s; i++) + { + Grob_info info = primitives[i]; + Item *primitive = dynamic_cast (info.grob ()); + int duration_log = Rhythmic_head::duration_log (primitive); - int input = (duration_log + 2) * 2 + ((delta_pitch < 0) ? 1 : 0); - state = apply_transition (primitives, state, input, i); - // TODO: if (state == STATE_ERROR) { ... } - } + Stream_event *nr = info.event_cause (); - state = apply_transition (primitives, state, INPUT_AE, primitives.size ()); - // TODO: if (state == STATE_ERROR) { ... } -} + /* + ugh. why not simply check for pitch? + */ + if (!nr->in_event_class ("note-event")) + { + nr->origin ()->warning + (_ ("cannot determine pitch of ligature primitive -> skipping")); + at_beginning = true; + continue; + } -void set_delta_pitch (Item *primitive, Grob_info info1, Grob_info info2) -{ - Pitch pitch1 = *unsmob_pitch (info1.music_cause ()->get_mus_property ("pitch")); - Pitch pitch2 = *unsmob_pitch (info2.music_cause ()->get_mus_property ("pitch")); - int delta_pitch = (pitch2.steps () - pitch1.steps ()); - primitive->set_grob_property ("delta-pitch", gh_int2scm (delta_pitch)); + int pitch = unsmob_pitch (nr->get_property ("pitch"))->steps (); + int prim = 0; + + if (at_beginning) + { + if (i == s - 1) + { + // we can get here after invalid input + nr->origin ()->warning + (_ ("single note ligature - skipping")); + break; + } + prev_semibrevis = prev_brevis_shape = false; + prev_primitive = NULL; + } + else + { + if (pitch == prev_pitch) + { + nr->origin ()->warning + (_ ("prime interval within ligature -> skipping")); + at_beginning = true; + prim = MLP_NONE; + continue; + } + } + + if (duration_log < -3 // is this possible at all??? + || duration_log > 0) + { + nr->origin ()->warning + (_ ("mensural ligature: duration none of Mx, L, B, S -> skipping")); + prim = MLP_NONE; + at_beginning = true; + continue; + } + + bool general_case = true; + bool make_flexa = false; + bool allow_flexa = true; + + // first check special cases + // 1. beginning + if (at_beginning) + { + // a. semibreves + if (duration_log == 0) + { + prim = MLP_UP | MLP_BREVIS; + general_case = false; + } + // b. descendens longa or brevis + else if (i < s - 1 + && (unsmob_pitch (primitives[i + 1].event_cause () + ->get_property ("pitch"))->steps () < pitch) + && duration_log > -3) + { + int left_stem = duration_log == -1 ? MLP_DOWN : 0; + prim = left_stem | MLP_BREVIS; + general_case = false; + } + } + // 2. initial semibrevis must be followed by another one + else if (prev_semibrevis) + { + prev_semibrevis = false; + if (duration_log == 0) + { + prim = MLP_BREVIS; + general_case = false; + } + else + { + nr->origin ()->warning + (_ ("semibrevis must be followed by another one -> skipping")); + prim = MLP_NONE; + at_beginning = true; + continue; + } + } + // 3. semibreves are otherwise not allowed + else if (duration_log == 0) + { + nr->origin ()->warning + (_ ("semibreves can only appear at the beginning of a ligature,\n" + "and there may be only zero or two of them")); + prim = MLP_NONE; + at_beginning = true; + continue; + } + // 4. end, descendens + else if (i == s - 1 && pitch < prev_pitch) + { + // brevis; previous note must be turned into flexa + if (duration_log == -1) + { + if (prev_brevis_shape) + { + make_flexa = true; + general_case = false; + } + else + { + nr->origin ()->warning + (_ ("invalid ligatura ending:\n" + "when the last note is a descending brevis,\n" + "the penultimate note must be another one,\n" + "or the ligatura must be LB or SSB")); + prim = MLP_NONE; + break; + } + } + // longa + else if (duration_log == -2) + { + prim = MLP_BREVIS; + general_case = allow_flexa = false; + } + // else maxima; fall through to regular case below + } + + if (allow_flexa + && to_boolean (primitive->get_property ("ligature-flexa"))) + { + /* + flexa requested, check whether allowed: + - there should be a previous note + - both of the notes must be of brevis shape + (i.e. can't be maxima or flexa; + longa is forbidden as well - it's nonexistent anyway) + - no compulsory flexa for the next note, + i.e. it's not an ultimate descending breve + */ + make_flexa = !at_beginning && prev_brevis_shape && duration_log > -2; + if (make_flexa && i == s - 2) + { + /* + check last condition: look ahead to next note + */ + Grob_info next_info = primitives[i + 1]; + Item *next_primitive = dynamic_cast (next_info.grob ()); + if (Rhythmic_head::duration_log (next_primitive) == -1) + { + /* + breve: check whether descending + */ + int const next_pitch = unsmob_pitch + (next_info.event_cause ()->get_property ("pitch"))->steps (); + if (next_pitch < pitch) + /* + sorry, forbidden + */ + make_flexa = false; + } + } + } + + if (general_case) + { + static int const shape[3] = {MLP_MAXIMA, MLP_LONGA, MLP_BREVIS}; + + prim = shape[duration_log + 3]; + } + + if (make_flexa) + { + /* + turn the note with the previous one into a flexa + */ + prev_primitive->set_property + ("primitive", + scm_from_int + (MLP_FLEXA_BEGIN + | (scm_to_int (prev_primitive->get_property ("primitive")) + & MLP_STEM))); + prev_primitive->set_property + ("flexa-interval", scm_from_int (pitch - prev_pitch)); + prim = MLP_FLEXA_END; + primitive->set_property + ("flexa-interval", scm_from_int (pitch - prev_pitch)); + } + + // join_primitives replacement + if (!(at_beginning || make_flexa)) + prev_primitive->set_property ("add-join", ly_bool2scm (true)); + + at_beginning = false; + prev_primitive = primitive; + prev_pitch = pitch; + primitive->set_property ("primitive", scm_from_int (prim)); + prev_brevis_shape = (prim & MLP_BREVIS) != 0; + prev_semibrevis = (prim & MLP_UP) != 0; + } } /* @@ -345,131 +332,160 @@ void set_delta_pitch (Item *primitive, Grob_info info1, Grob_info info2) * these properties via the concrete Ligature grob (like * MensuralLigature) and then copy these properties as necessary to * each of the NoteHead grobs. This is what - * propagate_properties() does. + * propagate_properties () does. */ void Mensural_ligature_engraver::propagate_properties (Spanner *ligature, - Array primitives) + vector primitives) { - SCM thickness_scm = ligature->get_grob_property ("thickness"); - Real thickness = (thickness_scm != SCM_EOL) ? - gh_scm2double (thickness_scm) : 1.4; - thickness *= ligature->get_paper ()->get_realvar (ly_symbol2scm ("linethickness")); - - Real head_width = - Font_interface::get_default_font (ligature)-> - find_by_name ("noteheads--1mensural").extent (X_AXIS).length (); - SCM flexa_width_scm = ligature->get_grob_property ("flexa-width"); - Real flexa_width = (flexa_width_scm != SCM_EOL) ? - gh_scm2double (flexa_width_scm) : 2.0; - flexa_width *= Staff_symbol_referencer::staff_space (ligature); - - Real half_flexa_width = 0.5 * (flexa_width + thickness); - - for (int i = 0; i < primitives.size (); i++) + Real thickness + = robust_scm2double (ligature->get_property ("thickness"), 1.4); + thickness + *= ligature->layout ()->get_dimension (ly_symbol2scm ("line-thickness")); + + Real head_width + = Font_interface::get_default_font (ligature)-> + find_by_name ("noteheads.sM1mensural").extent (X_AXIS).length (); + Real maxima_head_width + = Font_interface::get_default_font (ligature)-> + find_by_name ("noteheads.sM3ligmensural").extent (X_AXIS).length (); + + Item *prev_primitive = NULL; + for (vsize i = 0; i < primitives.size (); i++) { - Item *primitive = dynamic_cast (primitives[i].grob_); - int output = gh_scm2int (primitive->get_grob_property ("primitive")); - primitive->set_grob_property ("thickness", - gh_double2scm (thickness)); - switch (output) { - case MLP_NONE: - primitive->set_grob_property ("head-width", - gh_double2scm (half_flexa_width)); - break; - case MLP_sc: - case MLP_ss: - case MLP_cs: - primitive->set_grob_property ("head-width", - gh_double2scm (head_width)); - break; - case MLP_BB: - case MLP_LB: - case MLP_SS: - primitive->set_grob_property ("head-width", - gh_double2scm (half_flexa_width)); - primitive->set_grob_property ("flexa-width", - gh_double2scm (flexa_width)); - set_delta_pitch (primitive, - primitives[i], primitives[i+1]); - break; - default: - programming_error (_f ("unexpected case fall-through")); - break; + Item *primitive = dynamic_cast (primitives[i].grob ()); + int output = scm_to_int (primitive->get_property ("primitive")); + primitive->set_property ("thickness", + scm_from_double (thickness)); + + switch (output & MLP_ANY) { + case MLP_BREVIS: + case MLP_LONGA: + primitive->set_property ("head-width", scm_from_double (head_width)); + break; + case MLP_MAXIMA: + primitive->set_property ("head-width", + scm_from_double (maxima_head_width)); + break; + case MLP_FLEXA_BEGIN: + /* + the next note (should be MLP_FLEXA_END) will handle this one + */ + break; + case MLP_FLEXA_END: + { + SCM flexa_scm = primitive->get_property ("flexa-width"); + Real const flexa_width = robust_scm2double (flexa_scm, 2.0); + SCM head_width = scm_from_double (0.5 * (flexa_width + thickness)); + primitive->set_property ("head-width", head_width); + prev_primitive->set_property ("head-width", head_width); + prev_primitive->set_property ("flexa-width", flexa_scm); + } + break; + default: + programming_error (_ ("unexpected case fall-through")); + break; } + + prev_primitive = primitive; } } void -Mensural_ligature_engraver::fold_up_primitives (Array primitives) +Mensural_ligature_engraver::fold_up_primitives (vector primitives) { Item *first = 0; - Real distance = 0; - for (int i = 0; i < primitives.size (); i++) + Real distance = 0.0; + Real staff_space = 0.0; + Real thickness = 0.0; + + for (vsize i = 0; i < primitives.size (); i++) { - Item *current = dynamic_cast (primitives[i].grob_); + Item *current = dynamic_cast (primitives[i].grob ()); if (i == 0) { first = current; + staff_space = Staff_symbol_referencer::staff_space (first); + thickness = scm_to_double (current->get_property ("thickness")); } - get_set_column (current, first->get_column ()); + move_related_items_to_column (current, first->get_column (), + distance); - if (i > 0) - { -#if 0 - Rod r; - r.distance_ = distance; - r.item_l_drul_[LEFT] = first; - r.item_l_drul_[RIGHT] = current; - r.add_to_cols (); -#endif - current->translate_axis (distance, X_AXIS); - } - - distance += - gh_scm2double (current->get_grob_property ("head-width")) - - gh_scm2double (current->get_grob_property ("thickness")); - } -} + Real head_width = scm_to_double (current->get_property ("head-width")); + distance += head_width - thickness; -void -Mensural_ligature_engraver::join_primitives (Array primitives) -{ - Pitch last_pitch; - for (int i = 0; i < primitives.size (); i++) - { - Grob_info info = primitives[i]; - Pitch pitch = *unsmob_pitch (info.music_cause ()->get_mus_property ("pitch")); - if (i > 0) - { - Item *primitive = dynamic_cast (info.grob_); - int output = gh_scm2int (primitive->get_grob_property ("primitive")); - if (output & MLP_ANY) + if (Rhythmic_head::dot_count (current) > 0) + /* + Move dots above/behind the ligature. + dots should also avoid staff lines. + */ + { + Grob *dot_gr = Rhythmic_head::get_dots (current); + + bool const on_line = Staff_symbol_referencer::on_line + (current, + robust_scm2int (current->get_property ("staff-position"), 0)); + Real vert_shift = on_line ? staff_space * 0.5 : 0.0; + bool const flexa_begin = + scm_to_int (current->get_property ("primitive")) + & MLP_FLEXA_BEGIN; + + if (i + 1 < primitives.size ()) + /* + dot in the midst => avoid next note; + what to avoid and where depends on + being on a line or between lines + */ { - int delta_pitch = (pitch.steps () - last_pitch.steps ()); - primitive->set_grob_property ("join-left-amount", - gh_int2scm (delta_pitch)); + int const delta = + scm_to_int (current->get_property ("delta-position")); + if (flexa_begin) + vert_shift += delta < 0 + ? staff_space : (on_line ? -2.0 : -1.0) * staff_space; + else if (on_line) + { + if (0 < delta && delta < 3) + vert_shift -= staff_space; + } + else if (delta == 1 || delta == -1) + vert_shift -= delta * staff_space; } + + dot_gr->translate_axis (vert_shift, Y_AXIS); + + /* + move all dots behind head + */ + dot_gr->translate_axis + ((flexa_begin ? staff_space * 0.6 : head_width) - 2.0*thickness, X_AXIS); } - last_pitch = pitch; } } void Mensural_ligature_engraver::build_ligature (Spanner *ligature, - Array primitives) + vector primitives) { transform_heads (primitives); propagate_properties (ligature, primitives); fold_up_primitives (primitives); - join_primitives (primitives); } -ENTER_DESCRIPTION (Mensural_ligature_engraver, -/* descr */ "Handles Mensural_ligature_events by glueing special ligature heads together.", -/* creats*/ "MensuralLigature", -/* accepts */ "ligature-event abort-event", -/* acks */ "note-head-interface rest-interface", -/* reads */ "", -/* write */ ""); +ADD_ACKNOWLEDGER (Mensural_ligature_engraver, rest); +ADD_ACKNOWLEDGER (Mensural_ligature_engraver, ligature_head); + +ADD_TRANSLATOR (Mensural_ligature_engraver, + /* doc */ + "Handle @code{Mensural_ligature_events} by glueing special" + " ligature heads together.", + + /* create */ + "MensuralLigature ", + + /* read */ + "", + + /* write */ + "" + );