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