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