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);
220 measly attempt to save an eeny-weenie bit of memory.
222 if (dots != scm_to_int (d->get_property ("dot-count")))
223 d->set_property ("dot-count", scm_from_int (dots));
225 d->set_parent (note, Y_AXIS);
229 Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
231 int pos = pit->steps ();
232 SCM c0 = get_property ("middleCPosition");
233 if (scm_is_number (c0))
234 pos += scm_to_int (c0);
236 note->set_property ("staff-position", scm_from_int (pos));
237 notes_.push_back (note);
240 if (prev_notes_.size () == notes_.size ())
242 for (vsize i = 0; i < notes_.size (); i++)
244 Grob *p = make_spanner ("Tie", SCM_EOL);
245 Tie::set_head (p, LEFT, prev_notes_[i]);
246 Tie::set_head (p, RIGHT, notes_[i]);
252 left_to_do_ -= note_dur.get_length ();
255 don't do complicated arithmetic with grace notes.
258 && now_mom ().grace_part_)
259 left_to_do_ = Rational (0, 0);
263 Completion_heads_engraver::stop_translation_timestep ()
268 prev_notes_ = notes_;
273 for (vsize i = scratch_note_events_.size (); i--;)
274 scratch_note_events_[i]->unprotect ();
276 scratch_note_events_.clear ();
280 Completion_heads_engraver::start_translation_timestep ()
282 Moment now = now_mom ();
283 if (note_end_mom_.main_part_ <= now.main_part_)
285 note_events_.clear ();
286 prev_notes_.clear ();
290 Completion_heads_engraver::Completion_heads_engraver ()
294 ADD_TRANSLATOR (Completion_heads_engraver,
295 /* doc */ "This engraver replaces "
296 "@code{Note_heads_engraver}. It plays some trickery to "
297 "break long notes and automatically tie them into the next measure.",
298 /* create */ "NoteHead Dots Tie",
299 /* accept */ "note-event",
300 /* read */ "middleCPosition measurePosition measureLength",