2 mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver
4 source file of the GNU LilyPond music typesetter
6 (c) 2002--2004 Juergen Reuter <reuter@ipd.uka.de>
9 #include "coherent-ligature-engraver.hh"
11 #include "mensural-ligature.hh"
15 #include "paper-column.hh"
16 #include "note-column.hh"
17 #include "rhythmic-head.hh"
18 #include "note-head.hh"
19 #include "staff-symbol-referencer.hh"
20 #include "output-def.hh"
21 #include "font-interface.hh"
24 * TODO: My resources on Franco of Cologne's rules claim that his
25 * rules map ligature<->mensural timing in a non-ambigous way, but in
26 * fact, as presented in these resources, the rules become ambigous as
27 * soon as there appear durations other than breves within a ligature
28 * with more than two heads (ligatura ternaria etc.). Hence, the
29 * below implementation is an approximation of what I think the rules
30 * could look like if forced to be non-ambigous. This should be
31 * further investigated.
33 * TODO: The automat is quite complicated, and its design is error
34 * prone (and most probably, it behaves wrong for some very special
35 * cases). Maybe we can find a better paradigm for modelling Franco
38 * TODO: dotted heads: when applying Franco of Cologne's mapping, put
39 * dots *above* (rather than after) affected ligature heads.
41 * TODO: prohibit multiple voices within a ligature.
43 * TODO: enhance robustness: in case of an illegal ligature (e.g. the
44 * user events for a ligature that contains a minima or STATE_ERROR
45 * is reached), automatically break the ligature into smaller, valid
48 class Mensural_ligature_engraver : public Coherent_ligature_engraver
52 virtual Spanner *create_ligature_spanner ();
53 virtual void build_ligature (Spanner *ligature, Array<Grob_info> primitives);
56 TRANSLATOR_DECLARATIONS (Mensural_ligature_engraver);
59 int apply_transition (Array<Grob_info> primitives,
60 int state, int input, int i);
61 void transform_heads (Array<Grob_info> primitives);
62 void propagate_properties (Spanner *ligature, Array<Grob_info> primitives);
63 void fold_up_primitives (Array<Grob_info> primitives);
64 void join_primitives (Array<Grob_info> primitives);
68 Mensural_ligature_engraver::Mensural_ligature_engraver ()
73 Mensural_ligature_engraver::create_ligature_spanner ()
75 return make_spanner ("MensuralLigature", SCM_EOL);
79 * The following lines implement a finite state automat. Given a
80 * sequence of durations (Longa, Brevis, Semibrevis) or
81 * end-of-ligature-event as input, the automat outputs a sequence of
82 * events for grobs that form a proper ligature.
86 * This enumeration represents the set of possible input values to the
87 * automat. There may (potentially) be any sequence of Longa, Brevis,
88 * and Semibrevis duration symbols fed into the automat, with a final
89 * EndOfLigature symbol to terminate the ligature. Other durations
90 * are explicitly prohibited. Depending on the note's pitch of the
91 * preceding and the current input, the melodic line may be ascending
92 * or descending. Per definition, the melodic line must either ascend
93 * or descend, because if the pitches were twice the same, the two
94 * notes would be merged into a single one (as long as not resulting
95 * in a prohibited duration). In the case of the EndOfLigature
96 * symbol, the melodic line is undefined (but we still have ascending
97 * and descending case for the sake of consistency, making the automat
102 // Ascending/Descending Longa/Brevis/Semibrevis/EndOfLigature
114 * This enumeration represents all possible internal states of the
115 * automat. Besides the generic states START, ERROR, and END, the
116 * remaining states L, B, S, and SS describe pending values from the
117 * sequence of input values that have not yet been transformed to
118 * proper output values, including the melodic direction
119 * (ascending/descending) for state L.
123 // aL = ascending Longa, dL descending Longa, B = Brevis, S =
124 // Semibrevis, SS = 2 Semibreves
136 * The following array represents the transitions of the automat:
137 * given some state and input, it maps to a new state, according (with
138 * the limitations as described above) to the rules of Franco of
141 const int/*new state*/ transition_state[/*old state*/][8/*input*/] =
143 {STATE_aL, STATE_dL, STATE_B, STATE_B,
144 STATE_S, STATE_S, STATE_ERROR, STATE_ERROR}, // was: STATE_START
145 {STATE_aL, STATE_dL, STATE_B, STATE_START,
146 STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_aL
147 {STATE_aL, STATE_dL, STATE_B, STATE_START,
148 STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_dL
149 {STATE_aL, STATE_dL, STATE_B, STATE_START,
150 STATE_ERROR, STATE_ERROR, STATE_END, STATE_END}, // was: STATE_B
151 {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
152 STATE_SS, STATE_SS, STATE_ERROR, STATE_ERROR}, // was: STATE_S
153 {STATE_aL, STATE_dL, STATE_B, STATE_B,
154 STATE_S, STATE_S, STATE_END, STATE_END}, // was: STATE_SS
155 {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
156 STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_ERROR
157 {STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR,
158 STATE_ERROR, STATE_ERROR, STATE_ERROR, STATE_ERROR}, // was: STATE_END
162 * The following array represents the output of the automat while
163 * switching from one state to another: given some state and input, it
164 * maps to the output produced when switching to the next state,
165 * according (with the limitations as described above) to the rules of
168 const int/*output*/ transition_output[/*old state*/][8/*input*/] =
170 {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE,
171 MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_START
172 {MLP_sc, MLP_ss, MLP_sc, MLP_LB,
173 MLP_NONE, MLP_NONE, MLP_sc, MLP_sc}, // was: STATE_aL
174 {MLP_sc, MLP_ss, MLP_sc, MLP_LB,
175 MLP_NONE, MLP_NONE, MLP_ss, MLP_ss}, // was: STATE_dL
176 {MLP_ss, MLP_cs, MLP_ss, MLP_BB,
177 MLP_NONE, MLP_NONE, MLP_ss, MLP_ss} , // was: STATE_B
178 {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE,
179 MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_S
180 {MLP_SS, MLP_SS, MLP_SS, MLP_SS,
181 MLP_SS, MLP_SS, MLP_SS, MLP_SS} , // was: STATE_SS
182 {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE,
183 MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_ERROR
184 {MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE,
185 MLP_NONE, MLP_NONE, MLP_NONE, MLP_NONE}, // was: STATE_END
189 Mensural_ligature_engraver::apply_transition (Array<Grob_info> primitives,
190 int state, int input, int i)
192 int output = transition_output[state][input];
193 Item *last_last_primitive = (i > 1) ?
194 dynamic_cast<Item*> (primitives[i-2].grob_) : 0;
195 Item *last_primitive = (i > 0) ?
196 dynamic_cast<Item*> (primitives[i-1].grob_) : 0;
197 Item *primitive = (i < primitives.size ()) ?
198 dynamic_cast<Item*> (primitives[i].grob_) : 0;
202 // skip note head, expecting a primitive with two note heads
207 // primitive with single note head
210 programming_error ("last_primitive undefined");
213 last_primitive->set_property ("primitive", scm_int2num (output));
217 // primitive with two note heads
220 programming_error ("last_primitive undefined");
225 programming_error ("primitive undefined");
228 last_primitive->set_property ("primitive", scm_int2num (output));
229 primitive->set_property ("primitive", scm_int2num (MLP_NONE));
232 // delayed primitive with two note heads
233 if (!last_last_primitive)
235 programming_error ("last_last_primitive undefined");
240 programming_error ("last_primitive undefined");
243 last_last_primitive->set_property ("primitive", scm_int2num (output));
244 last_primitive->set_property ("primitive", scm_int2num (MLP_NONE));
247 programming_error (_f ("unexpected case fall-through"));
250 return transition_state[state][input];
254 Mensural_ligature_engraver::transform_heads (Array<Grob_info> primitives)
256 if (primitives.size () < 2)
258 warning (_f ("ligature with less than 2 heads -> skipping"));
261 int state = STATE_START;
262 Pitch last_pitch, pitch;
263 bool have_last_pitch = 0, have_pitch = 0;
264 for (int i = 0; i < primitives.size (); i++) {
266 have_last_pitch = have_pitch;
267 Grob_info info = primitives[i];
269 Note_head::get_balltype (dynamic_cast<Item*> (info.grob_));
271 Music *nr = info.music_cause ();
274 ugh. why not simply check for pitch?
276 if (!nr->is_mus_type ("note-event"))
278 info.music_cause ()->origin ()->warning (_f ("can not determine pitch of ligature primitive -> skipping"));
286 pitch = *unsmob_pitch (nr->get_property ("pitch"));
292 if (!have_last_pitch)
294 delta_pitch = 0; // first pitch; delta undefined
298 delta_pitch = (pitch.steps () - last_pitch.steps ());
299 if (Pitch::compare (last_pitch, pitch) == 0)
301 info.music_cause ()->origin ()->warning (_f ("prime interval within ligature -> skipping"));
309 if ((duration_log < -2) || (duration_log > 0))
311 info.music_cause ()->origin ()->warning (_f ("mensural ligature: duration none of L, B, S -> skipping"));
318 int input = (duration_log + 2) * 2 + ((delta_pitch < 0) ? 1 : 0);
319 state = apply_transition (primitives, state, input, i);
320 // TODO: if (state == STATE_ERROR) { ... }
323 state = apply_transition (primitives, state, INPUT_AE, primitives.size ());
324 // TODO: if (state == STATE_ERROR) { ... }
328 * A MensuralLigature grob consists of a bunch of NoteHead grobs that
329 * are glued together. It (a) does not make sense to change
330 * properties like thickness or flexa-width from one head to the next
331 * within a ligature (this would totally screw up alignment), and (b)
332 * some of these properties (like flexa-width) are specific to
333 * e.g. the MensuralLigature (as in contrast to e.g. LigatureBracket),
334 * and therefore should not be handled in the NoteHead code (which is
335 * also used by LigatureBracket). Therefore, we let the user control
336 * these properties via the concrete Ligature grob (like
337 * MensuralLigature) and then copy these properties as necessary to
338 * each of the NoteHead grobs. This is what
339 * propagate_properties () does.
342 Mensural_ligature_engraver::propagate_properties (Spanner *ligature,
343 Array<Grob_info> primitives)
345 Real thickness = robust_scm2double (ligature->get_property ("thickness"), 1.4);
346 thickness *= ligature->get_layout ()->get_dimension (ly_symbol2scm ("linethickness"));
349 Font_interface::get_default_font (ligature)->
350 find_by_name ("noteheads--1mensural").extent (X_AXIS).length ();
351 Real flexa_width = robust_scm2double (ligature->get_property ("flexa-width"), 2);
352 flexa_width *= Staff_symbol_referencer::staff_space (ligature);
354 Real half_flexa_width = 0.5 * (flexa_width + thickness);
356 for (int i = 0; i < primitives.size (); i++)
358 Item *primitive = dynamic_cast<Item*> (primitives[i].grob_);
359 int output = scm_to_int (primitive->get_property ("primitive"));
360 primitive->set_property ("thickness",
361 scm_make_real (thickness));
364 primitive->set_property ("head-width",
365 scm_make_real (half_flexa_width));
370 primitive->set_property ("head-width",
371 scm_make_real (head_width));
376 primitive->set_property ("head-width",
377 scm_make_real (half_flexa_width));
378 primitive->set_property ("flexa-width",
379 scm_make_real (flexa_width));
382 programming_error (_f ("unexpected case fall-through"));
389 Mensural_ligature_engraver::fold_up_primitives (Array<Grob_info> primitives)
393 for (int i = 0; i < primitives.size (); i++)
395 Item *current = dynamic_cast<Item*> (primitives[i].grob_);
401 get_set_column (current, first->get_column ());
405 current->translate_axis (distance, X_AXIS);
409 scm_to_double (current->get_property ("head-width")) -
410 scm_to_double (current->get_property ("thickness"));
415 Mensural_ligature_engraver::join_primitives (Array<Grob_info> primitives)
418 for (int i = 0; i < primitives.size (); i++)
420 Grob_info info = primitives[i];
421 Pitch pitch = *unsmob_pitch (info.music_cause ()->get_property ("pitch"));
424 Item *primitive = dynamic_cast<Item*> (info.grob_);
425 int output = scm_to_int (primitive->get_property ("primitive"));
426 if (output & MLP_ANY)
428 int delta_pitch = (pitch.steps () - last_pitch.steps ());
429 primitive->set_property ("join-left-amount",
430 scm_int2num (delta_pitch));
438 Mensural_ligature_engraver::build_ligature (Spanner *ligature,
439 Array<Grob_info> primitives)
441 transform_heads (primitives);
442 propagate_properties (ligature, primitives);
443 fold_up_primitives (primitives);
444 join_primitives (primitives);
447 ENTER_DESCRIPTION (Mensural_ligature_engraver,
448 /* descr */ "Handles Mensural_ligature_events by glueing special ligature heads together.",
449 /* creats*/ "MensuralLigature",
450 /* accepts */ "ligature-event",
451 /* acks */ "note-head-interface rest-interface",