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