]> git.donarmstrong.com Git - lilypond.git/blob - lily/figured-bass-engraver.cc
FiguredBass: No need to store rest event, use bool instead
[lilypond.git] / lily / figured-bass-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2005--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "engraver.hh"
22
23 #include "align-interface.hh"
24 #include "axis-group-interface.hh"
25 #include "context.hh"
26 #include "grob-array.hh"
27 #include "item.hh"
28 #include "pointer-group-interface.hh"
29 #include "spanner.hh"
30 #include "stream-event.hh"
31 #include "text-interface.hh"
32
33 #include "translator.icc"
34
35 struct Figure_group
36 {
37   Spanner *group_;
38   Spanner *continuation_line_;
39   
40   SCM number_;
41   SCM alteration_;
42   SCM augmented_;
43   SCM diminished_;
44   SCM augmented_slash_;
45   SCM text_;
46   
47   Item *figure_item_; 
48   Stream_event *current_event_;
49   
50   Figure_group ()
51   {
52     figure_item_ = 0;
53     continuation_line_ = 0;
54     reset_figure ();
55     group_ = 0;
56     current_event_ = 0;
57   }
58   /* Reset (or init) all figure information to FALSE */
59   void reset_figure ()
60   {
61     number_ = SCM_BOOL_F;
62     alteration_ = SCM_BOOL_F;
63     augmented_ = SCM_BOOL_F;
64     diminished_ = SCM_BOOL_F;
65     augmented_slash_ = SCM_BOOL_F;
66     text_ = SCM_BOOL_F;
67   }
68   /* Mark the members of the struct as used for the GUILE Garbage Collection */
69   void gc_mark () const
70   {
71     scm_gc_mark (number_);
72     scm_gc_mark (alteration_);
73     scm_gc_mark (augmented_);
74     scm_gc_mark (diminished_);
75     scm_gc_mark (augmented_slash_);
76     scm_gc_mark (text_);
77   }
78   bool group_is_equal_to (Stream_event *evt) const
79   {
80     return
81       ly_is_equal (number_, evt->get_property ("figure"))
82       && ly_is_equal (alteration_, evt->get_property ("alteration"))
83       && ly_is_equal (augmented_, evt->get_property ("augmented"))
84       && ly_is_equal (diminished_, evt->get_property ("diminished"))
85       && ly_is_equal (augmented_slash_, evt->get_property ("augmented-slash"))
86       && ly_is_equal (text_, evt->get_property ("text"));
87   }
88   bool is_continuation () const
89   {
90     return
91       current_event_
92       && group_is_equal_to (current_event_);
93   }
94 };
95
96 struct Figured_bass_engraver : public Engraver
97 {
98   TRANSLATOR_DECLARATIONS (Figured_bass_engraver);
99   void clear_spanners ();
100   void add_brackets ();
101   void create_grobs ();
102
103   void center_continuations (vector<Spanner*> const &consecutive_lines);
104   void center_repeated_continuations ();
105 protected:
106   vector<Figure_group> groups_;
107   Spanner *alignment_;
108   vector<Stream_event *> new_events_;
109   bool continuation_;
110   bool new_event_found_;
111   
112   Moment stop_moment_;
113   bool have_rest_;
114
115   DECLARE_TRANSLATOR_LISTENER (rest);
116   DECLARE_TRANSLATOR_LISTENER (bass_figure);
117
118   virtual void derived_mark () const; 
119
120   void start_translation_timestep ();
121   void stop_translation_timestep ();
122   void process_music ();
123 };
124
125 void
126 Figured_bass_engraver::derived_mark () const
127 {
128   for (vsize i = 0; i < groups_.size (); i++)
129     {
130       groups_[i].gc_mark ();
131     }
132 }
133
134 void
135 Figured_bass_engraver::stop_translation_timestep ()
136 {
137   if (groups_.empty ()
138       || now_mom ().main_part_ < stop_moment_.main_part_
139       || now_mom ().grace_part_ < Rational (0))
140     return ;
141   
142   bool found = false;
143   for (vsize i = 0; !found && i < groups_.size (); i++)
144     found  = found  || groups_[i].current_event_;
145
146   if (!found)
147     clear_spanners ();
148 }
149
150 Figured_bass_engraver::Figured_bass_engraver ()
151 {
152   alignment_ = 0;
153   continuation_ = false;
154   have_rest_ = 0;
155   new_event_found_ = false;
156 }
157
158 void
159 Figured_bass_engraver::start_translation_timestep ()
160 {
161   if (now_mom ().main_part_ < stop_moment_.main_part_
162       || now_mom ().grace_part_ < Rational (0))
163     return ;
164   
165   have_rest_ = 0;
166   new_events_.clear ();
167   for (vsize i = 0; i < groups_.size (); i++)
168     groups_[i].current_event_ = 0;
169
170   continuation_ = false;
171
172   
173 }
174
175 IMPLEMENT_TRANSLATOR_LISTENER (Figured_bass_engraver, rest);
176 void
177 Figured_bass_engraver::listen_rest (Stream_event *ev)
178 {
179   have_rest_ = true;
180 }
181
182 IMPLEMENT_TRANSLATOR_LISTENER (Figured_bass_engraver, bass_figure);
183 void
184 Figured_bass_engraver::listen_bass_figure (Stream_event *ev)
185 {
186   new_event_found_ = true;
187   Moment stop  = now_mom () + get_event_length (ev, now_mom ());
188   stop_moment_ = max (stop_moment_, stop);
189
190   // Handle no-continuation here, don't even add it to the already existing
191   // spanner... This fixes some layout issues (figure will be placed separately)
192   bool no_continuation = to_boolean (ev->get_property ("no-continuation"));
193   if (to_boolean (get_property ("useBassFigureExtenders")) && !no_continuation)
194     {
195       for (vsize i = 0; i < groups_.size (); i++)
196         {
197           if (!groups_[i].current_event_
198               && groups_[i].group_is_equal_to (ev))
199             {
200               groups_[i].current_event_ = ev;
201               continuation_ = true;
202               return;
203             }
204         }
205     }
206   new_events_.push_back (ev);
207 }
208
209 void
210 Figured_bass_engraver::center_continuations (vector<Spanner*> const &consecutive_lines)
211 {
212   if (consecutive_lines.size () == 2)
213     {
214       vector<Grob*> left_figs;
215       for (vsize j = consecutive_lines.size (); j--;)
216         left_figs.push_back (consecutive_lines[j]->get_bound (LEFT));
217
218       SCM  ga = Grob_array::make_array ();
219       unsmob_grob_array (ga)->set_array (left_figs);
220
221       for (vsize j = consecutive_lines.size (); j--;)
222         consecutive_lines[j]->set_object ("figures",
223                                           unsmob_grob_array (ga)->smobbed_copy ());
224     }
225 }
226
227 void
228 Figured_bass_engraver::center_repeated_continuations ()
229 {  
230   vector<Spanner*> consecutive_lines;
231   for (vsize i = 0; i <= groups_.size (); i++)
232     {
233       if (i < groups_.size ()
234           && groups_[i].continuation_line_
235           && (consecutive_lines.empty ()
236               || (consecutive_lines[0]->get_bound (LEFT)->get_column ()
237                   == groups_[i].continuation_line_->get_bound (LEFT)->get_column ()
238                   && consecutive_lines[0]->get_bound (RIGHT)->get_column ()
239                   == groups_[i].continuation_line_->get_bound (RIGHT)->get_column ())))
240         consecutive_lines.push_back (groups_[i].continuation_line_);      
241       else 
242         {
243           center_continuations (consecutive_lines);
244           consecutive_lines.clear ();
245         }
246     }
247 }
248
249 void
250 Figured_bass_engraver::clear_spanners ()
251 {
252   if (!alignment_)
253     return;
254
255   if (alignment_)
256     {
257       announce_end_grob (alignment_, SCM_EOL);
258       alignment_ = 0;
259     }
260
261   if (to_boolean (get_property ("figuredBassCenterContinuations")))
262     center_repeated_continuations ();
263   
264   for (vsize i = 0; i < groups_.size (); i++)
265     {
266       if (groups_[i].group_)
267         {
268           announce_end_grob (groups_[i].group_ , SCM_EOL);
269           groups_[i].group_ = 0;
270         }
271       
272       if (groups_[i].continuation_line_)
273         {
274           announce_end_grob (groups_[i].continuation_line_ , SCM_EOL);
275           groups_[i].continuation_line_ = 0;
276         }
277     }
278
279   /* Check me, groups_.clear () ? */
280 }
281
282 void
283 Figured_bass_engraver::add_brackets ()
284 {
285   vector<Grob*> encompass;
286   bool inside = false;
287   for (vsize i = 0; i < groups_.size (); i ++)
288     {
289       if (!groups_[i].current_event_)
290         continue;
291       
292       if (to_boolean (groups_[i].current_event_->get_property ("bracket-start")))       
293         inside = true;
294
295       if (inside && groups_[i].figure_item_)
296         encompass.push_back (groups_[i].figure_item_);
297
298        if (to_boolean (groups_[i].current_event_->get_property ("bracket-stop")))
299         {
300           inside = false;
301
302           Item * brack = make_item ("BassFigureBracket", groups_[i].current_event_->self_scm ());
303           for (vsize j = 0; j < encompass.size (); j++)
304             {
305               Pointer_group_interface::add_grob (brack,
306                                                  ly_symbol2scm ("elements"),
307                                                  encompass[j]);
308             }
309           encompass.clear ();
310         }
311     }
312 }
313
314 void
315 Figured_bass_engraver::process_music ()
316 {
317   bool use_extenders = to_boolean (get_property ("useBassFigureExtenders"));
318   if (alignment_ && !use_extenders)
319     clear_spanners ();
320         
321   // If we have a rest, or we have no new or continued events, clear all spanners
322   bool ignore_rest = to_boolean (get_property ("ignoreFiguredBassRest"));
323   if ((ignore_rest && have_rest_) ||
324       (!continuation_ && new_events_.empty ()))
325     {
326       clear_spanners ();
327       groups_.clear ();
328       return;
329     }
330
331   if (!new_event_found_)
332     return;
333   
334   new_event_found_ = false;
335
336   /*
337     Don't need to sync alignments, if we're not using extenders. 
338    */
339   if (!use_extenders)
340     {
341       clear_spanners ();
342     }
343   
344   if (!continuation_)
345     {
346       clear_spanners ();
347       groups_.clear ();
348     }
349
350   vsize k = 0;
351   for (vsize i = 0; i < new_events_.size (); i++)
352     {
353       while (k < groups_.size ()
354              && groups_[k].current_event_)
355         k++;
356       
357       if (k >= groups_.size ())
358         {
359           Figure_group group;
360           groups_.push_back (group);
361         }
362       
363       groups_[k].current_event_ = new_events_[i];
364       groups_[k].figure_item_ = 0;
365       k++;
366     }
367
368   for (vsize i = 0; i < groups_.size (); i++)
369     {
370       if (!groups_[i].is_continuation ())
371         {
372           groups_[i].reset_figure ();
373         }
374     }
375
376   if (use_extenders)
377     {
378       vector<int> junk_continuations;
379       for (vsize i = 0; i < groups_.size (); i++)
380         {
381           Figure_group &group = groups_[i];
382
383           if (group.is_continuation ())
384             {
385               if (!group.continuation_line_)
386                 {
387                   Spanner * line
388                     = make_spanner ("BassFigureContinuation", SCM_EOL);
389                   Item * item = group.figure_item_;
390                   group.continuation_line_ = line;
391                   line->set_bound (LEFT, item);
392
393                   /*
394                     Don't add as child. This will cache the wrong
395                     (pre-break) stencil when callbacks are triggered.
396                   */
397                   line->set_parent (group.group_, Y_AXIS);
398                   Pointer_group_interface::add_grob (line, ly_symbol2scm ("figures"), item);
399
400                   group.figure_item_ = 0;
401                 }
402             }
403           else if (group.continuation_line_) 
404             junk_continuations.push_back (i); 
405         }
406
407       /*
408         Ugh, repeated code.
409        */
410       vector<Spanner*> consecutive;
411       if (to_boolean (get_property ("figuredBassCenterContinuations")))
412         {
413           for (vsize i = 0; i <= junk_continuations.size (); i++)
414             {
415               if (i < junk_continuations.size ()
416                   && (i == 0 || junk_continuations[i-1] == junk_continuations[i] - 1))
417                 consecutive.push_back (groups_[junk_continuations[i]].continuation_line_);
418               else 
419                 {
420                   center_continuations (consecutive);
421                   consecutive.clear ();
422                   if (i < junk_continuations.size ())
423                     consecutive.push_back (groups_[junk_continuations[i]].continuation_line_);
424                 }
425             }
426         }
427       for (vsize i = 0; i < junk_continuations.size (); i++)
428         groups_[junk_continuations[i]].continuation_line_ = 0;
429     }
430   
431   create_grobs ();
432   add_brackets ();
433 }
434
435 void
436 Figured_bass_engraver::create_grobs () 
437 {
438   Grob *muscol
439     = dynamic_cast<Item*> (unsmob_grob (get_property ("currentMusicalColumn")));
440   if (!alignment_)
441     {
442       alignment_ = make_spanner ("BassFigureAlignment", SCM_EOL);
443       alignment_->set_bound (LEFT, muscol);
444     }
445   alignment_->set_bound (RIGHT, muscol);
446
447   SCM proc = get_property ("figuredBassFormatter");
448   for (vsize i = 0; i < groups_.size (); i++)
449     {
450       Figure_group &group = groups_[i];
451       
452       if (group.current_event_)
453         {
454           Item *item
455             = make_item ("BassFigure",
456                          group.current_event_->self_scm ());
457
458           
459           SCM fig = group.current_event_->get_property ("figure");
460           if (!group.group_)
461             {
462               group.group_ = make_spanner ("BassFigureLine", SCM_EOL);
463               group.group_->set_bound (LEFT, muscol);
464               Align_interface::add_element (alignment_,
465                                             group.group_);
466             }
467
468           if (scm_memq (fig, get_property ("implicitBassFigures")) != SCM_BOOL_F)
469             {
470               item->set_property ("transparent", SCM_BOOL_T); 
471               item->set_property ("implicit", SCM_BOOL_T);
472             }
473           
474           group.number_ = fig;
475           group.alteration_ = group.current_event_->get_property ("alteration");
476           group.augmented_ = group.current_event_->get_property ("augmented");
477           group.diminished_ = group.current_event_->get_property ("diminished");
478           group.augmented_slash_ = group.current_event_->get_property ("augmented-slash");
479           group.text_ = group.current_event_->get_property ("text");
480
481           SCM text = group.text_;
482           if (!Text_interface::is_markup (text)
483               && ly_is_procedure (proc))
484             {
485               text = scm_call_3 (proc, fig, group.current_event_->self_scm (),
486                                  context ()->self_scm ());
487             }
488
489           item->set_property ("text", text);
490           
491           Axis_group_interface::add_element (group.group_, item);
492           group.figure_item_ = item;
493         }
494
495       if (group.continuation_line_)
496         {
497           /*
498             UGH should connect to the bass staff, and get the note heads. 
499           */
500           group.figure_item_->set_property ("transparent", SCM_BOOL_T);
501           group.continuation_line_->set_bound (RIGHT, group.figure_item_);
502         }
503       
504       if (groups_[i].group_)
505         groups_[i].group_->set_bound (RIGHT, muscol);
506
507     }
508
509 }
510
511 ADD_TRANSLATOR (Figured_bass_engraver,
512                 /* doc */
513                 "Make figured bass numbers.",
514
515                 /* create */
516                 "BassFigure "
517                 "BassFigureAlignment "
518                 "BassFigureBracket "
519                 "BassFigureContinuation "
520                 "BassFigureLine ",
521
522                 /* read */
523                 "figuredBassAlterationDirection "
524                 "figuredBassCenterContinuations "
525                 "figuredBassFormatter "
526                 "implicitBassFigures "
527                 "useBassFigureExtenders "
528                 "ignoreFiguredBassRest ",
529
530                 /* write */
531                 ""
532                 );