2 completion-note-heads-engraver.cc -- Completion_heads_engraver
4 (c) 1997--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
10 #include "dot-column.hh"
12 #include "duration.hh"
13 #include "global-context.hh"
15 #include "output-def.hh"
17 #include "rhythmic-head.hh"
18 #include "score-engraver.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "stream-event.hh"
25 #include "translator.icc"
28 TODO: make matching rest engraver.
34 When we catch the note, we predict the end of the note. We keep the
35 events living until we reach the predicted end-time.
37 Every time process_music () is called and there are note events, we
38 figure out how long the note to typeset should be. It should be no
39 longer than what's specified, than what is left to do and it should
42 We copy the events into scratch note events, to make sure that we get
43 all durations exactly right.
46 class Completion_heads_engraver : public Engraver
49 vector<Item*> prev_notes_;
53 vector<Stream_event*> note_events_;
54 vector<Stream_event*> scratch_note_events_;
59 Rational do_nothing_until_;
61 Moment next_barline_moment ();
62 Duration find_nearest_duration (Rational length);
65 TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
68 virtual void initialize ();
69 void start_translation_timestep ();
70 void process_music ();
71 void stop_translation_timestep ();
72 DECLARE_TRANSLATOR_LISTENER (note);
76 Completion_heads_engraver::initialize ()
81 IMPLEMENT_TRANSLATOR_LISTENER (Completion_heads_engraver, note);
83 Completion_heads_engraver::listen_note (Stream_event *ev)
85 note_events_.push_back (ev);
88 Moment now = now_mom ();
89 Moment musiclen = get_event_length (ev, now);
91 note_end_mom_ = max (note_end_mom_, (now + musiclen));
92 do_nothing_until_ = Rational (0, 0);
96 The duration _until_ the next barline.
99 Completion_heads_engraver::next_barline_moment ()
101 Moment *e = unsmob_moment (get_property ("measurePosition"));
102 Moment *l = unsmob_moment (get_property ("measureLength"));
105 programming_error ("no timing props set?");
106 return Moment (1, 1);
113 Completion_heads_engraver::find_nearest_duration (Rational length)
120 this could surely be done more efficient. Left to the reader as an
122 while (d.get_length () > length && d.duration_log () < log_limit)
126 d = Duration (d.duration_log (), d.dot_count ()- 1);
130 d = Duration (d.duration_log () + 1, 2);
133 if (d.duration_log () >= log_limit)
136 d = Duration (d.duration_log (), 0);
139 d = d.compressed (length / d.get_length ());
146 Completion_heads_engraver::process_music ()
148 if (!is_first_ && !left_to_do_)
153 Moment now = now_mom ();
154 if (do_nothing_until_ > now.main_part_)
160 note_dur = find_nearest_duration (left_to_do_);
163 orig = unsmob_duration (note_events_[0]->get_property ("duration"));
166 Moment nb = next_barline_moment ();
167 if (nb < note_dur.get_length ())
169 note_dur = find_nearest_duration (nb.main_part_);
172 next.main_part_ += note_dur.get_length ();
174 get_global_context ()->add_moment_to_process (next);
175 do_nothing_until_ = next.main_part_;
179 left_to_do_ = orig->get_length ();
181 if (orig && note_dur.get_length () != orig->get_length ())
183 if (!scratch_note_events_.size ())
184 for (vsize i = 0; i < note_events_.size (); i++)
186 Stream_event *m = note_events_[i]->clone ();
187 scratch_note_events_.push_back (m);
192 for (vsize i = 0; i < note_events_.size (); i++)
194 Stream_event *c = note_events_[i]->clone ();
195 scratch_note_events_.push_back (c);
199 for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
201 Stream_event *event = note_events_[i];
202 if (scratch_note_events_.size ())
204 event = scratch_note_events_[i];
205 SCM pits = note_events_[i]->get_property ("pitch");
206 event->set_property ("pitch", pits);
209 event->set_property ("duration", note_dur.smobbed_copy ());
211 Item *note = make_item ("NoteHead", event->self_scm ());
212 note->set_property ("duration-log",
213 scm_from_int (note_dur.duration_log ()));
215 int dots = note_dur.dot_count ();
218 Item *d = make_item ("Dots", SCM_EOL);
219 Rhythmic_head::set_dots (note, d);
221 d->set_property ("dot-count", scm_from_int (dots));
223 d->set_parent (note, Y_AXIS);
227 Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
229 int pos = pit->steps ();
230 SCM c0 = get_property ("middleCPosition");
231 if (scm_is_number (c0))
232 pos += scm_to_int (c0);
234 note->set_property ("staff-position", scm_from_int (pos));
235 notes_.push_back (note);
238 if (prev_notes_.size () == notes_.size ())
240 for (vsize i = 0; i < notes_.size (); i++)
242 Grob *p = make_spanner ("Tie", SCM_EOL);
243 Tie::set_head (p, LEFT, prev_notes_[i]);
244 Tie::set_head (p, RIGHT, notes_[i]);
250 left_to_do_ -= note_dur.get_length ();
253 don't do complicated arithmetic with grace notes.
256 && now_mom ().grace_part_)
257 left_to_do_ = Rational (0, 0);
261 Completion_heads_engraver::stop_translation_timestep ()
266 prev_notes_ = notes_;
271 for (vsize i = scratch_note_events_.size (); i--;)
272 scratch_note_events_[i]->unprotect ();
274 scratch_note_events_.clear ();
278 Completion_heads_engraver::start_translation_timestep ()
280 Moment now = now_mom ();
281 if (note_end_mom_.main_part_ <= now.main_part_)
283 note_events_.clear ();
284 prev_notes_.clear ();
288 Completion_heads_engraver::Completion_heads_engraver ()
292 ADD_TRANSLATOR (Completion_heads_engraver,
293 /* doc */ "This engraver replaces "
294 "@code{Note_heads_engraver}. It plays some trickery to "
295 "break long notes and automatically tie them into the next measure.",