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