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