X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fmensural-ligature-engraver.cc;h=906ace106ca689f8f93082dae7b5418a541efc26;hb=5b4b0d6e9a197e8f9eb085b7c2ad78b8be3e5cfc;hp=233b60051c4edd4cca26260cd15b1d470ba331f8;hpb=e24df7c27635dc996c466295eacf2981bddccaf7;p=lilypond.git diff --git a/lily/mensural-ligature-engraver.cc b/lily/mensural-ligature-engraver.cc index 233b60051c..906ace106c 100644 --- a/lily/mensural-ligature-engraver.cc +++ b/lily/mensural-ligature-engraver.cc @@ -1,72 +1,74 @@ /* mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver - + source file of the GNU LilyPond music typesetter - - (c) 2002--2005 Juergen Reuter - */ -#include "coherent-ligature-engraver.hh" + (c) 2002--2008 Juergen Reuter , + Pal Benko +*/ +#include "coherent-ligature-engraver.hh" +#include "font-interface.hh" +#include "international.hh" #include "mensural-ligature.hh" -#include "event.hh" -#include "warn.hh" -#include "spanner.hh" -#include "paper-column.hh" #include "note-column.hh" -#include "rhythmic-head.hh" #include "note-head.hh" -#include "staff-symbol-referencer.hh" #include "output-def.hh" -#include "font-interface.hh" +#include "paper-column.hh" +#include "pitch.hh" +#include "rhythmic-head.hh" +#include "spanner.hh" +#include "staff-symbol-referencer.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); 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 * @@ -75,253 +77,215 @@ Mensural_ligature_engraver::create_ligature_spanner () return make_spanner ("MensuralLigature", SCM_EOL); } -/* - * 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 +void +Mensural_ligature_engraver::transform_heads (vector primitives) { - // 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, -}; + if (primitives.size () < 2) + { + warning (_ ("ligature with less than 2 heads -> skipping")); + return; + } + int prev_pitch = 0; + bool at_beginning = true; -/* - * 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 -}; + // needed so that we can check whether + // the previous note can be turned into a flexa + bool prev_brevis_shape = false; -/* - * 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 -}; + bool prev_semibrevis = false; + Item *prev_primitive = NULL; -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) + for (vsize i = 0, s = primitives.size (); i < s; i++) { - 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_property ("primitive", scm_int2num (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_property ("primitive", scm_int2num (output)); - primitive->set_property ("primitive", scm_int2num (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_property ("primitive", scm_int2num (output)); - last_primitive->set_property ("primitive", scm_int2num (MLP_NONE)); - break; - default: - programming_error (_f ("unexpected case fall-through")); - break; - } - return transition_state[state][input]; -} + Grob_info info = primitives[i]; + Item *primitive = dynamic_cast (info.grob ()); + int duration_log = Rhythmic_head::duration_log (primitive); -void -Mensural_ligature_engraver::transform_heads (Array primitives) -{ - if (primitives.size () < 2) - { - warning (_f ("ligature with less than 2 heads -> skipping")); - return; + Stream_event *nr = info.event_cause (); + + /* + 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; + } + + int pitch = unsmob_pitch (nr->get_property ("pitch"))->steps (); + int delta_pitch = 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 + { + delta_pitch = pitch - prev_pitch; + if (delta_pitch == 0) + { + nr->origin ()->warning + (_ ("prime interval within ligature -> skipping")); + at_beginning = true; + primitive->set_property ("primitive", + scm_from_int (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")); + primitive->set_property ("primitive", + scm_from_int (MLP_NONE)); + at_beginning = true; + continue; + } + + // apply_transition replacement begins + bool general_case = true; + + // first check special cases + // 1. beginning + if (at_beginning) + { + // a. semibreves + if (duration_log == 0) + { + primitive->set_property ("primitive", + scm_from_int (MLP_UP | MLP_BREVIS)); + prev_semibrevis = prev_brevis_shape = true; + 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; + + primitive->set_property ("primitive", + scm_from_int (left_stem | MLP_BREVIS)); + prev_brevis_shape = true; + prev_semibrevis = general_case = false; + } + } + // 2. initial semibrevis must be followed by another one + else if (prev_semibrevis) + { + prev_semibrevis = false; + if (duration_log == 0) + { + primitive->set_property ("primitive", scm_from_int (MLP_BREVIS)); + general_case = false; + } + else + { + nr->origin ()->warning + (_ ("semibrevis must be followed by another one -> skipping")); + primitive->set_property ("primitive", + scm_from_int (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")); + primitive->set_property ("primitive", + scm_from_int (MLP_NONE)); + at_beginning = true; + continue; + } + // 4. end, descendens + else if (i == s - 1 && delta_pitch < 0) + { + // brevis; previous note must be turned into flexa + if (duration_log == -1) + { + if (prev_brevis_shape) + { + prev_primitive->set_property + ("primitive", + scm_from_int + (MLP_FLEXA + | (scm_to_int (prev_primitive->get_property ("primitive")) + & MLP_DOWN))); + primitive->set_property ("primitive", scm_from_int (MLP_NONE)); + break; // no more notes, no join + } + 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")); + primitive->set_property ("primitive", scm_from_int (MLP_NONE)); + break; + } + } + // longa + else if (duration_log == -2) + { + primitive->set_property ("primitive", scm_from_int (MLP_BREVIS)); + general_case = false; + } + // else maxima; fall through regular case below + } + + if (general_case) + { + static int const shape[3] = {MLP_MAXIMA, MLP_LONGA, MLP_BREVIS}; + + primitive->set_property ("primitive", + scm_from_int (shape[duration_log + 3])); + prev_brevis_shape = duration_log == -1; + } + + // join_primitives replacement + if (!at_beginning) + { + /* + if the previous note is longa-shaped and this note is lower, + then the joining line may hide the stem, so it is made longer + to serve as stem as well + */ + if (delta_pitch < 0 + && (scm_to_int (prev_primitive->get_property ("primitive")) + & MLP_LONGA)) + { + delta_pitch -= 6; + // instead of number 6 + // the legth of the longa stem should be queried something like + // Font_interface::get_default_font (ligature)->find_by_name + // ("noteheads.sM2mensural").extent (Y_AXIS).length () + } + prev_primitive->set_property ("join-right-amount", + scm_from_int (delta_pitch)); + // perhaps set add-join as well + } + at_beginning = false; + prev_primitive = primitive; + prev_pitch = pitch; + // apply_transition replacement ends } - 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_property ("pitch")); - have_pitch = 1; - } - - int delta_pitch; - - 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; - } - } - - 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; - } - - int input = (duration_log + 2) * 2 + ((delta_pitch < 0) ? 1 : 0); - state = apply_transition (primitives, state, input, i); - // TODO: if (state == STATE_ERROR) { ... } - } - - state = apply_transition (primitives, state, INPUT_AE, primitives.size ()); - // TODO: if (state == STATE_ERROR) { ... } } /* @@ -340,114 +304,130 @@ Mensural_ligature_engraver::transform_heads (Array primitives) */ void Mensural_ligature_engraver::propagate_properties (Spanner *ligature, - Array primitives) + vector primitives) { - Real thickness = robust_scm2double (ligature->get_property ("thickness"), 1.4); - thickness *= ligature->get_layout ()->get_dimension (ly_symbol2scm ("linethickness")); + 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 flexa_width + = robust_scm2double (ligature->get_property ("flexa-width"), 2); + Real maxima_head_width + = Font_interface::get_default_font (ligature)-> + find_by_name ("noteheads.sM1neomensural").extent (X_AXIS).length (); - Real head_width = - Font_interface::get_default_font (ligature)-> - find_by_name ("noteheads.-1mensural").extent (X_AXIS).length (); - Real flexa_width = robust_scm2double (ligature->get_property ("flexa-width"), 2); 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++) + for (vsize i = 0; i < primitives.size (); i++) { - Item *primitive = dynamic_cast (primitives[i].grob_); + Item *primitive = dynamic_cast (primitives[i].grob ()); int output = scm_to_int (primitive->get_property ("primitive")); primitive->set_property ("thickness", - scm_make_real (thickness)); - switch (output) { + scm_from_double (thickness)); + + switch (output & MLP_ANY) + { case MLP_NONE: primitive->set_property ("head-width", - scm_make_real (half_flexa_width)); + scm_from_double (half_flexa_width)); + break; + case MLP_BREVIS: + case MLP_LONGA: + primitive->set_property ("head-width", + scm_from_double (head_width)); break; - case MLP_sc: - case MLP_ss: - case MLP_cs: + case MLP_MAXIMA: primitive->set_property ("head-width", - scm_make_real (head_width)); + scm_from_double (maxima_head_width)); break; - case MLP_BB: - case MLP_LB: - case MLP_SS: + case MLP_FLEXA: primitive->set_property ("head-width", - scm_make_real (half_flexa_width)); + scm_from_double (half_flexa_width)); primitive->set_property ("flexa-width", - scm_make_real (flexa_width)); + scm_from_double (flexa_width)); break; default: - programming_error (_f ("unexpected case fall-through")); + programming_error (_ ("unexpected case fall-through")); break; - } + } } } 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 dot_shift = 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; + dot_shift = 1.5 * Staff_symbol_referencer::staff_space (first); } - get_set_column (current, first->get_column ()); + move_related_items_to_column (current, first->get_column (), + distance); - if (i > 0) - { - current->translate_axis (distance, X_AXIS); - } - - distance += - scm_to_double (current->get_property ("head-width")) - - scm_to_double (current->get_property ("thickness")); - } -} + distance + += scm_to_double (current->get_property ("head-width")) + - scm_to_double (current->get_property ("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_property ("pitch")); - if (i > 0) - { - Item *primitive = dynamic_cast (info.grob_); - int output = scm_to_int (primitive->get_property ("primitive")); - if (output & MLP_ANY) + if (Rhythmic_head::dot_count (current) > 0) + // Move dots above/behind the ligature. + { + if (i + 1 < primitives.size ()) + // dot in the midst => move above head + { + // FIXME: Amount of vertical dot-shift should depend on + // pitch. + // + // FIXME: dot placement is horizontally slightly off. + Rhythmic_head::get_dots (current)->translate_axis (dot_shift, Y_AXIS); + } + else + // trailing dot => move behind head { - int delta_pitch = (pitch.steps () - last_pitch.steps ()); - primitive->set_property ("join-left-amount", - scm_int2num (delta_pitch)); + double head_width = + scm_to_double (current->get_property ("head-width")); + Rhythmic_head::get_dots (current)-> + translate_axis (head_width, 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); } +ADD_ACKNOWLEDGER (Mensural_ligature_engraver, rest); +ADD_ACKNOWLEDGER (Mensural_ligature_engraver, note_head); + ADD_TRANSLATOR (Mensural_ligature_engraver, -/* descr */ "Handles Mensural_ligature_events by glueing special ligature heads together.", -/* creats*/ "MensuralLigature", -/* accepts */ "ligature-event", -/* acks */ "note-head-interface rest-interface", -/* reads */ "", -/* write */ ""); + /* doc */ + "Handle @code{Mensural_ligature_events} by glueing special" + " ligature heads together.", + + /* create */ + "MensuralLigature ", + + /* read */ + "", + + /* write */ + "" + );