1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 2004--2020 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 "context.hh"
21 #include "dispatcher.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 using std::string;
30
31 /*
32 This iterator is hairy. It tracks both lyric and melody contexts,
33 and has a complicated communication route, reading/writing
34 properties in both.
35
36 In the future, this should rather be done with
37
38 \interpretAsMelodyFor { MUSIC } { LYRICS LYRICS LYRICS }
39
40 This can run an interpret step on MUSIC, generating a stream. Then
41 the stream can be perused at leisure to apply durations to all of
42 the LYRICS.
43 */
44
45 class Lyric_combine_music_iterator final : public Music_iterator
46 {
47 public:
48 Lyric_combine_music_iterator ();
49 Lyric_combine_music_iterator (Lyric_combine_music_iterator const &src);
50 DECLARE_SCHEME_CALLBACK (constructor, ());
51 protected:
52 void create_children () override;
53 void create_contexts () override;
54 void do_quit () override;
55 void process (Moment) override;
56 bool run_always () const override;
57 void derived_mark () const override;
58 void derived_substitute (Context *, Context *) override;
59 void set_music_context (Context *to);
60 private:
61 bool start_new_syllable () const;
62 Context *find_voice ();
63 void set_busy (SCM);
64 void check_new_context (SCM);
65
66 bool music_found_;
67 bool lyrics_found_;
68 Context *lyrics_context_;
69 Context *music_context_;
70 SCM lyricsto_voice_name_;
71 SCM lyricsto_voice_type_;
72
73 Moment busy_moment_ {-Rational::infinity ()};
74 Moment pending_grace_moment_ {Rational::infinity ()};
75
76 Music_iterator *lyric_iter_;
77 };
78
Lyric_combine_music_iterator()79 Lyric_combine_music_iterator::Lyric_combine_music_iterator ()
80 {
81 music_found_ = false;
82 lyrics_found_ = false;
83 lyric_iter_ = 0;
84 music_context_ = 0;
85 lyrics_context_ = 0;
86 lyricsto_voice_name_ = SCM_UNDEFINED;
87 lyricsto_voice_type_ = SCM_UNDEFINED;
88 }
89
90 /*
91 It's dubious whether we can ever make this fully work. Due to
92 associatedVoice switching, this routine may be triggered for
93 the wrong music_context_
94 */
95 void
set_busy(SCM se)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_ = std::max (music_context_->now_mom (),
104 busy_moment_);
105
106 }
107
108 void
set_music_context(Context * to)109 Lyric_combine_music_iterator::set_music_context (Context *to)
110 {
111 if (music_context_)
112 {
113 music_context_->events_below ()->
114 remove_listener (GET_LISTENER (this, set_busy), ly_symbol2scm ("rhythmic-event"));
115 }
116
117 music_context_ = to;
118 if (to)
119 {
120 to->events_below ()->add_listener (GET_LISTENER (this, set_busy),
121 ly_symbol2scm ("rhythmic-event"));
122 }
123 }
124
125 bool
start_new_syllable() const126 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 (!from_scm<bool> (get_property (lyrics_context_, "ignoreMelismata")))
135 {
136 bool m = melisma_busy (music_context_);
137 if (m)
138 return false;
139 }
140
141 return true;
142 }
143
144 bool
run_always() const145 Lyric_combine_music_iterator::run_always () const
146 {
147 return lyric_iter_ && lyric_iter_->ok ()
148 && !(music_context_ && music_context_->is_removable ());
149 }
150
151 void
derived_mark() const152 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 scm_gc_mark (lyricsto_voice_name_);
161 scm_gc_mark (lyricsto_voice_type_);
162 }
163
164 void
derived_substitute(Context * f,Context * t)165 Lyric_combine_music_iterator::derived_substitute (Context *f, Context *t)
166 {
167 if (lyric_iter_)
168 lyric_iter_->substitute_context (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
create_children()176 Lyric_combine_music_iterator::create_children ()
177 {
178 Music_iterator::create_children ();
179
180 Music *m = unsmob<Music> (get_property (get_music (), "element"));
181 SCM it_scm = get_static_get_iterator (m);
182 lyric_iter_ = unsmob<Music_iterator> (it_scm);
183 }
184
185 void
create_contexts()186 Lyric_combine_music_iterator::create_contexts ()
187 {
188 Music_iterator::create_contexts ();
189
190 if (!lyric_iter_)
191 return;
192 lyric_iter_->init_context (get_context ());
193 lyrics_context_ = find_context_below (lyric_iter_->get_context (),
194 ly_symbol2scm ("Lyrics"), "");
195
196 if (!lyrics_context_)
197 {
198 Music *m = unsmob<Music> (get_property (get_music (), "element"));
199 m->warning (_ ("argument of \\lyricsto should contain Lyrics context"));
200 }
201
202 lyricsto_voice_name_ = get_property (get_music (), "associated-context");
203 lyricsto_voice_type_ = get_property (get_music (), "associated-context-type");
204 if (!scm_is_symbol (lyricsto_voice_type_))
205 lyricsto_voice_type_ = ly_symbol2scm ("Voice");
206
207 Context *voice = find_voice ();
208 if (voice)
209 set_music_context (voice);
210
211 /*
212 Wait for a Create_context event. If this isn't done, lyrics can be
213 delayed when voices are created implicitly.
214 */
215 Context *g = find_top_context (get_context ());
216 g->events_below ()->add_listener (GET_LISTENER (this, check_new_context), ly_symbol2scm ("CreateContext"));
217
218 /*
219 We do not create a Lyrics context, because the user might
220 create one with a different name, and then we will not find that
221 one.
222 */
223 }
224
225 void
check_new_context(SCM)226 Lyric_combine_music_iterator::check_new_context (SCM /*sev*/)
227 {
228 if (!ok ())
229 return;
230
231 // Search for a possible candidate voice to attach the lyrics to. If none
232 // is found, we'll try next time again.
233 Context *voice = find_voice ();
234 if (voice)
235 {
236 set_music_context (voice);
237 }
238 }
239
240 /*
241 Look for a suitable voice to align lyrics to.
242
243 Returns 0 if nothing should change; i.e., if we already listen to the
244 right voice, or if we don't yet listen to a voice but no appropriate
245 voice could be found.
246 */
247 Context *
find_voice()248 Lyric_combine_music_iterator::find_voice ()
249 {
250 SCM voice_name = lyricsto_voice_name_;
251 SCM running = lyrics_context_
252 ? get_property (lyrics_context_, "associatedVoice")
253 : SCM_EOL;
254 SCM voice_type = lyricsto_voice_type_;
255 if (scm_is_string (running))
256 {
257 voice_name = running;
258 voice_type = get_property (lyrics_context_, "associatedVoiceType");
259 }
260
261 if (scm_is_string (voice_name)
262 && (!music_context_ || ly_scm2string (voice_name) != music_context_->id_string ())
263 && scm_is_symbol (voice_type))
264 {
265 return find_context_below (find_top_context (get_context ()),
266 voice_type, ly_scm2string (voice_name));
267 }
268
269 return 0;
270 }
271
272 void
process(Moment)273 Lyric_combine_music_iterator::process (Moment /* when */)
274 {
275 /* see if associatedVoice has been changed */
276 Context *new_voice = find_voice ();
277 if (new_voice)
278 set_music_context (new_voice);
279
280 lyrics_found_ = true;
281 if (!music_context_)
282 return;
283
284 if (!music_context_->get_parent ())
285 {
286 /*
287 The melody has died.
288 We die too.
289 */
290 if (lyrics_context_)
291 lyrics_context_->unset_property (ly_symbol2scm ("associatedVoiceContext"));
292 lyric_iter_ = 0;
293 set_music_context (0);
294 }
295
296 if (music_context_
297 && (start_new_syllable ()
298 || (busy_moment_ >= pending_grace_moment_))
299 && lyric_iter_->ok ())
300 {
301 Moment now = music_context_->now_mom ();
302 if (now.grace_part_ && !from_scm<bool> (get_property (lyrics_context_, "includeGraceNotes")))
303 {
304 pending_grace_moment_ = now;
305 pending_grace_moment_.grace_part_ = Rational (0);
306 return;
307 }
308 else
309 {
310 pending_grace_moment_.main_part_ = Rational::infinity ();
311 }
312
313 Moment m = lyric_iter_->pending_moment ();
314 set_property (lyrics_context_, ly_symbol2scm ("associatedVoiceContext"),
315 music_context_->self_scm ());
316 lyric_iter_->process (m);
317
318 music_found_ = true;
319 }
320
321 new_voice = find_voice ();
322 if (new_voice)
323 set_music_context (new_voice);
324 }
325
326 void
do_quit()327 Lyric_combine_music_iterator::do_quit ()
328 {
329 /* Don't print a warning for empty lyrics (in which case we don't try
330 to find the proper voice, so it will not be found) */
331 if (lyrics_found_ && !music_found_)
332 {
333 Music *m = get_music ();
334
335 // ugh: defaults are repeated elsewhere
336 SCM voice_type = get_property (m, "associated-context-type");
337 if (!scm_is_symbol (voice_type))
338 voice_type = ly_symbol2scm ("Voice");
339
340 string id = robust_scm2string (get_property (m, "associated-context"),
341 "");
342
343 Input *origin = m->origin ();
344 origin->warning (_f ("cannot find context: %s",
345 Context::diagnostic_id (voice_type, id).c_str ()));
346 }
347
348 if (lyric_iter_)
349 lyric_iter_->quit ();
350 }
351
352 IMPLEMENT_CTOR_CALLBACK (Lyric_combine_music_iterator);
353