]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
2003 -> 2004
[lilypond.git] / lily / completion-note-heads-engraver.cc
1 /*
2   head-grav.cc -- part of GNU LilyPond
3
4   (c) 1997--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
5 */
6
7 #include <ctype.h>
8
9 #include "rhythmic-head.hh"
10 #include "paper-def.hh"
11 #include "event.hh"
12 #include "dots.hh"
13 #include "dot-column.hh"
14 #include "staff-symbol-referencer.hh"
15 #include "item.hh"
16 #include "score-engraver.hh"
17 #include "warn.hh"
18 #include "spanner.hh"
19 #include "tie.hh"
20
21 /*
22
23   How does this work?
24
25   When we catch the note, we predict the end of the note. We keep the
26   events living until we reach the predicted end-time.
27
28   Every time process_music() is called and there are note events, we
29   figure out how long the note to typeset should be. It should be no
30   longer than what's specified, than what is left to do and it should
31   not cross barlines.
32   
33   We copy the reqs into scratch note reqs, to make sure that we get
34   all durations exactly right.
35  */
36
37 class Completion_heads_engraver : public Engraver
38 {
39   Link_array<Item> notes_;
40   Link_array<Item> prev_notes_;
41   Link_array<Grob> ties_;
42   
43   Link_array<Item> dots_;
44   Link_array<Music> note_reqs_;
45   Link_array<Music> scratch_note_reqs_;
46
47   Moment note_end_mom_;
48   bool first_b_;
49   Rational left_to_do_;
50   Rational do_nothing_until_;
51   
52   Moment next_barline_moment ();
53   Duration find_nearest_duration (Rational length);
54   
55 public:
56   TRANSLATOR_DECLARATIONS(Completion_heads_engraver);
57
58 protected:
59   virtual void initialize ();
60   virtual void start_translation_timestep ();
61   virtual bool try_music (Music *req) ;
62   virtual void process_music ();
63   virtual void stop_translation_timestep ();
64 };
65
66 void
67 Completion_heads_engraver::initialize ()
68 {
69   first_b_ = false;
70 }
71
72 bool
73 Completion_heads_engraver::try_music (Music *m) 
74 {
75   if (m->is_mus_type ("note-event"))
76     {
77       note_reqs_.push (m);
78
79       first_b_ = true;
80       Moment musiclen = m->get_length ();
81       Moment now = now_mom();
82
83       if (now_mom ().grace_part_)
84         {
85           musiclen.grace_part_ = musiclen.main_part_ ;
86           musiclen.main_part_ = Rational (0,1);
87         }
88       note_end_mom_  = note_end_mom_ >? (now + musiclen);
89       do_nothing_until_ = Rational (0,0);
90       
91       return true;
92     }
93   else if  (m->is_mus_type ("busy-playing-event"))
94     {
95       return note_reqs_.size ();
96     }
97   
98   return false;
99   
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         {
138           d = Duration (d.duration_log () + 1, 2);
139         }
140     }
141
142   if (d.duration_log () >= log_limit)
143     {
144       // junk the dots.
145       d = Duration (d.duration_log (), 0);
146
147       // scale up.
148       d = d.compressed (length / d.get_length ());
149     }
150   
151   return d;
152 }
153
154 void
155 Completion_heads_engraver::process_music ()
156 {
157   if (!first_b_ && !left_to_do_)
158     return ;
159   
160   first_b_ = false;
161
162   Moment now =  now_mom ();
163   if (do_nothing_until_ > now.main_part_)
164     return ;
165   
166   Duration note_dur;
167   Duration *orig = 0;
168   if (left_to_do_)
169     {
170       note_dur = find_nearest_duration (left_to_do_);
171     }
172   else
173     {
174       orig = unsmob_duration (note_reqs_[0]->get_mus_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       top_engraver ()->add_moment_to_process (next);
185       do_nothing_until_ = next.main_part_;
186     }
187
188   if (orig)
189     {
190       left_to_do_ = orig->get_length ();
191     }
192
193   if (orig && note_dur.get_length () != orig->get_length ())
194     {
195       if (!scratch_note_reqs_.size ())
196         for (int i = 0; i < note_reqs_.size (); i++)
197           {
198             Music * m = note_reqs_[i]->clone ();
199             scratch_note_reqs_.push (m);
200           }
201     }
202
203   
204   for (int i = 0;
205        left_to_do_ && i < note_reqs_.size (); i++)
206     {
207       Item *note  = make_item ("NoteHead");
208       
209       Music * req =  note_reqs_[i];
210       if (scratch_note_reqs_.size())
211         {
212           req = scratch_note_reqs_[i];
213           SCM pits = note_reqs_[i]->get_mus_property ("pitch");
214           req->set_mus_property ("pitch",pits);
215         }
216       
217       req->set_mus_property ("duration", note_dur.smobbed_copy ());
218       note->set_grob_property ("duration-log",
219                                  gh_int2scm (note_dur.duration_log ()));
220       
221       int dots= note_dur.dot_count ();
222       if (dots)
223         {
224           Item * d = make_item ("Dots");
225           Rhythmic_head::set_dots (note, d);
226
227           /*
228            measly attempt to save an eeny-weenie bit of memory.
229           */
230           if (dots != gh_scm2int (d->get_grob_property ("dot-count")))
231             d->set_grob_property ("dot-count", gh_int2scm (dots));
232
233           d->set_parent (note, Y_AXIS);
234           announce_grob (d, SCM_EOL);
235           dots_.push (d);
236         }
237
238       Pitch *pit =unsmob_pitch (req->get_mus_property ("pitch"));
239
240       int pos = pit->steps ();
241       SCM c0 = get_property ("centralCPosition");
242       if (gh_number_p (c0))
243         pos += gh_scm2int (c0);
244
245       note->set_grob_property ("staff-position",   gh_int2scm (pos));
246       announce_grob (note,req->self_scm ());
247       notes_.push (note);
248     }
249   
250   if (prev_notes_.size() == notes_.size ())
251     {
252       for (int i= 0; i < notes_.size(); i++)
253         {
254           Grob * p = make_spanner ("Tie");
255           Tie::set_interface (p); // cannot remove yet!
256           
257           Tie::set_head (p, LEFT, prev_notes_[i]);
258           Tie::set_head (p, RIGHT, notes_[i]);
259           
260           ties_.push (p);
261           announce_grob(p, SCM_EOL);
262         }
263     }
264
265   left_to_do_ -= note_dur.get_length ();
266
267   /*
268     don't do complicated arithmetic with grace notes.
269    */
270   if (orig
271       &&  now_mom().grace_part_ )
272     {
273       left_to_do_ = Rational (0,0);
274     }
275 }
276  
277 void
278 Completion_heads_engraver::stop_translation_timestep ()
279 {
280   for (int i = ties_.size (); i--;)
281     typeset_grob (ties_[i]); 
282   ties_.clear();
283   
284   for (int i=0; i < notes_.size (); i++)
285     {
286       typeset_grob (notes_[i]);
287     }
288   if (notes_.size())
289     prev_notes_ = notes_;
290   notes_.clear ();
291   
292   for (int i=0; i < dots_.size (); i++)
293     {
294       typeset_grob (dots_[i]);
295     }
296   dots_.clear ();
297
298   for (int i = scratch_note_reqs_.size(); i--;)
299     {
300       scm_gc_unprotect_object (scratch_note_reqs_[i]->self_scm () );
301       
302     }
303   
304   scratch_note_reqs_.clear();
305 }
306
307 void
308 Completion_heads_engraver::start_translation_timestep ()
309 {
310   Moment now = now_mom ();
311   if (note_end_mom_.main_part_ <= now.main_part_)
312     {
313       note_reqs_.clear ();
314       prev_notes_.clear ();
315     }
316 }
317
318 Completion_heads_engraver::Completion_heads_engraver()
319 {
320 }
321
322 ENTER_DESCRIPTION(Completion_heads_engraver,
323 /* descr */       "This engraver replaces "
324 "@code{Note_heads_engraver}. It plays some trickery to "
325 "break long notes and automatically tie them into the next measure.",
326 /* creats*/       "NoteHead Dots Tie",
327 /* accepts */     "busy-playing-event note-event",
328 /* acks  */      "",
329 /* reads */       "centralCPosition measurePosition measureLength",
330 /* write */       "");