]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
* lily/translator.cc, lily/context.cc:, lily/translator-group.cc:
[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           /*
220             measly attempt to save an eeny-weenie bit of memory.
221           */
222           if (dots != scm_to_int (d->get_property ("dot-count")))
223             d->set_property ("dot-count", scm_from_int (dots));
224
225           d->set_parent (note, Y_AXIS);
226           dots_.push_back (d);
227         }
228
229       Pitch *pit = unsmob_pitch (event->get_property ("pitch"));
230
231       int pos = pit->steps ();
232       SCM c0 = get_property ("middleCPosition");
233       if (scm_is_number (c0))
234         pos += scm_to_int (c0);
235
236       note->set_property ("staff-position", scm_from_int (pos));
237       notes_.push_back (note);
238     }
239
240   if (prev_notes_.size () == notes_.size ())
241     {
242       for (vsize i = 0; i < notes_.size (); i++)
243         {
244           Grob *p = make_spanner ("Tie", SCM_EOL);
245           Tie::set_head (p, LEFT, prev_notes_[i]);
246           Tie::set_head (p, RIGHT, notes_[i]);
247
248           ties_.push_back (p);
249         }
250     }
251
252   left_to_do_ -= note_dur.get_length ();
253
254   /*
255     don't do complicated arithmetic with grace notes.
256   */
257   if (orig
258       && now_mom ().grace_part_)
259     left_to_do_ = Rational (0, 0);
260 }
261
262 void
263 Completion_heads_engraver::stop_translation_timestep ()
264 {
265   ties_.clear ();
266
267   if (notes_.size ())
268     prev_notes_ = notes_;
269   notes_.clear ();
270
271   dots_.clear ();
272
273   for (vsize i = scratch_note_events_.size (); i--;)
274     scratch_note_events_[i]->unprotect ();
275
276   scratch_note_events_.clear ();
277 }
278
279 void
280 Completion_heads_engraver::start_translation_timestep ()
281 {
282   Moment now = now_mom ();
283   if (note_end_mom_.main_part_ <= now.main_part_)
284     {
285       note_events_.clear ();
286       prev_notes_.clear ();
287     }
288 }
289
290 Completion_heads_engraver::Completion_heads_engraver ()
291 {
292 }
293
294 ADD_TRANSLATOR (Completion_heads_engraver,
295                 /* doc */ "This engraver replaces "
296                 "@code{Note_heads_engraver}. It plays some trickery to "
297                 "break long notes and automatically tie them into the next measure.",
298                 /* create */ "NoteHead Dots Tie",
299                 /* accept */ "note-event",
300                 /* read */ "middleCPosition measurePosition measureLength",
301                 /* write */ "");