]> git.donarmstrong.com Git - lilypond.git/blob - lily/completion-note-heads-engraver.cc
8734b861c2a2d15eadb9b7367b508da6200619fa
[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<Stream_event*> 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   Item *make_note_head (Stream_event*);
61
62 public:
63   TRANSLATOR_DECLARATIONS (Completion_heads_engraver);
64
65 protected:
66   virtual void initialize ();
67   void start_translation_timestep ();
68   void process_music ();
69   void stop_translation_timestep ();
70   DECLARE_TRANSLATOR_LISTENER (note);
71 };
72
73 void
74 Completion_heads_engraver::initialize ()
75 {
76   is_first_ = false;
77 }
78
79 IMPLEMENT_TRANSLATOR_LISTENER (Completion_heads_engraver, note);
80 void
81 Completion_heads_engraver::listen_note (Stream_event *ev)
82 {
83   note_events_.push_back (ev);
84   
85   is_first_ = true;
86   Moment now = now_mom ();
87   Moment musiclen = get_event_length (ev, now);
88
89   note_end_mom_ = max (note_end_mom_, (now + musiclen));
90   do_nothing_until_ = Rational (0, 0);
91 }
92
93 /*
94   The duration _until_ the next barline.
95 */
96 Moment
97 Completion_heads_engraver::next_barline_moment ()
98 {
99   Moment *e = unsmob_moment (get_property ("measurePosition"));
100   Moment *l = unsmob_moment (get_property ("measureLength"));
101   if (!e || !l || !to_boolean (get_property ("timing")))
102     {
103       return Moment (0, 0);
104     }
105
106   return (*l - *e);
107 }
108
109 Item*
110 Completion_heads_engraver::make_note_head (Stream_event *ev)
111 {
112   Item *note = make_item ("NoteHead", ev->self_scm ());
113   Pitch *pit = unsmob_pitch (ev->get_property ("pitch"));
114
115   int pos = pit->steps ();
116   SCM c0 = get_property ("middleCPosition");
117   if (scm_is_number (c0))
118     pos += scm_to_int (c0);
119
120   note->set_property ("staff-position", scm_from_int (pos));
121
122   return note;
123 }
124
125 void
126 Completion_heads_engraver::process_music ()
127 {
128   if (!is_first_ && !left_to_do_)
129     return;
130
131   is_first_ = false;
132
133   Moment now = now_mom ();
134   if (do_nothing_until_ > now.main_part_)
135     return;
136
137   Duration note_dur;
138   Duration *orig = 0;
139   if (left_to_do_)
140     note_dur = Duration (left_to_do_, false);
141   else
142     {
143       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
144       note_dur = *orig;
145     }
146   Moment nb = next_barline_moment ();
147   if (nb.main_part_ && nb < note_dur.get_length ())
148     {
149       note_dur = Duration (nb.main_part_, false);
150
151       do_nothing_until_ = now.main_part_ + note_dur.get_length ();
152     }
153
154   if (orig)
155     left_to_do_ = orig->get_length ();
156
157   for (vsize i = 0; left_to_do_ && i < note_events_.size (); i++)
158     {
159       bool need_clone = !orig || *orig != note_dur;
160       Stream_event *event = note_events_[i];
161
162       if (need_clone)
163         event = event->clone ();
164
165       SCM pits = note_events_[i]->get_property ("pitch");
166
167       event->set_property ("pitch", pits);
168       event->set_property ("duration", note_dur.smobbed_copy ());
169       event->set_property ("length", Moment (note_dur.get_length ()).smobbed_copy ());
170       event->set_property ("duration-log", scm_from_int (note_dur.duration_log ()));
171
172       Item *note = make_note_head (event);
173       if (need_clone)
174         event->unprotect ();
175       notes_.push_back (note);
176     }
177
178   if (prev_notes_.size () == notes_.size ())
179     {
180       for (vsize i = 0; i < notes_.size (); i++)
181         {
182           Grob *p = make_spanner ("Tie", SCM_EOL);
183           Tie::set_head (p, LEFT, prev_notes_[i]);
184           Tie::set_head (p, RIGHT, notes_[i]);
185
186           ties_.push_back (p);
187         }
188     }
189
190   left_to_do_ -= note_dur.get_length ();
191   if (left_to_do_)
192     get_global_context ()->add_moment_to_process (now.main_part_ + note_dur.get_length());
193   /*
194     don't do complicated arithmetic with grace notes.
195   */
196   if (orig
197       && now_mom ().grace_part_)
198     left_to_do_ = Rational (0, 0);
199 }
200
201 void
202 Completion_heads_engraver::stop_translation_timestep ()
203 {
204   ties_.clear ();
205
206   if (notes_.size ())
207     prev_notes_ = notes_;
208   notes_.clear ();
209 }
210
211 void
212 Completion_heads_engraver::start_translation_timestep ()
213 {
214   Moment now = now_mom ();
215   if (note_end_mom_.main_part_ <= now.main_part_)
216     {
217       note_events_.clear ();
218       prev_notes_.clear ();
219     }
220   context ()->set_property ("completionBusy",
221                             ly_bool2scm (note_events_.size ()));
222 }
223
224 Completion_heads_engraver::Completion_heads_engraver ()
225 {
226 }
227
228 ADD_TRANSLATOR (Completion_heads_engraver,
229                 /* doc */
230                 "This engraver replaces @code{Note_heads_engraver}.  It plays"
231                 " some trickery to break long notes and automatically tie them"
232                 " into the next measure.",
233
234                 /* create */
235                 "NoteHead "
236                 "Dots "
237                 "Tie ",
238
239                 /* read */
240                 "middleCPosition "
241                 "measurePosition "
242                 "measureLength ",
243
244                 /* write */
245                 "completionBusy "
246                 );