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