]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
* lily/lexer.ll: change is_string -> ly_c_string_p
[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 "context.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_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_property ("text",
145                                    script_ev_->get_property ("text"));
146
147       
148       if (Direction d = to_dir (script_ev_->get_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           String msg = _ ("already have a decrescendo");
194           if (current_cresc_ev_->is_mus_type ("decrescendo-event"))
195             msg = _ ("already have a crescendo");
196
197           accepted_spanreqs_drul_[START]->origin ()->warning (msg);
198           current_cresc_ev_->origin ()->warning (_("Cresc started here"));
199         }
200       else
201         {
202           current_cresc_ev_ = accepted_spanreqs_drul_[START];
203
204           if (Direction d = to_dir (current_cresc_ev_->get_property ("direction")))
205             set_grob_direction (line_spanner_, d);
206
207           /*
208             TODO: Use symbols.
209           */
210
211           String start_type = 
212             ly_symbol2string (current_cresc_ev_->get_property ("name"));
213
214           /*
215             ugh. Use push/pop?
216           */
217           if (start_type == "DecrescendoEvent")
218             start_type = "decrescendo";
219           else if (start_type == "CrescendoEvent")
220             start_type = "crescendo";
221           
222           SCM s = get_property ((start_type + "Spanner").to_str0 ());
223           if (!is_symbol (s) || s == ly_symbol2scm ("hairpin"))
224             {
225               cresc_  = make_spanner ("Hairpin");
226               cresc_->set_property ("grow-direction",
227                                            scm_int2num ((start_type == "crescendo")
228                                                        ? BIGGER : SMALLER));
229               
230             }
231
232           
233           /*
234             This is a convenient (and legacy) interface to TextSpanners
235             for use in (de)crescendi.
236             Hmm.
237           */
238           else
239             {
240               cresc_  = make_spanner ("TextSpanner");
241               cresc_->set_property ("style", s);
242               get_parent_context ()->set_property ((start_type
243                                             + "Spanner").to_str0 (), SCM_EOL);
244               s = get_property ((start_type + "Text").to_str0 ());
245               /*
246                 FIXME: use get_markup () to check type.
247               */
248               if (ly_c_string_p (s) || ly_c_pair_p (s))
249                 {
250                   cresc_->set_property ("edge-text",
251                                              scm_cons (s, scm_makfrom0str ("")));
252                   get_parent_context ()->set_property ((start_type + "Text").to_str0 (),
253                                                 SCM_EOL);
254                 }
255             }
256
257           cresc_->set_bound (LEFT, script_
258                                ? script_
259                                : unsmob_grob (get_property ("currentMusicalColumn")));
260
261           Axis_group_interface::add_element (line_spanner_, cresc_);
262
263           add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
264           
265           announce_grob (cresc_, accepted_spanreqs_drul_[START]->self_scm ());
266         }
267     }
268 }
269
270 void
271 Dynamic_engraver::stop_translation_timestep ()
272 {
273   typeset_all ();
274   if (!current_cresc_ev_)
275     {
276       finished_line_spanner_ = line_spanner_;
277       line_spanner_ =0;
278       typeset_all ();
279     }
280
281   script_ev_ = 0;
282   accepted_spanreqs_drul_[START] = 0;
283   accepted_spanreqs_drul_[STOP] = 0;
284 }
285
286 void
287 Dynamic_engraver::finalize ()
288 {
289   typeset_all ();
290   
291   if (line_spanner_
292       && !line_spanner_->live ())
293     line_spanner_ = 0;
294   if (line_spanner_)
295     {
296       finished_line_spanner_ = line_spanner_;
297       typeset_all ();
298     }
299
300   if (cresc_
301       && !cresc_->live ())
302     cresc_ = 0;
303   if (cresc_)
304     {
305       current_cresc_ev_->origin ()->warning (_ ("unterminated (de)crescendo"));
306       cresc_->suicide ();
307       cresc_ = 0;
308     }
309 }
310
311 void
312 Dynamic_engraver::typeset_all ()
313 {  
314   /*
315     remove suicided spanners,
316     ugh: we'll need this for every spanner, beam, slur
317     Hmm, how to do this, cleanly?
318     Maybe just check at typeset_grob ()?
319   */
320   if (finished_cresc_
321       && !finished_cresc_->live ())
322     finished_cresc_ = 0;
323   if (finished_line_spanner_
324       && !finished_line_spanner_->live ())
325     finished_line_spanner_ = 0;
326
327   if (finished_cresc_)
328     {
329       if (!finished_cresc_->get_bound (RIGHT))
330         {
331           finished_cresc_->set_bound (RIGHT, script_
332                                         ? script_
333                                         : unsmob_grob (get_property ("currentMusicalColumn")));
334
335           if (finished_line_spanner_)
336             add_bound_item (finished_line_spanner_,
337                             finished_cresc_->get_bound (RIGHT));
338         }
339       typeset_grob (finished_cresc_);
340       finished_cresc_ =0;
341     }
342   
343   if (script_)
344     {
345       typeset_grob (script_);
346       script_ = 0;
347     }
348   if (finished_line_spanner_)
349     {
350       /*
351         We used to have
352         
353              extend-spanner-over-elements (finished_line_spanner_);
354
355         but this is rather kludgy, since finished_line_spanner_
356         typically has a staff-symbol field set , extending it over the
357         entire staff.
358
359       */
360
361       Grob * l = finished_line_spanner_->get_bound (LEFT );
362       Grob * r = finished_line_spanner_->get_bound (RIGHT);      
363       if (!r && l)
364         finished_line_spanner_->set_bound (RIGHT, l);
365       else if (!l && r)
366         finished_line_spanner_->set_bound (LEFT, r);
367       else if (!r && !l)
368         {
369           /*
370             This is a isolated dynamic apparently, and does not even have
371             any interesting support item.
372            */
373           Grob * cc = unsmob_grob (get_property ("currentMusicalColumn"));
374           Item * ci = dynamic_cast<Item*>(cc);
375           finished_line_spanner_->set_bound (RIGHT, ci);
376           finished_line_spanner_->set_bound (LEFT, ci);   
377         }
378         
379       typeset_grob (finished_line_spanner_);
380       finished_line_spanner_ = 0;
381     }
382 }
383
384 void
385 Dynamic_engraver::acknowledge_grob (Grob_info i)
386 {
387   if (!line_spanner_)
388     return ;
389   
390   if (Note_column::has_interface (i.grob_))
391     {
392       if (line_spanner_
393           /* Don't refill killed spanner */
394           && line_spanner_->live ())
395         {
396           Side_position_interface::add_support (line_spanner_,i.grob_);
397           add_bound_item (line_spanner_,dynamic_cast<Item*> (i.grob_));
398         }
399
400       if (script_ && !script_->get_parent (X_AXIS))
401         {
402           SCM head = scm_last_pair (i.grob_->get_property ("note-heads"));
403           if (ly_c_pair_p (head))
404             script_->set_parent (unsmob_grob (ly_car (head)),  X_AXIS);
405         }
406       
407     }
408   else if (Script_interface::has_interface (i.grob_) && script_)
409     {
410       SCM p = i.grob_->get_property ("script-priority");
411
412       /*
413         UGH.
414
415         DynamicText doesn't really have a script-priority field.
416        */
417       if (is_number (p)
418           && ly_scm2int (p) < ly_scm2int (script_->get_property ("script-priority")))
419         {
420           Side_position_interface::add_support (line_spanner_, i.grob_);
421
422         }         
423     }
424 }
425 ENTER_DESCRIPTION (Dynamic_engraver,
426 /* descr */       
427 "This engraver creates hairpins, dynamic texts, and their vertical\n"
428 "alignments.  The symbols are collected onto a DynamicLineSpanner grob\n"
429 "which takes care of vertical positioning.  "
430 ,
431                   
432 /* creats*/       "DynamicLineSpanner DynamicText Hairpin TextSpanner",
433 /* accepts */     "absolute-dynamic-event crescendo-event decrescendo-event",
434 /* acks  */      "note-column-interface script-interface",
435 /* reads */       "",
436 /* write */       "");