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