2 mensural-ligature-engraver.cc -- implement Mensural_ligature_engraver
4 source file of the GNU LilyPond music typesetter
6 (C) 2002 Juergen Reuter <reuter@ipd.uka.de>
9 #include "mensural-ligature.hh"
10 #include "ligature-engraver.hh"
11 #include "musical-request.hh"
16 #include "paper-column.hh"
17 #include "note-column.hh"
18 #include "rhythmic-head.hh"
19 #include "note-head.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "paper-def.hh"
22 #include "font-interface.hh"
25 * TODO: local accidentals: collect accidentals that occur within a
26 * ligature and put them before the ligature. If an accidental
27 * changes within a ligature, print a warning (user error) and ignore
28 * any further accidental for that pitch within that ligature
29 * (actually, in such a case, the user should split the ligature into
30 * two separate ligatures). Similarly, any object that, in ordinary
31 * notation, may be put to the left or to the right of a
32 * note-head/ligature-head, should be collected and put before or
35 * TODO: make spacing more robust: do not screw up spacing if user
36 * erroneously puts rest in ligature.
38 * TODO: My resources on Franco of Cologne's rules claim that his
39 * rules map ligature<->mensural timing in a non-ambigous way, but in
40 * fact, as presented in these resources, the rules become ambigous as
41 * soon as there appear durations other than breves within a ligature
42 * with more than two heads (ligatura ternaria etc.). Hence, the
43 * below implementation is an approximation of what I think the rules
44 * could look like if forced to be non-ambigous. This should be
45 * further investigated.
47 * TODO: The automat is quite complicated, and its design is error
48 * prone (and most probably, it behaves wrong for some very special
49 * cases). Maybe we can find a better paradigm for modelling Franco
52 * TODO: dotted heads: when applying Franco of Cologne's mapping, put
53 * dots *above* (rather than after) affected ligature heads.
55 * TODO: prohibit multiple voices within a ligature.
57 * TODO: for each ligature, add Rod that represents the total length
58 * of the ligature (to preemptively avoid collision with adjacent
59 * notes); or maybe just additionally create a mensural-ligature grob
60 * (via Mensural_ligature::brew_molecule(SCM)) that just consists of a
61 * bounding box around all primitives of the ligature.
63 * TODO: enhance robustness: in case of an illegal ligature (e.g. the
64 * user requests for a ligature that contains a minima or STATE_ERROR
65 * is reached), automatically break the ligature into smaller, valid
68 * TODO: In the future, there will be further ligature engravers
69 * implemented, such as a Vaticana_ligature_engraver. There will be
70 * redundant code between these engravers and the
71 * Mensural_ligature_engraver. In particular these are functions
72 * set_column_, fold_up_primitives, join_primitives, and
73 * ackowledge_grob; further the code for handling accidentals. It is
74 * not appropriate to put these things into Ligature_engraver, since,
75 * for example, Ligature_bracket_engraver does not share any of this
76 * code. Hence, we might to introduce a further subclass of
77 * Ligature_engraver which serves as super class for
78 * Mensural_ligature_engraver, Vaticana_ligature_engraver, among
81 class Mensural_ligature_engraver
: public Ligature_engraver
84 Array
<Grob_info
> primitives_
;
87 virtual void acknowledge_grob (Grob_info
);
88 virtual void try_stop_ligature ();
89 virtual Spanner
*create_ligature_spanner ();
92 TRANSLATOR_DECLARATIONS(Mensural_ligature_engraver
);
95 int apply_transition (int state
, int input
, int i
);
96 void transform_heads ();
97 void propagate_properties ();
98 void fold_up_primitives ();
99 void join_primitives ();
100 void get_set_column (Item
*item
, Paper_column
*new_col
);
104 Mensural_ligature_engraver::Mensural_ligature_engraver ()
110 Mensural_ligature_engraver::create_ligature_spanner ()
113 return new Spanner (get_property ("MensuralLigature"));
117 * TODO: move this function to class Item?
120 Mensural_ligature_engraver::get_set_column (Item
*item
, Paper_column
*column
)
122 Item
*parent
= dynamic_cast<Item
*> (item
->get_parent (X_AXIS
));
125 programming_error ("failed tweaking paper column in ligature");
129 String name
= parent
->name ();
130 if (!String::compare (name
, "PaperColumn"))
132 // Change column not only for targeted item (NoteColumn), but
133 // also for all associated grobs (NoteSpacing, SeparationItem).
134 Grob
*sl
= Staff_symbol_referencer::get_staff_symbol (item
);
135 for (SCM tail
= parent
->get_grob_property ("elements");
137 tail
= ly_cdr (tail
))
139 Item
*sibling
= unsmob_item (ly_car (tail
));
141 (Staff_symbol_referencer::get_staff_symbol (sibling
) == sl
))
143 sibling
->set_parent (column
, X_AXIS
);
149 get_set_column (parent
, column
);
154 * The following lines implement a finite state automat. Given a
155 * sequence of durations (Longa, Brevis, Semibrevis) or
156 * end-of-ligature-request as input, the automat outputs a sequence of
157 * requests for grobs that form a proper ligature.
161 * This enumeration represents the set of possible input values to the
162 * automat. There may (potentially) be any sequence of Longa, Brevis,
163 * and Semibrevis duration symbols fed into the automat, with a final
164 * EndOfLigature symbol to terminate the ligature. Other durations
165 * are explicitly prohibited. Depending on the note's pitch of the
166 * preceding and the current input, the melodic line may be ascending
167 * or descending. Per definition, the melodic line must either ascend
168 * or descend, because if the pitches were twice the same, the two
169 * notes would be merged into a single one (as long as not resulting
170 * in a prohibited duration). In the case of the EndOfLigature
171 * symbol, the melodic line is undefined (but we still have ascending
172 * and descending case for the sake of consistency, making the automat
177 // Ascending/Descending Longa/Brevis/Semibrevis/EndOfLigature
189 * This enumeration represents all possible internal states of the
190 * automat. Besides the generic states START, ERROR, and END, the
191 * remaining states L, B, S, and SS describe pending values from the
192 * sequence of input values that have not yet been transformed to
193 * proper output values, including the melodic direction
194 * (ascending/descending) for state L.
198 // aL = ascending Longa, dL descending Longa, B = Brevis, S =
199 // Semibrevis, SS = 2 Semibreves
211 * The following array represents the transitions of the automat:
212 * given some state and input, it maps to a new state, according (with
213 * the limitations as described above) to the rules of Franco of
216 const int/*new state*/ transition_state
[/*old state*/][8/*input*/] =
218 {STATE_aL
, STATE_dL
, STATE_B
, STATE_B
,
219 STATE_S
, STATE_S
, STATE_ERROR
, STATE_ERROR
}, // was: STATE_START
220 {STATE_aL
, STATE_dL
, STATE_B
, STATE_START
,
221 STATE_ERROR
, STATE_ERROR
, STATE_END
, STATE_END
}, // was: STATE_aL
222 {STATE_aL
, STATE_dL
, STATE_B
, STATE_START
,
223 STATE_ERROR
, STATE_ERROR
, STATE_END
, STATE_END
}, // was: STATE_dL
224 {STATE_aL
, STATE_dL
, STATE_B
, STATE_START
,
225 STATE_ERROR
, STATE_ERROR
, STATE_END
, STATE_END
}, // was: STATE_B
226 {STATE_ERROR
, STATE_ERROR
, STATE_ERROR
, STATE_ERROR
,
227 STATE_SS
, STATE_SS
, STATE_ERROR
, STATE_ERROR
}, // was: STATE_S
228 {STATE_aL
, STATE_dL
, STATE_B
, STATE_B
,
229 STATE_S
, STATE_S
, STATE_END
, STATE_END
}, // was: STATE_SS
230 {STATE_ERROR
, STATE_ERROR
, STATE_ERROR
, STATE_ERROR
,
231 STATE_ERROR
, STATE_ERROR
, STATE_ERROR
, STATE_ERROR
}, // was: STATE_ERROR
232 {STATE_ERROR
, STATE_ERROR
, STATE_ERROR
, STATE_ERROR
,
233 STATE_ERROR
, STATE_ERROR
, STATE_ERROR
, STATE_ERROR
}, // was: STATE_END
237 * The following array represents the output of the automat while
238 * switching from one state to another: given some state and input, it
239 * maps to the output produced when switching to the next state,
240 * according (with the limitations as described above) to the rules of
243 const int/*output*/ transition_output
[/*old state*/][8/*input*/] =
245 {MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
,
246 MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
}, // was: STATE_START
247 {MLP_sc
, MLP_ss
, MLP_sc
, MLP_LB
,
248 MLP_NONE
, MLP_NONE
, MLP_sc
, MLP_sc
}, // was: STATE_aL
249 {MLP_sc
, MLP_ss
, MLP_sc
, MLP_LB
,
250 MLP_NONE
, MLP_NONE
, MLP_ss
, MLP_ss
}, // was: STATE_dL
251 {MLP_ss
, MLP_cs
, MLP_ss
, MLP_BB
,
252 MLP_NONE
, MLP_NONE
, MLP_ss
, MLP_ss
} , // was: STATE_B
253 {MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
,
254 MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
}, // was: STATE_S
255 {MLP_SS
, MLP_SS
, MLP_SS
, MLP_SS
,
256 MLP_SS
, MLP_SS
, MLP_SS
, MLP_SS
} , // was: STATE_SS
257 {MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
,
258 MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
}, // was: STATE_ERROR
259 {MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
,
260 MLP_NONE
, MLP_NONE
, MLP_NONE
, MLP_NONE
}, // was: STATE_END
264 Mensural_ligature_engraver::apply_transition (int state
, int input
, int i
)
266 int output
= transition_output
[state
][input
];
267 Item
*last_last_primitive
= (i
> 1) ?
268 dynamic_cast<Item
*> (primitives_
[i
-2].grob_
) : 0;
269 Item
*last_primitive
= (i
> 0) ?
270 dynamic_cast<Item
*> (primitives_
[i
-1].grob_
) : 0;
271 Item
*primitive
= (i
< primitives_
.size ()) ?
272 dynamic_cast<Item
*> (primitives_
[i
].grob_
) : 0;
276 // skip note head, expecting a primitive with two note heads
281 // primitive with single note head
284 programming_error ("last_primitive undefined");
287 last_primitive
->set_grob_property ("primitive", gh_int2scm (output
));
291 // primitive with two note heads
294 programming_error ("last_primitive undefined");
299 programming_error ("primitive undefined");
302 last_primitive
->set_grob_property ("primitive", gh_int2scm (output
));
303 primitive
->set_grob_property ("primitive", gh_int2scm (MLP_NONE
));
306 // delayed primitive with two note heads
307 if (!last_last_primitive
)
309 programming_error ("last_last_primitive undefined");
314 programming_error ("last_primitive undefined");
317 last_last_primitive
->set_grob_property ("primitive", gh_int2scm (output
));
318 last_primitive
->set_grob_property ("primitive", gh_int2scm (MLP_NONE
));
321 programming_error (_f ("unexpected case fall-through"));
324 return transition_state
[state
][input
];
328 Mensural_ligature_engraver::transform_heads ()
330 if (primitives_
.size () < 2)
332 warning (_f ("ligature with less than 2 heads -> skipping"));
335 int state
= STATE_START
;
336 Pitch last_pitch
, pitch
;
337 bool have_last_pitch
= 0, have_pitch
= 0;
338 for (int i
= 0; i
< primitives_
.size (); i
++) {
340 have_last_pitch
= have_pitch
;
341 Grob_info info
= primitives_
[i
];
343 Note_head::get_balltype (dynamic_cast<Item
*> (info
.grob_
));
344 Note_req
*nr
= dynamic_cast<Note_req
*> (info
.music_cause ());
347 info
.music_cause ()->origin ()->warning (_f ("can not determine pitch of ligature primitive -> skipping"));
355 pitch
= *unsmob_pitch (nr
->get_mus_property ("pitch"));
361 if (!have_last_pitch
)
363 delta_pitch
= 0; // first pitch; delta undefined
367 delta_pitch
= (pitch
.steps () - last_pitch
.steps ());
368 if (Pitch::compare (last_pitch
, pitch
) == 0)
370 info
.music_cause ()->origin ()->warning (_f ("prime interval within ligature -> skipping"));
378 if ((duration_log
< -2) || (duration_log
> 0))
380 info
.music_cause ()->origin ()->warning (_f ("mensural ligature: duration none of L, B, S -> skipping"));
387 int input
= (duration_log
+ 2) * 2 + ((delta_pitch
< 0) ? 1 : 0);
388 state
= apply_transition (state
, input
, i
);
389 // TODO: if (state == STATE_ERROR) { ... }
392 state
= apply_transition (state
, INPUT_AE
, primitives_
.size ());
393 // TODO: if (state == STATE_ERROR) { ... }
396 void set_delta_pitch (Item
*primitive
, Grob_info info1
, Grob_info info2
)
398 Note_req
*nr1
= dynamic_cast<Note_req
*> (info1
.music_cause ());
399 Note_req
*nr2
= dynamic_cast<Note_req
*> (info2
.music_cause ());
400 Pitch pitch1
= *unsmob_pitch (nr1
->get_mus_property ("pitch"));
401 Pitch pitch2
= *unsmob_pitch (nr2
->get_mus_property ("pitch"));
402 int delta_pitch
= (pitch2
.steps () - pitch1
.steps ());
403 primitive
->set_grob_property ("delta-pitch", gh_int2scm (delta_pitch
));
407 * A MensuralLigature grob consists of a bunch of LigatureHead grobs
408 * that are glued together. It (a) does make sense to change
409 * properties like thickness or flexa-width from one head to the next
410 * within a ligature (this would totally screw up alignment), and (b)
411 * some of these properties (like flexa-width) are specific to
412 * e.g. the MensuralLigature (as in contrast to e.g. LigatureBracket),
413 * and therefore should not be handled in the generic LigatureHead
414 * (which is also used by LigatureBracket). Therefore, we let the
415 * user control these properties via the concrete Ligature grob (like
416 * MensuralLigature) and then copy these properties as necessary to
417 * each of the LigatureHead grobs. This is what
418 * propagate_properties() does.
421 Mensural_ligature_engraver::propagate_properties ()
424 finished_ligature_
->get_grob_property ("thickness");
425 Real thickness
= (thickness_scm
!= SCM_EOL
) ?
426 gh_scm2double (thickness_scm
) : 1.4;
427 thickness
*= finished_ligature_
->get_paper ()->get_var ("linethickness");
430 Font_interface::get_default_font (finished_ligature_
)->
431 find_by_name ("noteheads--1mensural").extent (X_AXIS
).length ();
432 SCM flexa_width_scm
=
433 finished_ligature_
->get_grob_property ("flexa-width");
434 Real flexa_width
= (flexa_width_scm
!= SCM_EOL
) ?
435 gh_scm2double (flexa_width_scm
) : 2.0;
436 flexa_width
*= Staff_symbol_referencer::staff_space (finished_ligature_
);
438 Real half_flexa_width
= 0.5 * (flexa_width
+ thickness
);
440 for (int i
= 0; i
< primitives_
.size (); i
++)
442 Item
*primitive
= dynamic_cast<Item
*> (primitives_
[i
].grob_
);
443 int output
= gh_scm2int (primitive
->get_grob_property ("primitive"));
444 primitive
->set_grob_property ("thickness",
445 gh_double2scm (thickness
));
448 primitive
->set_grob_property ("head-width",
449 gh_double2scm (half_flexa_width
));
454 primitive
->set_grob_property ("head-width",
455 gh_double2scm (head_width
));
460 primitive
->set_grob_property ("head-width",
461 gh_double2scm (half_flexa_width
));
462 primitive
->set_grob_property ("flexa-width",
463 gh_double2scm (flexa_width
));
464 set_delta_pitch (primitive
,
465 primitives_
[i
], primitives_
[i
+1]);
468 programming_error (_f ("unexpected case fall-through"));
475 Mensural_ligature_engraver::fold_up_primitives ()
478 for (int i
= 0; i
< primitives_
.size (); i
++)
480 Item
*current
= dynamic_cast<Item
*> (primitives_
[i
].grob_
);
486 get_set_column (current
, first
->get_column ());
492 r
.distance_
= distance_
;
493 r
.item_l_drul_
[LEFT
] = first
;
494 r
.item_l_drul_
[RIGHT
] = current
;
497 current
->translate_axis (distance_
, X_AXIS
);
501 gh_scm2double (current
->get_grob_property ("head-width")) -
502 gh_scm2double (current
->get_grob_property ("thickness"));
507 Mensural_ligature_engraver::join_primitives ()
510 for (int i
= 0; i
< primitives_
.size (); i
++)
512 Grob_info info
= primitives_
[i
];
513 Note_req
*nr
= dynamic_cast<Note_req
*> (info
.music_cause ());
514 Pitch pitch
= *unsmob_pitch (nr
->get_mus_property ("pitch"));
517 Item
*primitive
= dynamic_cast<Item
*> (info
.grob_
);
518 int output
= gh_scm2int (primitive
->get_grob_property ("primitive"));
519 if (output
& MLP_ANY
)
521 int delta_pitch
= (pitch
.steps () - last_pitch
.steps ());
522 primitive
->set_grob_property ("join-left",
523 gh_int2scm (delta_pitch
));
531 Mensural_ligature_engraver::try_stop_ligature ()
533 if (finished_ligature_
)
536 propagate_properties ();
537 fold_up_primitives ();
540 for (int i
= 0; i
< primitives_
.size (); i
++)
542 typeset_grob (primitives_
[i
].grob_
);
545 primitives_
.clear ();
546 finished_ligature_
= 0;
551 Mensural_ligature_engraver::acknowledge_grob (Grob_info info
)
553 Ligature_engraver::acknowledge_grob (info
);
556 if (Note_head::has_interface (info
.grob_
))
558 primitives_
.push (info
);
563 ENTER_DESCRIPTION (Mensural_ligature_engraver
,
564 /* descr */ "Handles Mensural_ligature_requests by glueing special ligature heads together.",
565 /* creats*/ "MensuralLigature",
566 /* acks */ "ligature-head-interface note-head-interface rest-interface",