]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
d9bdf7e94e5bb7a67ab9f784c2c591ae770dba51
[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 "note-column.hh"
20 #include "paper-column.hh"
21 #include "script-interface.hh"
22 #include "side-position-interface.hh"
23 #include "staff-symbol-referencer.hh"
24 #include "warn.hh"
25
26 /*
27   TODO:
28
29   * direction of text-dynamic-event if not equal to direction of
30   line-spanner
31
32   - TODO: this engraver is too complicated. We should split it into
33   the handling of the basic grobs and the  linespanner
34
35   - TODO: the line-spanner is not killed after the (de)crescs are
36   finished.
37
38 */
39
40 /**
41    print text & hairpin dynamics.
42  */
43 class Dynamic_engraver : public Engraver
44 {
45   Item *script_;
46   Spanner *line_spanner_;
47   Spanner *cresc_;
48
49   Spanner *finished_line_spanner_;
50   Spanner *finished_cresc_;
51
52   Music *script_ev_;
53   Music *current_cresc_ev_;
54   
55   Drul_array<Music*> accepted_spanreqs_drul_;
56
57   Link_array<Note_column> pending_columns_;
58   Link_array<Grob> pending_elements_;
59   
60   void typeset_all ();
61
62   TRANSLATOR_DECLARATIONS (Dynamic_engraver);
63   
64 protected:
65   virtual void finalize ();
66   virtual void acknowledge_grob (Grob_info);
67   virtual bool try_music (Music *req);
68   virtual void stop_translation_timestep ();
69   virtual void process_music ();  
70 };
71
72
73
74
75 Dynamic_engraver::Dynamic_engraver ()
76 {
77   script_ = 0;
78   finished_cresc_ = 0;
79   line_spanner_ = 0;
80   finished_line_spanner_ = 0;
81   current_cresc_ev_ = 0;
82   cresc_ =0;
83
84   script_ev_ = 0;
85   accepted_spanreqs_drul_[START] = 0;
86   accepted_spanreqs_drul_[STOP] = 0;
87 }
88
89 bool
90 Dynamic_engraver::try_music (Music *m)
91 {
92   if (m->is_mus_type ("absolute-dynamic-event"))
93     {
94       /*
95         TODO: probably broken.
96       */
97       script_ev_ = m;
98       return true;
99     }
100   else if (m->is_mus_type ("decrescendo-event")
101            || m->is_mus_type ("crescendo-event"))
102     {
103       Direction d = to_dir (m->get_property ("span-direction"));
104
105       accepted_spanreqs_drul_[d] = m;
106       if (current_cresc_ev_ && d == START)
107         accepted_spanreqs_drul_[STOP] = m;
108       return true;
109     }
110   return false;
111 }
112
113 void
114 Dynamic_engraver::process_music ()
115 {
116   if (accepted_spanreqs_drul_[START] || accepted_spanreqs_drul_[STOP] || script_ev_)
117     {
118       if (!line_spanner_)
119         {
120           Music * rq = accepted_spanreqs_drul_[START];
121           line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->self_scm (): SCM_EOL );
122
123           if (script_ev_)
124             rq =  script_ev_;
125         }
126     }
127   
128   /*
129     During a (de)crescendo, pending event will not be cleared,
130     and a line-spanner will always be created, as \< \! are already
131     two events.
132
133     Note: line-spanner must always have at least same duration
134     as (de)crecsendo, b.o. line-breaking.
135   */
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       
149       if (Direction d = to_dir (script_ev_->get_property ("direction")))
150         set_grob_direction (line_spanner_, d);
151
152       Axis_group_interface::add_element (line_spanner_, script_);
153     }
154
155   Music *stop_ev = accepted_spanreqs_drul_ [STOP] ?
156     accepted_spanreqs_drul_[STOP] : script_ev_;
157
158   if (accepted_spanreqs_drul_[STOP] || script_ev_)
159     {
160       /*
161         finish side position alignment if the (de)cresc ends here, and
162         there are no new dynamics.
163        */
164
165
166       if (cresc_)
167         {
168           assert (!finished_cresc_ && cresc_);
169
170           cresc_->set_bound (RIGHT, script_
171                                ? script_
172                                : unsmob_grob (get_property ("currentMusicalColumn")));
173           add_bound_item (line_spanner_, cresc_->get_bound (RIGHT));
174           
175
176           finished_cresc_ = cresc_;
177           cresc_ = 0;
178           current_cresc_ev_ = 0;
179         }
180       else if (accepted_spanreqs_drul_[STOP])
181         {
182           accepted_spanreqs_drul_[STOP]->origin ()->warning (_ ("can't find start of (de)crescendo"));
183           stop_ev = 0;
184         }
185       
186     }
187   
188   if (accepted_spanreqs_drul_[START])
189     {
190       if (current_cresc_ev_)
191         {
192           String msg = _ ("already have a decrescendo");
193           if (current_cresc_ev_->is_mus_type ("decrescendo-event"))
194             msg = _ ("already have a crescendo");
195
196           accepted_spanreqs_drul_[START]->origin ()->warning (msg);
197           current_cresc_ev_->origin ()->warning (_("Cresc started here"));
198         }
199       else
200         {
201           current_cresc_ev_ = accepted_spanreqs_drul_[START];
202
203           if (Direction d = to_dir (current_cresc_ev_->get_property ("direction")))
204             set_grob_direction (line_spanner_, d);
205
206           /*
207             TODO: Use symbols.
208           */
209
210           String start_type = 
211             ly_symbol2string (current_cresc_ev_->get_property ("name"));
212
213           /*
214             ugh. Use push/pop?
215           */
216           if (start_type == "DecrescendoEvent")
217             start_type = "decrescendo";
218           else if (start_type == "CrescendoEvent")
219             start_type = "crescendo";
220           
221           SCM s = get_property ((start_type + "Spanner").to_str0 ());
222           if (!scm_is_symbol (s) || s == ly_symbol2scm ("hairpin"))
223             {
224               cresc_  = make_spanner ("Hairpin", accepted_spanreqs_drul_[START]->self_scm ());
225               if (finished_cresc_)
226                 {
227                   Pointer_group_interface::add_grob (finished_cresc_,
228                                                      ly_symbol2scm ("adjacent-hairpins"),
229                                                      cresc_);
230                   
231                   Pointer_group_interface::add_grob (cresc_,
232                                                      ly_symbol2scm ("adjacent-hairpins"),
233                                                      finished_cresc_);
234                 }
235               cresc_->set_property ("grow-direction",
236                                     scm_int2num ((start_type == "crescendo")
237                                                  ? BIGGER : SMALLER));
238               
239             }
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_spanreqs_drul_[START]->self_scm ());
250               cresc_->set_property ("style", s);
251               context ()->set_property ((start_type
252                                          + "Spanner").to_str0 (), SCM_EOL);
253               s = get_property ((start_type + "Text").to_str0 ());
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").to_str0 (),
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   typeset_all ();
281   if (!current_cresc_ev_)
282     {
283       finished_line_spanner_ = line_spanner_;
284       line_spanner_ = 0;
285       typeset_all ();
286     }
287
288   if (cresc_ && !cresc_->get_bound (LEFT))
289     {
290       cresc_->set_bound (LEFT, unsmob_grob (get_property ("currentMusicalColumn")));
291       add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
292     }
293   
294   script_ev_ = 0;
295   accepted_spanreqs_drul_[START] = 0;
296   accepted_spanreqs_drul_[STOP] = 0;
297 }
298
299 void
300 Dynamic_engraver::finalize ()
301 {
302   typeset_all ();
303   
304   if (line_spanner_
305       && !line_spanner_->is_live ())
306     line_spanner_ = 0;
307   if (line_spanner_)
308     {
309       finished_line_spanner_ = line_spanner_;
310       typeset_all ();
311     }
312
313   if (cresc_
314       && !cresc_->is_live ())
315     cresc_ = 0;
316   if (cresc_)
317     {
318       current_cresc_ev_->origin ()->warning (_ ("unterminated (de)crescendo"));
319       cresc_->suicide ();
320       cresc_ = 0;
321     }
322 }
323
324 void
325 Dynamic_engraver::typeset_all ()
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       finished_cresc_ =0;
340     }
341   
342   script_ = 0;
343   if (finished_line_spanner_)
344     {
345       /*
346         We used to have
347         
348              extend-spanner-over-elements (finished_line_spanner_);
349
350         but this is rather kludgy, since finished_line_spanner_
351         typically has a staff-symbol field set , extending it over the
352         entire staff.
353
354       */
355
356       Grob * l = finished_line_spanner_->get_bound (LEFT);
357       Grob * r = finished_line_spanner_->get_bound (RIGHT);      
358       if (!r && l)
359         finished_line_spanner_->set_bound (RIGHT, l);
360       else if (!l && r)
361         finished_line_spanner_->set_bound (LEFT, r);
362       else if (!r && !l)
363         {
364           /*
365             This is a isolated dynamic apparently, and does not even have
366             any interesting support item.
367            */
368           Grob * cc = unsmob_grob (get_property ("currentMusicalColumn"));
369           Item * ci = dynamic_cast<Item*>(cc);
370           finished_line_spanner_->set_bound (RIGHT, ci);
371           finished_line_spanner_->set_bound (LEFT, ci);   
372         }
373         
374       finished_line_spanner_ = 0;
375     }
376 }
377
378 void
379 Dynamic_engraver::acknowledge_grob (Grob_info info)
380 {
381   if (!line_spanner_)
382     return;
383
384   if (Note_column::has_interface (info.grob_))
385     {
386       if (line_spanner_
387           /* Don't refill killed spanner */
388           && line_spanner_->is_live ())
389         {
390           Side_position_interface::add_support (line_spanner_,info.grob_);
391           add_bound_item (line_spanner_,dynamic_cast<Item*> (info.grob_));
392         }
393
394       if (script_ && !script_->get_parent (X_AXIS))
395         {
396           SCM head = scm_last_pair (info.grob_->get_property ("note-heads"));
397           if (scm_is_pair (head))
398             script_->set_parent (unsmob_grob (scm_car (head)),  X_AXIS);
399         }
400
401
402
403       if (cresc_ && !cresc_->get_bound (LEFT))
404         {
405           cresc_->set_bound (LEFT, info.grob_);
406           add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
407         }
408         
409     }
410   else if (Script_interface::has_interface (info.grob_) && script_)
411     {
412       SCM p = info.grob_->get_property ("script-priority");
413
414       /*
415         UGH.
416
417         DynamicText doesn't really have a script-priority field.
418        */
419       if (scm_is_number (p)
420           && scm_to_int (p)
421           < scm_to_int (script_->get_property ("script-priority")))
422         Side_position_interface::add_support (line_spanner_, info.grob_);
423     }
424 }
425
426 ENTER_DESCRIPTION (Dynamic_engraver,
427 /* descr */       
428 "This engraver creates hairpins, dynamic texts, and their vertical\n"
429 "alignments.  The symbols are collected onto a DynamicLineSpanner grob\n"
430 "which takes care of vertical positioning.  "
431 ,
432                   
433 /* creats*/       "DynamicLineSpanner DynamicText Hairpin TextSpanner",
434 /* accepts */     "absolute-dynamic-event crescendo-event decrescendo-event",
435 /* acks  */      "note-column-interface script-interface",
436 /* reads */       "",
437 /* write */       "");