]> git.donarmstrong.com Git - lilypond.git/blob - lily/auto-beam-engraver.cc
patch::: 1.3.118.jcn2
[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--2000 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   Beam::set_interface (beam_p);
223
224   for (int i = 0; i < stem_l_arr_p_->size (); i++)
225     {
226       /*
227         watch out for stem tremolos and abbreviation beams
228        */
229       if (Stem::beam_l ((*stem_l_arr_p_)[i]))
230         {
231           scm_unprotect_object (beam_p->self_scm ());
232           return 0;
233         }
234       Beam::add_stem (beam_p,(*stem_l_arr_p_)[i]);
235     }
236   
237   announce_grob (beam_p, 0);
238
239   return beam_p;
240 }
241
242 void
243 Auto_beam_engraver::begin_beam ()
244 {
245   assert (!stem_l_arr_p_);
246   stem_l_arr_p_ = new Link_array<Item>;
247   assert (!grouping_p_);
248   grouping_p_ = new Beaming_info_list;
249   beam_start_moment_ = now_mom ();
250   beam_start_location_ = *unsmob_moment (get_property ("measurePosition"));
251 }
252
253
254 void
255 Auto_beam_engraver::junk_beam () 
256 {
257   assert (stem_l_arr_p_);
258   
259   delete stem_l_arr_p_;
260   stem_l_arr_p_ = 0;
261   delete grouping_p_;
262   grouping_p_ = 0;
263   shortest_mom_ = Moment (1, 8);
264 }
265
266 void
267 Auto_beam_engraver::end_beam ()
268 {
269   if (stem_l_arr_p_->size () < 2)
270     {
271       junk_beam ();
272     }
273   else
274     {
275       finished_beam_p_ = create_beam_p ();
276       if (finished_beam_p_)
277         finished_grouping_p_ = grouping_p_;
278       delete stem_l_arr_p_;
279       stem_l_arr_p_ = 0;
280       grouping_p_ = 0;
281       shortest_mom_ = Moment (1, 8);
282     }
283 }
284
285 void
286 Auto_beam_engraver::typeset_beam ()
287 {
288   if (finished_beam_p_)
289     {
290       finished_grouping_p_->beamify ();
291       Beam::set_beaming (finished_beam_p_, finished_grouping_p_);
292       typeset_grob (finished_beam_p_);
293       finished_beam_p_ = 0;
294     
295       delete finished_grouping_p_;
296       finished_grouping_p_= 0;
297     }
298 }
299
300 void
301 Auto_beam_engraver::start_translation_timestep ()
302 {
303   count_i_ = 0;
304   /*
305     don't beam over skips
306    */
307   if (stem_l_arr_p_)
308     {
309       Moment now = now_mom ();
310       if (extend_mom_ < now)
311         {
312           end_beam ();
313         }
314     }
315 }
316
317 void
318 Auto_beam_engraver::stop_translation_timestep ()
319 {
320   
321   typeset_beam ();
322 }
323
324 void
325 Auto_beam_engraver::finalize ()
326 {
327   /* finished beams may be typeset */
328   typeset_beam ();
329   /* but unfinished may need another announce/acknowledge pass */
330   if (stem_l_arr_p_)
331     junk_beam ();
332 }
333
334 bool
335 Auto_beam_engraver::same_grace_state_b (Grob* e)
336 {
337   bool gr = e->get_grob_property ("grace") == SCM_BOOL_T;
338   SCM wg =get_property ("weAreGraceContext");
339   return (to_boolean (wg)) == gr;
340 }
341
342 void
343 Auto_beam_engraver::acknowledge_grob (Grob_info info)
344 {
345   if (!same_grace_state_b (info.elem_l_))
346     return;
347   
348   if (stem_l_arr_p_)
349     {
350       if (Beam::has_interface (info.elem_l_))
351         {
352           end_beam ();
353         }
354       else if (Bar::has_interface (info.elem_l_))
355         {
356           end_beam ();
357         }
358       else if (Rest::has_interface (info.elem_l_))
359         {
360           end_beam ();
361         }
362     }
363   
364   if (Stem::has_interface (info.elem_l_))
365     {
366       Item* stem_l = dynamic_cast<Item *> (info.elem_l_);
367                                        
368       Rhythmic_req *rhythmic_req = dynamic_cast <Rhythmic_req *> (info.req_l_);
369       if (!rhythmic_req)
370         {
371           programming_error ("Stem must have rhythmic structure");
372           return;
373         }
374       
375       /*
376         Don't (start) auto-beam over empty stems; skips or rests
377         */
378       if (!Stem::heads_i (stem_l))
379         {
380           if (stem_l_arr_p_)
381             end_beam ();
382           return;
383         }
384
385       if (Stem::beam_l (stem_l))
386         {
387           if (stem_l_arr_p_)
388             junk_beam ();
389           return ;
390         }
391               
392       int durlog  = unsmob_duration (rhythmic_req->get_mus_property ("duration"))->duration_log ();
393       
394       if (durlog <= 2)
395         {
396           if (stem_l_arr_p_)
397             end_beam ();
398           return;
399         }
400
401       Moment dur = unsmob_duration (rhythmic_req->get_mus_property ("duration"))->length_mom ();
402       /* FIXME:
403
404         This comment has been here since long:
405
406            if shortest duration would change
407             consider ending and beginning beam first. 
408
409         but the code didn't match: */
410 #if 1
411       consider_end (dur);
412       consider_begin (dur);
413
414       if (dur < shortest_mom_)
415         shortest_mom_ = dur;
416 #else
417       /* I very much suspect that we wanted: */
418
419       consider_end (shortest_mom_);
420       if (dur < shortest_mom_)
421         {
422           shortest_mom_ = dur;
423           consider_end (shortest_mom_);
424         }
425       consider_begin (shortest_mom_);
426 #endif
427
428       if (!stem_l_arr_p_)
429         return;
430       
431       Moment now = now_mom ();
432       
433       grouping_p_->add_stem (now - beam_start_moment_ + beam_start_location_,
434                              durlog - 2);
435       stem_l_arr_p_->push (stem_l);
436       last_add_mom_ = now;
437       extend_mom_ = extend_mom_ >? now + rhythmic_req->length_mom ();
438     }
439 }
440
441 void
442 Auto_beam_engraver::create_grobs ()
443 {
444   if (!count_i_)
445     {
446       consider_end (shortest_mom_);
447       consider_begin (shortest_mom_);
448     }
449   else if (count_i_ > 1)
450     {
451       if (stem_l_arr_p_)
452         {
453           Moment now = now_mom ();
454           if ((extend_mom_ < now)
455               || ((extend_mom_ == now) && (last_add_mom_ != now )))
456             {
457               end_beam ();
458             }
459           else if (!stem_l_arr_p_->size ())
460             {
461               junk_beam ();
462             }
463         }    
464     }
465
466   /*
467     count_i_++ -> 
468
469         auto-beam-engraver.cc:459: warning: value computed is not used (gcc: 2.96) */
470   count_i_ = count_i_ + 1;
471 }