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