]> git.donarmstrong.com Git - lilypond.git/blob - lily/ligature-engraver.cc
*** empty log message ***
[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
10 #include "ligature-engraver.hh"
11
12 #include "spanner.hh"
13 #include "score-engraver.hh"
14 #include "note-head.hh"
15 #include "rest.hh"
16 #include "warn.hh"
17 #include "context.hh"
18
19 /*
20  * This abstract class provides the general framework for ligatures of
21  * any kind.  It cares for handling start/stop ligatures requests and
22  * collecting all noteheads inbetween, but delegates creation of a
23  * ligature spanner for each start/stop pair and typesetting of the
24  * ligature spanner to a concrete subclass.
25  *
26  * A concrete ligature engraver must subclass this class and provide
27  * functions create_ligature_spanner () and typeset_ligature
28  * (Spanner *, Array<Grob_info>).  Subclasses of this class basically
29  * fall into two categories.
30  *
31  * The first category consists of engravers that engrave ligatures in
32  * a way that really deserves the name ligature.  That is, they
33  * produce a single connected graphical object of fixed width,
34  * consisting of noteheads and other primitives.  Space may be
35  * inserted only after each ligature, if necessary, but in no case
36  * between the primitives of the ligature.  Accidentals have to be put
37  * to the left of the ligature, and not to the left of individual
38  * noteheads.  Class Coherent_ligature_engraver is the common
39  * superclass for all of these engravers.
40  *
41  * The second category is for engravers that are relaxed in the sense
42  * that they do not require to produce a single connected graphical
43  * object.  For example, in contemporary editions, ligatures are often
44  * marked, but otherwise use contemporary notation and spacing.  In
45  * this category, there is currently only a single class,
46  * Ligature_bracket_engraver, which marks each ligature with a
47  * horizontal sqare bracket, but otherwise leaves the appearance
48  * untouched.
49  */
50
51 /*
52  * TODO: lyrics/melisma/syllables: there should be at most one
53  * syllable of lyrics per ligature (i.e. for the lyrics context, a
54  * ligature should count as a single note, regardless of how many
55  * heads the ligature consists of).
56  *
57  * TODO: currently, you have to add/remove the proper
58  * Ligature_engraver (Ligature_bracket_engraver,
59  * Mensural_ligature_engraver) to the proper translator
60  * (e.g. VoiceContext) to choose between various representations.
61  * Since adding/removing an engraver to a translator is a global
62  * action in the layout block, you can not mix various representations
63  * _within_ the same score.  Hence, for selecting a representation,
64  * one would rather like to have a property that can be set e.g. for
65  * several staves individually.  However, it seems that this approach
66  * would require to have a single, complicated Ligature_engraver that
67  * consists of all the code...  This needs further thoughts.
68  */
69 Ligature_engraver::Ligature_engraver ()
70 {
71   ligature_ = 0;
72   finished_ligature_ = 0;
73   reqs_drul_[LEFT] = reqs_drul_[RIGHT] = 0;
74   prev_start_req_ = 0;
75   last_bound_ = 0;
76   brew_ligature_primitive_proc = SCM_EOL;
77 }
78
79 bool
80 Ligature_engraver::try_music (Music *m)
81 {
82   if (m->is_mus_type ("ligature-event"))
83     {
84       Direction d = to_dir (m->get_property ("span-direction"));
85       reqs_drul_[d] = m;
86       return true;
87     }
88   return false;
89 }
90
91 Spanner *
92 Ligature_engraver::create_ligature_spanner ()
93 {
94   programming_error ("Ligature_engraver::create_ligature_spanner (): "
95                      "this is an abstract method that should not be called, "
96                      "but overridden by a subclass");
97   return 0;
98 }
99
100 /*
101  * This method should do something that comes close to the following
102  * .ly snippet:
103  *
104  * \property Voice.NoteHead \override #'print-function =
105  *     < value of #'ligature-primitive-callback of Voice.NoteHead >
106  *
107  * TODO: What we are doing here on the c++ level, should actually be
108  * performed on the SCM level.  However, I do not know how to teach
109  * lilypond to apply an \override and \revert on #'print-function,
110  * whenever lily encounters a \[ and \] in an .ly file, respectively.
111  * Also encounter, that lily should not crash if a user erronously
112  * nests \[ and \].
113  */
114 void
115 Ligature_engraver::override_stencil_callback ()
116 {
117   SCM target_callback = ly_symbol2scm ("print-function");
118   SCM source_callback = ly_symbol2scm ("ligature-primitive-callback");
119   SCM noteHeadProperties = updated_grob_properties (context (), ly_symbol2scm ("NoteHead"));
120   SCM value = scm_cdr (scm_sloppy_assq (source_callback, noteHeadProperties));
121   execute_pushpop_property (context (), ly_symbol2scm ("NoteHead"),
122                             target_callback, value);
123 }
124
125 /*
126  * This method should do something that comes close to the following
127  * .ly snippet:
128  *
129  * \property Voice.NoteHead \revert #'print-function
130  *
131  * TODO: What we are doing here on the c++ level, should actually be
132  * performed on the SCM level.  However, I do not know how to teach
133  * lilypond to apply an \override and \revert on #'print-function,
134  * whenever lily encounters a \[ and \] in an .ly file, respectively.
135  * Also encounter, that lily should not crash if a user erronously
136  * nests \[ and \].
137  */
138 void
139 Ligature_engraver::revert_stencil_callback ()
140 {
141   SCM symbol = ly_symbol2scm ("NoteHead");
142   SCM key = ly_symbol2scm ("print-function");
143   execute_pushpop_property (context (), symbol, key, SCM_UNDEFINED);
144 }
145
146 void
147 Ligature_engraver::process_music ()
148 {
149   if (reqs_drul_[STOP])
150     {
151       if (!ligature_)
152         {
153           reqs_drul_[STOP]->origin ()->warning (_ ("can't find start of ligature"));
154           return;
155         }
156
157       if (!last_bound_)
158         {
159           reqs_drul_[STOP]->origin ()->warning (_ ("no right bound"));
160         }
161       else
162         {
163           ligature_->set_bound (RIGHT, last_bound_);
164         }
165
166       prev_start_req_ = 0;
167       finished_primitives_ = primitives_;
168       finished_ligature_ = ligature_;
169       primitives_.clear ();
170       ligature_ = 0;
171       revert_stencil_callback ();
172     }
173   last_bound_ = unsmob_grob (get_property ("currentMusicalColumn"));
174
175   if (ligature_)
176     {
177       // TODO: maybe forbid breaks only if not transcribing
178       get_score_engraver ()->forbid_breaks ();
179     }
180
181   if (reqs_drul_[START])
182     {
183       if (ligature_)
184         {
185           reqs_drul_[START]->origin ()->warning (_ ("already have a ligature"));
186           return;
187         }
188
189       prev_start_req_ = reqs_drul_[START];
190       ligature_ = create_ligature_spanner ();
191       brew_ligature_primitive_proc =
192         ligature_->get_property ("ligature-primitive-callback");
193       if (brew_ligature_primitive_proc == SCM_EOL)
194         {
195           warning ("Ligature_engraver: ligature-primitive-callback undefined");
196         }
197
198       Grob *bound = unsmob_grob (get_property ("currentMusicalColumn"));
199       if (!bound)
200         {
201           reqs_drul_[START]->origin ()->warning (_ ("no left bound"));
202         }
203       else
204         {
205           ligature_->set_bound (LEFT, bound);
206         }
207
208       ligature_start_mom_ = now_mom ();
209
210       // TODO: dump cause into make_item/spanner. 
211       // announce_grob (ligature_, reqs_drul_[START]->self_scm ());
212       override_stencil_callback ();
213     }
214 }
215
216 void
217 Ligature_engraver::typeset_ligature (Spanner *, Array<Grob_info>)
218 {
219   programming_error ("Ligature_engraver::typeset_ligature (): "
220                      "this is an abstract method that should not be called, "
221                      "but overridden by a subclass");
222 }
223
224 void
225 Ligature_engraver::stop_translation_timestep ()
226 {
227   if (finished_ligature_)
228     {
229       if (!finished_primitives_.size ())
230         {
231           finished_ligature_->programming_error ("Ligature_engraver::stop_translation_timestep (): "
232                                                  "junking empty ligature");
233         }
234       else
235         {
236           typeset_ligature (finished_ligature_, finished_primitives_);
237           finished_primitives_.clear ();
238         }
239       finished_ligature_ = 0;
240     }
241
242   reqs_drul_[START] = 0;
243   reqs_drul_[STOP] = 0;
244 }
245
246 void
247 Ligature_engraver::finalize ()
248 {
249   if (finished_ligature_)
250     {
251       typeset_ligature (finished_ligature_, finished_primitives_);
252       finished_primitives_.clear ();
253       finished_ligature_ = 0;
254     }
255   if (ligature_)
256     {
257       prev_start_req_->origin ()->warning (_ ("unterminated ligature"));
258       ligature_->suicide ();
259     }
260 }
261
262 Spanner *
263 Ligature_engraver::current_ligature ()
264 {
265   return ligature_;
266 }
267
268 void
269 Ligature_engraver::acknowledge_grob (Grob_info info)
270 {
271   if (ligature_)
272     {
273       if (Note_head::has_interface (info.grob_))
274         {
275           primitives_.push (info);
276           info.grob_->set_property ("print-function",
277                                          brew_ligature_primitive_proc);
278         }
279       if (Rest::has_interface (info.grob_))
280         {
281           info.music_cause ()->origin ()->warning (_ ("ignoring rest: ligature may not contain rest"));
282           prev_start_req_->origin ()->warning (_ ("ligature was started here"));
283           // TODO: maybe better should stop ligature here rather than
284           // ignoring the rest?
285         }
286     }
287 }
288
289 ADD_TRANSLATOR (Ligature_engraver,
290 /* descr */       "Abstract class; a concrete subclass handles Ligature_events by engraving Ligatures in a concrete style.",
291 /* creats */      "",
292 /* accepts */     "ligature-event",
293 /* acks  */      "note-head-interface rest-interface",
294 /* reads */       "",
295 /* write */       "");