Merge branch 'master' of git://git.sv.gnu.org/lilypond
[lilypond.git] / lily / completion-note-heads-engraver.cc
blob8734b861c2a2d15eadb9b7367b508da6200619fa
1 /*
2 completion-note-heads-engraver.cc -- Completion_heads_engraver
4 (c) 1997--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 */
7 #include <cctype>
8 using namespace std;
10 #include "dot-column.hh"
11 #include "dots.hh"
12 #include "duration.hh"
13 #include "global-context.hh"
14 #include "item.hh"
15 #include "output-def.hh"
16 #include "pitch.hh"
17 #include "rhythmic-head.hh"
18 #include "score-engraver.hh"
19 #include "spanner.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "stream-event.hh"
22 #include "tie.hh"
23 #include "warn.hh"
25 #include "translator.icc"
28 TODO: make matching rest engraver.
32 How does this work?
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
40 not cross barlines.
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
48 vector<Item*> notes_;
49 vector<Item*> prev_notes_;
50 vector<Grob*> ties_;
52 vector<Stream_event*> note_events_;
54 Moment note_end_mom_;
55 bool is_first_;
56 Rational left_to_do_;
57 Rational do_nothing_until_;
59 Moment next_barline_moment ();
60 Item *make_note_head (Stream_event*);
62 public:
63 TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
65 protected:
66 virtual void initialize ();
67 void start_translation_timestep ();
68 void process_music ();
69 void stop_translation_timestep ();
70 DECLARE_TRANSLATOR_LISTENER (note);
73 void
74 Completion_heads_engraver::initialize ()
76 is_first_ = false;
79 IMPLEMENT_TRANSLATOR_LISTENER (Completion_heads_engraver, note);
80 void
81 Completion_heads_engraver::listen_note (Stream_event *ev)
83 note_events_.push_back (ev);
85 is_first_ = true;
86 Moment now = now_mom ();
87 Moment musiclen = get_event_length (ev, now);
89 note_end_mom_ = max (note_end_mom_, (now + musiclen));
90 do_nothing_until_ = Rational (0, 0);
94 The duration _until_ the next barline.
96 Moment
97 Completion_heads_engraver::next_barline_moment ()
99 Moment *e = unsmob_moment (get_property ("measurePosition"));
100 Moment *l = unsmob_moment (get_property ("measureLength"));
101 if (!e || !l || !to_boolean (get_property ("timing")))
103 return Moment (0, 0);
106 return (*l - *e);
109 Item*
110 Completion_heads_engraver::make_note_head (Stream_event *ev)
112 Item *note = make_item ("NoteHead", ev->self_scm ());
113 Pitch *pit = unsmob_pitch (ev->get_property ("pitch"));
115 int pos = pit->steps ();
116 SCM c0 = get_property ("middleCPosition");
117 if (scm_is_number (c0))
118 pos += scm_to_int (c0);
120 note->set_property ("staff-position", scm_from_int (pos));
122 return note;
125 void
126 Completion_heads_engraver::process_music ()
128 if (!is_first_ && !left_to_do_)
129 return;
131 is_first_ = false;
133 Moment now = now_mom ();
134 if (do_nothing_until_ > now.main_part_)
135 return;
137 Duration note_dur;
138 Duration *orig = 0;
139 if (left_to_do_)
140 note_dur = Duration (left_to_do_, false);
141 else
143 orig = unsmob_duration (note_events_[0]->get_property ("duration"));
144 note_dur = *orig;
146 Moment nb = next_barline_moment ();
147 if (nb.main_part_ && nb < note_dur.get_length ())
149 note_dur = Duration (nb.main_part_, false);
151 do_nothing_until_ = now.main_part_ + note_dur.get_length ();
154 if (orig)
155 left_to_do_ = orig->get_length ();
157 for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
159 bool need_clone = !orig || *orig != note_dur;
160 Stream_event *event = note_events_[i];
162 if (need_clone)
163 event = event->clone ();
165 SCM pits = note_events_[i]->get_property ("pitch");
167 event->set_property ("pitch", pits);
168 event->set_property ("duration", note_dur.smobbed_copy ());
169 event->set_property ("length", Moment (note_dur.get_length ()).smobbed_copy ());
170 event->set_property ("duration-log", scm_from_int (note_dur.duration_log ()));
172 Item *note = make_note_head (event);
173 if (need_clone)
174 event->unprotect ();
175 notes_.push_back (note);
178 if (prev_notes_.size () == notes_.size ())
180 for (vsize i = 0; i < notes_.size (); i++)
182 Grob *p = make_spanner ("Tie", SCM_EOL);
183 Tie::set_head (p, LEFT, prev_notes_[i]);
184 Tie::set_head (p, RIGHT, notes_[i]);
186 ties_.push_back (p);
190 left_to_do_ -= note_dur.get_length ();
191 if (left_to_do_)
192 get_global_context ()->add_moment_to_process (now.main_part_ + note_dur.get_length());
194 don't do complicated arithmetic with grace notes.
196 if (orig
197 && now_mom ().grace_part_)
198 left_to_do_ = Rational (0, 0);
201 void
202 Completion_heads_engraver::stop_translation_timestep ()
204 ties_.clear ();
206 if (notes_.size ())
207 prev_notes_ = notes_;
208 notes_.clear ();
211 void
212 Completion_heads_engraver::start_translation_timestep ()
214 Moment now = now_mom ();
215 if (note_end_mom_.main_part_ <= now.main_part_)
217 note_events_.clear ();
218 prev_notes_.clear ();
220 context ()->set_property ("completionBusy",
221 ly_bool2scm (note_events_.size ()));
224 Completion_heads_engraver::Completion_heads_engraver ()
228 ADD_TRANSLATOR (Completion_heads_engraver,
229 /* doc */
230 "This engraver replaces @code{Note_heads_engraver}. It plays"
231 " some trickery to break long notes and automatically tie them"
232 " into the next measure.",
234 /* create */
235 "NoteHead "
236 "Dots "
237 "Tie ",
239 /* read */
240 "middleCPosition "
241 "measurePosition "
242 "measureLength ",
244 /* write */
245 "completionBusy "