]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-combine-music-iterator.cc
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / lily / lyric-combine-music-iterator.cc
1 /*
2   new-lyric-combine-iterator.cc -- implement Lyric_combine_music_iterator
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2004--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "dispatcher.hh"
10 #include "global-context.hh"
11 #include "grob.hh"
12 #include "input.hh"
13 #include "international.hh"
14 #include "listener.hh"
15 #include "music-iterator.hh"
16 #include "music.hh"
17
18 /*
19   This iterator is hairy.  It tracks both lyric and melody contexts,
20   and has a complicated communication route, reading/writing
21   properties in both.
22
23   In the future, this should rather be done with
24
25      \interpretAsMelodyFor { MUSIC } { LYRICS LYRICS LYRICS }
26
27   This can run an interpret step on MUSIC, generating a stream.  Then
28   the stream can be perused at leisure to apply durations to all of
29   the LYRICS.
30 */
31
32 class Lyric_combine_music_iterator : public Music_iterator
33 {
34 public:
35   Lyric_combine_music_iterator ();
36   Lyric_combine_music_iterator (Lyric_combine_music_iterator const &src);
37   DECLARE_SCHEME_CALLBACK (constructor, ());
38 protected:
39   virtual void construct_children ();
40   virtual Moment pending_moment () const;
41   virtual void do_quit ();
42   virtual void process (Moment);
43   virtual bool run_always ()const;
44   virtual bool ok () const;
45   virtual void derived_mark () const;
46   virtual void derived_substitute (Context *, Context *);
47   void set_music_context (Context *to);
48 private:
49   bool start_new_syllable () const;
50   Context *find_voice ();
51   DECLARE_LISTENER (set_busy);
52   DECLARE_LISTENER (check_new_context);
53
54   bool music_found_;
55   Context *lyrics_context_;
56   Context *music_context_;
57   SCM lyricsto_voice_name_;
58
59   Moment busy_moment_;
60   Moment pending_grace_moment_;
61   
62   Music_iterator *lyric_iter_;
63 };
64
65 Lyric_combine_music_iterator::Lyric_combine_music_iterator ()
66 {
67   music_found_ = false;
68   pending_grace_moment_.set_infinite (1);
69   lyric_iter_ = 0;
70   music_context_ = 0;
71   lyrics_context_ = 0;
72   busy_moment_.set_infinite (-1);
73 }
74
75
76 /*
77   It's dubious whether we can ever make this fully work.  Due to
78   associatedVoice switching, this routine may be triggered for 
79   the wrong music_context_ 
80  */
81 IMPLEMENT_LISTENER (Lyric_combine_music_iterator, set_busy)
82 void
83 Lyric_combine_music_iterator::set_busy (SCM se)
84 {
85   Stream_event *e = unsmob_stream_event (se);
86
87   if ((e->in_event_class ("note-event") || e->in_event_class ("cluster-note-event"))
88       && music_context_)
89     
90     busy_moment_ = max (music_context_->now_mom (),
91                         busy_moment_);
92   
93 }
94
95 void
96 Lyric_combine_music_iterator::set_music_context (Context *to)
97 {
98   if (music_context_)
99     {
100       music_context_->event_source ()->remove_listener (GET_LISTENER (set_busy), ly_symbol2scm ("music-event"));
101     }
102
103   music_context_ = to;
104   if (to)
105     {
106       to->event_source ()->add_listener (GET_LISTENER (set_busy), ly_symbol2scm ("music-event"));
107     }
108 }
109
110 bool
111 Lyric_combine_music_iterator::start_new_syllable () const
112 {
113   if (busy_moment_ < music_context_->now_mom ())
114     return false;
115
116   if (!lyrics_context_)
117     return false;
118
119   if (!to_boolean (lyrics_context_->get_property ("ignoreMelismata")))
120     {
121       bool m = melisma_busy (music_context_);
122       if (m)
123         return false;
124     }
125
126   return true;
127 }
128
129 Moment
130 Lyric_combine_music_iterator::pending_moment () const
131 {
132   Moment m;
133
134   m.set_infinite (1);
135
136   return m;
137 }
138
139 bool
140 Lyric_combine_music_iterator::run_always () const
141 {
142   return true;
143 }
144
145 bool
146 Lyric_combine_music_iterator::ok () const
147 {
148   return lyric_iter_ && lyric_iter_->ok ();
149 }
150
151 void
152 Lyric_combine_music_iterator::derived_mark ()const
153 {
154   if (lyric_iter_)
155     scm_gc_mark (lyric_iter_->self_scm ());
156   if (lyrics_context_)
157     scm_gc_mark (lyrics_context_->self_scm ());
158   if (music_context_)
159     scm_gc_mark (music_context_->self_scm ());
160 }
161
162 void
163 Lyric_combine_music_iterator::derived_substitute (Context *f, Context *t)
164 {
165   if (lyric_iter_)
166     lyric_iter_->substitute_outlet (f, t);
167   if (lyrics_context_ && lyrics_context_ == f)
168     lyrics_context_ = t;
169   if (music_context_ && music_context_ == f)
170     set_music_context (t);
171 }
172
173 void
174 Lyric_combine_music_iterator::construct_children ()
175 {
176   Music *m = unsmob_music (get_music ()->get_property ("element"));
177   lyric_iter_ = unsmob_iterator (get_iterator (m));
178   if (!lyric_iter_)
179     return;
180   lyrics_context_ = find_context_below (lyric_iter_->get_outlet (),
181                                         ly_symbol2scm ("Lyrics"), "");
182
183   if (!lyrics_context_)
184     {
185       m->origin ()->warning ("argument of \\lyricsto should contain Lyrics context");
186     }
187   
188   lyricsto_voice_name_ = get_music ()->get_property ("associated-context");
189
190   Context *voice = find_voice ();
191   if (voice)
192     set_music_context (voice);
193
194   /*
195     Wait for a Create_context event. If this isn't done, lyrics can be 
196     delayed when voices are created implicitly.
197   */
198   Global_context *g = get_outlet ()->get_global_context ();
199   g->events_below ()->add_listener (GET_LISTENER (check_new_context), ly_symbol2scm ("CreateContext"));
200
201   /*
202     We do not create a Lyrics context, because the user might
203     create one with a different name, and then we will not find that
204     one.
205   */
206 }
207
208 IMPLEMENT_LISTENER (Lyric_combine_music_iterator, check_new_context)
209 void
210 Lyric_combine_music_iterator::check_new_context (SCM sev)
211 {
212   // TODO: Check first if type=Voice and if id matches
213   Stream_event * ev = unsmob_stream_event (sev);
214   if (ev->get_property ("type") != ly_symbol2scm ("Voice"))
215     return ;
216   
217   Context *voice = find_voice ();
218
219   if (voice)
220     {
221       set_music_context (voice);
222     }
223 }
224
225 /*
226   Look for a suitable voice to align lyrics to.
227
228   Returns 0 if nothing should change; i.e., if we already listen to the
229   right voice, or if we don't yet listen to a voice but no appropriate
230   voice could be found.
231 */
232 Context *
233 Lyric_combine_music_iterator::find_voice ()
234 {
235   SCM voice_name = lyricsto_voice_name_;
236   SCM running = lyrics_context_
237     ? lyrics_context_->get_property ("associatedVoice")
238     : SCM_EOL;
239
240   if (scm_is_string (running))
241     voice_name = running;
242
243   if (scm_is_string (voice_name)
244       && (!music_context_ || ly_scm2string (voice_name) != music_context_->id_string ()))
245     {
246       Context *t = get_outlet ();
247       while (t && t->get_parent_context ())
248         t = t->get_parent_context ();
249
250       string name = ly_scm2string (voice_name);
251       return find_context_below (t, ly_symbol2scm ("Voice"), name);
252     }
253
254   return 0;
255 }
256
257 void
258 Lyric_combine_music_iterator::process (Moment when)
259 {
260   (void) when;
261   
262   /* see if associatedVoice has been changed */
263   Context *new_voice = find_voice ();
264   if (new_voice)
265     set_music_context (new_voice);
266
267   if (!music_context_)
268     return;
269
270   if (!music_context_->get_parent_context ())
271     {
272       /*
273         The melody has died.
274         We die too.
275       */
276       if (lyrics_context_)
277         lyrics_context_->unset_property (ly_symbol2scm ("associatedVoiceContext"));
278       lyric_iter_ = 0;
279       set_music_context (0);
280     }
281
282
283   if (music_context_
284       && (start_new_syllable () ||
285           (busy_moment_ >= pending_grace_moment_))
286       && lyric_iter_->ok ())
287     {
288       Moment now = music_context_->now_mom ();
289       if (now.grace_part_)
290         {
291           pending_grace_moment_ = now;
292           pending_grace_moment_.grace_part_ = Rational (0);
293           return;
294         }
295       else
296         {
297           pending_grace_moment_.set_infinite (1);
298         }
299       
300       Moment m = lyric_iter_->pending_moment ();
301       lyrics_context_->set_property (ly_symbol2scm ("associatedVoiceContext"),
302                                      music_context_->self_scm ());
303       lyric_iter_->process (m);
304
305       music_found_ = true;
306     }
307
308   new_voice = find_voice ();
309   if (new_voice)
310     set_music_context (new_voice);
311 }
312
313 void
314 Lyric_combine_music_iterator::do_quit ()
315 {
316   if (!music_found_)
317     {
318       SCM voice_name = get_music ()->get_property ("associated-context");
319
320       string name;
321       if (scm_is_string (voice_name))
322         name = ly_scm2string (voice_name);
323
324       get_music ()->origin ()->warning (_f ("cannot find Voice `%s'",
325                                             name.c_str ()) + "\n");
326     }
327
328   if (lyric_iter_)
329     lyric_iter_->quit ();
330 }
331
332 IMPLEMENT_CTOR_CALLBACK (Lyric_combine_music_iterator);