]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-combine-music-iterator.cc
Release: bump VERSION_DEVEL.
[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--2014 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   SCM lyricsto_voice_type_;
71
72   Moment busy_moment_;
73   Moment pending_grace_moment_;
74
75   Music_iterator *lyric_iter_;
76 };
77
78 Lyric_combine_music_iterator::Lyric_combine_music_iterator ()
79 {
80   music_found_ = false;
81   lyrics_found_ = false;
82   pending_grace_moment_.set_infinite (1);
83   lyric_iter_ = 0;
84   music_context_ = 0;
85   lyrics_context_ = 0;
86   busy_moment_.set_infinite (-1);
87   lyricsto_voice_name_ = SCM_UNDEFINED;
88   lyricsto_voice_type_ = SCM_UNDEFINED;
89 }
90
91 /*
92   It's dubious whether we can ever make this fully work.  Due to
93   associatedVoice switching, this routine may be triggered for
94   the wrong music_context_
95  */
96 IMPLEMENT_LISTENER (Lyric_combine_music_iterator, set_busy)
97 void
98 Lyric_combine_music_iterator::set_busy (SCM se)
99 {
100   Stream_event *e = Stream_event::unsmob (se);
101
102   if ((e->in_event_class ("note-event") || e->in_event_class ("cluster-note-event"))
103       && music_context_)
104
105     busy_moment_ = max (music_context_->now_mom (),
106                         busy_moment_);
107
108 }
109
110 void
111 Lyric_combine_music_iterator::set_music_context (Context *to)
112 {
113   if (music_context_)
114     {
115       music_context_->events_below ()->
116       remove_listener (GET_LISTENER (set_busy), ly_symbol2scm ("rhythmic-event"));
117     }
118
119   music_context_ = to;
120   if (to)
121     {
122       to->events_below ()->add_listener (GET_LISTENER (set_busy),
123                                          ly_symbol2scm ("rhythmic-event"));
124     }
125 }
126
127 bool
128 Lyric_combine_music_iterator::start_new_syllable () const
129 {
130   if (busy_moment_ < music_context_->now_mom ())
131     return false;
132
133   if (!lyrics_context_)
134     return false;
135
136   if (!to_boolean (lyrics_context_->get_property ("ignoreMelismata")))
137     {
138       bool m = melisma_busy (music_context_);
139       if (m)
140         return false;
141     }
142
143   return true;
144 }
145
146 Moment
147 Lyric_combine_music_iterator::pending_moment () const
148 {
149   Moment m;
150
151   m.set_infinite (1);
152
153   return m;
154 }
155
156 bool
157 Lyric_combine_music_iterator::run_always () const
158 {
159   return true;
160 }
161
162 bool
163 Lyric_combine_music_iterator::ok () const
164 {
165   return lyric_iter_ && lyric_iter_->ok ();
166 }
167
168 void
169 Lyric_combine_music_iterator::derived_mark ()const
170 {
171   if (lyric_iter_)
172     scm_gc_mark (lyric_iter_->self_scm ());
173   if (lyrics_context_)
174     scm_gc_mark (lyrics_context_->self_scm ());
175   if (music_context_)
176     scm_gc_mark (music_context_->self_scm ());
177   scm_gc_mark (lyricsto_voice_name_);
178   scm_gc_mark (lyricsto_voice_type_);
179 }
180
181 void
182 Lyric_combine_music_iterator::derived_substitute (Context *f, Context *t)
183 {
184   if (lyric_iter_)
185     lyric_iter_->substitute_outlet (f, t);
186   if (lyrics_context_ && lyrics_context_ == f)
187     lyrics_context_ = t;
188   if (music_context_ && music_context_ == f)
189     set_music_context (t);
190 }
191
192 void
193 Lyric_combine_music_iterator::construct_children ()
194 {
195   Music *m = Music::unsmob (get_music ()->get_property ("element"));
196   lyric_iter_ = Music_iterator::unsmob (get_iterator (m));
197   if (!lyric_iter_)
198     return;
199   lyrics_context_ = find_context_below (lyric_iter_->get_outlet (),
200                                         ly_symbol2scm ("Lyrics"), "");
201
202   if (!lyrics_context_)
203     {
204       m->origin ()->warning (_ ("argument of \\lyricsto should contain Lyrics context"));
205     }
206
207   lyricsto_voice_name_ = get_music ()->get_property ("associated-context");
208   lyricsto_voice_type_ = get_music ()->get_property ("associated-context-type");
209   if (!scm_is_symbol (lyricsto_voice_type_))
210     lyricsto_voice_type_ = ly_symbol2scm ("Voice");
211
212   Context *voice = find_voice ();
213   if (voice)
214     set_music_context (voice);
215
216   /*
217     Wait for a Create_context event. If this isn't done, lyrics can be
218     delayed when voices are created implicitly.
219   */
220   Global_context *g = get_outlet ()->get_global_context ();
221   g->events_below ()->add_listener (GET_LISTENER (check_new_context), ly_symbol2scm ("CreateContext"));
222
223   /*
224     We do not create a Lyrics context, because the user might
225     create one with a different name, and then we will not find that
226     one.
227   */
228 }
229
230 IMPLEMENT_LISTENER (Lyric_combine_music_iterator, check_new_context)
231 void
232 Lyric_combine_music_iterator::check_new_context (SCM /*sev*/)
233 {
234   if (!ok ())
235     return;
236
237   // Search for a possible candidate voice to attach the lyrics to. If none
238   // is found, we'll try next time again.
239   Context *voice = find_voice ();
240   if (voice)
241     {
242       set_music_context (voice);
243     }
244 }
245
246 /*
247   Look for a suitable voice to align lyrics to.
248
249   Returns 0 if nothing should change; i.e., if we already listen to the
250   right voice, or if we don't yet listen to a voice but no appropriate
251   voice could be found.
252 */
253 Context *
254 Lyric_combine_music_iterator::find_voice ()
255 {
256   SCM voice_name = lyricsto_voice_name_;
257   SCM running = lyrics_context_
258                 ? lyrics_context_->get_property ("associatedVoice")
259                 : SCM_EOL;
260   SCM voice_type = lyricsto_voice_type_;
261   if (scm_is_string (running)) {
262     voice_name = running;
263     voice_type = lyrics_context_->get_property ("associatedVoiceType");
264   }
265
266   if (scm_is_string (voice_name)
267       && (!music_context_ || ly_scm2string (voice_name) != music_context_->id_string ())
268       && scm_is_symbol (voice_type))
269     {
270       Context *t = get_outlet ();
271       while (t && t->get_parent_context ())
272         t = t->get_parent_context ();
273
274       string name = ly_scm2string (voice_name);
275       return find_context_below (t, voice_type, name);
276     }
277
278   return 0;
279 }
280
281 void
282 Lyric_combine_music_iterator::process (Moment /* when */)
283 {
284   /* see if associatedVoice has been changed */
285   Context *new_voice = find_voice ();
286   if (new_voice)
287     set_music_context (new_voice);
288
289   lyrics_found_ = true;
290   if (!music_context_)
291     return;
292
293   if (!music_context_->get_parent_context ())
294     {
295       /*
296         The melody has died.
297         We die too.
298       */
299       if (lyrics_context_)
300         lyrics_context_->unset_property (ly_symbol2scm ("associatedVoiceContext"));
301       lyric_iter_ = 0;
302       set_music_context (0);
303     }
304
305   if (music_context_
306       && (start_new_syllable ()
307           || (busy_moment_ >= pending_grace_moment_))
308       && lyric_iter_->ok ())
309     {
310       Moment now = music_context_->now_mom ();
311       if (now.grace_part_ && !to_boolean (lyrics_context_->get_property ("includeGraceNotes")))
312         {
313           pending_grace_moment_ = now;
314           pending_grace_moment_.grace_part_ = Rational (0);
315           return;
316         }
317       else
318         {
319           pending_grace_moment_.set_infinite (1);
320         }
321
322       Moment m = lyric_iter_->pending_moment ();
323       lyrics_context_->set_property (ly_symbol2scm ("associatedVoiceContext"),
324                                      music_context_->self_scm ());
325       lyric_iter_->process (m);
326
327       music_found_ = true;
328     }
329
330   new_voice = find_voice ();
331   if (new_voice)
332     set_music_context (new_voice);
333 }
334
335 void
336 Lyric_combine_music_iterator::do_quit ()
337 {
338   if (!music_found_)
339     {
340       SCM voice_name = get_music ()->get_property ("associated-context");
341       SCM voice_type = get_music ()->get_property ("associated-context-type");
342       string name, type;
343       if (scm_is_string (voice_name))
344         name = ly_scm2string (voice_name);
345       type = robust_symbol2string (voice_type, "Voice");
346       /* Don't print a warning for empty lyrics (in which case we don't try
347          to find the proper voice, so it will not be found) */
348       if (lyrics_found_)
349         get_music ()->origin ()->warning (_f ("cannot find %s `%s'",
350                                               type.c_str (), name.c_str ()) + "\n");
351     }
352
353   if (lyric_iter_)
354     lyric_iter_->quit ();
355 }
356
357 IMPLEMENT_CTOR_CALLBACK (Lyric_combine_music_iterator);