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