]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
ab25e271c03771f6a9e26af72c39eb4bd9f55d77
[lilypond.git] / lily / completion-note-heads-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2011 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 <cctype>
21 using namespace std;
22
23 #include "dot-column.hh"
24 #include "dots.hh"
25 #include "duration.hh"
26 #include "global-context.hh"
27 #include "item.hh"
28 #include "output-def.hh"
29 #include "pitch.hh"
30 #include "rhythmic-head.hh"
31 #include "score-engraver.hh"
32 #include "spanner.hh"
33 #include "staff-symbol-referencer.hh"
34 #include "stream-event.hh"
35 #include "tie.hh"
36 #include "tie-column.hh"
37 #include "warn.hh"
38
39 #include "translator.icc"
40
41 /*
42   How does this work?
43
44   When we catch the note, we predict the end of the note. We keep the
45   events living until we reach the predicted end-time.
46
47   Every time process_music () is called and there are note events, we
48   figure out how long the note to typeset should be. It should be no
49   longer than what's specified, than what is left to do and it should
50   not cross barlines.
51
52   We copy the events into scratch note events, to make sure that we get
53   all durations exactly right.
54 */
55
56 class Completion_heads_engraver : public Engraver
57 {
58   vector<Item *> notes_;
59   vector<Item *> prev_notes_;
60   // Must remember notes for explicit ties.
61   vector<Item *> tie_note_candidates_;
62   vector<Stream_event *> tie_note_candidate_events_;
63   vector<Grob *> ties_;
64   vector<Stream_event *> note_events_;
65   Spanner *tie_column_;
66   Moment note_end_mom_;
67   bool is_first_;
68   Rational left_to_do_;
69   Rational do_nothing_until_;
70   Rational factor_;
71
72   Moment next_barline_moment ();
73   Item *make_note_head (Stream_event *);
74
75 public:
76   TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
77
78 protected:
79   virtual void initialize ();
80   void make_tie (Grob *, Grob *);
81   void start_translation_timestep ();
82   void process_music ();
83   void stop_translation_timestep ();
84   DECLARE_TRANSLATOR_LISTENER (note);
85 };
86
87 void
88 Completion_heads_engraver::initialize ()
89 {
90   is_first_ = false;
91 }
92
93 IMPLEMENT_TRANSLATOR_LISTENER (Completion_heads_engraver, note);
94 void
95 Completion_heads_engraver::listen_note (Stream_event *ev)
96 {
97   note_events_.push_back (ev);
98
99   is_first_ = true;
100   Moment now = now_mom ();
101   Moment musiclen = get_event_length (ev, now);
102
103   note_end_mom_ = max (note_end_mom_, (now + musiclen));
104   do_nothing_until_ = Rational (0, 0);
105 }
106
107 /*
108   The duration _until_ the next bar line.
109 */
110 Moment
111 Completion_heads_engraver::next_barline_moment ()
112 {
113   Moment *e = unsmob_moment (get_property ("measurePosition"));
114   Moment *l = unsmob_moment (get_property ("measureLength"));
115   if (!e || !l || !to_boolean (get_property ("timing")))
116     {
117       return Moment (0, 0);
118     }
119
120   return (*l - *e);
121 }
122
123 Item *
124 Completion_heads_engraver::make_note_head (Stream_event *ev)
125 {
126   Item *note = make_item ("NoteHead", ev->self_scm ());
127   Pitch *pit = unsmob_pitch (ev->get_property ("pitch"));
128
129   int pos = pit->steps ();
130   SCM c0 = get_property ("middleCPosition");
131   if (scm_is_number (c0))
132     pos += scm_to_int (c0);
133
134   note->set_property ("staff-position", scm_from_int (pos));
135
136   return note;
137 }
138
139 void
140 Completion_heads_engraver::process_music ()
141 {
142   if (!is_first_ && !left_to_do_)
143     return;
144
145   is_first_ = false;
146
147   Moment now = now_mom ();
148   if (do_nothing_until_ > now.main_part_)
149     return;
150
151   Duration note_dur;
152   Duration *orig = 0;
153   if (left_to_do_)
154     {
155       /*
156         note that note_dur may be strictly less than left_to_do_
157         (say, if left_to_do_ == 5/8)
158       */
159       if (factor_.denominator () == 1 && factor_ > Rational (1, 1))
160         note_dur = Duration (left_to_do_, false);
161       else
162         note_dur = Duration (left_to_do_ / factor_, false).compressed (factor_);
163     }
164   else
165     {
166       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
167       note_dur = *orig;
168       factor_ = note_dur.factor ();
169       left_to_do_ = orig->get_length ();
170     }
171   Moment nb = next_barline_moment ();
172   if (nb.main_part_ && nb < note_dur.get_length ())
173     {
174       if (factor_.denominator () == 1 && factor_ > Rational (1, 1))
175         note_dur = Duration (nb.main_part_, false);
176       else
177         note_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_);
178     }
179
180   do_nothing_until_ = now.main_part_ + note_dur.get_length ();
181
182   for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
183     {
184       bool need_clone = !orig || *orig != note_dur;
185       Stream_event *event = note_events_[i];
186
187       if (need_clone)
188         event = event->clone ();
189
190       SCM pits = note_events_[i]->get_property ("pitch");
191       event->set_property ("pitch", pits);
192       event->set_property ("duration", note_dur.smobbed_copy ());
193       event->set_property ("length", Moment (note_dur.get_length ()).smobbed_copy ());
194       event->set_property ("duration-log", scm_from_int (note_dur.duration_log ()));
195
196       /*
197         The Completion_heads_engraver splits an event into a group of consecutive events.
198         For each event in the group, the property "autosplit-end" denotes whether the current event
199         was truncated during splitting. Based on "autosplit-end", the Tie_engraver decides whether a
200         tie event should be processed.
201       */
202       event->set_property ("autosplit-end",
203                            ly_bool2scm (left_to_do_ - note_dur.get_length () > Rational (0)));
204
205       Item *note = make_note_head (event);
206       if (need_clone)
207         event->unprotect ();
208       notes_.push_back (note);
209     }
210
211   if (prev_notes_.size () == notes_.size ())
212     {
213       for (vsize i = 0; i < notes_.size (); i++)
214         make_tie (prev_notes_[i], notes_[i]);
215     }
216
217   if (ties_.size () && !tie_column_)
218     tie_column_ = make_spanner ("TieColumn", ties_[0]->self_scm ());
219
220   if (tie_column_)
221     for (vsize i = ties_.size (); i--;)
222       Tie_column::add_tie (tie_column_, ties_[i]);
223
224   left_to_do_ -= note_dur.get_length ();
225   if (left_to_do_)
226     get_global_context ()->add_moment_to_process (now.main_part_ + note_dur.get_length ());
227   /*
228     don't do complicated arithmetic with grace notes.
229   */
230   if (orig && now_mom ().grace_part_)
231     left_to_do_ = Rational (0, 0);
232 }
233
234 void
235 Completion_heads_engraver::make_tie (Grob *left, Grob *right)
236 {
237   Grob *p = make_spanner ("Tie", SCM_EOL);
238   Tie::set_head (p, LEFT, left);
239   Tie::set_head (p, RIGHT, right);
240   ties_.push_back (p);
241 }
242
243 void
244 Completion_heads_engraver::stop_translation_timestep ()
245 {
246   ties_.clear ();
247   tie_column_ = 0;
248
249   if (notes_.size ())
250     prev_notes_ = notes_;
251   notes_.clear ();
252 }
253
254 void
255 Completion_heads_engraver::start_translation_timestep ()
256 {
257   Moment now = now_mom ();
258   if (note_end_mom_.main_part_ <= now.main_part_)
259     {
260       tie_note_candidate_events_ = note_events_;
261       tie_note_candidates_ = prev_notes_;
262
263       note_events_.clear ();
264       prev_notes_.clear ();
265     }
266   context ()->set_property ("completionBusy",
267                             ly_bool2scm (note_events_.size ()));
268 }
269
270 Completion_heads_engraver::Completion_heads_engraver ()
271 {
272   tie_column_ = 0;
273 }
274
275 ADD_TRANSLATOR (Completion_heads_engraver,
276                 /* doc */
277                 "This engraver replaces @code{Note_heads_engraver}.  It plays"
278                 " some trickery to break long notes and automatically tie them"
279                 " into the next measure.",
280
281                 /* create */
282                 "NoteHead "
283                 "Tie "
284                 "TieColumn ",
285
286                 /* read */
287                 "measureLength "
288                 "measurePosition "
289                 "middleCPosition "
290                 "timing ",
291
292                 /* write */
293                 "completionBusy "
294                );