]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
*** empty log message ***
[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 "item.hh"
18 #include "new-slur.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   Grob *slur_;
48   Spanner *cresc_;
49
50   Spanner *finished_line_spanner_;
51   Spanner *finished_cresc_;
52
53   Music *script_ev_;
54   Music *current_cresc_ev_;
55   
56   Drul_array<Music*> accepted_spanreqs_drul_;
57
58   Link_array<Note_column> pending_columns_;
59   Link_array<Grob> pending_elements_;
60   
61   void typeset_all ();
62
63   TRANSLATOR_DECLARATIONS (Dynamic_engraver);
64   
65 protected:
66   virtual void finalize ();
67   virtual void acknowledge_grob (Grob_info);
68   virtual bool try_music (Music *req);
69   virtual void stop_translation_timestep ();
70   virtual void process_music ();  
71 };
72
73
74
75
76 Dynamic_engraver::Dynamic_engraver ()
77 {
78   script_ = 0;
79   slur_ = 0;
80   finished_cresc_ = 0;
81   line_spanner_ = 0;
82   finished_line_spanner_ = 0;
83   current_cresc_ev_ = 0;
84   cresc_ =0;
85
86   script_ev_ = 0;
87   accepted_spanreqs_drul_[START] = 0;
88   accepted_spanreqs_drul_[STOP] = 0;
89 }
90
91 bool
92 Dynamic_engraver::try_music (Music * m)
93 {
94   if (m->is_mus_type ("absolute-dynamic-event"))
95     {
96       /*
97         TODO: probably broken.
98       */
99       script_ev_ = m;
100       return true;
101     }
102   else if (m->is_mus_type ("decrescendo-event")
103            || m->is_mus_type ("crescendo-event"))
104     {
105       Direction d = to_dir (m->get_property ("span-direction"));
106
107       accepted_spanreqs_drul_[d] = m;
108       if (current_cresc_ev_ && d == START)
109         accepted_spanreqs_drul_[STOP] = m;
110       return true;
111     }
112   return false;
113 }
114
115 void
116 Dynamic_engraver::process_music ()
117 {
118   if (accepted_spanreqs_drul_[START] || accepted_spanreqs_drul_[STOP] || script_ev_)
119     {
120       if (!line_spanner_)
121         {
122           Music * rq = accepted_spanreqs_drul_[START];
123           line_spanner_ = make_spanner ("DynamicLineSpanner", rq ? rq->self_scm (): SCM_EOL );
124
125           if (script_ev_)
126             rq =  script_ev_;
127         }
128     }
129   
130   /*
131     During a (de)crescendo, pending event will not be cleared,
132     and a line-spanner will always be created, as \< \! are already
133     two events.
134
135     Note: line-spanner must always have at least same duration
136     as (de)crecsendo, b.o. line-breaking.
137   */
138
139   
140
141   /*
142     maybe we should leave dynamic texts to the text-engraver and
143     simply acknowledge them?
144   */
145   if (script_ev_)
146     {
147       script_ = make_item ("DynamicText", script_ev_->self_scm ());
148       script_->set_property ("text",
149                              script_ev_->get_property ("text"));
150
151       
152       if (Direction d = to_dir (script_ev_->get_property ("direction")))
153         set_grob_direction (line_spanner_, d);
154
155       Axis_group_interface::add_element (line_spanner_, script_);
156     }
157
158   Music *stop_ev = accepted_spanreqs_drul_ [STOP] ?
159     accepted_spanreqs_drul_[STOP] : script_ev_;
160
161   if (accepted_spanreqs_drul_[STOP] || script_ev_)
162     {
163       /*
164         finish side position alignment if the (de)cresc ends here, and
165         there are no new dynamics.
166        */
167
168
169       if (cresc_)
170         {
171           assert (!finished_cresc_ && cresc_);
172
173           cresc_->set_bound (RIGHT, script_
174                                ? script_
175                                : unsmob_grob (get_property ("currentMusicalColumn")));
176           add_bound_item (line_spanner_, cresc_->get_bound (RIGHT));
177           
178
179           finished_cresc_ = cresc_;
180           cresc_ = 0;
181           current_cresc_ev_ = 0;
182         }
183       else if (accepted_spanreqs_drul_[STOP])
184         {
185           accepted_spanreqs_drul_[STOP]->origin ()->warning (_ ("can't find start of (de)crescendo"));
186           stop_ev = 0;
187         }
188       
189     }
190   
191   if (accepted_spanreqs_drul_[START])
192     {
193       if (current_cresc_ev_)
194         {
195           String msg = _ ("already have a decrescendo");
196           if (current_cresc_ev_->is_mus_type ("decrescendo-event"))
197             msg = _ ("already have a crescendo");
198
199           accepted_spanreqs_drul_[START]->origin ()->warning (msg);
200           current_cresc_ev_->origin ()->warning (_("Cresc started here"));
201         }
202       else
203         {
204           current_cresc_ev_ = accepted_spanreqs_drul_[START];
205
206           if (Direction d = to_dir (current_cresc_ev_->get_property ("direction")))
207             set_grob_direction (line_spanner_, d);
208
209           /*
210             TODO: Use symbols.
211           */
212
213           String start_type = 
214             ly_symbol2string (current_cresc_ev_->get_property ("name"));
215
216           /*
217             ugh. Use push/pop?
218           */
219           if (start_type == "DecrescendoEvent")
220             start_type = "decrescendo";
221           else if (start_type == "CrescendoEvent")
222             start_type = "crescendo";
223           
224           SCM s = get_property ((start_type + "Spanner").to_str0 ());
225           if (!ly_c_symbol_p (s) || s == ly_symbol2scm ("hairpin"))
226             {
227               cresc_  = make_spanner ("Hairpin", accepted_spanreqs_drul_[START]->self_scm ());
228               cresc_->set_property ("grow-direction",
229                                     scm_int2num ((start_type == "crescendo")
230                                                  ? BIGGER : SMALLER));
231               
232             }
233
234           
235           /*
236             This is a convenient (and legacy) interface to TextSpanners
237             for use in (de)crescendi.
238             Hmm.
239           */
240           else
241             {
242               cresc_  = make_spanner ("DynamicTextSpanner", accepted_spanreqs_drul_[START]->self_scm ());
243               cresc_->set_property ("style", s);
244               context ()->set_property ((start_type
245                                          + "Spanner").to_str0 (), SCM_EOL);
246               s = get_property ((start_type + "Text").to_str0 ());
247               /*
248                 FIXME: use get_markup () to check type.
249               */
250               if (ly_c_string_p (s) || ly_c_pair_p (s))
251                 {
252                   cresc_->set_property ("edge-text",
253                                         scm_cons (s, scm_makfrom0str ("")));
254                   context ()->set_property ((start_type + "Text").to_str0 (),
255                                             SCM_EOL);
256                 }
257             }
258
259           cresc_->set_bound (LEFT, script_
260                              ? script_
261                              : unsmob_grob (get_property ("currentMusicalColumn")));
262
263           Axis_group_interface::add_element (line_spanner_, cresc_);
264
265           add_bound_item (line_spanner_, cresc_->get_bound (LEFT));
266         }
267     }
268 }
269
270 void
271 Dynamic_engraver::stop_translation_timestep ()
272 {
273   typeset_all ();
274   if (!current_cresc_ev_)
275     {
276       
277       finished_line_spanner_ = line_spanner_;
278       line_spanner_ = 0;
279       typeset_all ();
280       slur_ = 0;
281     }
282
283   script_ev_ = 0;
284   accepted_spanreqs_drul_[START] = 0;
285   accepted_spanreqs_drul_[STOP] = 0;
286 }
287
288 void
289 Dynamic_engraver::finalize ()
290 {
291   typeset_all ();
292   
293   if (line_spanner_
294       && !line_spanner_->is_live ())
295     line_spanner_ = 0;
296   if (line_spanner_)
297     {
298       finished_line_spanner_ = line_spanner_;
299       typeset_all ();
300     }
301
302   if (cresc_
303       && !cresc_->is_live ())
304     cresc_ = 0;
305   if (cresc_)
306     {
307       current_cresc_ev_->origin ()->warning (_ ("unterminated (de)crescendo"));
308       cresc_->suicide ();
309       cresc_ = 0;
310     }
311 }
312
313 void
314 Dynamic_engraver::typeset_all ()
315 {
316   /* Simplistic slur collision handling.  This fixes simple collisions
317      like
318      
319          a\p( b)
320
321      which are unacceptable, but it most probably breaks for more
322      interesting cases.  Maybe make a new colission engraver.  */
323   if (finished_line_spanner_ && slur_
324       && get_slur_dir (slur_) == get_grob_direction (finished_line_spanner_))
325     {
326       Real ss = Staff_symbol_referencer::staff_space (finished_line_spanner_);
327       Real pad = robust_scm2double (finished_line_spanner_->get_property ("padding"), 0);
328       /* FIXME: 1ss padding hardcoded */
329       finished_line_spanner_->set_property ("padding",
330                                             scm_make_real (pad + ss));
331     }
332
333   if (finished_cresc_)
334     {
335       if (!finished_cresc_->get_bound (RIGHT))
336         {
337           finished_cresc_->set_bound (RIGHT, script_
338                                       ? script_
339                                       : unsmob_grob (get_property ("currentMusicalColumn")));
340           
341           if (finished_line_spanner_)
342             add_bound_item (finished_line_spanner_,
343                             finished_cresc_->get_bound (RIGHT));
344         }
345       finished_cresc_ =0;
346     }
347   
348   script_ = 0;
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       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_->is_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   else if (Script_interface::has_interface (i.grob_) && script_)
408     {
409       SCM p = i.grob_->get_property ("script-priority");
410
411       /*
412         UGH.
413
414         DynamicText doesn't really have a script-priority field.
415        */
416       if (ly_c_number_p (p)
417           && ly_scm2int (p)
418           < ly_scm2int (script_->get_property ("script-priority")))
419         Side_position_interface::add_support (line_spanner_, i.grob_);
420     }
421   else if (New_slur::has_interface (i.grob_) && line_spanner_)
422     slur_ = i.grob_;
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 new-slur-interface",
435 /* reads */       "",
436 /* write */       "");