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