]> git.donarmstrong.com Git - lilypond.git/blob - lily/dynamic-engraver.cc
release: 1.3.37
[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--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8 #include "debug.hh"
9 #include "dimensions.hh"
10 #include "dimension-cache.hh"
11 #include "crescendo.hh"
12 #include "musical-request.hh"
13 #include "lookup.hh"
14 #include "paper-def.hh"
15 #include "paper-column.hh"
16 #include "staff-symbol.hh"
17 #include "note-column.hh"
18 #include "text-item.hh"
19 #include "side-position-interface.hh"
20 #include "engraver.hh"
21 #include "stem.hh"
22 #include "note-head.hh"
23 #include "group-interface.hh"
24 #include "directional-element-interface.hh"
25 #include "staff-symbol-referencer.hh"
26 #include "translator-group.hh"
27 #include "axis-group-interface.hh"
28
29
30 /*
31   TODO:
32
33   * direction of text-dynamic-request if not equalt to direction
34   of line-spanner
35  */
36
37 class Dynamic_line_spanner : public Spanner
38 {
39 public:
40   Dynamic_line_spanner ();
41   VIRTUAL_COPY_CONS(Score_element);
42   void add_column (Note_column*);
43   void add_element (Score_element*);
44 };
45
46 Dynamic_line_spanner::Dynamic_line_spanner ()
47 {
48   set_elt_property ("transparent", SCM_BOOL_T);
49   side_position (this).set_axis (Y_AXIS);
50   Axis_group_interface (this).set_interface ();
51   Axis_group_interface (this).set_axes (X_AXIS, Y_AXIS);
52 }
53
54 void
55 Dynamic_line_spanner::add_column (Note_column* n)
56 {
57   if (!get_bound (LEFT))
58     set_bound (LEFT, n);
59   else
60     set_bound (RIGHT, n);
61
62   add_dependency (n);
63 }
64
65
66 void
67 Dynamic_line_spanner::add_element (Score_element* e)
68 {
69   e->set_parent (this, Y_AXIS);
70   Axis_group_interface (this).add_element (e); 
71 }
72
73 /**
74    print text & hairpin dynamics.
75  */
76 class Dynamic_engraver : public Engraver
77 {
78   Text_item * text_p_;
79   Crescendo * finished_cresc_p_;
80   Crescendo * cresc_p_;
81
82   Text_script_req* text_req_l_;
83   Span_req * span_start_req_l_;
84   Drul_array<Span_req*> span_req_l_drul_;
85
86   Dynamic_line_spanner* line_spanner_;
87   Dynamic_line_spanner* finished_line_spanner_;
88   Moment last_request_mom_;
89
90   Note_column* pending_column_;
91   Link_array<Score_element> pending_element_arr_;
92   
93   void  typeset_all ();
94
95 public:
96   VIRTUAL_COPY_CONS(Translator);
97   Dynamic_engraver ();
98   
99 protected:
100   void announce_element (Score_element_info);
101   
102   virtual void do_removal_processing ();
103   virtual void acknowledge_element (Score_element_info);
104   virtual bool do_try_music (Music *req_l);
105   virtual void do_process_music ();
106   virtual void do_pre_move_processing ();
107   virtual void do_post_move_processing ();
108 };
109
110 ADD_THIS_TRANSLATOR (Dynamic_engraver);
111
112 void
113 Dynamic_engraver::announce_element (Score_element_info i)
114 {
115   group (i.elem_l_, "interfaces").add_thing (ly_symbol2scm ("dynamic"));
116   
117   Engraver::announce_element (i);
118 }
119
120
121 Dynamic_engraver::Dynamic_engraver ()
122 {
123   text_p_ = 0;
124   finished_cresc_p_ = 0;
125   line_spanner_ = 0;
126   finished_line_spanner_ = 0;
127   span_start_req_l_ = 0;
128   cresc_p_ =0;
129   pending_column_ = 0;
130
131   text_req_l_ = 0;
132   span_req_l_drul_[START] = 0;
133   span_req_l_drul_[STOP] = 0;
134 }
135
136 void
137 Dynamic_engraver::do_post_move_processing ()
138 {
139   text_req_l_ = 0;
140   span_req_l_drul_[START] = 0;
141   span_req_l_drul_[STOP] = 0;
142
143   /* ugr; we must attach the Dynamic_line_spanner to something
144      to be sure that the linebreaker will not be confused
145   */
146   // if (line_spanner_)
147   // line_spanner_->add_column (LEFT, get_staff_info ().command_pcol_l ());
148 }
149
150 bool
151 Dynamic_engraver::do_try_music (Music * m)
152 {
153   if (Text_script_req* d = dynamic_cast <Text_script_req*> (m))
154     {
155       if (d->style_str_ == "dynamic")
156         {
157           text_req_l_ = d;
158           return true;
159         }
160     }
161   else if (Span_req* s =  dynamic_cast <Span_req*> (m))
162     {
163       if ((s->span_type_str_ == "crescendo"
164            || s->span_type_str_ == "decrescendo"))
165         {
166           span_req_l_drul_[s->span_dir_] = s;
167           return true;
168         }
169     }
170   return false;
171 }
172
173 void
174 Dynamic_engraver::do_process_music ()
175 {
176   if ((span_req_l_drul_[START] || span_req_l_drul_[STOP] || text_req_l_)
177       && !line_spanner_
178       && pending_element_arr_.size ())
179     {
180       line_spanner_ = new Dynamic_line_spanner;
181       assert (pending_column_);
182       line_spanner_->add_column (pending_column_);
183       announce_element (Score_element_info
184                         (line_spanner_,
185                          text_req_l_ ? text_req_l_ : span_req_l_drul_[START]));
186
187     }
188
189   if (line_spanner_ && pending_element_arr_.size ())
190     {
191       for (int i = 0; i < pending_element_arr_.size (); i++)
192         line_spanner_->add_element (pending_element_arr_[i]);
193       pending_element_arr_.clear ();
194     }
195
196   if (span_req_l_drul_[START] || span_req_l_drul_[STOP] || text_req_l_)
197     last_request_mom_ = now_mom ();
198   else
199     {
200
201 #if 0
202       /*
203         Maybe always creating a line-spanner for a (de)crescendo (see
204         below) is not a good idea:
205
206             a\< b\p \!c
207
208         the \p will be centred on the line-spanner, and thus clash
209         with the hairpin.  When axis-group code is in place, the \p
210         should move below the hairpin, which is probably better?
211
212         Urg, but line-spanner must always have at least same duration
213         as (de)crecsendo, b.o. line-breaking.
214        */
215       if (now_mom () > last_request_mom_)
216 #else
217       /*
218         During a (de)crescendo, pending request will not be cleared,
219         and a line-spanner will always be created, as \< \! are already
220         two requests.
221        */
222       if (now_mom () > last_request_mom_ && !span_start_req_l_)
223 #endif  
224         {
225           for (int i = 0; i < pending_element_arr_.size (); i++)
226             {
227               Score_element* e = pending_element_arr_[i];
228               side_position (e).set_axis (Y_AXIS);
229               side_position (e).add_staff_support ();
230
231               /*
232                 UGH UGH 
233               */
234               Direction d = directional_element (e).get ();
235               if (!d)
236                 {
237                   SCM s = get_property ("dynamicDirection");
238                   if (!isdir_b (s))
239                     s = get_property ("verticalDirection");
240                   if (isdir_b (s))
241                     d = to_dir (s);
242                   directional_element (e).set (d);
243                 }
244           
245               SCM s = get_property ("dynamicPadding");
246               if (gh_number_p (s))
247                 e->set_elt_property ("padding", s);
248               s = get_property ("dynamicMinimumSpace");
249               if (gh_number_p (s))
250                 e->set_elt_property ("minimum-space", s);
251             }
252           pending_element_arr_.clear ();
253           finished_line_spanner_ = line_spanner_;
254           line_spanner_ = 0;
255         }
256     } 
257
258   if (text_req_l_)
259     {
260       String loud = text_req_l_->text_str_;
261
262       text_p_ = new Text_item;
263       text_p_->set_elt_property ("text",
264                                           ly_str02scm (loud.ch_C ()));
265       text_p_->set_elt_property ("style", gh_str02scm ("dynamic"));
266       text_p_->set_elt_property ("script-priority",
267                                           gh_int2scm (100));
268       if (Direction d=text_req_l_->get_direction ())
269         directional_element (text_p_).set (d);
270       pending_element_arr_.push (text_p_);
271       text_p_->set_elt_property ("self-alignment-Y", gh_int2scm (0));
272       text_p_->add_offset_callback (Side_position_interface::aligned_on_self,
273                                     Y_AXIS);
274       announce_element (Score_element_info (text_p_, text_req_l_));
275     }
276
277   if (span_req_l_drul_[STOP])
278     {
279       if (!cresc_p_)
280         {
281           span_req_l_drul_[STOP]->warning
282             (_ ("can't find start of (de)crescendo"));
283         }
284       else
285         {
286           assert (!finished_cresc_p_);
287           cresc_p_->set_bound(RIGHT, get_staff_info ().musical_pcol_l ());
288           finished_cresc_p_ = cresc_p_;
289           cresc_p_ = 0;
290           span_start_req_l_ = 0;
291         }
292     }
293
294   if (span_req_l_drul_[START])
295     {
296       if (span_start_req_l_)
297         {
298           span_req_l_drul_[START]->warning
299             (span_start_req_l_->span_dir_ == 1
300              ?
301              _ ("already have a crescendo")
302              : _ ("already have a decrescendo"));
303         }
304       else
305         {
306           span_start_req_l_ = span_req_l_drul_[START];
307           cresc_p_  = new Crescendo;
308           cresc_p_->set_elt_property
309             ("grow-direction",
310              gh_int2scm ((span_req_l_drul_[START]->span_type_str_ == "crescendo")
311                          ? BIGGER : SMALLER));
312               
313           SCM s = get_property (span_req_l_drul_[START]->span_type_str_ + "Text");
314           if (gh_string_p (s))
315             {
316               cresc_p_->set_elt_property ("start-text", s);
317               daddy_trans_l_->set_property (span_req_l_drul_[START]->span_type_str_
318                                             + "Text", SCM_UNDEFINED);
319             }
320
321           s = get_property (span_req_l_drul_[START]->span_type_str_ + "Spanner");
322           if (gh_string_p (s)) //&& ly_scm2string (s) != "hairpin")
323             {
324               cresc_p_->set_elt_property ("spanner", s);
325               daddy_trans_l_->set_property (span_req_l_drul_[START]->span_type_str_
326                                             + "Spanner", SCM_UNDEFINED);
327             }
328
329           cresc_p_->set_bound(LEFT, get_staff_info ().musical_pcol_l ());
330
331
332           /* 
333               We know how wide the text is, if we can be sure that the
334               text already has relevant pointers into the paperdef,
335               and it has its font-size property set.
336
337               Since font-size may be set by a context higher up, we
338               can not be sure of the size.
339
340
341               We shouldn't try to do this stuff here, the Item should
342               do it when the score is finished.  We could maybe
343               set a callback to have the Item do the alignment if
344               it is not a special symbol, like Crescendo.
345           */
346
347              
348           if (text_p_)
349             {
350               index_set_cell (cresc_p_->get_elt_property ("dynamic-drul"),
351                               LEFT, SCM_BOOL_T);
352               if (finished_cresc_p_)
353                 index_set_cell (finished_cresc_p_->get_elt_property ("dynamic-drul"),
354                                 RIGHT, SCM_BOOL_T);
355             }
356           pending_element_arr_.push (cresc_p_);
357           cresc_p_->set_elt_property ("self-alignment-Y", gh_int2scm (0));
358           cresc_p_->add_offset_callback
359             (Side_position_interface::aligned_on_self, Y_AXIS);
360           announce_element (Score_element_info (cresc_p_, span_req_l_drul_[START]));
361         }
362     }
363 }
364
365 void
366 Dynamic_engraver::do_pre_move_processing ()
367 {
368   typeset_all ();
369 }
370
371 void
372 Dynamic_engraver::do_removal_processing ()
373 {
374   if (cresc_p_)
375     {
376       typeset_element (cresc_p_ );
377       span_start_req_l_->warning (_ ("unterminated (de)crescendo"));
378       cresc_p_ =0;
379     }
380   typeset_all ();
381   if (line_spanner_)
382     {
383       side_position (line_spanner_).add_staff_support ();
384       typeset_element (line_spanner_);
385       line_spanner_ = 0;
386     }
387 }
388
389 void
390 Dynamic_engraver::typeset_all ()
391 {  
392   if (finished_cresc_p_)
393     {
394       typeset_element (finished_cresc_p_);
395       finished_cresc_p_ =0;
396     }
397   
398   if (text_p_)
399     {
400       typeset_element (text_p_);
401       text_p_ = 0;
402     }
403
404   /*
405     TODO: This should be optionised:
406       * break when group of dynamic requests ends
407       * break now 
408       * continue through piece */
409   //  if (line_spanner_ && last_request_mom_ < now_mom ())
410   if (finished_line_spanner_)
411     {
412       side_position (finished_line_spanner_).add_staff_support ();
413       typeset_element (finished_line_spanner_);
414       finished_line_spanner_ = 0;
415     }
416 }
417
418 void
419 Dynamic_engraver::acknowledge_element (Score_element_info i)
420 {
421   if (Note_column* n = dynamic_cast<Note_column*> (i.elem_l_))
422     {
423       if (line_spanner_)
424         {
425           side_position (line_spanner_).add_support (n);
426           line_spanner_->add_column (n);
427         }
428       else
429         {
430           pending_column_ = n;
431         }
432     }
433 }