2 ligature-engraver.cc -- implement Ligature_engraver
4 source file of the GNU LilyPond music typesetter
6 (c) 2002--2005 Juergen Reuter <reuter@ipd.uka.de>
9 #include "ligature-engraver.hh"
12 #include "score-engraver.hh"
13 #include "note-head.hh"
18 #include "translator.icc"
21 * This abstract class provides the general framework for ligatures of
22 * any kind. It cares for handling start/stop ligatures events and
23 * collecting all noteheads inbetween, but delegates creation of a
24 * ligature spanner for each start/stop pair and typesetting of the
25 * ligature spanner to a concrete subclass.
27 * A concrete ligature engraver must subclass this class and provide
28 * functions create_ligature_spanner () and typeset_ligature
29 * (Spanner *, Array<Grob_info>). Subclasses of this class basically
30 * fall into two categories.
32 * The first category consists of engravers that engrave ligatures in
33 * a way that really deserves the name ligature. That is, they
34 * produce a single connected graphical object of fixed width,
35 * consisting of noteheads and other primitives. Space may be
36 * inserted only after each ligature, if necessary, but in no case
37 * between the primitives of the ligature. Accidentals have to be put
38 * to the left of the ligature, and not to the left of individual
39 * noteheads. Class Coherent_ligature_engraver is the common
40 * superclass for all of these engravers.
42 * The second category is for engravers that are relaxed in the sense
43 * that they do not require to produce a single connected graphical
44 * object. For example, in contemporary editions, ligatures are often
45 * marked, but otherwise use contemporary notation and spacing. In
46 * this category, there is currently only a single class,
47 * Ligature_bracket_engraver, which marks each ligature with a
48 * horizontal sqare bracket, but otherwise leaves the appearance
53 * TODO: lyrics/melisma/syllables: there should be at most one
54 * syllable of lyrics per ligature (i.e. for the lyrics context, a
55 * ligature should count as a single note, regardless of how many
56 * heads the ligature consists of).
58 * TODO: currently, you have to add/remove the proper
59 * Ligature_engraver (Ligature_bracket_engraver,
60 * Mensural_ligature_engraver) to the proper translator
61 * (e.g. VoiceContext) to choose between various representations.
62 * Since adding/removing an engraver to a translator is a global
63 * action in the layout block, you cannot mix various representations
64 * _within_ the same score. Hence, for selecting a representation,
65 * one would rather like to have a property that can be set e.g. for
66 * several staves individually. However, it seems that this approach
67 * would require to have a single, complicated Ligature_engraver that
68 * consists of all the code... This needs further thoughts.
70 Ligature_engraver::Ligature_engraver ()
73 finished_ligature_ = 0;
74 events_drul_[LEFT] = events_drul_[RIGHT] = 0;
75 prev_start_event_ = 0;
77 brew_ligature_primitive_proc = SCM_EOL;
81 Ligature_engraver::try_music (Music *m)
83 if (m->is_mus_type ("ligature-event"))
85 Direction d = to_dir (m->get_property ("span-direction"));
93 * This method should do something that comes close to the following
96 * \property Voice.NoteHead \override #'print-function =
97 * < value of #'ligature-primitive-callback of Voice.NoteHead >
99 * TODO: What we are doing here on the c++ level, should actually be
100 * performed on the SCM level. However, I do not know how to teach
101 * lilypond to apply an \override and \revert on #'print-function,
102 * whenever lily encounters a \[ and \] in an .ly file, respectively.
103 * Also encounter, that lily should not crash if a user erronously
107 Ligature_engraver::override_stencil_callback ()
111 This has been broken with the introduction of generic callbacks.
113 SCM target_callback = ly_symbol2scm ("print-function");
114 SCM source_callback = ly_symbol2scm ("ligature-primitive-callback");
115 SCM noteHeadProperties = updated_grob_properties (context (), ly_symbol2scm ("NoteHead"));
116 SCM value = scm_cdr (scm_sloppy_assq (source_callback, noteHeadProperties));
117 execute_pushpop_property (context (), ly_symbol2scm ("NoteHead"),
118 target_callback, value);
124 * This method should do something that comes close to the following
127 * \property Voice.NoteHead \revert #'print-function
129 * TODO: What we are doing here on the c++ level, should actually be
130 * performed on the SCM level. However, I do not know how to teach
131 * lilypond to apply an \override and \revert on #'print-function,
132 * whenever lily encounters a \[ and \] in an .ly file, respectively.
133 * Also encounter, that lily should not crash if a user erronously
137 Ligature_engraver::revert_stencil_callback ()
139 SCM symbol = ly_symbol2scm ("NoteHead");
140 SCM key = ly_symbol2scm ("print-function");
141 execute_pushpop_property (context (), symbol, key, SCM_UNDEFINED);
145 Ligature_engraver::process_music ()
147 if (events_drul_[STOP])
151 events_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
156 events_drul_[STOP]->origin ()->warning (_ ("no right bound"));
158 ligature_->set_bound (RIGHT, last_bound_);
160 prev_start_event_ = 0;
161 finished_primitives_ = primitives_;
162 finished_ligature_ = ligature_;
163 primitives_.clear ();
165 revert_stencil_callback ();
167 last_bound_ = unsmob_grob (get_property ("currentMusicalColumn"));
171 // TODO: maybe forbid breaks only if not transcribing
172 get_score_engraver ()->forbid_breaks ();
175 if (events_drul_[START])
179 events_drul_[START]->origin ()->warning (_ ("already have a ligature"));
183 prev_start_event_ = events_drul_[START];
184 ligature_ = create_ligature_spanner ();
185 brew_ligature_primitive_proc
186 = ligature_->get_property ("ligature-primitive-callback");
187 if (brew_ligature_primitive_proc == SCM_EOL)
188 programming_error ("Ligature_engraver: ligature-primitive-callback undefined");
190 Grob *bound = unsmob_grob (get_property ("currentMusicalColumn"));
192 events_drul_[START]->origin ()->warning (_ ("no left bound"));
194 ligature_->set_bound (LEFT, bound);
196 ligature_start_mom_ = now_mom ();
198 // TODO: dump cause into make_item/spanner.
199 // announce_grob (ligature_, events_drul_[START]->self_scm ());
200 override_stencil_callback ();
205 Ligature_engraver::stop_translation_timestep ()
207 if (finished_ligature_)
209 if (!finished_primitives_.size ())
211 finished_ligature_->programming_error ("Ligature_engraver::stop_translation_timestep (): "
212 "junking empty ligature");
216 typeset_ligature (finished_ligature_, finished_primitives_);
217 finished_primitives_.clear ();
219 finished_ligature_ = 0;
222 events_drul_[START] = 0;
223 events_drul_[STOP] = 0;
227 Ligature_engraver::finalize ()
229 if (finished_ligature_)
231 typeset_ligature (finished_ligature_, finished_primitives_);
232 finished_primitives_.clear ();
233 finished_ligature_ = 0;
237 prev_start_event_->origin ()->warning (_ ("unterminated ligature"));
238 ligature_->suicide ();
243 Ligature_engraver::current_ligature ()
249 Ligature_engraver::acknowledge_note_head (Grob_info info)
253 primitives_.push (info);
256 info.grob ()->set_callback (ly_symbol2scm ("stencil"),
257 brew_ligature_primitive_proc);
263 Ligature_engraver::acknowledge_rest (Grob_info info)
267 info.music_cause ()->origin ()->warning (_ ("ignoring rest: ligature may not contain rest"));
268 prev_start_event_->origin ()->warning (_ ("ligature was started here"));
269 // TODO: maybe better should stop ligature here rather than
270 // ignoring the rest?
274 // no ADD_ACKNOWLEDGER / ADD_ACKNOWLEDGER / ADD_TRANSLATOR macro calls
275 // since this class is abstract