1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 1999--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 "engraver.hh"
21 #include "moment.hh"
22 #include "note-spacing.hh"
23 #include "paper-column.hh"
24 #include "pointer-group-interface.hh"
25 #include "pqueue.hh"
26 #include "spanner.hh"
27 #include "staff-spacing.hh"
28 #include "stream-event.hh"
29 
30 #include "translator.icc"
31 
32 using std::vector;
33 
34 struct Rhythmic_tuple
35 {
36   Grob_info info_;
37   Moment end_;
38 
Rhythmic_tupleRhythmic_tuple39   Rhythmic_tuple (Grob_info i, Moment m) : info_ (i), end_ (m) {}
40 
41   static int time_compare (Rhythmic_tuple const &, Rhythmic_tuple const &);
42 };
43 
44 inline int
compare(Rhythmic_tuple const & a,Rhythmic_tuple const & b)45 compare (Rhythmic_tuple const &a, Rhythmic_tuple const &b)
46 {
47   return Rhythmic_tuple::time_compare (a, b);
48 }
49 
50 int
time_compare(Rhythmic_tuple const & h1,Rhythmic_tuple const & h2)51 Rhythmic_tuple::time_compare (Rhythmic_tuple const &h1,
52                               Rhythmic_tuple const &h2)
53 {
54   return (h1.end_ - h2.end_).main_part_.sign ();
55 }
56 
57 /****************************************************************/
58 
59 /*
60   Acknowledge rhythmic elements, for initializing spacing fields in
61   the columns.
62 */
63 class Spacing_engraver : public Engraver
64 {
65   PQueue<Rhythmic_tuple> playing_durations_;
66   vector<Rhythmic_tuple> now_durations_;
67   vector<Rhythmic_tuple> stopped_durations_;
68   Moment now_;
69   Spanner *spacing_;
70   Stream_event *start_section_;
71 
72   TRANSLATOR_DECLARATIONS (Spacing_engraver);
73 
74 protected:
75   void acknowledge_staff_spacing (Grob_info);
76   void acknowledge_note_spacing (Grob_info);
77   void acknowledge_rhythmic_head (Grob_info);
78   void acknowledge_rhythmic_grob (Grob_info);
79   void listen_spacing_section (Stream_event *);
80 
81   void start_translation_timestep ();
82   void stop_translation_timestep ();
83   void process_music ();
84   void add_starter_duration (Grob_info i);
85 
86   void initialize () override;
87   void finalize () override;
88 
89   void start_spanner ();
90   void stop_spanner ();
91 };
92 
Spacing_engraver(Context * c)93 Spacing_engraver::Spacing_engraver (Context *c)
94   : Engraver (c)
95 {
96   spacing_ = 0;
97   start_section_ = 0;
98 }
99 
100 void
listen_spacing_section(Stream_event * ev)101 Spacing_engraver::listen_spacing_section (Stream_event *ev)
102 {
103   ASSIGN_EVENT_ONCE (start_section_, ev);
104 }
105 
106 void
process_music()107 Spacing_engraver::process_music ()
108 {
109   if (start_section_ && spacing_)
110     stop_spanner ();
111 
112   if (!spacing_)
113     start_spanner ();
114 }
115 
116 void
start_spanner()117 Spacing_engraver::start_spanner ()
118 {
119   assert (!spacing_);
120 
121   spacing_ = make_spanner ("SpacingSpanner", SCM_EOL);
122   auto *col = unsmob<Grob> (get_property (this, "currentCommandColumn"));
123   spacing_->set_bound (LEFT, col);
124 }
125 
126 void
initialize()127 Spacing_engraver::initialize ()
128 {
129   now_ = now_mom ();
130 }
131 
132 void
finalize()133 Spacing_engraver::finalize ()
134 {
135   stop_spanner ();
136 }
137 
138 void
stop_spanner()139 Spacing_engraver::stop_spanner ()
140 {
141   if (spacing_)
142     {
143       Grob *p = unsmob<Grob> (get_property (this, "currentCommandColumn"));
144 
145       spacing_->set_bound (RIGHT, p);
146       spacing_ = 0;
147     }
148 }
149 
150 void
acknowledge_note_spacing(Grob_info i)151 Spacing_engraver::acknowledge_note_spacing (Grob_info i)
152 {
153   Pointer_group_interface::add_grob (spacing_, ly_symbol2scm ("wishes"), i.grob ());
154 }
155 
156 void
acknowledge_staff_spacing(Grob_info i)157 Spacing_engraver::acknowledge_staff_spacing (Grob_info i)
158 {
159   Pointer_group_interface::add_grob (spacing_, ly_symbol2scm ("wishes"), i.grob ());
160 }
161 
162 void
acknowledge_rhythmic_grob(Grob_info i)163 Spacing_engraver::acknowledge_rhythmic_grob (Grob_info i)
164 {
165   add_starter_duration (i);
166 }
167 
168 void
acknowledge_rhythmic_head(Grob_info i)169 Spacing_engraver::acknowledge_rhythmic_head (Grob_info i)
170 {
171   add_starter_duration (i);
172 }
173 
174 void
add_starter_duration(Grob_info i)175 Spacing_engraver::add_starter_duration (Grob_info i)
176 {
177   if (i.grob ()->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface"))
178       || i.grob ()->internal_has_interface (ly_symbol2scm ("multi-measure-interface")))
179     return;
180 
181   /*
182     only pay attention to durations that are not grace notes.
183   */
184   if (!now_.grace_part_)
185     {
186       Stream_event *r = i.event_cause ();
187       if (r && r->in_event_class ("rhythmic-event"))
188         {
189           auto len = get_event_length (r, now_);
190           Rhythmic_tuple t (i, now_mom () + len);
191           now_durations_.push_back (t);
192         }
193     }
194 }
195 
196 void
stop_translation_timestep()197 Spacing_engraver::stop_translation_timestep ()
198 {
199   Paper_column *musical_column
200     = unsmob<Paper_column> (get_property (this, "currentMusicalColumn"));
201 
202   if (!spacing_)
203     start_spanner ();
204 
205   set_object (musical_column, "spacing", spacing_->self_scm ());
206   set_object (unsmob<Grob> (get_property (this, "currentCommandColumn")),
207               "spacing", spacing_->self_scm ());
208 
209   SCM proportional = get_property (this, "proportionalNotationDuration");
210   if (unsmob<Moment> (proportional))
211     {
212       set_property (musical_column, "shortest-playing-duration", proportional);
213       set_property (musical_column, "shortest-starter-duration", proportional);
214       set_property (musical_column, "used", SCM_BOOL_T);
215       return;
216     }
217 
218   auto shortest_playing = Moment::infinity ();
219   for (vsize i = 0; i < playing_durations_.size (); i++)
220     {
221       Stream_event *ev = playing_durations_[i].info_.event_cause ();
222       if (ev)
223         {
224           auto m = get_event_length (ev);
225           shortest_playing = std::min (shortest_playing, m);
226         }
227     }
228   auto starter = Moment::infinity ();
229 
230   for (vsize i = 0; i < now_durations_.size (); i++)
231     {
232       auto m = get_event_length (now_durations_[i].info_.event_cause ());
233       if (m)
234         {
235           starter = std::min (starter, m);
236           playing_durations_.insert (now_durations_[i]);
237         }
238     }
239   now_durations_.clear ();
240 
241   shortest_playing = std::min (shortest_playing, starter);
242 
243   assert (starter);
244   SCM sh = shortest_playing.smobbed_copy ();
245   SCM st = starter.smobbed_copy ();
246 
247   set_property (musical_column, "shortest-playing-duration", sh);
248   set_property (musical_column, "shortest-starter-duration", st);
249 }
250 
251 void
start_translation_timestep()252 Spacing_engraver::start_translation_timestep ()
253 {
254   start_section_ = 0;
255 
256   now_ = now_mom ();
257   stopped_durations_.clear ();
258 
259   while (playing_durations_.size () && playing_durations_.front ().end_ < now_)
260     playing_durations_.delmin ();
261   while (playing_durations_.size () && playing_durations_.front ().end_ == now_)
262     stopped_durations_.push_back (playing_durations_.get ());
263 }
264 
265 void
boot()266 Spacing_engraver::boot ()
267 {
268   ADD_LISTENER (Spacing_engraver, spacing_section);
269   ADD_ACKNOWLEDGER (Spacing_engraver, staff_spacing);
270   ADD_ACKNOWLEDGER (Spacing_engraver, note_spacing);
271   ADD_ACKNOWLEDGER (Spacing_engraver, rhythmic_head);
272   ADD_ACKNOWLEDGER (Spacing_engraver, rhythmic_grob);
273 }
274 
275 ADD_TRANSLATOR (Spacing_engraver,
276                 /* doc */
277                 "Make a @code{SpacingSpanner} and do bookkeeping of shortest"
278                 " starting and playing notes.",
279 
280                 /* create */
281                 "SpacingSpanner ",
282 
283                 /* read */
284                 "currentMusicalColumn "
285                 "currentCommandColumn "
286                 "proportionalNotationDuration ",
287 
288                 /* write */
289                 ""
290                );
291