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