]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
2003 -> 2004
[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 #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 ("decrescendo-event")
98            || m->is_mus_type ("crescendo-event"))
99     {
100       Direction d = to_dir (m->get_mus_property ("span-direction"));
101
102       accepted_spanreqs_drul_[d] = m;
103       if (current_cresc_ev_ && d == START)
104         accepted_spanreqs_drul_[STOP] = m;
105       return true;
106     }
107   return false;
108 }
109
110 void
111 Dynamic_engraver::process_music ()
112 {
113   if (accepted_spanreqs_drul_[START] || accepted_spanreqs_drul_[STOP] || script_ev_)
114     {
115       if (!line_spanner_)
116         {
117           line_spanner_ = make_spanner ("DynamicLineSpanner");
118
119           Music * rq = accepted_spanreqs_drul_[START];
120           if (script_ev_)
121             rq =  script_ev_ ;
122           announce_grob(line_spanner_, rq ? rq->self_scm(): SCM_EOL);
123         }
124     }
125   
126   /*
127     During a (de)crescendo, pending event will not be cleared,
128     and a line-spanner will always be created, as \< \! are already
129     two events.
130
131     Note: line-spanner must always have at least same duration
132     as (de)crecsendo, b.o. line-breaking.
133   */
134
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");
144       script_->set_grob_property ("text",
145                                    script_ev_->get_mus_property ("text"));
146
147       
148       if (Direction d = to_dir (script_ev_->get_mus_property ("direction")))
149         set_grob_direction (line_spanner_, d);
150
151       Axis_group_interface::add_element (line_spanner_, script_);
152
153       announce_grob(script_, script_ev_->self_scm());
154     }
155
156   Music *stop_ev = accepted_spanreqs_drul_ [STOP] ?
157     accepted_spanreqs_drul_[STOP] : script_ev_;
158
159   if (accepted_spanreqs_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
167       if (cresc_)
168         {
169           assert (!finished_cresc_ && cresc_);
170
171           cresc_->set_bound (RIGHT, script_
172                                ? script_
173                                : unsmob_grob (get_property ("currentMusicalColumn")));
174           add_bound_item (line_spanner_, cresc_->get_bound (RIGHT));
175           
176
177           finished_cresc_ = cresc_;
178           cresc_ = 0;
179           current_cresc_ev_ = 0;
180         }
181       else if (accepted_spanreqs_drul_[STOP] )
182         {
183           accepted_spanreqs_drul_[STOP]->origin ()->warning(_ ("can't find start of (de)crescendo"));
184           stop_ev = 0;
185         }
186       
187     }
188   
189   if (accepted_spanreqs_drul_[START])
190     {
191       if (current_cresc_ev_)
192         {
193           Direction sd = to_dir (current_cresc_ev_->get_mus_property ("span-direction"));
194           String msg = sd == 1
195             ? _ ("already have a crescendo")
196             : _ ("already have a decrescendo");
197       
198           accepted_spanreqs_drul_[START]->origin ()->warning (msg);
199           current_cresc_ev_->origin ()->warning (_("Cresc started here"));
200         }
201       else
202         {
203           current_cresc_ev_ = accepted_spanreqs_drul_[START];
204
205           if (Direction d = to_dir (current_cresc_ev_->get_mus_property ("direction")))
206             set_grob_direction (line_spanner_, d);
207
208           /*
209             TODO: Use symbols.
210           */
211
212           String start_type = 
213             ly_symbol2string (current_cresc_ev_->get_mus_property ("name"));
214
215           /*
216             ugh. Use push/pop?
217           */
218           if (start_type == "DecrescendoEvent")
219             start_type = "decrescendo";
220           else if (start_type == "CrescendoEvent")
221             start_type = "crescendo";
222           
223           SCM s = get_property ((start_type + "Spanner").to_str0 ());
224           if (!gh_symbol_p (s) || s == ly_symbol2scm ("hairpin"))
225             {
226               cresc_  = make_spanner ("Hairpin");
227               cresc_->set_grob_property ("grow-direction",
228                                            gh_int2scm ((start_type == "crescendo")
229                                                        ? BIGGER : SMALLER));
230               
231             }
232
233           
234           /*
235             This is a convenient (and legacy) interface to TextSpanners
236             for use in (de)crescendi.
237             Hmm.
238           */
239           else
240             {
241               cresc_  = make_spanner ("TextSpanner");
242               cresc_->set_grob_property ("style", s);
243               daddy_trans_->set_property ((start_type
244                                             + "Spanner").to_str0 (), SCM_EOL);
245               s = get_property ((start_type + "Text").to_str0 ());
246               /*
247                 FIXME: use get_markup () to check type.
248               */
249               if (gh_string_p (s) || gh_pair_p (s))
250                 {
251                   cresc_->set_grob_property ("edge-text",
252                                              gh_cons (s, scm_makfrom0str ("")));
253                   daddy_trans_->set_property ((start_type + "Text").to_str0 (),
254                                                 SCM_EOL);
255                 }
256             }
257
258           cresc_->set_bound (LEFT, script_
259                                ? script_
260                                : unsmob_grob (get_property ("currentMusicalColumn")));
261
262           Axis_group_interface::add_element (line_spanner_, cresc_);
263
264           add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
265           
266           announce_grob(cresc_, accepted_spanreqs_drul_[START]->self_scm());
267         }
268     }
269 }
270
271 void
272 Dynamic_engraver::stop_translation_timestep ()
273 {
274   typeset_all ();
275   if (!current_cresc_ev_)
276     {
277       finished_line_spanner_ = line_spanner_;
278       line_spanner_ =0;
279       typeset_all ();
280     }
281
282   script_ev_ = 0;
283   accepted_spanreqs_drul_[START] = 0;
284   accepted_spanreqs_drul_[STOP] = 0;
285 }
286
287 void
288 Dynamic_engraver::finalize ()
289 {
290   typeset_all ();
291   
292   if (line_spanner_
293       && !line_spanner_->live())
294     line_spanner_ = 0;
295   if (line_spanner_)
296     {
297       finished_line_spanner_ = line_spanner_;
298       typeset_all ();
299     }
300
301   if (cresc_
302       && !cresc_->live())
303     cresc_ = 0;
304   if (cresc_)
305     {
306       current_cresc_ev_->origin ()->warning (_ ("unterminated (de)crescendo"));
307       cresc_->suicide ();
308       cresc_ = 0;
309     }
310 }
311
312 void
313 Dynamic_engraver::typeset_all ()
314 {  
315   /*
316     remove suicided spanners,
317     ugh: we'll need this for every spanner, beam, slur
318     Hmm, how to do this, cleanly?
319     Maybe just check at typeset_grob ()?
320   */
321   if (finished_cresc_
322       && !finished_cresc_->live())
323     finished_cresc_ = 0;
324   if (finished_line_spanner_
325       && !finished_line_spanner_->live())
326     finished_line_spanner_ = 0;
327
328   if (finished_cresc_)
329     {
330       if (!finished_cresc_->get_bound (RIGHT))
331         {
332           finished_cresc_->set_bound (RIGHT, script_
333                                         ? script_
334                                         : unsmob_grob (get_property ("currentMusicalColumn")));
335
336           if (finished_line_spanner_)
337             add_bound_item (finished_line_spanner_,
338                             finished_cresc_->get_bound (RIGHT));
339         }
340       typeset_grob (finished_cresc_);
341       finished_cresc_ =0;
342     }
343   
344   if (script_)
345     {
346       typeset_grob (script_);
347       script_ = 0;
348     }
349   if (finished_line_spanner_)
350     {
351       /*
352         We used to have
353         
354              extend-spanner-over-elements (finished_line_spanner_);
355
356         but this is rather kludgy, since finished_line_spanner_
357         typically has a staff-symbol field set , extending it over the
358         entire staff.
359
360       */
361
362       Grob * l = finished_line_spanner_->get_bound (LEFT );
363       Grob * r = finished_line_spanner_->get_bound (RIGHT);      
364       if (!r && l)
365         finished_line_spanner_->set_bound (RIGHT, l);
366       else if (!l && r)
367         finished_line_spanner_->set_bound (LEFT, r);
368       else if (!r && !l)
369         {
370           /*
371             This is a isolated dynamic apparently, and does not even have
372             any interesting support item.
373            */
374           Grob * cc = unsmob_grob (get_property ("currentMusicalColumn"));
375           Item * ci = dynamic_cast<Item*>(cc);
376           finished_line_spanner_->set_bound (RIGHT, ci);
377           finished_line_spanner_->set_bound (LEFT, ci);   
378         }
379         
380       typeset_grob (finished_line_spanner_);
381       finished_line_spanner_ = 0;
382     }
383 }
384
385 void
386 Dynamic_engraver::acknowledge_grob (Grob_info i)
387 {
388   if (!line_spanner_)
389     return ;
390   
391   if (Note_column::has_interface (i.grob_))
392     {
393       if (line_spanner_
394           /* Don't refill killed spanner */
395           && line_spanner_->live())
396         {
397           Side_position_interface::add_support (line_spanner_,i.grob_);
398           add_bound_item (line_spanner_,dynamic_cast<Item*> (i.grob_));
399         }
400
401       if (script_ && !script_->get_parent (X_AXIS))
402         {
403           script_->set_parent (i.grob_,  X_AXIS);
404         }
405       
406     }
407   else if (Script_interface::has_interface (i.grob_) && script_)
408     {
409       SCM p = i.grob_->get_grob_property ("script-priority");
410
411       /*
412         UGH.
413
414         DynamicText doesn't really have a script-priority field.
415        */
416       if (gh_number_p (p)
417           && gh_scm2int (p) < gh_scm2int (script_->get_grob_property ("script-priority")))
418         {
419           Side_position_interface::add_support (line_spanner_, i.grob_);
420
421         }         
422     }
423 }
424 ENTER_DESCRIPTION(Dynamic_engraver,
425 /* descr */       
426 "This engraver creates hairpins, dynamic texts, and their vertical\n"
427 "alignments.  The symbols are collected onto a DynamicLineSpanner grob\n"
428 "which takes care of vertical positioning.  "
429 ,
430                   
431 /* creats*/       "DynamicLineSpanner DynamicText Hairpin TextSpanner",
432 /* accepts */     "absolute-dynamic-event crescendo-event decrescendo-event",
433 /* acks  */      "note-column-interface script-interface",
434 /* reads */       "",
435 /* write */       "");