]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
9a72b6d5ed077495addb33a1fa82ea63b4c91676
[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 "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"
24
25 #include "translator.icc"
26
27 /*
28   TODO: make matching rest engraver.
29 */
30
31 /*
32   How does this work?
33
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.
36
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.
41
42   We copy the events into scratch note events, to make sure that we get
43   all durations exactly right.
44 */
45
46 class Completion_heads_engraver : public Engraver
47 {
48   vector<Item*> notes_;
49   vector<Item*> prev_notes_;
50   vector<Grob*> ties_;
51
52   vector<Item*> dots_;
53   vector<Stream_event*> note_events_;
54   vector<Stream_event*> scratch_note_events_;
55
56   Moment note_end_mom_;
57   bool is_first_;
58   Rational left_to_do_;
59   Rational do_nothing_until_;
60
61   Moment next_barline_moment ();
62   Duration find_nearest_duration (Rational length);
63
64 public:
65   TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
66
67 protected:
68   virtual void initialize ();
69   void start_translation_timestep ();
70   void process_music ();
71   void stop_translation_timestep ();
72   DECLARE_TRANSLATOR_LISTENER (note);
73 };
74
75 void
76 Completion_heads_engraver::initialize ()
77 {
78   is_first_ = false;
79 }
80
81 IMPLEMENT_TRANSLATOR_LISTENER (Completion_heads_engraver, note);
82 void
83 Completion_heads_engraver::listen_note (Stream_event *ev)
84 {
85   note_events_.push_back (ev);
86   
87   is_first_ = true;
88   Moment musiclen = get_event_length (ev);
89   Moment now = now_mom ();
90
91   if (now_mom ().grace_part_)
92     {
93       musiclen.grace_part_ = musiclen.main_part_;
94       musiclen.main_part_ = Rational (0, 1);
95     }
96
97   note_end_mom_ = max (note_end_mom_, (now + musiclen));
98   do_nothing_until_ = Rational (0, 0);
99 }
100
101 /*
102   The duration _until_ the next barline.
103 */
104 Moment
105 Completion_heads_engraver::next_barline_moment ()
106 {
107   Moment *e = unsmob_moment (get_property ("measurePosition"));
108   Moment *l = unsmob_moment (get_property ("measureLength"));
109   if (!e || !l)
110     {
111       programming_error ("no timing props set?");
112       return Moment (1, 1);
113     }
114
115   return (*l - *e);
116 }
117
118 Duration
119 Completion_heads_engraver::find_nearest_duration (Rational length)
120 {
121   int log_limit = 6;
122
123   Duration d (0, 0);
124
125   /*
126     this could surely be done more efficient. Left to the reader as an
127     excercise.  */
128   while (d.get_length () > length && d.duration_log () < log_limit)
129     {
130       if (d.dot_count ())
131         {
132           d = Duration (d.duration_log (), d.dot_count ()- 1);
133           continue;
134         }
135       else
136         d = Duration (d.duration_log () + 1, 2);
137     }
138
139   if (d.duration_log () >= log_limit)
140     {
141       // junk the dots.
142       d = Duration (d.duration_log (), 0);
143
144       // scale up.
145       d = d.compressed (length / d.get_length ());
146     }
147
148   return d;
149 }
150
151 void
152 Completion_heads_engraver::process_music ()
153 {
154   if (!is_first_ && !left_to_do_)
155     return;
156
157   is_first_ = false;
158
159   Moment now = now_mom ();
160   if (do_nothing_until_ > now.main_part_)
161     return;
162
163   Duration note_dur;
164   Duration *orig = 0;
165   if (left_to_do_)
166     note_dur = find_nearest_duration (left_to_do_);
167   else
168     {
169       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
170       note_dur = *orig;
171     }
172   Moment nb = next_barline_moment ();
173   if (nb < note_dur.get_length ())
174     {
175       note_dur = find_nearest_duration (nb.main_part_);
176
177       Moment next = now;
178       next.main_part_ += note_dur.get_length ();
179
180       get_global_context ()->add_moment_to_process (next);
181       do_nothing_until_ = next.main_part_;
182     }
183
184   if (orig)
185     left_to_do_ = orig->get_length ();
186
187   if (orig && note_dur.get_length () != orig->get_length ())
188     {
189       if (!scratch_note_events_.size ())
190         for (vsize i = 0; i < note_events_.size (); i++)
191           {
192             Stream_event *m = note_events_[i]->clone ();
193             scratch_note_events_.push_back (m);
194           }
195     }
196
197   for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
198     {
199       Stream_event *event = note_events_[i];
200       if (scratch_note_events_.size ())
201         {
202           event = scratch_note_events_[i];
203           SCM pits = note_events_[i]->get_property ("pitch");
204           event->set_property ("pitch", pits);
205         }
206
207       event->set_property ("duration", note_dur.smobbed_copy ());
208
209       Item *note = make_item ("NoteHead", event->self_scm ());
210       note->set_property ("duration-log",
211                           scm_from_int (note_dur.duration_log ()));
212
213       int dots = note_dur.dot_count ();
214       if (dots)
215         {
216           Item *d = make_item ("Dots", SCM_EOL);
217           Rhythmic_head::set_dots (note, d);
218
219           d->set_property ("dot-count", scm_from_int (dots));
220
221           d->set_parent (note, Y_AXIS);
222           dots_.push_back (d);
223         }
224
225       Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
226
227       int pos = pit->steps ();
228       SCM c0 = get_property ("middleCPosition");
229       if (scm_is_number (c0))
230         pos += scm_to_int (c0);
231
232       note->set_property ("staff-position", scm_from_int (pos));
233       notes_.push_back (note);
234     }
235
236   if (prev_notes_.size () == notes_.size ())
237     {
238       for (vsize i = 0; i < notes_.size (); i++)
239         {
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]);
243
244           ties_.push_back (p);
245         }
246     }
247
248   left_to_do_ -= note_dur.get_length ();
249
250   /*
251     don't do complicated arithmetic with grace notes.
252   */
253   if (orig
254       && now_mom ().grace_part_)
255     left_to_do_ = Rational (0, 0);
256 }
257
258 void
259 Completion_heads_engraver::stop_translation_timestep ()
260 {
261   ties_.clear ();
262
263   if (notes_.size ())
264     prev_notes_ = notes_;
265   notes_.clear ();
266
267   dots_.clear ();
268
269   for (vsize i = scratch_note_events_.size (); i--;)
270     scratch_note_events_[i]->unprotect ();
271
272   scratch_note_events_.clear ();
273 }
274
275 void
276 Completion_heads_engraver::start_translation_timestep ()
277 {
278   Moment now = now_mom ();
279   if (note_end_mom_.main_part_ <= now.main_part_)
280     {
281       note_events_.clear ();
282       prev_notes_.clear ();
283     }
284 }
285
286 Completion_heads_engraver::Completion_heads_engraver ()
287 {
288 }
289
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.",
294                 /* create */
295                 "NoteHead "
296                 "Dots "
297                 "Tie",
298
299                 /* accept */ "note-event",
300                 /* read */
301                 "middleCPosition "
302                 "measurePosition "
303                 "measureLength",
304
305                 /* write */ "");