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