]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
Fix some bugs in the dynamic engraver and PostScript backend
[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 "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   vector<Item*> notes_;
47   vector<Item*> prev_notes_;
48   vector<Grob*> ties_;
49
50   vector<Item*> dots_;
51   vector<Music*> note_events_;
52   vector<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_back (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 (vsize i = 0; i < note_events_.size (); i++)
196           {
197             Music *m = note_events_[i]->clone ();
198             scratch_note_events_.push_back (m);
199           }
200     }
201
202   for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
203     {
204       Music *event = note_events_[i];
205       if (scratch_note_events_.size ())
206         {
207           event = scratch_note_events_[i];
208           SCM pits = note_events_[i]->get_property ("pitch");
209           event->set_property ("pitch", pits);
210         }
211
212       event->set_property ("duration", note_dur.smobbed_copy ());
213
214       Item *note = make_item ("NoteHead", event->self_scm ());
215       note->set_property ("duration-log",
216                           scm_from_int (note_dur.duration_log ()));
217
218       int dots = note_dur.dot_count ();
219       if (dots)
220         {
221           Item *d = make_item ("Dots", SCM_EOL);
222           Rhythmic_head::set_dots (note, d);
223
224           /*
225             measly attempt to save an eeny-weenie bit of memory.
226           */
227           if (dots != scm_to_int (d->get_property ("dot-count")))
228             d->set_property ("dot-count", scm_from_int (dots));
229
230           d->set_parent (note, Y_AXIS);
231           dots_.push_back (d);
232         }
233
234       Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
235
236       int pos = pit->steps ();
237       SCM c0 = get_property ("middleCPosition");
238       if (scm_is_number (c0))
239         pos += scm_to_int (c0);
240
241       note->set_property ("staff-position", scm_from_int (pos));
242       notes_.push_back (note);
243     }
244
245   if (prev_notes_.size () == notes_.size ())
246     {
247       for (vsize i = 0; i < notes_.size (); i++)
248         {
249           Grob *p = make_spanner ("Tie", SCM_EOL);
250           Tie::set_head (p, LEFT, prev_notes_[i]);
251           Tie::set_head (p, RIGHT, notes_[i]);
252
253           ties_.push_back (p);
254         }
255     }
256
257   left_to_do_ -= note_dur.get_length ();
258
259   /*
260     don't do complicated arithmetic with grace notes.
261   */
262   if (orig
263       && now_mom ().grace_part_)
264     left_to_do_ = Rational (0, 0);
265 }
266
267 void
268 Completion_heads_engraver::stop_translation_timestep ()
269 {
270   ties_.clear ();
271
272   if (notes_.size ())
273     prev_notes_ = notes_;
274   notes_.clear ();
275
276   dots_.clear ();
277
278   for (vsize i = scratch_note_events_.size (); i--;)
279     scratch_note_events_[i]->unprotect ();
280
281   scratch_note_events_.clear ();
282 }
283
284 void
285 Completion_heads_engraver::start_translation_timestep ()
286 {
287   Moment now = now_mom ();
288   if (note_end_mom_.main_part_ <= now.main_part_)
289     {
290       note_events_.clear ();
291       prev_notes_.clear ();
292     }
293 }
294
295 Completion_heads_engraver::Completion_heads_engraver ()
296 {
297 }
298
299 #include "translator.icc"
300
301 ADD_TRANSLATOR (Completion_heads_engraver,
302                 /* doc */ "This engraver replaces "
303                 "@code{Note_heads_engraver}. It plays some trickery to "
304                 "break long notes and automatically tie them into the next measure.",
305                 /* create */ "NoteHead Dots Tie",
306                 /* accept */ "busy-playing-event note-event",
307                 /* read */ "middleCPosition measurePosition measureLength",
308                 /* write */ "");