]> git.donarmstrong.com Git - lilypond.git/blob - lily/ligature-engraver.cc
* lily/ligature-engaver.cc, lily/coherent-ligature-engaver.cc,
[lilypond.git] / lily / ligature-engraver.cc
1 /*
2   ligature-engraver.cc -- implement Ligature_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2002--2005 Juergen Reuter <reuter@ipd.uka.de>
7 */
8
9 #include "ligature-engraver.hh"
10
11 #include "spanner.hh"
12 #include "score-engraver.hh"
13 #include "note-head.hh"
14 #include "rest.hh"
15 #include "warn.hh"
16 #include "context.hh"
17
18 #include "translator.icc"
19
20 /*
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.
26  *
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.
31  *
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.
41  *
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
49  * untouched.
50  */
51
52 /*
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).
57  *
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.
69  */
70 Ligature_engraver::Ligature_engraver ()
71 {
72   ligature_ = 0;
73   finished_ligature_ = 0;
74   events_drul_[LEFT] = events_drul_[RIGHT] = 0;
75   prev_start_event_ = 0;
76   last_bound_ = 0;
77   brew_ligature_primitive_proc = SCM_EOL;
78 }
79
80 bool
81 Ligature_engraver::try_music (Music *m)
82 {
83   if (m->is_mus_type ("ligature-event"))
84     {
85       Direction d = to_dir (m->get_property ("span-direction"));
86       events_drul_[d] = m;
87       return true;
88     }
89   return false;
90 }
91
92 /*
93  * This method should do something that comes close to the following
94  * .ly snippet:
95  *
96  * \property Voice.NoteHead \override #'print-function =
97  *     < value of #'ligature-primitive-callback of Voice.NoteHead >
98  *
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
104  * nests \[ and \].
105  */
106 void
107 Ligature_engraver::override_stencil_callback ()
108 {
109   SCM target_callback = ly_symbol2scm ("print-function");
110   SCM source_callback = ly_symbol2scm ("ligature-primitive-callback");
111   SCM noteHeadProperties = updated_grob_properties (context (), ly_symbol2scm ("NoteHead"));
112   SCM value = scm_cdr (scm_sloppy_assq (source_callback, noteHeadProperties));
113   execute_pushpop_property (context (), ly_symbol2scm ("NoteHead"),
114                             target_callback, value);
115 }
116
117 /*
118  * This method should do something that comes close to the following
119  * .ly snippet:
120  *
121  * \property Voice.NoteHead \revert #'print-function
122  *
123  * TODO: What we are doing here on the c++ level, should actually be
124  * performed on the SCM level.  However, I do not know how to teach
125  * lilypond to apply an \override and \revert on #'print-function,
126  * whenever lily encounters a \[ and \] in an .ly file, respectively.
127  * Also encounter, that lily should not crash if a user erronously
128  * nests \[ and \].
129  */
130 void
131 Ligature_engraver::revert_stencil_callback ()
132 {
133   SCM symbol = ly_symbol2scm ("NoteHead");
134   SCM key = ly_symbol2scm ("print-function");
135   execute_pushpop_property (context (), symbol, key, SCM_UNDEFINED);
136 }
137
138 void
139 Ligature_engraver::process_music ()
140 {
141   if (events_drul_[STOP])
142     {
143       if (!ligature_)
144         {
145           events_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
146           return;
147         }
148
149       if (!last_bound_)
150         {
151           events_drul_[STOP]->origin ()->warning (_ ("no right bound"));
152         }
153       else
154         {
155           ligature_->set_bound (RIGHT, last_bound_);
156         }
157
158       prev_start_event_ = 0;
159       finished_primitives_ = primitives_;
160       finished_ligature_ = ligature_;
161       primitives_.clear ();
162       ligature_ = 0;
163       revert_stencil_callback ();
164     }
165   last_bound_ = unsmob_grob (get_property ("currentMusicalColumn"));
166
167   if (ligature_)
168     {
169       // TODO: maybe forbid breaks only if not transcribing
170       get_score_engraver ()->forbid_breaks ();
171     }
172
173   if (events_drul_[START])
174     {
175       if (ligature_)
176         {
177           events_drul_[START]->origin ()->warning (_ ("already have a ligature"));
178           return;
179         }
180
181       prev_start_event_ = events_drul_[START];
182       ligature_ = create_ligature_spanner ();
183       brew_ligature_primitive_proc
184         = ligature_->get_property ("ligature-primitive-callback");
185       if (brew_ligature_primitive_proc == SCM_EOL)
186         {
187           programming_error ("Ligature_engraver: ligature-primitive-callback undefined");
188         }
189
190       Grob *bound = unsmob_grob (get_property ("currentMusicalColumn"));
191       if (!bound)
192         {
193           events_drul_[START]->origin ()->warning (_ ("no left bound"));
194         }
195       else
196         {
197           ligature_->set_bound (LEFT, bound);
198         }
199
200       ligature_start_mom_ = now_mom ();
201
202       // TODO: dump cause into make_item/spanner. 
203       // announce_grob (ligature_, events_drul_[START]->self_scm ());
204       override_stencil_callback ();
205     }
206 }
207
208 void
209 Ligature_engraver::stop_translation_timestep ()
210 {
211   if (finished_ligature_)
212     {
213       if (!finished_primitives_.size ())
214         {
215           finished_ligature_->programming_error ("Ligature_engraver::stop_translation_timestep (): "
216                                                  "junking empty ligature");
217         }
218       else
219         {
220           typeset_ligature (finished_ligature_, finished_primitives_);
221           finished_primitives_.clear ();
222         }
223       finished_ligature_ = 0;
224     }
225
226   events_drul_[START] = 0;
227   events_drul_[STOP] = 0;
228 }
229
230 void
231 Ligature_engraver::finalize ()
232 {
233   if (finished_ligature_)
234     {
235       typeset_ligature (finished_ligature_, finished_primitives_);
236       finished_primitives_.clear ();
237       finished_ligature_ = 0;
238     }
239   if (ligature_)
240     {
241       prev_start_event_->origin ()->warning (_ ("unterminated ligature"));
242       ligature_->suicide ();
243     }
244 }
245
246 Spanner *
247 Ligature_engraver::current_ligature ()
248 {
249   return ligature_;
250 }
251
252 void
253 Ligature_engraver::acknowledge_note_head (Grob_info info)
254 {
255   if (ligature_)
256     {
257       primitives_.push (info);
258       if (info.grob ())
259         {
260           info.grob ()->set_property ("print-function",
261                                       brew_ligature_primitive_proc);
262         }
263     }
264 }
265
266 void
267 Ligature_engraver::acknowledge_rest (Grob_info info)
268 {
269   if (ligature_)
270     {
271       info.music_cause ()->origin ()->warning (_ ("ignoring rest: ligature may not contain rest"));
272       prev_start_event_->origin ()->warning (_ ("ligature was started here"));
273       // TODO: maybe better should stop ligature here rather than
274       // ignoring the rest?
275     }
276 }
277
278 // no ADD_ACKNOWLEDGER / ADD_ACKNOWLEDGER / ADD_TRANSLATOR macro calls
279 // since this class is abstract