]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur-engraver.cc
Better approximations for cross-staff slurs
[lilypond.git] / lily / slur-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "engraver.hh"
21
22 #include "context.hh"
23 #include "directional-element-interface.hh"
24 #include "international.hh"
25 #include "note-column.hh"
26 #include "slur.hh"
27 #include "spanner.hh"
28 #include "stream-event.hh"
29 #include "warn.hh"
30
31 #include "translator.icc"
32
33 #include <algorithm>
34
35 /*
36   NOTE NOTE NOTE
37
38   This is largely similar to Phrasing_slur_engraver. Check if fixes
39   apply there too.
40
41   (on principle, engravers don't use inheritance for code sharing)
42
43  */
44
45 /*
46   It is possible that a slur starts and ends on the same note.  At
47   least, it is for phrasing slurs: a note can be both beginning and
48   ending of a phrase.
49 */
50
51 Slur_info::Slur_info (Grob *slur)
52 {
53   slur_ = slur;
54 }
55
56 class Slur_engraver : public Engraver
57 {
58   vector<Stream_event *> start_events_;
59   vector<Stream_event *> stop_events_;
60   vector<Slur_info> slur_infos_;
61   vector<Slur_info> end_slur_infos_;
62   vector<Grob_info> objects_to_acknowledge_;
63
64   void set_melisma (bool);
65
66 protected:
67   DECLARE_TRANSLATOR_LISTENER (slur);
68   DECLARE_ACKNOWLEDGER (inline_accidental);
69   DECLARE_ACKNOWLEDGER (fingering);
70   DECLARE_ACKNOWLEDGER (note_column);
71   DECLARE_ACKNOWLEDGER (script);
72   DECLARE_ACKNOWLEDGER (dots);
73   DECLARE_ACKNOWLEDGER (text_script);
74   DECLARE_END_ACKNOWLEDGER (tie);
75   DECLARE_ACKNOWLEDGER (tuplet_number);
76
77   void acknowledge_extra_object (Grob_info);
78   void stop_translation_timestep ();
79   void process_music ();
80
81   virtual void finalize ();
82   virtual void derived_mark () const;
83
84 public:
85   TRANSLATOR_DECLARATIONS (Slur_engraver);
86 };
87
88 Slur_engraver::Slur_engraver ()
89 {
90 }
91
92 void
93 Slur_engraver::derived_mark () const
94 {
95   for (vsize i = start_events_.size (); i--;)
96     scm_gc_mark (start_events_[i]->self_scm ());
97   for (vsize i = stop_events_.size (); i--;)
98     scm_gc_mark (stop_events_[i]->self_scm ());
99 }
100
101 IMPLEMENT_TRANSLATOR_LISTENER (Slur_engraver, slur);
102 void
103 Slur_engraver::listen_slur (Stream_event *ev)
104 {
105   Direction d = to_dir (ev->get_property ("span-direction"));
106   if (d == START)
107     start_events_.push_back (ev);
108   else if (d == STOP)
109     stop_events_.push_back (ev);
110   else ev->origin ()->warning (_f ("direction of %s invalid: %d",
111                                      "slur-event", int (d)));
112 }
113
114 void
115 Slur_engraver::set_melisma (bool m)
116 {
117   context ()->set_property ("slurMelismaBusy", m ? SCM_BOOL_T : SCM_BOOL_F);
118 }
119
120 void
121 Slur_engraver::acknowledge_note_column (Grob_info info)
122 {
123   /*
124    * For every active slur, we create a slur stub.
125    * As we do not yet know what vertical axis groups note columns belong to,
126    * we create a stub for each note and then suicide duplicate stubs on
127    * axis groups.
128    * These slurs should be used ONLY to approximate cross-staff slurs
129    * in vertical skylines.
130    */
131
132   Grob *e = info.grob ();
133   for (vsize i = slur_infos_.size (); i--;)
134     {
135       Slur::add_column (slur_infos_[i].slur_, e);
136       Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ());
137       slur_infos_[i].stubs_.push_back (stub);
138     }
139   for (vsize i = end_slur_infos_.size (); i--;)
140     {
141       Slur::add_column (end_slur_infos_[i].slur_, e);
142       Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ());
143       end_slur_infos_[i].stubs_.push_back (stub);
144     }
145 }
146
147 void
148 Slur_engraver::acknowledge_extra_object (Grob_info info)
149 {
150   objects_to_acknowledge_.push_back (info);
151 }
152
153 void
154 Slur_engraver::acknowledge_inline_accidental (Grob_info info)
155 {
156   acknowledge_extra_object (info);
157 }
158
159 void
160 Slur_engraver::acknowledge_dots (Grob_info info)
161 {
162   acknowledge_extra_object (info);
163 }
164
165 void
166 Slur_engraver::acknowledge_fingering (Grob_info info)
167 {
168   acknowledge_extra_object (info);
169 }
170
171 void
172 Slur_engraver::acknowledge_tuplet_number (Grob_info info)
173 {
174   acknowledge_extra_object (info);
175 }
176
177 void
178 Slur_engraver::acknowledge_script (Grob_info info)
179 {
180   if (!info.grob ()->internal_has_interface (ly_symbol2scm ("dynamic-interface")))
181     acknowledge_extra_object (info);
182 }
183
184 void
185 Slur_engraver::acknowledge_text_script (Grob_info info)
186 {
187   acknowledge_extra_object (info);
188 }
189
190 void
191 Slur_engraver::acknowledge_end_tie (Grob_info info)
192 {
193   acknowledge_extra_object (info);
194 }
195
196 void
197 Slur_engraver::finalize ()
198 {
199   for (vsize i = 0; i < slur_infos_.size (); i++)
200     {
201       slur_infos_[i].slur_->warning (_ ("unterminated slur"));
202       slur_infos_[i].slur_->suicide ();
203       for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++)
204         slur_infos_[i].stubs_[j]->suicide ();
205     }
206
207   slur_infos_.clear ();
208 }
209
210 void
211 Slur_engraver::process_music ()
212 {
213   for (vsize i = 0; i < stop_events_.size (); i++)
214     {
215       Stream_event *ev = stop_events_[i];
216       string id = robust_scm2string (ev->get_property ("spanner-id"), "");
217
218       // Find the slurs that are ended with this event (by checking the spanner-id)
219       bool ended = false;
220       for (vsize j = slur_infos_.size (); j--;)
221         {
222           if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), ""))
223             {
224               ended = true;
225               end_slur_infos_.push_back (slur_infos_[j]);
226               slur_infos_.erase (slur_infos_.begin () + j);
227             }
228         }
229       if (ended)
230         {
231           // Ignore redundant stop events for this id
232           for (vsize j = stop_events_.size (); --j > i;)
233             {
234               if (id == robust_scm2string (stop_events_[j]->get_property ("spanner-id"), ""))
235                 stop_events_.erase (stop_events_.begin () + j);
236             }
237         }
238       else
239         ev->origin ()->warning (_ ("cannot end slur"));
240     }
241
242   vsize old_slurs = slur_infos_.size ();
243   for (vsize i = start_events_.size (); i--;)
244     {
245       Stream_event *ev = start_events_[i];
246       string id = robust_scm2string (ev->get_property ("spanner-id"), "");
247       Direction updown = to_dir (ev->get_property ("direction"));
248
249       bool completed;
250       for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));)
251         {
252           // Check if we already have a slur with the same spanner-id.
253           if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), ""))
254             {
255               if (j < old_slurs)
256                 {
257                   // We already have an old slur, so give a warning
258                   // and completely ignore the new slur.
259                   ev->origin ()->warning (_ ("already have slur"));
260                   start_events_.erase (start_events_.begin () + i);
261                   break;
262                 }
263
264               // If this slur event has no direction, it will not
265               // contribute anything new to the existing slur(s), so
266               // we can ignore it.
267
268               if (!updown)
269                 break;
270
271               Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_property ("cause"));
272
273               if (!c)
274                 {
275                   slur_infos_[j].slur_->programming_error ("slur without a cause");
276                   continue;
277                 }
278
279               Direction slur_dir = to_dir (c->get_property ("direction"));
280
281               // If the existing slur does not have a direction yet,
282               // we'd rather take the new one.
283
284               if (!slur_dir)
285                 {
286                   slur_infos_[j].slur_->suicide ();
287                   for (vsize k = 0; k < slur_infos_[j].stubs_.size (); k++)
288                     slur_infos_[j].stubs_[k]->suicide ();
289                   slur_infos_.erase (slur_infos_.begin () + j);
290                   continue;
291                 }
292
293               // If the existing slur has the same direction as ours, drop ours
294
295               if (slur_dir == updown)
296                 break;
297             }
298         }
299       // If the loop completed, our slur is new
300       if (completed)
301         {
302           Grob *slur = make_spanner ("Slur", ev->self_scm ());
303           slur->set_property ("spanner-id", ly_string2scm (id));
304           if (updown)
305             set_grob_direction (slur, updown);
306           slur_infos_.push_back (Slur_info (slur));
307
308           if (to_boolean (get_property ("doubleSlurs")))
309             {
310               set_grob_direction (slur, DOWN);
311               slur = make_spanner ("Slur", ev->self_scm ());
312               slur->set_property ("spanner-id", ly_string2scm (id));
313               set_grob_direction (slur, UP);
314               slur_infos_.push_back (Slur_info (slur));
315             }
316         }
317     }
318   set_melisma (slur_infos_.size ());
319 }
320
321 void
322 Slur_engraver::stop_translation_timestep ()
323 {
324   if (Grob *g = unsmob_grob (get_property ("currentCommandColumn")))
325     {
326       for (vsize i = 0; i < end_slur_infos_.size (); i++)
327         Slur::add_extra_encompass (end_slur_infos_[i].slur_, g);
328
329       if (!start_events_.size ())
330         for (vsize i = 0; i < slur_infos_.size (); i++)
331           Slur::add_extra_encompass (slur_infos_[i].slur_, g);
332     }
333
334   for (vsize i = 0; i < end_slur_infos_.size (); i++)
335     {
336       Spanner *s = dynamic_cast<Spanner *> (end_slur_infos_[i].slur_);
337       if (!s->get_bound (RIGHT))
338         s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn")));
339       announce_end_grob (s, SCM_EOL);
340     }
341
342   for (vsize i = 0; i < objects_to_acknowledge_.size (); i++)
343     Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_infos_, end_slur_infos_);
344
345   for (vsize i = 0; i < end_slur_infos_.size (); i++)
346     {
347       // There are likely SlurStubs we don't need. Get rid of them
348       // and only keep one per VerticalAxisGroup.
349       vector<Grob *> vags;
350       vector<Grob *> stubs;
351       for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++)
352         {
353           Grob *stub = end_slur_infos_[i].stubs_[j];
354           Grob *vag = Grob::get_vertical_axis_group (stub);
355           if (vag)
356             {
357               vector<Grob *>::const_iterator it =
358                 find (vags.begin (), vags.end (), vag);
359               if (it != vags.end ())
360                 stub->suicide ();
361               else
362                 {
363                   vags.push_back (vag);
364                   stubs.push_back (stub);
365                 }
366             }
367           else
368             {
369               end_slur_infos_[i].slur_->programming_error ("Cannot find vertical axis group for NoteColumn.");
370               stub->suicide ();
371             }
372         }
373       for (vsize j = 0; j < stubs.size (); j++)
374         Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]);
375     }
376
377   objects_to_acknowledge_.clear ();
378   end_slur_infos_.clear ();
379   start_events_.clear ();
380   stop_events_.clear ();
381 }
382
383 ADD_ACKNOWLEDGER (Slur_engraver, inline_accidental);
384 ADD_ACKNOWLEDGER (Slur_engraver, fingering);
385 ADD_ACKNOWLEDGER (Slur_engraver, note_column);
386 ADD_ACKNOWLEDGER (Slur_engraver, script);
387 ADD_ACKNOWLEDGER (Slur_engraver, text_script);
388 ADD_ACKNOWLEDGER (Slur_engraver, dots);
389 ADD_END_ACKNOWLEDGER (Slur_engraver, tie);
390 ADD_ACKNOWLEDGER (Slur_engraver, tuplet_number);
391 ADD_TRANSLATOR (Slur_engraver,
392                 /* doc */
393                 "Build slur grobs from slur events.",
394
395                 /* create */
396                 "Slur ",
397
398                 /* read */
399                 "slurMelismaBusy "
400                 "doubleSlurs ",
401
402                 /* write */
403                 ""
404                );