]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
* lily/include/translator.icc (ADD_ACKNOWLEDGER): new macro.
[lilypond.git] / lily / dynamic-engraver.cc
1 /*
2   dynamic-engraver.cc -- implement Dynamic_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include "axis-group-interface.hh"
10 #include "context.hh"
11 #include "dimensions.hh"
12 #include "directional-element-interface.hh"
13 #include "engraver.hh"
14 #include "hairpin.hh"
15 #include "interval.hh"
16 #include "note-column.hh"
17 #include "paper-column.hh"
18 #include "script-interface.hh"
19 #include "side-position-interface.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "warn.hh"
22 #include "self-alignment-interface.hh"
23 #include "pointer-group-interface.hh"
24
25 #include "translator.icc"
26
27 /*
28   TODO:
29
30   * direction of text-dynamic-event if not equal to direction of
31   line-spanner
32
33   - TODO: this engraver is too complicated. We should split it into
34   the handling of the basic grobs and the linespanner
35
36   - TODO: the line-spanner is not killed after the (de)crescs are
37   finished.
38 */
39
40 /**
41    print text & hairpin dynamics.
42 */
43 class Dynamic_engraver : public Engraver
44 {
45   Item *script_;
46   Spanner *line_spanner_;
47   Spanner *cresc_;
48
49   Spanner *finished_line_spanner_;
50   Spanner *finished_cresc_;
51
52   Music *script_ev_;
53   Music *current_cresc_ev_;
54
55   Drul_array<Music *> accepted_spanevents_drul_;
56
57   Link_array<Note_column> pending_columns_;
58   Link_array<Grob> pending_elements_;
59
60   void typeset_all ();
61
62   TRANSLATOR_DECLARATIONS (Dynamic_engraver);
63   DECLARE_ACKNOWLEDGER(script);
64   DECLARE_ACKNOWLEDGER(note_column);
65
66
67 protected:
68   virtual void finalize ();
69   virtual bool try_music (Music *event);
70   PRECOMPUTED_VIRTUAL void stop_translation_timestep ();
71   PRECOMPUTED_VIRTUAL void process_music ();
72 };
73
74 Dynamic_engraver::Dynamic_engraver ()
75 {
76   script_ = 0;
77   finished_cresc_ = 0;
78   line_spanner_ = 0;
79   finished_line_spanner_ = 0;
80   current_cresc_ev_ = 0;
81   cresc_ = 0;
82
83   script_ev_ = 0;
84   accepted_spanevents_drul_[START] = 0;
85   accepted_spanevents_drul_[STOP] = 0;
86 }
87
88 bool
89 Dynamic_engraver::try_music (Music *m)
90 {
91   if (m->is_mus_type ("absolute-dynamic-event"))
92     {
93       /*
94         TODO: probably broken.
95       */
96       script_ev_ = m;
97       return true;
98     }
99   else if (m->is_mus_type ("decrescendo-event")
100            || m->is_mus_type ("crescendo-event"))
101     {
102       Direction d = to_dir (m->get_property ("span-direction"));
103
104       accepted_spanevents_drul_[d] = m;
105       if (current_cresc_ev_ && d == START)
106         accepted_spanevents_drul_[STOP] = m;
107       return true;
108     }
109   return false;
110 }
111
112 void
113 Dynamic_engraver::process_music ()
114 {
115   if (accepted_spanevents_drul_[START] || accepted_spanevents_drul_[STOP] || script_ev_)
116     {
117       if (!line_spanner_)
118         {
119           Music *rq = accepted_spanevents_drul_[START];
120           line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->self_scm () : SCM_EOL);
121
122           if (script_ev_)
123             rq = script_ev_;
124         }
125     }
126
127   /*
128     During a (de)crescendo, pending event will not be cleared,
129     and a line-spanner will always be created, as \< \! are already
130     two events.
131
132     Note: line-spanner must always have at least same duration
133     as (de)crecsendo, b.o. line-breaking.
134   */
135
136   /*
137     maybe we should leave dynamic texts to the text-engraver and
138     simply acknowledge them?
139   */
140   if (script_ev_)
141     {
142       script_ = make_item ("DynamicText", script_ev_->self_scm ());
143       script_->set_property ("text",
144                              script_ev_->get_property ("text"));
145
146       if (Direction d = to_dir (script_ev_->get_property ("direction")))
147         set_grob_direction (line_spanner_, d);
148
149       Axis_group_interface::add_element (line_spanner_, script_);
150     }
151
152   Music *stop_ev = accepted_spanevents_drul_ [STOP]
153     ? accepted_spanevents_drul_[STOP] : script_ev_;
154
155   if (accepted_spanevents_drul_[STOP] || script_ev_)
156     {
157       /*
158         finish side position alignment if the (de)cresc ends here, and
159         there are no new dynamics.
160       */
161
162       if (cresc_)
163         {
164           assert (!finished_cresc_ && cresc_);
165
166           if (script_)
167             {
168               cresc_->set_bound (RIGHT, script_);
169               add_bound_item (line_spanner_, script_);
170             }
171           
172           finished_cresc_ = cresc_;
173           cresc_ = 0;
174           current_cresc_ev_ = 0;
175         }
176       else if (accepted_spanevents_drul_[STOP])
177         {
178           accepted_spanevents_drul_[STOP]->origin ()->warning (_ ("can't find start of (de)crescendo"));
179           stop_ev = 0;
180         }
181     }
182
183   if (accepted_spanevents_drul_[START])
184     {
185       if (current_cresc_ev_)
186         {
187           String msg = _ ("already have a decrescendo");
188           if (current_cresc_ev_->is_mus_type ("decrescendo-event"))
189             msg = _ ("already have a crescendo");
190
191           accepted_spanevents_drul_[START]->origin ()->warning (msg);
192           current_cresc_ev_->origin ()->warning (_ ("cresc starts here"));
193         }
194       else
195         {
196           current_cresc_ev_ = accepted_spanevents_drul_[START];
197
198           if (Direction d = to_dir (current_cresc_ev_->get_property ("direction")))
199             set_grob_direction (line_spanner_, d);
200
201           /*
202             TODO: Use symbols.
203           */
204
205           String start_type
206             = ly_symbol2string (current_cresc_ev_->get_property ("name"));
207
208           /*
209             ugh. Use push/pop?
210           */
211           if (start_type == "DecrescendoEvent")
212             start_type = "decrescendo";
213           else if (start_type == "CrescendoEvent")
214             start_type = "crescendo";
215
216           SCM s = get_property ((start_type + "Spanner").to_str0 ());
217           if (!scm_is_symbol (s) || s == ly_symbol2scm ("hairpin"))
218             {
219               cresc_ = make_spanner ("Hairpin", accepted_spanevents_drul_[START]->self_scm ());
220               if (finished_cresc_)
221                 {
222                   Pointer_group_interface::add_grob (finished_cresc_,
223                                                      ly_symbol2scm ("adjacent-hairpins"),
224                                                      cresc_);
225
226                   Pointer_group_interface::add_grob (cresc_,
227                                                      ly_symbol2scm ("adjacent-hairpins"),
228                                                      finished_cresc_);
229                 }
230               cresc_->set_property ("grow-direction",
231                                     scm_int2num ((start_type == "crescendo")
232                                                  ? BIGGER : SMALLER));
233             }
234
235           /*
236             This is a convenient (and legacy) interface to TextSpanners
237             for use in (de)crescendi.
238             Hmm.
239           */
240           else
241             {
242               cresc_ = make_spanner ("DynamicTextSpanner", accepted_spanevents_drul_[START]->self_scm ());
243               cresc_->set_property ("style", s);
244               context ()->set_property ((start_type
245                                          + "Spanner").to_str0 (), SCM_EOL);
246               s = get_property ((start_type + "Text").to_str0 ());
247               /*
248                 FIXME: use get_markup () to check type.
249               */
250               if (scm_is_string (s) || scm_is_pair (s))
251                 {
252                   cresc_->set_property ("edge-text",
253                                         scm_cons (s, scm_makfrom0str ("")));
254                   context ()->set_property ((start_type + "Text").to_str0 (),
255                                             SCM_EOL);
256                 }
257             }
258
259           if (script_)
260             {
261               cresc_->set_bound (LEFT, script_);
262               add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
263             }
264
265           Axis_group_interface::add_element (line_spanner_, cresc_);
266         }
267     }
268 }
269
270 void
271 Dynamic_engraver::stop_translation_timestep ()
272 {
273   if (!current_cresc_ev_ && line_spanner_)
274     {
275       assert (!finished_line_spanner_);
276       finished_line_spanner_ = line_spanner_;
277       line_spanner_ = 0;
278     }
279   
280   typeset_all ();
281
282   if (cresc_ && !cresc_->get_bound (LEFT))
283     {
284       cresc_->set_bound (LEFT, unsmob_grob (get_property ("currentMusicalColumn")));
285       add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
286     }
287
288   script_ev_ = 0;
289   accepted_spanevents_drul_[START] = 0;
290   accepted_spanevents_drul_[STOP] = 0;
291 }
292
293 void
294 Dynamic_engraver::finalize ()
295 {
296   typeset_all ();
297
298   if (line_spanner_
299       && !line_spanner_->is_live ())
300     line_spanner_ = 0;
301   if (line_spanner_)
302     {
303       finished_line_spanner_ = line_spanner_;
304       typeset_all ();
305     }
306
307   if (cresc_
308       && !cresc_->is_live ())
309     cresc_ = 0;
310   if (cresc_)
311     {
312       current_cresc_ev_->origin ()->warning (_ ("unterminated (de)crescendo"));
313       cresc_->suicide ();
314       cresc_ = 0;
315     }
316 }
317
318 void
319 Dynamic_engraver::typeset_all ()
320 {
321   if (finished_cresc_)
322     {
323       if (!finished_cresc_->get_bound (RIGHT))
324         {
325           finished_cresc_->set_bound (RIGHT, script_
326                                       ? script_
327                                       : unsmob_grob (get_property ("currentMusicalColumn")));
328
329           if (finished_line_spanner_)
330             add_bound_item (finished_line_spanner_,
331                             finished_cresc_->get_bound (RIGHT));
332         }
333       finished_cresc_ = 0;
334     }
335
336   script_ = 0;
337   if (finished_line_spanner_)
338     {
339       /*
340         We used to have
341
342         extend-spanner-over-elements (finished_line_spanner_);
343
344         but this is rather kludgy, since finished_line_spanner_
345         typically has a staff-symbol field set , extending it over the
346         entire staff.
347
348       */
349
350       Grob *l = finished_line_spanner_->get_bound (LEFT);
351       Grob *r = finished_line_spanner_->get_bound (RIGHT);
352       if (!r && l)
353         finished_line_spanner_->set_bound (RIGHT, l);
354       else if (!l && r)
355         finished_line_spanner_->set_bound (LEFT, r);
356       else if (!r && !l)
357         {
358           /*
359             This is a isolated dynamic apparently, and does not even have
360             any interesting support item.
361           */
362           Grob *cc = unsmob_grob (get_property ("currentMusicalColumn"));
363           Item *ci = dynamic_cast<Item *> (cc);
364           finished_line_spanner_->set_bound (RIGHT, ci);
365           finished_line_spanner_->set_bound (LEFT, ci);
366         }
367
368       finished_line_spanner_ = 0;
369     }
370 }
371
372 void
373 Dynamic_engraver::acknowledge_note_column (Grob_info info)
374 {
375   if (!line_spanner_)
376     return;
377
378   if (line_spanner_
379       /* Don't refill killed spanner */
380       && line_spanner_->is_live ())
381     {
382       Side_position_interface::add_support (line_spanner_, info.grob ());
383       add_bound_item (line_spanner_, dynamic_cast<Item *> (info.grob ()));
384     }
385
386   if (script_ && !script_->get_parent (X_AXIS))
387     {
388       extract_grob_set (info.grob (), "note-heads", heads);
389       if (heads.size())
390         {
391           Grob *head = heads[0];
392           script_->set_parent (head, X_AXIS);
393           script_->add_offset_callback (Self_alignment_interface::centered_on_parent_proc,
394                                         X_AXIS);
395
396         }
397     }
398
399   if (cresc_)
400     {
401       if (!cresc_->get_bound (LEFT))
402         {
403           cresc_->set_bound (LEFT, info.grob ());
404           add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
405         }
406     }
407
408   if (finished_cresc_ && !finished_cresc_->get_bound (RIGHT))
409     {
410       finished_cresc_->set_bound (RIGHT, info.grob ());
411     }
412 }
413
414 void
415 Dynamic_engraver::acknowledge_script (Grob_info info)
416 {
417   if (!line_spanner_ || !script_)
418     return;
419
420   SCM p = info.grob ()->get_property ("script-priority");
421
422   /*
423     UGH.
424
425     DynamicText doesn't really have a script-priority field.
426   */
427   if (scm_is_number (p)
428       && scm_to_int (p)
429       < scm_to_int (script_->get_property ("script-priority")))
430     Side_position_interface::add_support (line_spanner_, info.grob ());
431 }
432
433
434 ADD_ACKNOWLEDGER(Dynamic_engraver,script);
435 ADD_ACKNOWLEDGER(Dynamic_engraver,note_column);
436
437 ADD_TRANSLATOR (Dynamic_engraver,
438                 /* descr */
439                 "This engraver creates hairpins, dynamic texts, and their vertical\n"
440                 "alignments.  The symbols are collected onto a DynamicLineSpanner grob\n"
441                 "which takes care of vertical positioning.  ",
442
443                 /* creats*/ "DynamicLineSpanner DynamicText Hairpin TextSpanner",
444                 /* accepts */ "absolute-dynamic-event crescendo-event decrescendo-event",
445                 /* acks  */ "note-column-interface script-interface",
446                 /* reads */ "",
447                 /* write */ "");