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