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