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