1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 2004--2021 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 "music-wrapper-iterator.hh"
21
22 #include "context.hh"
23 #include "dispatcher.hh"
24 #include "input.hh"
25 #include "international.hh"
26 #include "lily-guile.hh"
27 #include "music-sequence.hh"
28 #include "music.hh"
29 #include "warn.hh"
30
31 #include <string>
32
33 class Quote_iterator final : public Music_wrapper_iterator
34 {
35 public:
36 Quote_iterator () = default;
37 Context_handle quote_handle_;
38
39 // zero moment of this music in the timeline of the score; unknown until the
40 // first call to process ()
41 Moment zero_mom_ = -Moment::infinity ();
42 SCM event_vector_ = SCM_EOL;
43 vsize event_idx_ = 0; // left closed
44 vsize end_idx_ = 0; // right open
45 bool first_time_ = true;
46
47 SCM transposed_musics_ = SCM_EOL;
48
49 DECLARE_SCHEME_CALLBACK (constructor, ());
50 bool accept_music_type (Stream_event *, bool is_cue = true) const;
51
52 protected:
53 void derived_mark () const override;
54 void create_children () override;
55 void create_contexts () override;
56 Moment pending_moment () const override;
57 void process (Moment) override;
58 void do_quit () override;
59 };
60
61 void
do_quit()62 Quote_iterator::do_quit ()
63 {
64 Music_wrapper_iterator::do_quit ();
65 quote_handle_.set_context (0);
66 }
67
68 bool
accept_music_type(Stream_event * ev,bool is_cue) const69 Quote_iterator::accept_music_type (Stream_event *ev, bool is_cue) const
70 {
71 SCM accept = SCM_EOL;
72 // Cue notes use the quotedCueEventTypes property, otherwise (and as fallback
73 // for cue notes if quotedCueEventTypes is not set) use quotedEventTypes
74 if (is_cue)
75 accept = get_property (get_context (), "quotedCueEventTypes");
76 if (scm_is_null (accept))
77 accept = get_property (get_context (), "quotedEventTypes");
78
79 for (; scm_is_pair (accept); accept = scm_cdr (accept))
80 {
81 if (ev->internal_in_event_class (scm_car (accept)))
82 return true;
83 }
84 return false;
85 }
86
87 void
derived_mark() const88 Quote_iterator::derived_mark () const
89 {
90 Music_wrapper_iterator::derived_mark ();
91 scm_gc_mark (transposed_musics_);
92 }
93
94 // lower bound: binary search returning the index of the first element that is
95 // not less than the key
96 vsize
binsearch_scm_vector(SCM vec,SCM key,bool is_less (SCM a,SCM b))97 binsearch_scm_vector (SCM vec, SCM key, bool is_less (SCM a, SCM b))
98 {
99 vsize lo = 0;
100 vsize hi = scm_c_vector_length (vec);
101
102 while (lo < hi)
103 {
104 vsize cmp = (lo + hi) / 2;
105
106 SCM when = scm_caar (scm_c_vector_ref (vec, cmp));
107 if (is_less (when, key))
108 lo = cmp + 1;
109 else
110 hi = cmp;
111 }
112
113 return lo;
114 }
115
116 void
create_children()117 Quote_iterator::create_children ()
118 {
119 Music_wrapper_iterator::create_children ();
120
121 event_vector_ = get_property (get_music (), "quoted-events");
122 }
123
124 void
create_contexts()125 Quote_iterator::create_contexts ()
126 {
127 Music_wrapper_iterator::create_contexts ();
128
129 Context *cue_context = 0;
130
131 SCM name = get_property (get_music (), "quoted-context-type");
132 if (scm_is_symbol (name))
133 {
134 SCM id = get_property (get_music (), "quoted-context-id");
135 std::string c_id = robust_scm2string (id, "");
136 cue_context = get_context ()->find_create_context (CENTER,
137 name, c_id, SCM_EOL);
138 if (!cue_context)
139 {
140 warning (_f ("cannot find or create context: %s",
141 Context::diagnostic_id (name, c_id).c_str ()));
142 }
143 }
144
145 if (!cue_context)
146 cue_context = get_context ()->get_default_interpreter ();
147 quote_handle_.set_context (cue_context);
148 }
149
150 Moment
pending_moment() const151 Quote_iterator::pending_moment () const
152 {
153 auto m = Music_wrapper_iterator::pending_moment ();
154
155 if (event_idx_ < end_idx_)
156 {
157 SCM entry = scm_c_vector_ref (event_vector_, event_idx_);
158 if (auto *const event_mom = unsmob<Moment> (scm_caar (entry)))
159 {
160 // If event_mom is not a moment, process () should issue a diagnostic
161 // later, so just ignore it here.
162 m = std::min (m, *event_mom - zero_mom_);
163 }
164 }
165
166 return m;
167 }
168
169 void
process(Moment m)170 Quote_iterator::process (Moment m)
171 {
172 if (Music_wrapper_iterator::pending_moment () <= m)
173 Music_wrapper_iterator::process (m);
174
175 if (first_time_)
176 {
177 first_time_ = false;
178
179 // start moment of this music in the timeline of the score
180 const auto start_mom = get_context ()->now_mom ();
181
182 zero_mom_ = start_mom - music_start_mom ();
183
184 if (scm_is_vector (event_vector_))
185 {
186 // To quote grace notes, the user currently has to provide grace time
187 // in the wrapped music. It would be nicer to include all grace
188 // notes leading into the quote automatically. That likely requires
189 // an infrastructure to precompute the start moment (at least the
190 // main part) before the first call to pending_moment (). It
191 // possibly also requires improvements to handle music where the
192 // grace part of the start moment is unknown prior to iteration.
193 event_idx_ = binsearch_scm_vector (event_vector_,
194 start_mom.smobbed_copy (),
195 moment_less);
196
197 // end moment of this music, excluding any grace notes leading to an
198 // unquoted note
199 const Moment end_mom (zero_mom_.main_part_
200 + music_get_length ().main_part_,
201 -Rational::infinity ());
202
203 end_idx_ = binsearch_scm_vector (event_vector_,
204 end_mom.smobbed_copy (),
205 moment_less);
206 }
207 }
208
209 m = zero_mom_ + m;
210 for (/**/; event_idx_ < end_idx_; ++event_idx_)
211 {
212 SCM entry = scm_c_vector_ref (event_vector_, event_idx_);
213
214 if (auto *const event_mom = unsmob<Moment> (scm_caar (entry)))
215 {
216 if (*event_mom > m) // not time to process this entry yet
217 return;
218 }
219 else
220 {
221 std::string s ("expected moment in event vector: ");
222 s += ly_scm_write_string (scm_caar (entry));
223 programming_error (s);
224 continue;
225 }
226
227 Pitch *quote_pitch = unsmob<Pitch> (scm_cdar (entry));
228
229 /*
230 The pitch that sounds when written central C is played.
231 */
232 Pitch temp_pitch;
233 Pitch *me_pitch = unsmob<Pitch> (get_property (get_music (), "quoted-transposition"));
234 if (!me_pitch)
235 me_pitch = unsmob<Pitch> (get_property (get_context (), "instrumentTransposition"));
236 else
237 {
238 // We are not going to win a beauty contest with this one,
239 // but it is slated for replacement and touches little code.
240 // quoted-transposition currently has a different sign
241 // convention than instrumentTransposition
242 temp_pitch = me_pitch->negated ();
243 me_pitch = &temp_pitch;
244 }
245 SCM cid = get_property (get_music (), "quoted-context-id");
246 bool is_cue = scm_is_string (cid) && (ly_scm2string (cid) == "cue");
247
248 for (SCM s = scm_cdr (entry); scm_is_pair (s); s = scm_cdr (s))
249 {
250 SCM ev_acc = scm_car (s);
251
252 Stream_event *ev = unsmob<Stream_event> (scm_car (ev_acc));
253 if (!ev)
254 programming_error ("no music found in quote");
255 else if (accept_music_type (ev, is_cue))
256 {
257 /* create a transposed copy if necessary */
258 if (quote_pitch || me_pitch)
259 {
260 Pitch qp, mp;
261 if (quote_pitch)
262 qp = *quote_pitch;
263 if (me_pitch)
264 mp = *me_pitch;
265
266 Pitch diff = pitch_interval (mp, qp);
267 ev = ev->clone ();
268 ev->make_transposable ();
269 ev->transpose (diff);
270 transposed_musics_ = scm_cons (ev->unprotect (), transposed_musics_);
271 }
272 quote_handle_.get_context ()->event_source ()->broadcast (ev);
273 }
274 }
275 }
276 }
277
278 IMPLEMENT_CTOR_CALLBACK (Quote_iterator);
279