]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
Merge branch 'master' of ssh+git://git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / lily / completion-note-heads-engraver.cc
1 /*
2   completion-note-heads-engraver.cc -- Completion_heads_engraver
3
4   (c) 1997--2007 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 now = now_mom ();
89   Moment musiclen = get_event_length (ev, now);
90
91   note_end_mom_ = max (note_end_mom_, (now + musiclen));
92   do_nothing_until_ = Rational (0, 0);
93 }
94
95 /*
96   The duration _until_ the next barline.
97 */
98 Moment
99 Completion_heads_engraver::next_barline_moment ()
100 {
101   Moment *e = unsmob_moment (get_property ("measurePosition"));
102   Moment *l = unsmob_moment (get_property ("measureLength"));
103   if (!e || !l)
104     {
105       programming_error ("no timing props set?");
106       return Moment (1, 1);
107     }
108
109   return (*l - *e);
110 }
111
112 Duration
113 Completion_heads_engraver::find_nearest_duration (Rational length)
114 {
115   int log_limit = 6;
116
117   Duration d (0, 0);
118
119   /*
120     this could surely be done more efficient. Left to the reader as an
121     excercise.  */
122   while (d.get_length () > length && d.duration_log () < log_limit)
123     {
124       if (d.dot_count ())
125         {
126           d = Duration (d.duration_log (), d.dot_count ()- 1);
127           continue;
128         }
129       else
130         d = Duration (d.duration_log () + 1, 2);
131     }
132
133   if (d.duration_log () >= log_limit)
134     {
135       // junk the dots.
136       d = Duration (d.duration_log (), 0);
137
138       // scale up.
139       d = d.compressed (length / d.get_length ());
140     }
141
142   return d;
143 }
144
145 void
146 Completion_heads_engraver::process_music ()
147 {
148   if (!is_first_ && !left_to_do_)
149     return;
150
151   is_first_ = false;
152
153   Moment now = now_mom ();
154   if (do_nothing_until_ > now.main_part_)
155     return;
156
157   Duration note_dur;
158   Duration *orig = 0;
159   if (left_to_do_)
160     note_dur = find_nearest_duration (left_to_do_);
161   else
162     {
163       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
164       note_dur = *orig;
165     }
166   Moment nb = next_barline_moment ();
167   if (nb < note_dur.get_length ())
168     {
169       note_dur = find_nearest_duration (nb.main_part_);
170
171       Moment next = now;
172       next.main_part_ += note_dur.get_length ();
173
174       get_global_context ()->add_moment_to_process (next);
175       do_nothing_until_ = next.main_part_;
176     }
177
178   if (orig)
179     left_to_do_ = orig->get_length ();
180
181   if (orig && note_dur.get_length () != orig->get_length ())
182     {
183       if (!scratch_note_events_.size ())
184         for (vsize i = 0; i < note_events_.size (); i++)
185           {
186             Stream_event *m = note_events_[i]->clone ();
187             scratch_note_events_.push_back (m);
188           }
189     }
190   else
191     {
192       for (vsize i = 0; i < note_events_.size (); i++)
193         {
194           Stream_event *c =  note_events_[i]->clone ();
195           scratch_note_events_.push_back (c);
196         }
197     }
198   
199   for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
200     {
201       Stream_event *event = note_events_[i];
202       if (scratch_note_events_.size ())
203         {
204           event = scratch_note_events_[i];
205           SCM pits = note_events_[i]->get_property ("pitch");
206           event->set_property ("pitch", pits);
207         }
208
209       event->set_property ("duration", note_dur.smobbed_copy ());
210
211       Item *note = make_item ("NoteHead", event->self_scm ());
212       note->set_property ("duration-log",
213                           scm_from_int (note_dur.duration_log ()));
214
215       int dots = note_dur.dot_count ();
216       if (dots)
217         {
218           Item *d = make_item ("Dots", SCM_EOL);
219           Rhythmic_head::set_dots (note, d);
220
221           d->set_property ("dot-count", scm_from_int (dots));
222
223           d->set_parent (note, Y_AXIS);
224           dots_.push_back (d);
225         }
226
227       Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
228
229       int pos = pit->steps ();
230       SCM c0 = get_property ("middleCPosition");
231       if (scm_is_number (c0))
232         pos += scm_to_int (c0);
233
234       note->set_property ("staff-position", scm_from_int (pos));
235       notes_.push_back (note);
236     }
237
238   if (prev_notes_.size () == notes_.size ())
239     {
240       for (vsize i = 0; i < notes_.size (); i++)
241         {
242           Grob *p = make_spanner ("Tie", SCM_EOL);
243           Tie::set_head (p, LEFT, prev_notes_[i]);
244           Tie::set_head (p, RIGHT, notes_[i]);
245
246           ties_.push_back (p);
247         }
248     }
249
250   left_to_do_ -= note_dur.get_length ();
251
252   /*
253     don't do complicated arithmetic with grace notes.
254   */
255   if (orig
256       && now_mom ().grace_part_)
257     left_to_do_ = Rational (0, 0);
258 }
259
260 void
261 Completion_heads_engraver::stop_translation_timestep ()
262 {
263   ties_.clear ();
264
265   if (notes_.size ())
266     prev_notes_ = notes_;
267   notes_.clear ();
268
269   dots_.clear ();
270
271   for (vsize i = scratch_note_events_.size (); i--;)
272     scratch_note_events_[i]->unprotect ();
273
274   scratch_note_events_.clear ();
275 }
276
277 void
278 Completion_heads_engraver::start_translation_timestep ()
279 {
280   Moment now = now_mom ();
281   if (note_end_mom_.main_part_ <= now.main_part_)
282     {
283       note_events_.clear ();
284       prev_notes_.clear ();
285     }
286 }
287
288 Completion_heads_engraver::Completion_heads_engraver ()
289 {
290 }
291
292 ADD_TRANSLATOR (Completion_heads_engraver,
293                 /* doc */ "This engraver replaces "
294                 "@code{Note_heads_engraver}. It plays some trickery to "
295                 "break long notes and automatically tie them into the next measure.",
296                 /* create */
297                 "NoteHead "
298                 "Dots "
299                 "Tie",
300                 /* read */
301                 "middleCPosition "
302                 "measurePosition "
303                 "measureLength",
304
305                 /* write */ "");