]> git.donarmstrong.com Git - lilypond.git/blob - lily/figured-bass-engraver.cc
FiguredBass: Don't continue spanner on unequal figures (e.g. diff. accidentals)
[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   bool force_no_continuation_;
50   
51   Figure_group ()
52   {
53     figure_item_ = 0;
54     force_no_continuation_ = false;
55     continuation_line_ = 0;
56     number_ = SCM_EOL;
57     alteration_ = SCM_EOL;
58     augmented_ = SCM_EOL;
59     diminished_ = SCM_EOL;
60     augmented_slash_ = SCM_EOL;
61     text_ = SCM_EOL;
62     group_ = 0;
63     current_event_ = 0;
64   }
65   /* Mark the members of the struct as used for the GUILE Garbage Collection */
66   void gc_mark () const
67   {
68     scm_gc_mark (number_);
69     scm_gc_mark (alteration_);
70     scm_gc_mark (augmented_);
71     scm_gc_mark (diminished_);
72     scm_gc_mark (augmented_slash_);
73     scm_gc_mark (text_);
74   }
75   bool group_is_equal_to (Stream_event *evt) const
76   {
77     return
78       ly_is_equal (number_, evt->get_property ("figure"))
79       && ly_is_equal (alteration_, evt->get_property ("alteration"))
80       && ly_is_equal (augmented_, evt->get_property ("augmented"))
81       && ly_is_equal (diminished_, evt->get_property ("diminished"))
82       && ly_is_equal (augmented_slash_, evt->get_property ("augmented-slash"))
83       && ly_is_equal (text_, evt->get_property ("text"));
84   }
85   bool is_continuation () const
86   {
87     return
88       current_event_
89       && !force_no_continuation_
90       && group_is_equal_to (current_event_);
91   }
92 };
93
94 struct Figured_bass_engraver : public Engraver
95 {
96   TRANSLATOR_DECLARATIONS (Figured_bass_engraver);
97   void clear_spanners ();
98   void add_brackets ();
99   void create_grobs ();
100
101   void center_continuations (vector<Spanner*> const &consecutive_lines);
102   void center_repeated_continuations ();
103 protected:
104   vector<Figure_group> groups_;
105   Spanner *alignment_;
106   vector<Stream_event *> new_events_;
107   bool continuation_;
108   bool new_event_found_;
109   
110   Moment stop_moment_;
111   Stream_event *rest_event_; 
112
113   DECLARE_TRANSLATOR_LISTENER (rest);
114   DECLARE_TRANSLATOR_LISTENER (bass_figure);
115
116   virtual void derived_mark () const; 
117
118   void start_translation_timestep ();
119   void stop_translation_timestep ();
120   void process_music ();
121 };
122
123 void
124 Figured_bass_engraver::derived_mark () const
125 {
126   for (vsize i = 0; i < groups_.size (); i++)
127     {
128       groups_[i].gc_mark ();
129     }
130 }
131
132 void
133 Figured_bass_engraver::stop_translation_timestep ()
134 {
135   if (groups_.empty ()
136       || now_mom ().main_part_ < stop_moment_.main_part_
137       || now_mom ().grace_part_ < Rational (0))
138     return ;
139   
140   bool found = false;
141   for (vsize i = 0; !found && i < groups_.size (); i++)
142     found  = found  || groups_[i].current_event_;
143
144   if (!found)
145     clear_spanners ();
146 }
147
148 Figured_bass_engraver::Figured_bass_engraver ()
149 {
150   alignment_ = 0;
151   continuation_ = false;
152   rest_event_ = 0;
153   new_event_found_ = false;
154 }
155
156 void
157 Figured_bass_engraver::start_translation_timestep ()
158 {
159   if (now_mom ().main_part_ < stop_moment_.main_part_
160       || now_mom ().grace_part_ < Rational (0))
161     return ;
162   
163   rest_event_ = 0;
164   new_events_.clear ();
165   for (vsize i = 0; i < groups_.size (); i++)
166     groups_[i].current_event_ = 0;
167
168   continuation_ = false;
169
170   
171 }
172
173 IMPLEMENT_TRANSLATOR_LISTENER (Figured_bass_engraver, rest);
174 void
175 Figured_bass_engraver::listen_rest (Stream_event *ev)
176 {
177   if (to_boolean (get_property ("ignoreFiguredBassRest")))
178     {
179       new_event_found_ = true;
180
181       /*
182         No ASSIGN_EVENT_ONCE () ; otherwise we get warnings about
183         polyphonic rests.
184        */
185       rest_event_ = ev;
186     }
187 }
188
189 IMPLEMENT_TRANSLATOR_LISTENER (Figured_bass_engraver, bass_figure);
190 void
191 Figured_bass_engraver::listen_bass_figure (Stream_event *ev)
192 {
193   new_event_found_ = true;
194   Moment stop  = now_mom () + get_event_length (ev, now_mom ());
195   stop_moment_ = max (stop_moment_, stop);
196
197   if (to_boolean (get_property ("useBassFigureExtenders")))
198     {
199       for (vsize i = 0; i < groups_.size (); i++)
200         {
201           if (!groups_[i].current_event_
202               && groups_[i].group_is_equal_to (ev))
203             {
204               groups_[i].current_event_ = ev;
205               bool no_cont = to_boolean (ev->get_property ("no-continuation"));
206               groups_[i].force_no_continuation_ = no_cont;
207               // Exit only if this is a real continuation. If it is broken,
208               // continue just as usual (otherwise the figure will still be
209               // vertically aligned with the previous figure!)
210               if (!no_cont) {
211                 continuation_ = true;
212                 return;
213               }
214             }
215         }
216     }
217   new_events_.push_back (ev);
218 }
219
220 void
221 Figured_bass_engraver::center_continuations (vector<Spanner*> const &consecutive_lines)
222 {
223   if (consecutive_lines.size () == 2)
224     {
225       vector<Grob*> left_figs;
226       for (vsize j = consecutive_lines.size (); j--;)
227         left_figs.push_back (consecutive_lines[j]->get_bound (LEFT));
228
229       SCM  ga = Grob_array::make_array ();
230       unsmob_grob_array (ga)->set_array (left_figs);
231
232       for (vsize j = consecutive_lines.size (); j--;)
233         consecutive_lines[j]->set_object ("figures",
234                                           unsmob_grob_array (ga)->smobbed_copy ());
235     }
236 }
237
238 void
239 Figured_bass_engraver::center_repeated_continuations ()
240 {  
241   vector<Spanner*> consecutive_lines;
242   for (vsize i = 0; i <= groups_.size (); i++)
243     {
244       if (i < groups_.size ()
245           && groups_[i].continuation_line_
246           && (consecutive_lines.empty ()
247               || (consecutive_lines[0]->get_bound (LEFT)->get_column ()
248                   == groups_[i].continuation_line_->get_bound (LEFT)->get_column ()
249                   && consecutive_lines[0]->get_bound (RIGHT)->get_column ()
250                   == groups_[i].continuation_line_->get_bound (RIGHT)->get_column ())))
251         consecutive_lines.push_back (groups_[i].continuation_line_);      
252       else 
253         {
254           center_continuations (consecutive_lines);
255           consecutive_lines.clear ();
256         }
257     }
258 }
259
260 void
261 Figured_bass_engraver::clear_spanners ()
262 {
263   if (!alignment_)
264     return;
265
266   if (alignment_)
267     {
268       announce_end_grob (alignment_, SCM_EOL);
269       alignment_ = 0;
270     }
271
272   if (to_boolean (get_property ("figuredBassCenterContinuations")))
273     center_repeated_continuations ();
274   
275   for (vsize i = 0; i < groups_.size (); i++)
276     {
277       if (groups_[i].group_)
278         {
279           announce_end_grob (groups_[i].group_ , SCM_EOL);
280           groups_[i].group_ = 0;
281         }
282       
283       if (groups_[i].continuation_line_)
284         {
285           announce_end_grob (groups_[i].continuation_line_ , SCM_EOL);
286           groups_[i].continuation_line_ = 0;
287         }
288     }
289
290   /* Check me, groups_.clear () ? */
291 }
292
293 void
294 Figured_bass_engraver::add_brackets ()
295 {
296   vector<Grob*> encompass;
297   bool inside = false;
298   for (vsize i = 0; i < groups_.size (); i ++)
299     {
300       if (!groups_[i].current_event_)
301         continue;
302       
303       if (to_boolean (groups_[i].current_event_->get_property ("bracket-start")))       
304         inside = true;
305
306       if (inside && groups_[i].figure_item_)
307         encompass.push_back (groups_[i].figure_item_);
308
309        if (to_boolean (groups_[i].current_event_->get_property ("bracket-stop")))
310         {
311           inside = false;
312
313           Item * brack = make_item ("BassFigureBracket", groups_[i].current_event_->self_scm ());
314           for (vsize j = 0; j < encompass.size (); j++)
315             {
316               Pointer_group_interface::add_grob (brack,
317                                                  ly_symbol2scm ("elements"),
318                                                  encompass[j]);
319             }
320           encompass.clear ();
321         }
322     }
323 }
324
325 void
326 Figured_bass_engraver::process_music ()
327 {
328   bool use_extenders = to_boolean (get_property ("useBassFigureExtenders"));
329   if (alignment_ && !use_extenders)
330     clear_spanners ();
331         
332   if (rest_event_)
333     {
334       clear_spanners ();
335       groups_.clear ();
336       return;
337     }
338   
339   if (!continuation_
340       && new_events_.empty ())
341     {
342       clear_spanners ();
343       groups_.clear ();
344       return;
345     }
346
347   if (!new_event_found_)
348     return;
349   
350   new_event_found_ = false;
351
352   /*
353     Don't need to sync alignments, if we're not using extenders. 
354    */
355   if (!use_extenders)
356     {
357       clear_spanners ();
358     }
359   
360   if (!continuation_)
361     {
362       clear_spanners ();
363       groups_.clear ();
364     }
365
366   vsize k = 0;
367   for (vsize i = 0; i < new_events_.size (); i++)
368     {
369       while (k < groups_.size ()
370              && groups_[k].current_event_)
371         k++;
372       
373       if (k >= groups_.size ())
374         {
375           Figure_group group;
376           groups_.push_back (group);
377         }
378       
379       groups_[k].current_event_ = new_events_[i];
380       groups_[k].figure_item_ = 0;
381       k++;
382     }
383
384   for (vsize i = 0; i < groups_.size (); i++)
385     {
386       if (!groups_[i].is_continuation ())
387         {
388           groups_[i].number_ = SCM_BOOL_F;
389           groups_[i].alteration_ = SCM_BOOL_F;
390           groups_[i].augmented_ = SCM_BOOL_F;
391           groups_[i].diminished_ = SCM_BOOL_F;
392           groups_[i].augmented_slash_ = SCM_BOOL_F;
393           groups_[i].text_ = SCM_BOOL_F;
394         }
395     }
396
397   if (use_extenders)
398     {
399       vector<int> junk_continuations;
400       for (vsize i = 0; i < groups_.size (); i++)
401         {
402           Figure_group &group = groups_[i];
403
404           if (group.is_continuation ())
405             {
406               if (!group.continuation_line_)
407                 {
408                   Spanner * line
409                     = make_spanner ("BassFigureContinuation", SCM_EOL);
410                   Item * item = group.figure_item_;
411                   group.continuation_line_ = line;
412                   line->set_bound (LEFT, item);
413
414                   /*
415                     Don't add as child. This will cache the wrong
416                     (pre-break) stencil when callbacks are triggered.
417                   */
418                   line->set_parent (group.group_, Y_AXIS);
419                   Pointer_group_interface::add_grob (line, ly_symbol2scm ("figures"), item);
420
421                   group.figure_item_ = 0;
422                 }
423             }
424           else if (group.continuation_line_) 
425             junk_continuations.push_back (i); 
426         }
427
428       /*
429         Ugh, repeated code.
430        */
431       vector<Spanner*> consecutive;
432       if (to_boolean (get_property ("figuredBassCenterContinuations")))
433         {
434           for (vsize i = 0; i <= junk_continuations.size (); i++)
435             {
436               if (i < junk_continuations.size ()
437                   && (i == 0 || junk_continuations[i-1] == junk_continuations[i] - 1))
438                 consecutive.push_back (groups_[junk_continuations[i]].continuation_line_);
439               else 
440                 {
441                   center_continuations (consecutive);
442                   consecutive.clear ();
443                   if (i < junk_continuations.size ())
444                     consecutive.push_back (groups_[junk_continuations[i]].continuation_line_);
445                 }
446             }
447         }
448       for (vsize i = 0; i < junk_continuations.size (); i++)
449         groups_[junk_continuations[i]].continuation_line_ = 0;
450     }
451   
452   create_grobs ();
453   add_brackets ();
454 }
455
456 void
457 Figured_bass_engraver::create_grobs () 
458 {
459   Grob *muscol
460     = dynamic_cast<Item*> (unsmob_grob (get_property ("currentMusicalColumn")));
461   if (!alignment_)
462     {
463       alignment_ = make_spanner ("BassFigureAlignment", SCM_EOL);
464       alignment_->set_bound (LEFT, muscol);
465     }
466   alignment_->set_bound (RIGHT, muscol);
467
468   SCM proc = get_property ("figuredBassFormatter");
469   for (vsize i = 0; i < groups_.size (); i++)
470     {
471       Figure_group &group = groups_[i];
472       
473       if (group.current_event_)
474         {
475           Item *item
476             = make_item ("BassFigure",
477                          group.current_event_->self_scm ());
478
479           
480           SCM fig = group.current_event_->get_property ("figure");
481           if (!group.group_)
482             {
483               group.group_ = make_spanner ("BassFigureLine", SCM_EOL);
484               group.group_->set_bound (LEFT, muscol);
485               Align_interface::add_element (alignment_,
486                                             group.group_);
487             }
488
489           if (scm_memq (fig, get_property ("implicitBassFigures")) != SCM_BOOL_F)
490             {
491               item->set_property ("transparent", SCM_BOOL_T); 
492               item->set_property ("implicit", SCM_BOOL_T);
493             }
494           
495           group.number_ = fig;
496           group.alteration_ = group.current_event_->get_property ("alteration");
497           group.augmented_ = group.current_event_->get_property ("augmented");
498           group.diminished_ = group.current_event_->get_property ("diminished");
499           group.augmented_slash_ = group.current_event_->get_property ("augmented-slash");
500           group.text_ = group.current_event_->get_property ("text");
501
502           SCM text = group.text_;
503           if (!Text_interface::is_markup (text)
504               && ly_is_procedure (proc))
505             {
506               text = scm_call_3 (proc, fig, group.current_event_->self_scm (),
507                                  context ()->self_scm ());
508             }
509
510           item->set_property ("text", text);
511           
512           Axis_group_interface::add_element (group.group_, item);
513           group.figure_item_ = item;
514         }
515
516       if (group.continuation_line_)
517         {
518           /*
519             UGH should connect to the bass staff, and get the note heads. 
520           */
521           group.figure_item_->set_property ("transparent", SCM_BOOL_T);
522           group.continuation_line_->set_bound (RIGHT, group.figure_item_);
523         }
524       
525       if (groups_[i].group_)
526         groups_[i].group_->set_bound (RIGHT, muscol);
527
528     }
529
530 }
531
532 ADD_TRANSLATOR (Figured_bass_engraver,
533                 /* doc */
534                 "Make figured bass numbers.",
535
536                 /* create */
537                 "BassFigure "
538                 "BassFigureAlignment "
539                 "BassFigureBracket "
540                 "BassFigureContinuation "
541                 "BassFigureLine ",
542
543                 /* read */
544                 "figuredBassAlterationDirection "
545                 "figuredBassCenterContinuations "
546                 "figuredBassFormatter "
547                 "implicitBassFigures "
548                 "useBassFigureExtenders "
549                 "ignoreFiguredBassRest ",
550
551                 /* write */
552                 ""
553                 );