]> git.donarmstrong.com Git - lilypond.git/blob - lily/phrasing-slur-engraver.cc
Better approximations for cross-staff slurs
[lilypond.git] / lily / phrasing-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 Slur_engraver. Check if fixes
39   apply there too.
40
41   (on principle, engravers don't use inheritance for code sharing)
42
43   For info on SlurStubs, check out slur-engraver.cc.
44
45  */
46
47 /*
48   It is possible that a slur starts and ends on the same note.  At
49   least, it is for phrasing slurs: a note can be both beginning and
50   ending of a phrase.
51 */
52 class Phrasing_slur_engraver : public Engraver
53 {
54   vector<Stream_event *> start_events_;
55   vector<Stream_event *> stop_events_;
56   vector<Slur_info> slur_infos_;
57   vector<Slur_info> end_slur_infos_;
58   vector<Grob_info> objects_to_acknowledge_;
59
60 protected:
61   DECLARE_TRANSLATOR_LISTENER (phrasing_slur);
62   DECLARE_ACKNOWLEDGER (inline_accidental);
63   DECLARE_ACKNOWLEDGER (fingering);
64   DECLARE_ACKNOWLEDGER (note_column);
65   DECLARE_ACKNOWLEDGER (slur);
66   DECLARE_ACKNOWLEDGER (script);
67   DECLARE_ACKNOWLEDGER (dots);
68   DECLARE_ACKNOWLEDGER (text_script);
69   DECLARE_END_ACKNOWLEDGER (tie);
70   DECLARE_ACKNOWLEDGER (tuplet_number);
71
72   void acknowledge_extra_object (Grob_info);
73   void stop_translation_timestep ();
74   void process_music ();
75
76   virtual void finalize ();
77   virtual void derived_mark () const;
78
79 public:
80   TRANSLATOR_DECLARATIONS (Phrasing_slur_engraver);
81 };
82
83 Phrasing_slur_engraver::Phrasing_slur_engraver ()
84 {
85 }
86
87 void
88 Phrasing_slur_engraver::derived_mark () const
89 {
90   for (vsize i = start_events_.size (); i--;)
91     scm_gc_mark (start_events_[i]->self_scm ());
92   for (vsize i = stop_events_.size (); i--;)
93     scm_gc_mark (stop_events_[i]->self_scm ());
94 }
95
96 IMPLEMENT_TRANSLATOR_LISTENER (Phrasing_slur_engraver, phrasing_slur);
97 void
98 Phrasing_slur_engraver::listen_phrasing_slur (Stream_event *ev)
99 {
100   Direction d = to_dir (ev->get_property ("span-direction"));
101   if (d == START)
102     start_events_.push_back (ev);
103   else if (d == STOP)
104     stop_events_.push_back (ev);
105   else ev->origin ()->warning (_f ("direction of %s invalid: %d",
106                                      "phrasing-slur-event", int (d)));
107 }
108
109 void
110 Phrasing_slur_engraver::acknowledge_note_column (Grob_info info)
111 {
112   Grob *e = info.grob ();
113   for (vsize i = slur_infos_.size (); i--;)
114     {
115       Slur::add_column (slur_infos_[i].slur_, e);
116       Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ());
117       slur_infos_[i].stubs_.push_back (stub);
118     }
119   for (vsize i = end_slur_infos_.size (); i--;)
120     {
121       Slur::add_column (end_slur_infos_[i].slur_, e);
122       Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ());
123       end_slur_infos_[i].stubs_.push_back (stub);
124     }
125 }
126
127 void
128 Phrasing_slur_engraver::acknowledge_extra_object (Grob_info info)
129 {
130   objects_to_acknowledge_.push_back (info);
131 }
132
133 void
134 Phrasing_slur_engraver::acknowledge_inline_accidental (Grob_info info)
135 {
136   acknowledge_extra_object (info);
137 }
138
139 void
140 Phrasing_slur_engraver::acknowledge_dots (Grob_info info)
141 {
142   acknowledge_extra_object (info);
143 }
144
145 void
146 Phrasing_slur_engraver::acknowledge_fingering (Grob_info info)
147 {
148   acknowledge_extra_object (info);
149 }
150
151 void
152 Phrasing_slur_engraver::acknowledge_tuplet_number (Grob_info info)
153 {
154   acknowledge_extra_object (info);
155 }
156
157 void
158 Phrasing_slur_engraver::acknowledge_script (Grob_info info)
159 {
160   if (!info.grob ()->internal_has_interface (ly_symbol2scm ("dynamic-interface")))
161
162     acknowledge_extra_object (info);
163 }
164
165 void
166 Phrasing_slur_engraver::acknowledge_text_script (Grob_info info)
167 {
168   acknowledge_extra_object (info);
169 }
170
171 void
172 Phrasing_slur_engraver::acknowledge_end_tie (Grob_info info)
173 {
174   acknowledge_extra_object (info);
175 }
176
177 void
178 Phrasing_slur_engraver::acknowledge_slur (Grob_info info)
179 {
180   if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface")))
181     acknowledge_extra_object (info);
182 }
183
184 void
185 Phrasing_slur_engraver::finalize ()
186 {
187   for (vsize i = 0; i < slur_infos_.size (); i++)
188     {
189       slur_infos_[i].slur_->warning (_ ("unterminated phrasing slur"));
190       slur_infos_[i].slur_->suicide ();
191       for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++)
192         slur_infos_[i].stubs_[j]->suicide ();
193     }
194   slur_infos_.clear ();
195 }
196
197 void
198 Phrasing_slur_engraver::process_music ()
199 {
200   for (vsize i = 0; i < stop_events_.size (); i++)
201     {
202       Stream_event *ev = stop_events_[i];
203       string id = robust_scm2string (ev->get_property ("spanner-id"), "");
204
205       // Find the slurs that are ended with this event (by checking the spanner-id)
206       bool ended = false;
207       for (vsize j = slur_infos_.size (); j--;)
208         {
209           if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), ""))
210             {
211               ended = true;
212               end_slur_infos_.push_back (slur_infos_[j].slur_);
213               slur_infos_.erase (slur_infos_.begin () + j);
214             }
215         }
216       if (ended)
217         {
218           // Ignore redundant stop events for this id
219           for (vsize j = stop_events_.size (); --j > i;)
220             {
221               if (id == robust_scm2string (stop_events_[j]->get_property ("spanner-id"), ""))
222                 stop_events_.erase (stop_events_.begin () + j);
223             }
224         }
225       else
226         ev->origin ()->warning (_ ("cannot end phrasing slur"));
227     }
228
229   vsize old_slurs = slur_infos_.size ();
230   for (vsize i = start_events_.size (); i--;)
231     {
232       Stream_event *ev = start_events_[i];
233       string id = robust_scm2string (ev->get_property ("spanner-id"), "");
234       Direction updown = to_dir (ev->get_property ("direction"));
235
236       bool completed;
237       for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));)
238         {
239           // Check if we already have a slur with the same spanner-id.
240           if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), ""))
241             {
242               if (j < old_slurs)
243                 {
244                   // We already have an old slur, so give a warning
245                   // and completely ignore the new slur.
246                   ev->origin ()->warning (_ ("already have phrasing slur"));
247                   start_events_.erase (start_events_.begin () + i);
248                   break;
249                 }
250
251               // If this slur event has no direction, it will not
252               // contribute anything new to the existing slur(s), so
253               // we can ignore it.
254
255               if (!updown)
256                 break;
257
258               Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_property ("cause"));
259
260               if (!c)
261                 {
262                   slur_infos_[j].slur_->programming_error ("phrasing slur without a cause");
263                   continue;
264                 }
265
266               Direction slur_dir = to_dir (c->get_property ("direction"));
267
268               // If the existing slur does not have a direction yet,
269               // we'd rather take the new one.
270
271               if (!slur_dir)
272                 {
273                   slur_infos_[j].slur_->suicide ();
274                   for (vsize k = 0; k < slur_infos_[i].stubs_.size (); k++)
275                     slur_infos_[j].stubs_[k]->suicide ();
276                   slur_infos_.erase (slur_infos_.begin () + j);
277                   continue;
278                 }
279
280               // If the existing slur has the same direction as ours, drop ours
281
282               if (slur_dir == updown)
283                 break;
284             }
285         }
286       // If the loop completed, our slur is new
287       if (completed)
288         {
289           Grob *slur = make_spanner ("PhrasingSlur", ev->self_scm ());
290           slur->set_property ("spanner-id", ly_string2scm (id));
291           if (updown)
292             set_grob_direction (slur, updown);
293           slur_infos_.push_back (slur);
294         }
295     }
296 }
297
298 void
299 Phrasing_slur_engraver::stop_translation_timestep ()
300 {
301   if (Grob *g = unsmob_grob (get_property ("currentCommandColumn")))
302     {
303       for (vsize i = 0; i < end_slur_infos_.size (); i++)
304         Slur::add_extra_encompass (end_slur_infos_[i].slur_, g);
305
306       if (!start_events_.size ())
307         for (vsize i = 0; i < slur_infos_.size (); i++)
308           Slur::add_extra_encompass (slur_infos_[i].slur_, g);
309     }
310
311   for (vsize i = 0; i < end_slur_infos_.size (); i++)
312     {
313       Spanner *s = dynamic_cast<Spanner *> (end_slur_infos_[i].slur_);
314       if (!s->get_bound (RIGHT))
315         s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn")));
316       announce_end_grob (s, SCM_EOL);
317     }
318
319   for (vsize i = 0; i < objects_to_acknowledge_.size (); i++)
320     Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_infos_, end_slur_infos_);
321
322   for (vsize i = 0; i < end_slur_infos_.size (); i++)
323     {
324       // There are likely SlurStubs we don't need. Get rid of them.
325       vector<Grob *> vags;
326       vector<Grob *> stubs;
327       for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++)
328         {
329           Grob *stub = end_slur_infos_[i].stubs_[j];
330           Grob *vag = Grob::get_vertical_axis_group (stub);
331           if (vag)
332             {
333               vector<Grob *>::const_iterator it =
334                 find (vags.begin (), vags.end (), vag);
335               if (it != vags.end ())
336                 stub->suicide ();
337               else
338                 {
339                   vags.push_back (vag);
340                   stubs.push_back (stub);
341                 }
342             }
343           else
344             {
345               end_slur_infos_[i].slur_->programming_error ("Cannot find vertical axis group for NoteColumn.");
346               stub->suicide ();
347             }
348         }
349       for (vsize j = 0; j < stubs.size (); j++)
350         Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]);
351     }
352
353   objects_to_acknowledge_.clear ();
354   end_slur_infos_.clear ();
355   start_events_.clear ();
356   stop_events_.clear ();
357 }
358
359 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, inline_accidental);
360 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, fingering)
361 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, note_column);
362 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, slur);
363 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, script);
364 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, dots);
365 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, text_script);
366 ADD_END_ACKNOWLEDGER (Phrasing_slur_engraver, tie);
367 ADD_ACKNOWLEDGER (Phrasing_slur_engraver, tuplet_number);
368
369 ADD_TRANSLATOR (Phrasing_slur_engraver,
370                 /* doc */
371                 "Print phrasing slurs.  Similar to @ref{Slur_engraver}.",
372
373                 /* create */
374                 "PhrasingSlur ",
375
376                 /* read */
377                 "",
378
379                 /* write */
380                 ""
381                );