]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
14d4d405c239e36aacfbf7d5350e9ee1260fc9b2
[lilypond.git] / lily / completion-note-heads-engraver.cc
1 /*
2   head-grav.cc -- part of GNU LilyPond
3
4   (c)  1997--2002 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 "musical-request.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
19 /*
20
21   How does this work?
22
23   When we catch the note, we predict the end of the note. We keep the
24   requests living until we reach the predicted end-time.
25
26   Every time process_music() is called and there are note requests, we
27   figure out how long the note to typeset should be. It should be no
28   longer than what's specified, than what is left to do and it should
29   not cross barlines.
30   
31   We copy the reqs into scratch note reqs, to make sure that we get
32   all durations exactly right.
33  */
34
35 class Completion_heads_engraver : public Engraver
36 {
37   Link_array<Item> notes_;
38   
39   Link_array<Item> dots_;
40   Link_array<Music> note_reqs_;
41   Link_array<Music> scratch_note_reqs_;
42
43   Moment note_end_mom_;
44   bool first_b_;
45   Rational left_to_do_;
46
47   Moment next_barline_moment ();
48   Duration find_nearest_duration (Rational length);
49   
50 public:
51   TRANSLATOR_DECLARATIONS(Completion_heads_engraver);
52
53 protected:
54   virtual void initialize ();
55   virtual void start_translation_timestep ();
56   virtual bool try_music (Music *req) ;
57   virtual void process_music ();
58   virtual void stop_translation_timestep ();
59 };
60
61 void
62 Completion_heads_engraver::initialize ()
63 {
64   first_b_ = false;
65 }
66
67 bool
68 Completion_heads_engraver::try_music (Music *m) 
69 {
70   if (Note_req * n =dynamic_cast <Note_req *> (m))
71     {
72       note_reqs_.push (n);
73
74       first_b_ = true;
75       Moment musiclen = m->length_mom ();
76       Moment now = now_mom();
77
78       if (now_mom ().grace_part_)
79         {
80           musiclen.grace_part_ = musiclen.main_part_ ;
81           musiclen.main_part_ = Rational (0,1);
82         }
83       note_end_mom_  = note_end_mom_ >? (now + musiclen);
84       return true;
85     }
86   else if (dynamic_cast<Busy_playing_req*> (m))
87     {
88       return note_reqs_.size ();
89     }
90   
91   return false;
92   
93 }
94
95 Moment
96 Completion_heads_engraver::next_barline_moment ( )
97 {
98   Moment *e = unsmob_moment (get_property ("measurePosition"));
99   Moment *l = unsmob_moment (get_property ("measureLength"));
100   if (!e || !l)
101     {
102       programming_error ("No timing props set?");
103       return Moment (1,1);
104     }
105
106   return (*l - *e);
107 }
108
109 Duration  
110 Completion_heads_engraver::find_nearest_duration (Rational length)
111 {
112   int log_limit= 6;
113
114   Duration d(0,0);
115
116   /*
117     this could surely be done more efficient. Left to the reader as an
118     excercise.  */
119   while (d.length_mom () > length && d.duration_log () < log_limit)
120     {
121       if (d.dot_count ())
122         {
123           d = Duration (d.duration_log (), d.dot_count ()- 1);
124           continue;
125         }
126       else
127         {
128           d = Duration (d.duration_log () + 1, 2);
129         }
130     }
131
132   if (d.duration_log () >= log_limit)
133     {
134       // junk the dots.
135       d = Duration (d.duration_log (), 0);
136
137       // scale up.
138       d = d.compressed (length / d.length_mom ());
139     }
140   
141   return d;
142 }
143
144 void
145 Completion_heads_engraver::process_music ()
146 {
147   if (!first_b_ && !left_to_do_)
148     return ;
149
150   first_b_ = false;
151   
152   Duration note_dur;
153   Duration *orig = 0;
154   if (left_to_do_)
155     {
156       note_dur = find_nearest_duration (left_to_do_);
157     }
158   else
159     {
160       orig = unsmob_duration (note_reqs_[0]->get_mus_property ("duration"));
161       note_dur = *orig;
162     }
163
164   Moment nb = next_barline_moment ();
165   if (nb < note_dur.length_mom ())
166     {
167       note_dur = find_nearest_duration (nb.main_part_);
168
169       Moment next = now_mom();
170       next.main_part_ += note_dur.length_mom ();
171       top_engraver ()->add_moment_to_process (next);
172     }
173
174   if (orig)
175     {
176       left_to_do_ = orig->length_mom ();
177     }
178
179   if (orig && note_dur.length_mom() != orig->length_mom())
180     {
181       if (!scratch_note_reqs_.size ())
182         for (int i = 0; i < note_reqs_.size (); i++)
183           {
184             Music * m = note_reqs_[i]->clone ();
185             scratch_note_reqs_.push (m);
186           }
187     }
188
189   
190   for (int i = 0;
191        left_to_do_ && i < note_reqs_.size (); i++)
192     {
193       Item *note  = new Item (get_property ("NoteHead"));
194       
195       Music * req =  note_reqs_[i];
196       if (scratch_note_reqs_.size())
197         {
198           req = scratch_note_reqs_[i];
199           SCM pits = note_reqs_[i]->get_mus_property ("pitch");
200           req->set_mus_property ("pitch",pits);
201         }
202       
203       req->set_mus_property ("duration", note_dur.smobbed_copy ());
204       note->set_grob_property ("duration-log",
205                                  gh_int2scm (note_dur.duration_log ()));
206       
207       int dots= note_dur.dot_count ();
208       if (dots)
209         {
210           Item * d = new Item (get_property ("Dots"));
211           Rhythmic_head::set_dots (note, d);
212
213           /*
214            measly attempt to save an eeny-weenie bit of memory.
215           */
216           if (dots != gh_scm2int (d->get_grob_property ("dot-count")))
217             d->set_grob_property ("dot-count", gh_int2scm (dots));
218
219           d->set_parent (note, Y_AXIS);
220           announce_grob (d, SCM_EOL);
221           dots_.push (d);
222         }
223
224       Pitch *pit =unsmob_pitch (req->get_mus_property ("pitch"));
225
226       int pos = pit->steps ();
227       SCM c0 = get_property ("centralCPosition");
228       if (gh_number_p (c0))
229         pos += gh_scm2int (c0);
230
231       note->set_grob_property ("staff-position",   gh_int2scm (pos));
232       announce_grob (note,req->self_scm ());
233       notes_.push (note);
234     }
235
236   left_to_do_ -= note_dur.length_mom ();
237
238
239   /*
240     don't do complicated arithmetic with grace notes.
241    */
242   if (orig
243       &&  now_mom().grace_part_ )
244     {
245       left_to_do_ = Rational (0,0);
246     }
247   
248 }
249  
250 void
251 Completion_heads_engraver::stop_translation_timestep ()
252 {
253   for (int i=0; i < notes_.size (); i++)
254     {
255       typeset_grob (notes_[i]);
256     }
257   notes_.clear ();
258   
259   for (int i=0; i < dots_.size (); i++)
260     {
261       typeset_grob (dots_[i]);
262     }
263   dots_.clear ();
264
265   for (int i = scratch_note_reqs_.size(); i--;)
266     {
267       scm_gc_unprotect_object (scratch_note_reqs_[i]->self_scm () );
268       
269     }
270   scratch_note_reqs_.clear();
271 }
272
273 Tie_req * tie_req = 0;
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_reqs_.clear ();
282     }
283
284   if (left_to_do_)
285     {
286       if (!tie_req)
287         tie_req = new Tie_req;
288       
289       bool succ = daddy_trans_->try_music (tie_req);
290       if (!succ)
291         {
292           programming_error ("Completion_heads_engraver: no-one to make tie.");
293         }
294     }
295 }
296
297 Completion_heads_engraver::Completion_heads_engraver()
298 {
299 }
300
301 ENTER_DESCRIPTION(Completion_heads_engraver,
302 /* descr */       "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 /* creats*/       "NoteHead Dots",
306 /* acks  */       "",
307 /* reads */       "centralCPosition measurePosition measureLength",
308 /* write */       "");