]> git.donarmstrong.com Git - lilypond.git/blob - lily/auto-beam-engraver.cc
Fix issue 1641 -- Make \noBeam terminate the current autobeam
[lilypond.git] / lily / auto-beam-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1999--2011 Jan Nieuwenhuizen <janneke@gnu.org>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "bar-line.hh"
21 #include "beaming-pattern.hh"
22 #include "beam.hh"
23 #include "context.hh"
24 #include "duration.hh"
25 #include "engraver.hh"
26 #include "item.hh"
27 #include "rest.hh"
28 #include "spanner.hh"
29 #include "stream-event.hh"
30 #include "stem.hh"
31 #include "warn.hh"
32
33 #include "translator.icc"
34
35 class Auto_beam_engraver : public Engraver
36 {
37   TRANSLATOR_DECLARATIONS (Auto_beam_engraver);
38
39 protected:
40   void stop_translation_timestep ();
41   void process_music ();
42   virtual void finalize ();
43   virtual void derived_mark () const;
44
45   DECLARE_ACKNOWLEDGER (rest);
46   DECLARE_ACKNOWLEDGER (beam);
47   DECLARE_ACKNOWLEDGER (bar_line);
48   DECLARE_ACKNOWLEDGER (breathing_sign);
49   DECLARE_ACKNOWLEDGER (stem);
50   DECLARE_TRANSLATOR_LISTENER (beam_forbid);
51
52   void process_acknowledged ();
53
54 private:
55   bool test_moment (Direction, Moment, Moment);
56   void consider_begin (Moment, Moment);
57   void consider_end (Moment, Moment);
58   Spanner *create_beam ();
59   void begin_beam ();
60   void end_beam ();
61   void junk_beam ();
62   bool is_same_grace_state (Grob *e);
63   void recheck_beam ();
64   void typeset_beam ();
65   vector<Item *> *remove_end_stems (vsize);
66
67   Stream_event *forbid_;
68   /*
69     shortest_mom_ is the shortest note in the beam.
70   */
71   Moment shortest_mom_;
72   Spanner *finished_beam_;
73   vector<Item *> *stems_;
74
75   int process_acknowledged_count_;
76   Moment last_add_mom_;
77   /*
78     Projected ending of the  beam we're working on.
79   */
80   Moment extend_mom_;
81   Moment beam_start_moment_;
82   Moment beam_start_location_;
83
84   // We act as if beam were created, and start a grouping anyway.
85   Beaming_pattern *grouping_;
86   SCM beam_settings_;
87
88   Beaming_pattern *finished_grouping_;
89
90
91   Beaming_options beaming_options_;
92   Beaming_options finished_beaming_options_;
93
94
95   void check_bar_property ();
96 };
97
98 void
99 Auto_beam_engraver::derived_mark () const
100 {
101   scm_gc_mark (beam_settings_);
102 }
103
104 void
105 Auto_beam_engraver::check_bar_property ()
106 {
107   /* Duplicated from process_music (), since
108      Repeat_acknowledge_engraver::process_music () may also set whichBar.  */
109
110   Moment now = now_mom ();
111   if (scm_is_string (get_property ("whichBar"))
112       && beam_start_moment_ < now)
113     {
114       consider_end (measure_position (context ()), shortest_mom_);
115       junk_beam ();
116     }
117 }
118
119 void
120 Auto_beam_engraver::process_music ()
121 {
122   Moment now = now_mom ();
123   /*
124     don't beam over skips
125   */
126   if (stems_)
127     {
128       if (extend_mom_ < now)
129         end_beam ();
130     }
131
132   if (scm_is_string (get_property ("whichBar")))
133     {
134       consider_end (measure_position (context ()), shortest_mom_);
135       junk_beam ();
136     }
137
138   if (forbid_)
139     {
140       if (stems_)
141         end_beam ();
142       else
143         junk_beam ();
144     }
145 }
146
147 Auto_beam_engraver::Auto_beam_engraver ()
148 {
149   forbid_ = 0;
150   process_acknowledged_count_ = 0;
151   stems_ = 0;
152   shortest_mom_ = Moment (Rational (1, 4));
153   finished_beam_ = 0;
154   finished_grouping_ = 0;
155   grouping_ = 0;
156   beam_settings_ = SCM_EOL;
157 }
158
159 IMPLEMENT_TRANSLATOR_LISTENER (Auto_beam_engraver, beam_forbid);
160 void
161 Auto_beam_engraver::listen_beam_forbid (Stream_event *ev)
162 {
163   ASSIGN_EVENT_ONCE (forbid_, ev);
164 }
165
166 bool
167 Auto_beam_engraver::test_moment (Direction dir, Moment test_mom, Moment dur)
168 {
169   return scm_call_4 (get_property ("autoBeamCheck"),
170                      context ()->self_scm (),
171                      scm_from_int (dir),
172                      test_mom.smobbed_copy (),
173                      dur.smobbed_copy ())
174     != SCM_BOOL_F;
175 }
176
177 void
178 Auto_beam_engraver::consider_begin (Moment test_mom, Moment dur)
179 {
180   bool on = to_boolean (get_property ("autoBeaming"));
181   if (!stems_ && on
182       && !forbid_)
183     {
184       bool b = test_moment (START, test_mom, dur);
185       if (b)
186         begin_beam ();
187     }
188 }
189
190 void
191 Auto_beam_engraver::consider_end (Moment test_mom, Moment dur)
192 {
193   if (stems_)
194     {
195       /* Allow already started autobeam to end:
196          don't check for autoBeaming */
197       bool b = test_moment (STOP, test_mom, dur);
198       if (b)
199         end_beam ();
200     }
201 }
202
203 Spanner *
204 Auto_beam_engraver::create_beam ()
205 {
206   if (to_boolean (get_property ("skipTypesetting")))
207     return 0;
208
209   for (vsize i = 0; i < stems_->size (); i++)
210     if (Stem::get_beam ((*stems_)[i]))
211       return 0;
212
213   /*
214     Can't use make_spanner () because we have to use
215     beam_settings_.
216   */
217   Spanner *beam = new Spanner (beam_settings_);
218
219   for (vsize i = 0; i < stems_->size (); i++)
220     Beam::add_stem (beam, (*stems_)[i]);
221
222   announce_grob (beam, (*stems_)[0]->self_scm ());
223
224   return beam;
225 }
226
227 void
228 Auto_beam_engraver::begin_beam ()
229 {
230   if (stems_ || grouping_)
231     {
232       programming_error ("already have autobeam");
233       return;
234     }
235
236   stems_ = new vector<Item *>;
237   grouping_ = new Beaming_pattern ();
238   beaming_options_.from_context (context ());
239   beam_settings_ = updated_grob_properties (context (), ly_symbol2scm ("Beam"));
240
241   beam_start_moment_ = now_mom ();
242   beam_start_location_
243     = robust_scm2moment (get_property ("measurePosition"), Moment (0));
244 }
245
246 void
247 Auto_beam_engraver::junk_beam ()
248 {
249   if (!stems_)
250     return;
251
252   delete stems_;
253   stems_ = 0;
254   delete grouping_;
255   grouping_ = 0;
256   beam_settings_ = SCM_EOL;
257
258   shortest_mom_ = Moment (Rational (1, 4));
259 }
260
261 void
262 Auto_beam_engraver::end_beam ()
263 {
264   if (stems_->size () < 2)
265     junk_beam ();
266   else
267     {
268       finished_beam_ = create_beam ();
269
270       if (finished_beam_)
271         {
272           announce_end_grob (finished_beam_, SCM_EOL);
273           finished_grouping_ = grouping_;
274           finished_beaming_options_ = beaming_options_;
275         }
276       delete stems_;
277       stems_ = 0;
278       grouping_ = 0;
279       beam_settings_ = SCM_EOL;
280     }
281
282   shortest_mom_ = Moment (Rational (1, 4));
283 }
284
285 void
286 Auto_beam_engraver::typeset_beam ()
287 {
288   if (finished_beam_)
289     {
290       if (!finished_beam_->get_bound (RIGHT))
291         finished_beam_->set_bound (RIGHT, finished_beam_->get_bound (LEFT));
292
293       finished_grouping_->beamify (finished_beaming_options_);
294       Beam::set_beaming (finished_beam_, finished_grouping_);
295       finished_beam_ = 0;
296
297       delete finished_grouping_;
298       finished_grouping_ = 0;
299     }
300 }
301
302 void
303 Auto_beam_engraver::stop_translation_timestep ()
304 {
305   typeset_beam ();
306   process_acknowledged_count_ = 0;
307   forbid_ = 0;
308 }
309
310 void
311 Auto_beam_engraver::finalize ()
312 {
313   /* finished beams may be typeset */
314   typeset_beam ();
315   /* but unfinished may need another announce/acknowledge pass */
316   if (stems_)
317     junk_beam ();
318 }
319
320
321 void
322 Auto_beam_engraver::acknowledge_beam (Grob_info /* info */)
323 {
324   check_bar_property ();
325   if (stems_)
326     end_beam ();
327 }
328
329 void
330 Auto_beam_engraver::acknowledge_bar_line (Grob_info /* info */)
331 {
332   check_bar_property ();
333   if (stems_)
334     end_beam ();
335 }
336
337 void
338 Auto_beam_engraver::acknowledge_breathing_sign (Grob_info /* info */)
339 {
340   check_bar_property ();
341   if (stems_)
342     end_beam ();
343 }
344
345 void
346 Auto_beam_engraver::acknowledge_rest (Grob_info /* info */)
347 {
348   check_bar_property ();
349   if (stems_)
350     end_beam ();
351 }
352
353 void
354 Auto_beam_engraver::acknowledge_stem (Grob_info info)
355 {
356   check_bar_property ();
357   Item *stem = dynamic_cast<Item *> (info.grob ());
358   Stream_event *ev = info.ultimate_event_cause ();
359   if (!ev->in_event_class ("rhythmic-event"))
360     {
361       programming_error ("stem must have rhythmic structure");
362       return;
363     }
364
365   /*
366     Don't (start) auto-beam over empty stems; skips or rests
367   */
368   if (!Stem::head_count (stem))
369     {
370       if (stems_)
371         end_beam ();
372       return;
373     }
374
375   if (Stem::get_beam (stem))
376     {
377       if (stems_)
378         junk_beam ();
379       return;
380     }
381
382   int durlog = unsmob_duration (ev->get_property ("duration"))->duration_log ();
383
384   if (durlog <= 2)
385     {
386       if (stems_)
387         end_beam ();
388       return;
389     }
390
391   /*
392     ignore grace notes.
393   */
394   Moment now = now_mom ();
395   if (bool (beam_start_location_.grace_part_) != bool (now.grace_part_))
396     return;
397
398   Moment dur = unsmob_duration (ev->get_property ("duration"))->get_length ();
399   Moment measure_now = measure_position (context ());
400   bool recheck_needed = false;
401
402   if (dur < shortest_mom_)
403     {
404     /* new shortest moment, so store it and set recheck_needed */
405     shortest_mom_ = dur;
406     recheck_needed = true;
407     }
408
409   /* end should be based on shortest_mom_, begin should be
410      based on current duration  */
411   consider_end (measure_now, shortest_mom_);
412   consider_begin (measure_now, dur);
413
414   if (!stems_)
415     return;
416
417   grouping_->add_stem (now - beam_start_moment_ + beam_start_location_,
418                        durlog - 2,
419                        Stem::is_invisible (stem));
420   stems_->push_back (stem);
421   last_add_mom_ = now;
422   extend_mom_ = max (extend_mom_, now) + get_event_length (ev, now);
423   if (recheck_needed)
424     recheck_beam ();
425 }
426
427 void
428 Auto_beam_engraver::recheck_beam ()
429 {
430   /*
431     Recheck the beam after the shortest duration has changed
432     If shorter duration has created a new break, typeset the
433     first part of the beam and reset the current beam to just
434     the last part of the beam
435   */
436   Beaming_pattern *new_grouping_ = 0;
437   vector<Item *> *new_stems_ = 0;
438   Moment temporary_shortest_mom;
439   SCM temporary_beam_settings;
440
441   bool found_end;
442
443
444   for (vsize i = 0; i < stems_->size () - 1;)
445     {
446       found_end = test_moment (STOP,
447                                grouping_->end_moment (i),
448                                shortest_mom_);
449       if (!found_end)
450         i++;
451       else
452         {
453           /*
454             Save the current beam settings and shortest_mom_
455             Necessary because end_beam destroys them
456           */
457           temporary_shortest_mom = shortest_mom_;
458           temporary_beam_settings = beam_settings_;
459
460           /* Eliminate (and save) the items no longer part of the first beam */
461
462           new_grouping_ = grouping_->split_pattern (i);
463           new_stems_ = remove_end_stems (i);
464
465           end_beam ();
466           typeset_beam ();
467
468           /* now recreate the unbeamed data structures */
469           stems_ = new_stems_;
470           grouping_ = new_grouping_;
471           shortest_mom_ = temporary_shortest_mom;
472           beam_settings_ = temporary_beam_settings;
473
474           i = 0;
475         }
476
477     }
478
479 }
480
481 /*
482   Remove all stems with an index greater than split_index
483   from stems_, and return a vector containing all of the
484   removed stems
485 */
486 vector <Item *> *
487 Auto_beam_engraver::remove_end_stems (vsize split_index)
488 {
489   vector <Item *> *removed_stems = 0;
490   removed_stems = new vector <Item *>;
491
492   for (vsize j = split_index + 1; j < stems_->size (); j++)
493     removed_stems->push_back ((*stems_).at (j));
494   for (vsize j = split_index + 1; j < stems_->size ();)
495     stems_->pop_back ();
496   return removed_stems;
497 }
498
499 void
500 Auto_beam_engraver::process_acknowledged ()
501 {
502   Moment now = now_mom();
503   if (extend_mom_ > now)
504     return;
505
506   if (!process_acknowledged_count_)
507     {
508       Moment measure_now = measure_position (context ());
509       consider_end (measure_now, shortest_mom_);
510     }
511   else if (process_acknowledged_count_ > 1)
512     {
513       if (stems_)
514         {
515           if ((extend_mom_ < now)
516               || ((extend_mom_ == now) && (last_add_mom_ != now)))
517             end_beam ();
518           else if (!stems_->size ())
519             junk_beam ();
520         }
521     }
522
523   process_acknowledged_count_++;
524 }
525
526 ADD_ACKNOWLEDGER (Auto_beam_engraver, stem);
527 ADD_ACKNOWLEDGER (Auto_beam_engraver, bar_line);
528 ADD_ACKNOWLEDGER (Auto_beam_engraver, beam);
529 ADD_ACKNOWLEDGER (Auto_beam_engraver, breathing_sign);
530 ADD_ACKNOWLEDGER (Auto_beam_engraver, rest);
531 ADD_TRANSLATOR (Auto_beam_engraver,
532                 /* doc */
533                 "Generate beams based on measure characteristics and observed"
534                 " Stems.  Uses @code{baseMoment}, @code{beatStructure},"
535                 " @code{beamExceptions}, @code{measureLength}, and"
536                 " @code{measurePosition} to decide when to start and stop a"
537                 " beam.  Overriding beaming is done through"
538                 " @ref{Stem_engraver} properties @code{stemLeftBeamCount} and"
539                 " @code{stemRightBeamCount}.",
540
541                 /* create */
542                 "Beam ",
543
544                 /* read */
545                 "autoBeaming "
546                 "baseMoment "
547                 "beamExceptions "
548                 "beatStructure "
549                 "subdivideBeams ",
550
551                 /* write */
552                 ""
553                 );