]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-combine-music-iterator.cc
Merge branch 'master' of git://git.sv.gnu.org/lilypond.git into td-lily
[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 ()->
101         remove_listener (GET_LISTENER (set_busy), ly_symbol2scm ("music-event"));
102     }
103
104   music_context_ = to;
105   if (to)
106     {
107       to->event_source ()->add_listener (GET_LISTENER (set_busy),
108                                          ly_symbol2scm ("music-event"));
109     }
110 }
111
112 bool
113 Lyric_combine_music_iterator::start_new_syllable () const
114 {
115   if (busy_moment_ < music_context_->now_mom ())
116     return false;
117
118   if (!lyrics_context_)
119     return false;
120
121   if (!to_boolean (lyrics_context_->get_property ("ignoreMelismata")))
122     {
123       bool m = melisma_busy (music_context_);
124       if (m)
125         return false;
126     }
127
128   return true;
129 }
130
131 Moment
132 Lyric_combine_music_iterator::pending_moment () const
133 {
134   Moment m;
135
136   m.set_infinite (1);
137
138   return m;
139 }
140
141 bool
142 Lyric_combine_music_iterator::run_always () const
143 {
144   return true;
145 }
146
147 bool
148 Lyric_combine_music_iterator::ok () const
149 {
150   return lyric_iter_ && lyric_iter_->ok ();
151 }
152
153 void
154 Lyric_combine_music_iterator::derived_mark ()const
155 {
156   if (lyric_iter_)
157     scm_gc_mark (lyric_iter_->self_scm ());
158   if (lyrics_context_)
159     scm_gc_mark (lyrics_context_->self_scm ());
160   if (music_context_)
161     scm_gc_mark (music_context_->self_scm ());
162 }
163
164 void
165 Lyric_combine_music_iterator::derived_substitute (Context *f, Context *t)
166 {
167   if (lyric_iter_)
168     lyric_iter_->substitute_outlet (f, t);
169   if (lyrics_context_ && lyrics_context_ == f)
170     lyrics_context_ = t;
171   if (music_context_ && music_context_ == f)
172     set_music_context (t);
173 }
174
175 void
176 Lyric_combine_music_iterator::construct_children ()
177 {
178   Music *m = unsmob_music (get_music ()->get_property ("element"));
179   lyric_iter_ = unsmob_iterator (get_iterator (m));
180   if (!lyric_iter_)
181     return;
182   lyrics_context_ = find_context_below (lyric_iter_->get_outlet (),
183                                         ly_symbol2scm ("Lyrics"), "");
184
185   if (!lyrics_context_)
186     {
187       m->origin ()->warning ("argument of \\lyricsto should contain Lyrics context");
188     }
189   
190   lyricsto_voice_name_ = get_music ()->get_property ("associated-context");
191
192   Context *voice = find_voice ();
193   if (voice)
194     set_music_context (voice);
195
196   /*
197     Wait for a Create_context event. If this isn't done, lyrics can be 
198     delayed when voices are created implicitly.
199   */
200   Global_context *g = get_outlet ()->get_global_context ();
201   g->events_below ()->add_listener (GET_LISTENER (check_new_context), ly_symbol2scm ("CreateContext"));
202
203   /*
204     We do not create a Lyrics context, because the user might
205     create one with a different name, and then we will not find that
206     one.
207   */
208 }
209
210 IMPLEMENT_LISTENER (Lyric_combine_music_iterator, check_new_context)
211 void
212 Lyric_combine_music_iterator::check_new_context (SCM sev)
213 {
214   if (!ok ())
215     return ;
216   
217   // TODO: Check first if type=Voice and if id matches
218   Stream_event * ev = unsmob_stream_event (sev);
219   if (ev->get_property ("type") != ly_symbol2scm ("Voice"))
220     return ;
221   
222   Context *voice = find_voice ();
223
224   if (voice)
225     {
226       set_music_context (voice);
227     }
228 }
229
230 /*
231   Look for a suitable voice to align lyrics to.
232
233   Returns 0 if nothing should change; i.e., if we already listen to the
234   right voice, or if we don't yet listen to a voice but no appropriate
235   voice could be found.
236 */
237 Context *
238 Lyric_combine_music_iterator::find_voice ()
239 {
240   SCM voice_name = lyricsto_voice_name_;
241   SCM running = lyrics_context_
242     ? lyrics_context_->get_property ("associatedVoice")
243     : SCM_EOL;
244
245   if (scm_is_string (running))
246     voice_name = running;
247
248   if (scm_is_string (voice_name)
249       && (!music_context_ || ly_scm2string (voice_name) != music_context_->id_string ()))
250     {
251       Context *t = get_outlet ();
252       while (t && t->get_parent_context ())
253         t = t->get_parent_context ();
254
255       string name = ly_scm2string (voice_name);
256       return find_context_below (t, ly_symbol2scm ("Voice"), name);
257     }
258
259   return 0;
260 }
261
262 void
263 Lyric_combine_music_iterator::process (Moment when)
264 {
265   (void) when;
266   
267   /* see if associatedVoice has been changed */
268   Context *new_voice = find_voice ();
269   if (new_voice)
270     set_music_context (new_voice);
271
272   if (!music_context_)
273     return;
274
275   if (!music_context_->get_parent_context ())
276     {
277       /*
278         The melody has died.
279         We die too.
280       */
281       if (lyrics_context_)
282         lyrics_context_->unset_property (ly_symbol2scm ("associatedVoiceContext"));
283       lyric_iter_ = 0;
284       set_music_context (0);
285     }
286
287
288   if (music_context_
289       && (start_new_syllable () ||
290           (busy_moment_ >= pending_grace_moment_))
291       && lyric_iter_->ok ())
292     {
293       Moment now = music_context_->now_mom ();
294       if (now.grace_part_)
295         {
296           pending_grace_moment_ = now;
297           pending_grace_moment_.grace_part_ = Rational (0);
298           return;
299         }
300       else
301         {
302           pending_grace_moment_.set_infinite (1);
303         }
304       
305       Moment m = lyric_iter_->pending_moment ();
306       lyrics_context_->set_property (ly_symbol2scm ("associatedVoiceContext"),
307                                      music_context_->self_scm ());
308       lyric_iter_->process (m);
309
310       music_found_ = true;
311     }
312
313   new_voice = find_voice ();
314   if (new_voice)
315     set_music_context (new_voice);
316 }
317
318 void
319 Lyric_combine_music_iterator::do_quit ()
320 {
321   if (!music_found_)
322     {
323       SCM voice_name = get_music ()->get_property ("associated-context");
324
325       string name;
326       if (scm_is_string (voice_name))
327         name = ly_scm2string (voice_name);
328
329       get_music ()->origin ()->warning (_f ("cannot find Voice `%s'",
330                                             name.c_str ()) + "\n");
331     }
332
333   if (lyric_iter_)
334     lyric_iter_->quit ();
335 }
336
337 IMPLEMENT_CTOR_CALLBACK (Lyric_combine_music_iterator);