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