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