2 completion-note-heads-engraver.cc -- Completion_heads_engraver
4 (c) 1997--2006 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 musiclen = get_event_length (ev);
89 Moment now = now_mom ();
91 if (now_mom ().grace_part_)
93 musiclen.grace_part_ = musiclen.main_part_;
94 musiclen.main_part_ = Rational (0, 1);
97 note_end_mom_ = max (note_end_mom_, (now + musiclen));
98 do_nothing_until_ = Rational (0, 0);
102 The duration _until_ the next barline.
105 Completion_heads_engraver::next_barline_moment ()
107 Moment *e = unsmob_moment (get_property ("measurePosition"));
108 Moment *l = unsmob_moment (get_property ("measureLength"));
111 programming_error ("no timing props set?");
112 return Moment (1, 1);
119 Completion_heads_engraver::find_nearest_duration (Rational length)
126 this could surely be done more efficient. Left to the reader as an
128 while (d.get_length () > length && d.duration_log () < log_limit)
132 d = Duration (d.duration_log (), d.dot_count ()- 1);
136 d = Duration (d.duration_log () + 1, 2);
139 if (d.duration_log () >= log_limit)
142 d = Duration (d.duration_log (), 0);
145 d = d.compressed (length / d.get_length ());
152 Completion_heads_engraver::process_music ()
154 if (!is_first_ && !left_to_do_)
159 Moment now = now_mom ();
160 if (do_nothing_until_ > now.main_part_)
166 note_dur = find_nearest_duration (left_to_do_);
169 orig = unsmob_duration (note_events_[0]->get_property ("duration"));
172 Moment nb = next_barline_moment ();
173 if (nb < note_dur.get_length ())
175 note_dur = find_nearest_duration (nb.main_part_);
178 next.main_part_ += note_dur.get_length ();
180 get_global_context ()->add_moment_to_process (next);
181 do_nothing_until_ = next.main_part_;
185 left_to_do_ = orig->get_length ();
187 if (orig && note_dur.get_length () != orig->get_length ())
189 if (!scratch_note_events_.size ())
190 for (vsize i = 0; i < note_events_.size (); i++)
192 Stream_event *m = note_events_[i]->clone ();
193 scratch_note_events_.push_back (m);
197 for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
199 Stream_event *event = note_events_[i];
200 if (scratch_note_events_.size ())
202 event = scratch_note_events_[i];
203 SCM pits = note_events_[i]->get_property ("pitch");
204 event->set_property ("pitch", pits);
207 event->set_property ("duration", note_dur.smobbed_copy ());
209 Item *note = make_item ("NoteHead", event->self_scm ());
210 note->set_property ("duration-log",
211 scm_from_int (note_dur.duration_log ()));
213 int dots = note_dur.dot_count ();
216 Item *d = make_item ("Dots", SCM_EOL);
217 Rhythmic_head::set_dots (note, d);
219 d->set_property ("dot-count", scm_from_int (dots));
221 d->set_parent (note, Y_AXIS);
225 Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
227 int pos = pit->steps ();
228 SCM c0 = get_property ("middleCPosition");
229 if (scm_is_number (c0))
230 pos += scm_to_int (c0);
232 note->set_property ("staff-position", scm_from_int (pos));
233 notes_.push_back (note);
236 if (prev_notes_.size () == notes_.size ())
238 for (vsize i = 0; i < notes_.size (); i++)
240 Grob *p = make_spanner ("Tie", SCM_EOL);
241 Tie::set_head (p, LEFT, prev_notes_[i]);
242 Tie::set_head (p, RIGHT, notes_[i]);
248 left_to_do_ -= note_dur.get_length ();
251 don't do complicated arithmetic with grace notes.
254 && now_mom ().grace_part_)
255 left_to_do_ = Rational (0, 0);
259 Completion_heads_engraver::stop_translation_timestep ()
264 prev_notes_ = notes_;
269 for (vsize i = scratch_note_events_.size (); i--;)
270 scratch_note_events_[i]->unprotect ();
272 scratch_note_events_.clear ();
276 Completion_heads_engraver::start_translation_timestep ()
278 Moment now = now_mom ();
279 if (note_end_mom_.main_part_ <= now.main_part_)
281 note_events_.clear ();
282 prev_notes_.clear ();
286 Completion_heads_engraver::Completion_heads_engraver ()
290 ADD_TRANSLATOR (Completion_heads_engraver,
291 /* doc */ "This engraver replaces "
292 "@code{Note_heads_engraver}. It plays some trickery to "
293 "break long notes and automatically tie them into the next measure.",
299 /* accept */ "note-event",