]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie-engraver.cc
Merge remote branch 'origin/master' into release/unstable
[lilypond.git] / lily / tie-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1998--2014 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 "international.hh"
24 #include "item.hh"
25 #include "note-head.hh"
26 #include "protected-scm.hh"
27 #include "spanner.hh"
28 #include "staff-symbol-referencer.hh"
29 #include "stream-event.hh"
30 #include "tie-column.hh"
31 #include "tie.hh"
32 #include "warn.hh"
33
34 #include "translator.icc"
35
36 /**
37    Manufacture ties.  Acknowledge note heads, and put them into a
38    priority queue. If we have a TieEvent, connect the notes that finish
39    just at this time, and note that start at this time.
40
41    TODO: Remove the dependency on musical info. We should tie on the
42    basis of position and duration-log of the heads (not of the events).
43 */
44
45 struct Head_event_tuple
46 {
47   Grob *head_;
48   Moment end_moment_;
49   Stream_event *tie_stream_event_;
50   Stream_event *tie_event_;
51   Spanner *tie_;
52   // Indicate whether a tie from the same moment has been processed successfully
53   // This is needed for tied chords, e.g. <c e g>~ g, because otherwise the c
54   // and e will trigger a warning for an unterminated tie!
55   bool tie_from_chord_created;
56
57   Head_event_tuple ()
58   {
59     head_ = 0;
60     tie_event_ = 0;
61     tie_stream_event_ = 0;
62     tie_from_chord_created = false;
63     tie_ = 0;
64   }
65 };
66
67 class Tie_engraver : public Engraver
68 {
69   /*
70     Whether tie event has been processed and can be deleted or should
71     be kept for later portions of a split note.
72   */
73   bool event_processed_;
74   Stream_event *event_;
75   vector<Grob *> now_heads_;
76   vector<Head_event_tuple> heads_to_tie_;
77   vector<Grob *> ties_;
78
79   Spanner *tie_column_;
80
81 protected:
82   void process_acknowledged ();
83   void stop_translation_timestep ();
84   void start_translation_timestep ();
85   DECLARE_ACKNOWLEDGER (note_head);
86   DECLARE_TRANSLATOR_LISTENER (tie);
87   void process_music ();
88   void typeset_tie (Grob *);
89   void report_unterminated_tie (Head_event_tuple const &);
90   bool has_autosplit_end (Stream_event *event);
91 public:
92   TRANSLATOR_DECLARATIONS (Tie_engraver);
93 };
94
95 Tie_engraver::Tie_engraver ()
96 {
97   event_ = 0;
98   tie_column_ = 0;
99   event_processed_ = false;
100 }
101
102 IMPLEMENT_TRANSLATOR_LISTENER (Tie_engraver, tie);
103 void
104 Tie_engraver::listen_tie (Stream_event *ev)
105 {
106   if (!to_boolean (get_property ("skipTypesetting")))
107     {
108       ASSIGN_EVENT_ONCE (event_, ev);
109     }
110 }
111
112 void Tie_engraver::report_unterminated_tie (Head_event_tuple const &tie_start)
113 {
114   // If tie_from_chord_created is set, we have another note at the same
115   // moment that created a tie, so this is not necessarily an unterminated
116   // tie. Happens e.g. for <c e g>~ g
117   if (!tie_start.tie_from_chord_created)
118     {
119       tie_start.tie_->warning (_ ("unterminated tie"));
120       tie_start.tie_->suicide ();
121     }
122 }
123
124 /*
125   Determines whether the end of an event was created by
126   a split in Completion_heads_engraver or by user input.
127 */
128 bool
129 Tie_engraver::has_autosplit_end (Stream_event *event)
130 {
131   if (event)
132     return to_boolean (event->get_property ("autosplit-end"));
133   return false;
134 }
135
136 void
137 Tie_engraver::process_music ()
138 {
139   bool busy = event_;
140   for (vsize i = 0; !busy && i < heads_to_tie_.size (); i++)
141     busy |= (heads_to_tie_[i].tie_event_
142              || heads_to_tie_[i].tie_stream_event_);
143
144   if (busy)
145     context ()->set_property ("tieMelismaBusy", SCM_BOOL_T);
146 }
147
148 void
149 Tie_engraver::acknowledge_note_head (Grob_info i)
150 {
151   Grob *h = i.grob ();
152
153   now_heads_.push_back (h);
154   for (vsize i = 0; i < heads_to_tie_.size (); i++)
155     {
156       Grob *th = heads_to_tie_[i].head_;
157       Stream_event *right_ev = Stream_event::unsmob (h->get_property ("cause"));
158       Stream_event *left_ev = Stream_event::unsmob (th->get_property ("cause"));
159
160       /*
161         maybe should check positions too.
162       */
163       if (!right_ev || !left_ev)
164         continue;
165
166       /*
167         Make a tie only if pitches are equal or if event end was not generated by
168         Completion_heads_engraver.
169       */
170       if (ly_is_equal (right_ev->get_property ("pitch"), left_ev->get_property ("pitch"))
171           && (!Tie_engraver::has_autosplit_end (left_ev)))
172         {
173           Grob *p = heads_to_tie_[i].tie_;
174           Moment end = heads_to_tie_[i].end_moment_;
175
176           Stream_event *cause = heads_to_tie_[i].tie_event_
177                                     ? heads_to_tie_[i].tie_event_
178                                     : heads_to_tie_[i].tie_stream_event_;
179
180           announce_end_grob (p, cause->self_scm ());
181
182           Tie::set_head (p, LEFT, th);
183           Tie::set_head (p, RIGHT, h);
184
185           if (is_direction (cause->get_property ("direction")))
186             {
187               Direction d = to_dir (cause->get_property ("direction"));
188               p->set_property ("direction", scm_from_int (d));
189             }
190
191           ties_.push_back (p);
192           heads_to_tie_.erase (heads_to_tie_.begin () + i);
193
194           /*
195             Prevent all other tied notes ending at the same moment (assume
196             implicitly the notes have also started at the same moment!)
197             from triggering an "unterminated tie" warning. Needed e.g. for
198             <c e g>~ g
199           */
200           for (vsize j = heads_to_tie_.size (); j--;)
201             {
202               if (heads_to_tie_[j].end_moment_ == end)
203                 heads_to_tie_[j].tie_from_chord_created = true;
204             }
205         }
206       else
207         i++;
208     }
209
210   if (ties_.size () && ! tie_column_)
211     tie_column_ = make_spanner ("TieColumn", ties_[0]->self_scm ());
212
213   if (tie_column_)
214     for (vsize i = 0; i < ties_.size (); i++)
215       Tie_column::add_tie (tie_column_, ties_[i]);
216 }
217
218 void
219 Tie_engraver::start_translation_timestep ()
220 {
221   if (heads_to_tie_.size () && !to_boolean (get_property ("tieWaitForNote")))
222     {
223       Moment now = now_mom ();
224       for (vsize i = heads_to_tie_.size (); i--;)
225         {
226           if (now > heads_to_tie_[i].end_moment_)
227             {
228               report_unterminated_tie (heads_to_tie_[i]);
229               heads_to_tie_.erase (heads_to_tie_.begin () + i);
230             }
231         }
232     }
233
234   context ()->set_property ("tieMelismaBusy",
235                             ly_bool2scm (heads_to_tie_.size ()));
236 }
237
238 void
239 Tie_engraver::process_acknowledged ()
240 {
241   bool wait = to_boolean (get_property ("tieWaitForNote"));
242   if (ties_.size ())
243     {
244       if (!wait)
245         {
246           vector<Head_event_tuple>::iterator it = heads_to_tie_.begin ();
247           for (; it < heads_to_tie_.end (); it++)
248             report_unterminated_tie (*it);
249           heads_to_tie_.clear ();
250         }
251
252       for (vsize i = 0; i < ties_.size (); i++)
253         typeset_tie (ties_[i]);
254
255       ties_.clear ();
256       tie_column_ = 0;
257     }
258
259   vector<Head_event_tuple> new_heads_to_tie;
260
261
262   for (vsize i = 0; i < now_heads_.size (); i++)
263     {
264       Grob *head = now_heads_[i];
265       Stream_event *left_ev
266         = Stream_event::unsmob (head->get_property ("cause"));
267
268       if (!left_ev)
269         {
270           // may happen for ambitus
271           continue;
272         }
273
274       // We only want real notes to cause ties, not e.g. pitched trills
275       if (!left_ev->in_event_class ("note-event"))
276         continue;
277
278       SCM left_articulations = left_ev->get_property ("articulations");
279
280       Stream_event *tie_event = 0;
281       Stream_event *tie_stream_event = event_;
282       for (SCM s = left_articulations;
283            !tie_event && !tie_stream_event && scm_is_pair (s);
284            s = scm_cdr (s))
285         {
286           Stream_event *ev = Stream_event::unsmob (scm_car (s));
287           if (!ev)
288             continue;
289
290           if (ev->in_event_class ("tie-event"))
291             tie_event = ev;
292         }
293
294       if (left_ev && (tie_event || tie_stream_event)
295           && (!Tie_engraver::has_autosplit_end (left_ev)))
296         {
297           event_processed_ = true;
298
299           Head_event_tuple event_tup;
300
301           event_tup.head_ = head;
302           event_tup.tie_event_ = tie_event;
303           event_tup.tie_stream_event_ = tie_stream_event;
304           event_tup.tie_ = make_spanner ("Tie", tie_event
305                                     ? tie_event->self_scm ()
306                                     : tie_stream_event->self_scm ());
307
308           Moment end = now_mom ();
309           if (end.grace_part_)
310             {
311               end.grace_part_ += get_event_length (left_ev).main_part_;
312             }
313           else
314             {
315               end += get_event_length (left_ev);
316             }
317           event_tup.end_moment_ = end;
318
319           new_heads_to_tie.push_back (event_tup);
320         }
321     }
322
323   if (!wait && new_heads_to_tie.size ())
324     {
325       vector<Head_event_tuple>::iterator it = heads_to_tie_.begin ();
326       for (; it < heads_to_tie_.end (); it++)
327         report_unterminated_tie (*it);
328       heads_to_tie_.clear ();
329     }
330
331   // hmmm, how to do with copy () ?
332   for (vsize i = 0; i < new_heads_to_tie.size (); i++)
333     heads_to_tie_.push_back (new_heads_to_tie[i]);
334
335   now_heads_.clear ();
336 }
337
338 void
339 Tie_engraver::stop_translation_timestep ()
340 {
341   /*
342     Discard event only if it has been processed with at least one
343     appropriate note.
344   */
345   if (event_processed_)
346     event_ = 0;
347
348   event_processed_ = false;
349 }
350
351 void
352 Tie_engraver::typeset_tie (Grob *her)
353 {
354   if (! (Tie::head (her, LEFT) && Tie::head (her, RIGHT)))
355     warning (_ ("lonely tie"));
356
357   Drul_array<Grob *> new_head_drul;
358   new_head_drul[LEFT] = Tie::head (her, LEFT);
359   new_head_drul[RIGHT] = Tie::head (her, RIGHT);
360   for (LEFT_and_RIGHT (d))
361     {
362       if (!Tie::head (her, d))
363         new_head_drul[d] = Tie::head (her, (Direction) - d);
364     }
365
366   Spanner *sp = dynamic_cast<Spanner *> (her);
367   sp->set_bound (LEFT, new_head_drul[LEFT]);
368   sp->set_bound (RIGHT, new_head_drul[RIGHT]);
369 }
370
371 ADD_ACKNOWLEDGER (Tie_engraver, note_head);
372 ADD_TRANSLATOR (Tie_engraver,
373                 /* doc */
374                 "Generate ties between note heads of equal pitch.",
375
376                 /* create */
377                 "Tie "
378                 "TieColumn ",
379
380                 /* read */
381                 "skipTypesetting "
382                 "tieWaitForNote ",
383
384                 /* write */
385                 "tieMelismaBusy "
386                );