]> git.donarmstrong.com Git - lilypond.git/blob - lily/auto-beam-engraver.cc
27283b34dde2f7b69444f9ea3f730d7b480e7e9e
[lilypond.git] / lily / auto-beam-engraver.cc
1 /*   
2   auto-beam-engraver.cc --  implement Auto_beam_engraver
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 1999--2001 Jan Nieuwenhuizen <janneke@gnu.org>
7   
8  */
9
10 #include "beaming.hh"
11 #include "musical-request.hh"
12 #include "beam.hh"
13 #include "stem.hh"
14 #include "debug.hh"
15 #include "engraver-group-engraver.hh"
16 #include "bar.hh"
17 #include "rest.hh"
18 #include "engraver.hh"
19 #include "item.hh"
20 #include "spanner.hh"
21
22 /*
23   TODO: figure what to do in grace?
24
25   TODO: documentme.
26  */
27 class Auto_beam_engraver : public Engraver
28 {
29 public:
30   Auto_beam_engraver ();
31   VIRTUAL_COPY_CONS (Translator);
32
33 protected:
34   virtual void stop_translation_timestep ();
35   virtual void start_translation_timestep ();
36   virtual void finalize ();
37   virtual void acknowledge_grob (Grob_info);
38   virtual void create_grobs ();
39
40 private:
41   bool test_moment (Direction, Moment);
42   void consider_begin (Moment);
43   void consider_end (Moment);
44   Spanner* create_beam_p ();
45   void begin_beam ();
46   void end_beam ();
47   void junk_beam ();
48   bool same_grace_state_b (Grob* e);
49   void typeset_beam ();
50
51   /*
52     shortest_mom is the shortest note in the beam.
53    */
54   Moment shortest_mom_;
55   Spanner *finished_beam_p_;
56   Link_array<Item>* stem_l_arr_p_;
57
58
59   int count_i_;
60   Moment last_add_mom_;
61
62   /*
63     Projected ending of the  beam we're working on.
64    */
65   Moment extend_mom_;
66   Moment beam_start_moment_;
67   Moment beam_start_location_;
68   
69   // We act as if beam were created, and start a grouping anyway.
70   Beaming_info_list*grouping_p_;  
71   Beaming_info_list*finished_grouping_p_;
72 };
73
74 ADD_THIS_TRANSLATOR (Auto_beam_engraver);
75
76 Auto_beam_engraver::Auto_beam_engraver ()
77 {
78   count_i_ = 0;
79   stem_l_arr_p_ = 0;
80   shortest_mom_ = Moment (1, 8);
81   finished_beam_p_ = 0;
82   finished_grouping_p_ = 0;
83   grouping_p_ = 0;
84 }
85
86 /*
87   Determine end moment for auto beaming (or begin moment, but mostly
88   0==anywhere) In order of increasing priority:
89   
90   i.   begin anywhere, end at every beat
91   ii.  end   *    <num> <den>
92   iii. end <type> <num> <den>
93   
94   iv.  end   *      *     *
95   v.   end <type>   *     *
96   
97   
98   Rationale:
99   
100   [to be defined in config file]
101   i.   easy catch-all rule
102   ii.  exceptions for time signature
103   iii. exceptions for time signature, for specific duration type
104   
105   [user override]
106   iv.  generic override
107   v.   override for specific duration type
108   
109 */
110 bool
111 Auto_beam_engraver::test_moment (Direction dir, Moment test_mom)
112 {
113   SCM wild = gh_list (ly_symbol2scm ("*"), ly_symbol2scm ("*"), SCM_UNDEFINED);
114   SCM function;
115   if (dir == START)
116     function = gh_list (ly_symbol2scm ("begin"), SCM_UNDEFINED);
117   else
118     function = gh_list (ly_symbol2scm ("end"), SCM_UNDEFINED);
119
120   Moment one_beat = *unsmob_moment( get_property ("beatLength"));
121   int num = *unsmob_moment (get_property("measureLength")) / one_beat;
122   int den = one_beat.den_i ();
123   SCM time = gh_list (gh_int2scm (num), gh_int2scm (den), SCM_UNDEFINED);
124
125   SCM type = gh_list (gh_int2scm (test_mom.num_i ()),
126                       gh_int2scm (test_mom.den_i ()), SCM_UNDEFINED);
127
128   SCM settings = get_property ("autoBeamSettings");
129   
130   /* first guess */
131   
132   /* begin beam at any position
133    (and fallback for end) */
134   Moment moment (0);
135   
136   /* end beam at end of beat */
137   if (dir == STOP)
138     {
139       SCM beat (get_property ("beatLength"));
140       
141       if (unsmob_moment (beat))
142         moment = *unsmob_moment (beat);
143     }
144
145   /* second guess: property generic time exception */
146   SCM m = gh_assoc (gh_append3 (function, wild, time), settings);
147   
148   if (m != SCM_BOOL_F && unsmob_moment (gh_cdr (m)))
149     moment = * unsmob_moment (gh_cdr (m));
150
151   /* third guess: property time exception, specific for duration type */
152   m = gh_assoc (gh_append3 (function, type, time), settings);
153   if (m != SCM_BOOL_F && unsmob_moment (gh_cdr (m)))
154     moment = * unsmob_moment (gh_cdr (m));
155
156   /* fourth guess [user override]: property plain generic */
157   m = gh_assoc (gh_append3 (function, wild, wild), settings);
158   if (m != SCM_BOOL_F && unsmob_moment (gh_cdr (m)))
159     moment = * unsmob_moment (gh_cdr (m));
160
161   /* fifth guess [user override]: property plain, specific for duration type */
162   m = gh_assoc (gh_append3 (function, type, wild), settings);
163   if (m != SCM_BOOL_F && unsmob_moment (gh_cdr (m)))
164     moment = * unsmob_moment (gh_cdr (m));
165   
166   Rational r;
167   if (moment)
168     {
169       /* Ugh? measurePosition can be negative, when \partial
170          We may have to fix this elsewhere (timing translator)
171         r = unsmob_moment (get_property ("measurePosition"))->mod_rat (moment);
172       */
173       Moment pos = * unsmob_moment (get_property ("measurePosition"));
174       if (pos < Moment (0))
175         {
176           Moment length = * unsmob_moment (get_property ("measureLength"));
177           pos = length - pos;
178         }
179       r = pos.mod_rat (moment);
180     }
181   else
182     {
183       if (dir == START)
184         /* if undefined, starting is ok */
185         r = 0;
186       else
187         /* but ending is not */
188         r = 1;
189     }
190   return !r;
191 }
192
193 void
194 Auto_beam_engraver::consider_begin (Moment test_mom)
195 {
196   bool off = to_boolean (get_property ("noAutoBeaming"));
197   if (!stem_l_arr_p_ && ! off)
198     {
199       bool b = test_moment (START, test_mom);
200       if (b)
201         begin_beam ();
202     }
203 }
204
205 void
206 Auto_beam_engraver::consider_end (Moment test_mom)
207 {
208   if (stem_l_arr_p_)
209     {
210       /* Allow already started autobeam to end:
211          don't check for noAutoBeaming */
212       bool b = test_moment (STOP, test_mom);
213       if (b)
214         end_beam ();
215     }
216 }
217
218 Spanner*
219 Auto_beam_engraver::create_beam_p ()
220 {
221   Spanner* beam_p = new Spanner (get_property ("Beam"));
222   for (int i = 0; i < stem_l_arr_p_->size (); i++)
223     {
224       /*
225         watch out for stem tremolos and abbreviation beams
226        */
227       if (Stem::beam_l ((*stem_l_arr_p_)[i]))
228         {
229           scm_unprotect_object (beam_p->self_scm ());
230           return 0;
231         }
232       Beam::add_stem (beam_p,(*stem_l_arr_p_)[i]);
233     }
234   
235   announce_grob (beam_p, 0);
236
237   return beam_p;
238 }
239
240 void
241 Auto_beam_engraver::begin_beam ()
242 {
243   assert (!stem_l_arr_p_);
244   stem_l_arr_p_ = new Link_array<Item>;
245   assert (!grouping_p_);
246   grouping_p_ = new Beaming_info_list;
247   beam_start_moment_ = now_mom ();
248   beam_start_location_ = *unsmob_moment (get_property ("measurePosition"));
249 }
250
251
252 void
253 Auto_beam_engraver::junk_beam () 
254 {
255   assert (stem_l_arr_p_);
256   
257   delete stem_l_arr_p_;
258   stem_l_arr_p_ = 0;
259   delete grouping_p_;
260   grouping_p_ = 0;
261   shortest_mom_ = Moment (1, 8);
262 }
263
264 void
265 Auto_beam_engraver::end_beam ()
266 {
267   if (stem_l_arr_p_->size () < 2)
268     {
269       junk_beam ();
270     }
271   else
272     {
273       finished_beam_p_ = create_beam_p ();
274       if (finished_beam_p_)
275         finished_grouping_p_ = grouping_p_;
276       delete stem_l_arr_p_;
277       stem_l_arr_p_ = 0;
278       grouping_p_ = 0;
279       shortest_mom_ = Moment (1, 8);
280     }
281 }
282
283 void
284 Auto_beam_engraver::typeset_beam ()
285 {
286   if (finished_beam_p_)
287     {
288       finished_grouping_p_->beamify ();
289       Beam::set_beaming (finished_beam_p_, finished_grouping_p_);
290       typeset_grob (finished_beam_p_);
291       finished_beam_p_ = 0;
292     
293       delete finished_grouping_p_;
294       finished_grouping_p_= 0;
295     }
296 }
297
298 void
299 Auto_beam_engraver::start_translation_timestep ()
300 {
301   count_i_ = 0;
302   /*
303     don't beam over skips
304    */
305   if (stem_l_arr_p_)
306     {
307       Moment now = now_mom ();
308       if (extend_mom_ < now)
309         {
310           end_beam ();
311         }
312     }
313 }
314
315 void
316 Auto_beam_engraver::stop_translation_timestep ()
317 {
318   
319   typeset_beam ();
320 }
321
322 void
323 Auto_beam_engraver::finalize ()
324 {
325   /* finished beams may be typeset */
326   typeset_beam ();
327   /* but unfinished may need another announce/acknowledge pass */
328   if (stem_l_arr_p_)
329     junk_beam ();
330 }
331
332 bool
333 Auto_beam_engraver::same_grace_state_b (Grob* e)
334 {
335   bool gr = e->get_grob_property ("grace") == SCM_BOOL_T;
336   SCM wg =get_property ("weAreGraceContext");
337   return (to_boolean (wg)) == gr;
338 }
339
340 void
341 Auto_beam_engraver::acknowledge_grob (Grob_info info)
342 {
343   if (!same_grace_state_b (info.elem_l_))
344     return;
345   
346   if (stem_l_arr_p_)
347     {
348       if (Beam::has_interface (info.elem_l_))
349         {
350           end_beam ();
351         }
352       else if (Bar::has_interface (info.elem_l_))
353         {
354           end_beam ();
355         }
356       else if (Rest::has_interface (info.elem_l_))
357         {
358           end_beam ();
359         }
360     }
361   
362   if (Stem::has_interface (info.elem_l_))
363     {
364       Item* stem_l = dynamic_cast<Item *> (info.elem_l_);
365                                        
366       Rhythmic_req *rhythmic_req = dynamic_cast <Rhythmic_req *> (info.req_l_);
367       if (!rhythmic_req)
368         {
369           programming_error ("Stem must have rhythmic structure");
370           return;
371         }
372       
373       /*
374         Don't (start) auto-beam over empty stems; skips or rests
375         */
376       if (!Stem::heads_i (stem_l))
377         {
378           if (stem_l_arr_p_)
379             end_beam ();
380           return;
381         }
382
383       if (Stem::beam_l (stem_l))
384         {
385           if (stem_l_arr_p_)
386             junk_beam ();
387           return ;
388         }
389               
390       int durlog  = unsmob_duration (rhythmic_req->get_mus_property ("duration"))->duration_log ();
391       
392       if (durlog <= 2)
393         {
394           if (stem_l_arr_p_)
395             end_beam ();
396           return;
397         }
398
399       Moment dur = unsmob_duration (rhythmic_req->get_mus_property ("duration"))->length_mom ();
400       /* FIXME:
401
402         This comment has been here since long:
403
404            if shortest duration would change
405             consider ending and beginning beam first. 
406
407         but the code didn't match: */
408 #if 1
409       consider_end (dur);
410       consider_begin (dur);
411
412       if (dur < shortest_mom_)
413         shortest_mom_ = dur;
414 #else
415       /* I very much suspect that we wanted: */
416
417       consider_end (shortest_mom_);
418       if (dur < shortest_mom_)
419         {
420           shortest_mom_ = dur;
421           consider_end (shortest_mom_);
422         }
423       consider_begin (shortest_mom_);
424 #endif
425
426       if (!stem_l_arr_p_)
427         return;
428       
429       Moment now = now_mom ();
430       
431       grouping_p_->add_stem (now - beam_start_moment_ + beam_start_location_,
432                              durlog - 2);
433       stem_l_arr_p_->push (stem_l);
434       last_add_mom_ = now;
435       extend_mom_ = extend_mom_ >? now + rhythmic_req->length_mom ();
436     }
437 }
438
439 void
440 Auto_beam_engraver::create_grobs ()
441 {
442   if (!count_i_)
443     {
444       consider_end (shortest_mom_);
445       consider_begin (shortest_mom_);
446     }
447   else if (count_i_ > 1)
448     {
449       if (stem_l_arr_p_)
450         {
451           Moment now = now_mom ();
452           if ((extend_mom_ < now)
453               || ((extend_mom_ == now) && (last_add_mom_ != now )))
454             {
455               end_beam ();
456             }
457           else if (!stem_l_arr_p_->size ())
458             {
459               junk_beam ();
460             }
461         }    
462     }
463
464   /*
465     count_i_++ -> 
466
467         auto-beam-engraver.cc:459: warning: value computed is not used (gcc: 2.96) */
468   count_i_ = count_i_ + 1;
469 }