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