]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
* lily/include/debug.hh: deprecate.
[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--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8 #include "warn.hh"
9 #include "dimensions.hh"
10 #include "hairpin.hh"
11 #include "musical-request.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-request 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_p_;
43   Spanner * finished_cresc_p_;
44   Spanner * cresc_p_;
45
46   Text_script_req* script_req_l_;
47   
48   Span_req * current_cresc_req_;
49   Drul_array<Span_req*> accepted_spanreqs_drul_;
50
51   Spanner* line_spanner_;
52   Spanner* finished_line_spanner_;
53
54   Link_array<Note_column> pending_column_arr_;
55   Link_array<Grob> pending_element_arr_;
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_l);
65   virtual void stop_translation_timestep ();
66   virtual void process_music ();  
67   virtual void start_translation_timestep ();
68 };
69
70
71
72
73 Dynamic_engraver::Dynamic_engraver ()
74 {
75   script_p_ = 0;
76   finished_cresc_p_ = 0;
77   line_spanner_ = 0;
78   finished_line_spanner_ = 0;
79   current_cresc_req_ = 0;
80   cresc_p_ =0;
81
82   script_req_l_ = 0;
83   accepted_spanreqs_drul_[START] = 0;
84   accepted_spanreqs_drul_[STOP] = 0;
85 }
86
87 void
88 Dynamic_engraver::start_translation_timestep ()
89 {
90   script_req_l_ = 0;
91   accepted_spanreqs_drul_[START] = 0;
92   accepted_spanreqs_drul_[STOP] = 0;
93 }
94
95 bool
96 Dynamic_engraver::try_music (Music * m)
97 {
98   if (dynamic_cast <Text_script_req*> (m)
99       && m->get_mus_property ("text-type") == ly_symbol2scm ("dynamic"))
100     {
101       script_req_l_ = dynamic_cast<Text_script_req*> (m);
102       return true;
103     }
104   else if (Span_req* s =  dynamic_cast <Span_req*> (m))
105     {
106       String t = ly_scm2string (s->get_mus_property ("span-type"));
107       if (t== "abort")
108         {
109           accepted_spanreqs_drul_[LEFT] = 0;
110           accepted_spanreqs_drul_[RIGHT] = 0;
111           /*
112             Let's not kill the line spanner, since that would fuck up
113             earlier, not-to-be-terminated stuff.
114
115             It will disappear by itself when stop_translation_timestep
116  () finds that there is nothing to support anymore.  */
117           
118           if (cresc_p_)
119             cresc_p_->suicide ();
120           cresc_p_ = 0;
121         }
122       else if (t == "crescendo"
123            || t == "decrescendo")
124         {
125           accepted_spanreqs_drul_[s->get_span_dir ()] = s;
126           return true;
127         }
128     }
129   return false;
130 }
131
132 void
133 Dynamic_engraver::process_music ()
134 {
135   if (accepted_spanreqs_drul_[START] || accepted_spanreqs_drul_[STOP] || script_req_l_)
136     {
137       if (!line_spanner_)
138         {
139           line_spanner_ = new Spanner (get_property ("DynamicLineSpanner"));
140
141           Music * rq = accepted_spanreqs_drul_[START];
142           if (script_req_l_)
143             rq =  script_req_l_ ;
144           announce_grob(line_spanner_, rq ? rq->self_scm(): SCM_EOL);
145         }
146     }
147   
148   /*
149     During a (de)crescendo, pending request will not be cleared,
150     and a line-spanner will always be created, as \< \! are already
151     two requests.
152
153     Note: line-spanner must always have at least same duration
154     as (de)crecsendo, b.o. line-breaking.
155   */
156
157   
158
159   /*
160     maybe we should leave dynamic texts to the text-engraver and
161     simply acknowledge them?
162   */
163   if (script_req_l_)
164     {
165       script_p_ = new Item (get_property ("DynamicText"));
166       script_p_->set_grob_property ("text",
167                                    script_req_l_->get_mus_property ("text"));
168       
169       if (Direction d = script_req_l_->get_direction ())
170         Directional_element_interface::set (line_spanner_, d);
171
172       Axis_group_interface::add_element (line_spanner_, script_p_);
173
174       announce_grob(script_p_, script_req_l_->self_scm());
175     }
176
177   if (accepted_spanreqs_drul_[STOP])
178     {
179       /*
180         finish side position alignment if the (de)cresc ends here, and
181         there are no new dynamics.
182        */
183  
184       if (!cresc_p_)
185         {
186           accepted_spanreqs_drul_[STOP]->origin ()->warning
187  (_ ("can't find start of (de)crescendo"));
188           accepted_spanreqs_drul_[STOP] = 0;
189         }
190       else
191         {
192           assert (!finished_cresc_p_ && cresc_p_);
193
194           cresc_p_->set_bound (RIGHT, script_p_
195                                ? script_p_
196                                : unsmob_grob (get_property ("currentMusicalColumn")));
197           add_bound_item (line_spanner_, cresc_p_->get_bound (RIGHT));
198           
199
200           finished_cresc_p_ = cresc_p_;
201           cresc_p_ = 0;
202           current_cresc_req_ = 0;
203         }
204     }
205   
206   if (accepted_spanreqs_drul_[START])
207     {
208       if (current_cresc_req_)
209         {
210           String msg = current_cresc_req_->get_span_dir () == 1
211             ? _ ("already have a crescendo")
212             : _ ("already have a decrescendo");
213       
214           accepted_spanreqs_drul_[START]->origin ()->warning (msg);
215           current_cresc_req_->origin ()->warning (_("Cresc started here"));
216         }
217       else
218         {
219           current_cresc_req_ = accepted_spanreqs_drul_[START];
220
221           /*
222             TODO: Use symbols.
223           */
224
225           String start_type = ly_scm2string (accepted_spanreqs_drul_[START]->get_mus_property ("span-type"));
226
227           /*
228             ugh. Use push/pop?
229           */
230           SCM s = get_property ((start_type + "Spanner").ch_C ());
231           if (!gh_symbol_p (s) || s == ly_symbol2scm ("hairpin"))
232             {
233               cresc_p_  = new Spanner (get_property ("Hairpin"));
234               cresc_p_->set_grob_property ("grow-direction",
235                                            gh_int2scm ((start_type == "crescendo")
236                                                        ? BIGGER : SMALLER));
237               
238             }
239           /*
240             This is a convenient (and legacy) interface to TextSpanners
241             for use in (de)crescendi.
242             Hmm.
243           */
244           else
245             {
246               cresc_p_  = new Spanner (get_property ("TextSpanner"));
247               cresc_p_->set_grob_property ("type", s);
248               daddy_trans_l_->set_property ((start_type
249                                             + "Spanner").ch_C(), SCM_UNDEFINED);
250               s = get_property ((start_type + "Text").ch_C ());
251               /*
252                 FIXME: use markup_p () to check type.
253               */
254               if (gh_string_p (s) || gh_pair_p (s))
255                 {
256                   cresc_p_->set_grob_property ("edge-text",
257                                                gh_cons (s, ly_str02scm ("")));
258                   daddy_trans_l_->set_property ((start_type + "Text").ch_C(),
259                                                 SCM_EOL);
260                 }
261             }
262
263           cresc_p_->set_bound (LEFT, script_p_
264                                ? script_p_
265                                : unsmob_grob (get_property ("currentMusicalColumn")));
266
267           Axis_group_interface::add_element (line_spanner_, cresc_p_);
268
269           add_bound_item (line_spanner_, cresc_p_->get_bound (LEFT));
270           
271           announce_grob(cresc_p_, accepted_spanreqs_drul_[START]->self_scm());
272         }
273     }
274 }
275
276 void
277 Dynamic_engraver::stop_translation_timestep ()
278 {
279   typeset_all ();
280   if (!current_cresc_req_)
281     {
282       finished_line_spanner_ = line_spanner_;
283       line_spanner_ =0;
284       typeset_all ();
285     }
286 }
287
288 void
289 Dynamic_engraver::finalize ()
290 {
291   typeset_all ();
292   
293   if (line_spanner_
294       && !line_spanner_->live())
295     line_spanner_ = 0;
296   if (line_spanner_)
297     {
298       finished_line_spanner_ = line_spanner_;
299       typeset_all ();
300     }
301
302   if (cresc_p_
303       && !cresc_p_->live())
304     cresc_p_ = 0;
305   if (cresc_p_)
306     {
307       current_cresc_req_->origin ()->warning (_ ("unterminated (de)crescendo"));
308       cresc_p_->suicide ();
309       cresc_p_ = 0;
310     }
311 }
312
313 void
314 Dynamic_engraver::typeset_all ()
315 {  
316   /*
317     remove suicided spanners,
318     ugh: we'll need this for every spanner, beam, slur
319     Hmm, how to do this, cleanly?
320     Maybe just check at typeset_grob ()?
321   */
322   if (finished_cresc_p_
323       && !finished_cresc_p_->live())
324     finished_cresc_p_ = 0;
325   if (finished_line_spanner_
326       && !finished_line_spanner_->live())
327     finished_line_spanner_ = 0;
328
329   if (finished_cresc_p_)
330     {
331       if (!finished_cresc_p_->get_bound (RIGHT))
332         {
333           finished_cresc_p_->set_bound (RIGHT, script_p_
334                                         ? script_p_
335                                         : unsmob_grob (get_property ("currentMusicalColumn")));
336
337           if (finished_line_spanner_)
338             add_bound_item (finished_line_spanner_,
339                             finished_cresc_p_->get_bound (RIGHT));
340         }
341       typeset_grob (finished_cresc_p_);
342       finished_cresc_p_ =0;
343     }
344   
345   if (script_p_)
346     {
347       typeset_grob (script_p_);
348       script_p_ = 0;
349     }
350   if (finished_line_spanner_)
351     {
352       /* To make sure that this works */
353       Side_position_interface::add_staff_support (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       typeset_grob (finished_line_spanner_);
385       finished_line_spanner_ = 0;
386     }
387 }
388
389 void
390 Dynamic_engraver::acknowledge_grob (Grob_info i)
391 {
392   if (!line_spanner_)
393     return ;
394   
395   if (Note_column::has_interface (i.grob_l_))
396     {
397       if (line_spanner_
398           /* Don't refill killed spanner */
399           && line_spanner_->live())
400         {
401           Side_position_interface::add_support (line_spanner_,i.grob_l_);
402           add_bound_item (line_spanner_,dynamic_cast<Item*> (i.grob_l_));
403         }
404
405       if (script_p_ && !script_p_->get_parent (X_AXIS))
406         {
407           script_p_->set_parent (i.grob_l_,  X_AXIS);
408         }
409       
410     }
411   else if (Script_interface::has_interface (i.grob_l_) && script_p_)
412     {
413       SCM p = i.grob_l_->get_grob_property ("script-priority");
414
415       if (gh_number_p (p)
416           && gh_scm2int (p) < gh_scm2int (script_p_->get_grob_property ("script-priority")))
417         {
418           Side_position_interface::add_support (line_spanner_, i.grob_l_);
419
420         }         
421     }
422 }
423 ENTER_DESCRIPTION(Dynamic_engraver,
424 /* descr */       "
425 This engraver creates hairpins, dynamic texts, and their vertical
426 alignments.  The symbols are collected onto a DynamicLineSpanner grob
427 which takes care of vertical positioning.  
428 ",
429                   
430 /* creats*/       "DynamicLineSpanner DynamicText Hairpin TextSpanner",
431 /* acks  */       "note-column-interface script-interface",
432 /* reads */       "",
433 /* write */       "");