]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
d4b65a5ac7f580ce6d94115b8029d4fdb6f93680
[lilypond.git] / lily / completion-note-heads-engraver.cc
1 /*
2   completion-note-heads-engraver.cc -- Completion_heads_engraver
3
4   (c) 1997--2005 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 */
6
7 #include <cctype>
8 using namespace std;
9
10 #include "rhythmic-head.hh"
11 #include "output-def.hh"
12 #include "music.hh"
13 #include "dots.hh"
14 #include "dot-column.hh"
15 #include "staff-symbol-referencer.hh"
16 #include "item.hh"
17 #include "score-engraver.hh"
18 #include "warn.hh"
19 #include "spanner.hh"
20 #include "tie.hh"
21 #include "global-context.hh"
22 #include "duration.hh"
23 #include "pitch.hh"
24
25 /*
26   TODO: make matching rest engraver.
27 */
28
29 /*
30   How does this work?
31
32   When we catch the note, we predict the end of the note. We keep the
33   events living until we reach the predicted end-time.
34
35   Every time process_music () is called and there are note events, we
36   figure out how long the note to typeset should be. It should be no
37   longer than what's specified, than what is left to do and it should
38   not cross barlines.
39
40   We copy the events into scratch note events, to make sure that we get
41   all durations exactly right.
42 */
43
44 class Completion_heads_engraver : public Engraver
45 {
46   Link_array<Item> notes_;
47   Link_array<Item> prev_notes_;
48   Link_array<Grob> ties_;
49
50   Link_array<Item> dots_;
51   Link_array<Music> note_events_;
52   Link_array<Music> scratch_note_events_;
53
54   Moment note_end_mom_;
55   bool is_first_;
56   Rational left_to_do_;
57   Rational do_nothing_until_;
58
59   Moment next_barline_moment ();
60   Duration find_nearest_duration (Rational length);
61
62 public:
63   TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
64
65 protected:
66   virtual void initialize ();
67   void start_translation_timestep ();
68   virtual bool try_music (Music *event);
69   void process_music ();
70   void stop_translation_timestep ();
71 };
72
73 void
74 Completion_heads_engraver::initialize ()
75 {
76   is_first_ = false;
77 }
78
79 bool
80 Completion_heads_engraver::try_music (Music *m)
81 {
82   if (m->is_mus_type ("note-event"))
83     {
84       note_events_.push (m);
85
86       is_first_ = true;
87       Moment musiclen = m->get_length ();
88       Moment now = now_mom ();
89
90       if (now_mom ().grace_part_)
91         {
92           musiclen.grace_part_ = musiclen.main_part_;
93           musiclen.main_part_ = Rational (0, 1);
94         }
95       note_end_mom_ = max (note_end_mom_, (now + musiclen));
96       do_nothing_until_ = Rational (0, 0);
97
98       return true;
99     }
100   else if (m->is_mus_type ("busy-playing-event"))
101     return note_events_.size () && is_first_;
102
103   return false;
104 }
105
106 /*
107   The duration _until_ the next barline.
108 */
109 Moment
110 Completion_heads_engraver::next_barline_moment ()
111 {
112   Moment *e = unsmob_moment (get_property ("measurePosition"));
113   Moment *l = unsmob_moment (get_property ("measureLength"));
114   if (!e || !l)
115     {
116       programming_error ("no timing props set?");
117       return Moment (1, 1);
118     }
119
120   return (*l - *e);
121 }
122
123 Duration
124 Completion_heads_engraver::find_nearest_duration (Rational length)
125 {
126   int log_limit = 6;
127
128   Duration d (0, 0);
129
130   /*
131     this could surely be done more efficient. Left to the reader as an
132     excercise.  */
133   while (d.get_length () > length && d.duration_log () < log_limit)
134     {
135       if (d.dot_count ())
136         {
137           d = Duration (d.duration_log (), d.dot_count ()- 1);
138           continue;
139         }
140       else
141         d = Duration (d.duration_log () + 1, 2);
142     }
143
144   if (d.duration_log () >= log_limit)
145     {
146       // junk the dots.
147       d = Duration (d.duration_log (), 0);
148
149       // scale up.
150       d = d.compressed (length / d.get_length ());
151     }
152
153   return d;
154 }
155
156 void
157 Completion_heads_engraver::process_music ()
158 {
159   if (!is_first_ && !left_to_do_)
160     return;
161
162   is_first_ = false;
163
164   Moment now = now_mom ();
165   if (do_nothing_until_ > now.main_part_)
166     return;
167
168   Duration note_dur;
169   Duration *orig = 0;
170   if (left_to_do_)
171     note_dur = find_nearest_duration (left_to_do_);
172   else
173     {
174       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
175       note_dur = *orig;
176     }
177   Moment nb = next_barline_moment ();
178   if (nb < note_dur.get_length ())
179     {
180       note_dur = find_nearest_duration (nb.main_part_);
181
182       Moment next = now;
183       next.main_part_ += note_dur.get_length ();
184
185       get_global_context ()->add_moment_to_process (next);
186       do_nothing_until_ = next.main_part_;
187     }
188
189   if (orig)
190     left_to_do_ = orig->get_length ();
191
192   if (orig && note_dur.get_length () != orig->get_length ())
193     {
194       if (!scratch_note_events_.size ())
195         for (int i = 0; i < note_events_.size (); i++)
196           {
197             Music *m = note_events_[i]->clone ();
198             scratch_note_events_.push (m);
199           }
200     }
201
202   for (int i = 0;
203        left_to_do_ && i < note_events_.size (); i++)
204     {
205       Music *event = note_events_[i];
206       if (scratch_note_events_.size ())
207         {
208           event = scratch_note_events_[i];
209           SCM pits = note_events_[i]->get_property ("pitch");
210           event->set_property ("pitch", pits);
211         }
212
213       event->set_property ("duration", note_dur.smobbed_copy ());
214
215       Item *note = make_item ("NoteHead", event->self_scm ());
216       note->set_property ("duration-log",
217                           scm_from_int (note_dur.duration_log ()));
218
219       int dots = note_dur.dot_count ();
220       if (dots)
221         {
222           Item *d = make_item ("Dots", SCM_EOL);
223           Rhythmic_head::set_dots (note, d);
224
225           /*
226             measly attempt to save an eeny-weenie bit of memory.
227           */
228           if (dots != scm_to_int (d->get_property ("dot-count")))
229             d->set_property ("dot-count", scm_from_int (dots));
230
231           d->set_parent (note, Y_AXIS);
232           dots_.push (d);
233         }
234
235       Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
236
237       int pos = pit->steps ();
238       SCM c0 = get_property ("middleCPosition");
239       if (scm_is_number (c0))
240         pos += scm_to_int (c0);
241
242       note->set_property ("staff-position", scm_from_int (pos));
243       notes_.push (note);
244     }
245
246   if (prev_notes_.size () == notes_.size ())
247     {
248       for (int i = 0; i < notes_.size (); i++)
249         {
250           Grob *p = make_spanner ("Tie", SCM_EOL);
251           Tie::set_head (p, LEFT, prev_notes_[i]);
252           Tie::set_head (p, RIGHT, notes_[i]);
253
254           ties_.push (p);
255         }
256     }
257
258   left_to_do_ -= note_dur.get_length ();
259
260   /*
261     don't do complicated arithmetic with grace notes.
262   */
263   if (orig
264       && now_mom ().grace_part_)
265     left_to_do_ = Rational (0, 0);
266 }
267
268 void
269 Completion_heads_engraver::stop_translation_timestep ()
270 {
271   ties_.clear ();
272
273   if (notes_.size ())
274     prev_notes_ = notes_;
275   notes_.clear ();
276
277   dots_.clear ();
278
279   for (int i = scratch_note_events_.size (); i--;)
280     scratch_note_events_[i]->unprotect ();
281
282   scratch_note_events_.clear ();
283 }
284
285 void
286 Completion_heads_engraver::start_translation_timestep ()
287 {
288   Moment now = now_mom ();
289   if (note_end_mom_.main_part_ <= now.main_part_)
290     {
291       note_events_.clear ();
292       prev_notes_.clear ();
293     }
294 }
295
296 Completion_heads_engraver::Completion_heads_engraver ()
297 {
298 }
299
300 #include "translator.icc"
301
302 ADD_TRANSLATOR (Completion_heads_engraver,
303                 /* doc */ "This engraver replaces "
304                 "@code{Note_heads_engraver}. It plays some trickery to "
305                 "break long notes and automatically tie them into the next measure.",
306                 /* create */ "NoteHead Dots Tie",
307                 /* accept */ "busy-playing-event note-event",
308                 /* read */ "middleCPosition measurePosition measureLength",
309                 /* write */ "");