1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 2006--2020 Joe Neeman <joeneeman@gmail.com>
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
22 #include "context.hh"
23 #include "duration.hh"
24 #include "grob.hh"
25 #include "international.hh"
26 #include "paper-column.hh"
27 #include "stream-event.hh"
28 #include "warn.hh"
29
30 #include "translator.icc"
31
32 using std::string;
33 using std::vector;
34
35 class Page_turn_event
36 {
37 public:
38 SCM permission_;
39 Real penalty_;
40 Interval_t<Rational> duration_;
41
Page_turn_event(Rational start,Rational end,SCM perm,Real pen)42 Page_turn_event (Rational start, Rational end, SCM perm, Real pen)
43 {
44 duration_[LEFT] = start;
45 duration_[RIGHT] = end;
46 permission_ = perm;
47 penalty_ = pen;
48 }
49
50 /* Suppose we have decided on a possible page turn, only to change
51 out mind later (for example, if there is a volta repeat and it
52 would be difficult to turn the page back). Then we need to
53 re-penalize a region of the piece. Depending on how the events
54 intersect, we may have to split it into as many as 3 pieces.
55 */
penalize(Page_turn_event const & penalty)56 vector<Page_turn_event> penalize (Page_turn_event const &penalty)
57 {
58 Interval_t<Rational> intersect = intersection (duration_, penalty.duration_);
59 vector<Page_turn_event> ret;
60
61 if (intersect.is_empty ())
62 {
63 ret.push_back (*this);
64 return ret;
65 }
66
67 Real new_pen = std::max (penalty_, penalty.penalty_);
68
69 if (duration_[LEFT] < penalty.duration_[LEFT])
70 ret.push_back (Page_turn_event (duration_[LEFT], penalty.duration_[LEFT], permission_, penalty_));
71
72 if (!scm_is_null (penalty.permission_))
73 ret.push_back (Page_turn_event (intersect[LEFT], intersect[RIGHT], permission_, new_pen));
74
75 if (penalty.duration_[RIGHT] < duration_[RIGHT])
76 ret.push_back (Page_turn_event (penalty.duration_[RIGHT], duration_[RIGHT], permission_, penalty_));
77
78 return ret;
79 }
80 };
81
82 class Page_turn_engraver : public Engraver
83 {
84 Moment rest_begin_;
85 Moment repeat_begin_;
86 Moment note_end_;
87 Rational repeat_begin_rest_length_;
88
89 vector<Page_turn_event> forced_breaks_;
90 vector<Page_turn_event> automatic_breaks_;
91 vector<Page_turn_event> repeat_penalties_;
92
93 /* the next 3 are in sync (ie. same number of elements, etc.) */
94 vector<Rational> breakable_moments_;
95 vector<Grob *> breakable_columns_;
96 vector<bool> special_barlines_;
97
98 SCM max_permission (SCM perm1, SCM perm2);
99 Real penalty (Rational rest_len);
100 Grob *breakable_column (Page_turn_event const &);
101
102 protected:
103 void listen_break (Stream_event *);
104 void acknowledge_note_head (Grob_info);
105
106 public:
107 TRANSLATOR_DECLARATIONS (Page_turn_engraver);
108 void stop_translation_timestep ();
109 void start_translation_timestep ();
110 void finalize () override;
111 };
112
Page_turn_engraver(Context * c)113 Page_turn_engraver::Page_turn_engraver (Context *c)
114 : Engraver (c)
115 {
116 repeat_begin_ = Moment (-1);
117 repeat_begin_rest_length_ = 0;
118 rest_begin_ = 0;
119 note_end_ = 0;
120 }
121
122 Grob *
breakable_column(Page_turn_event const & brk)123 Page_turn_engraver::breakable_column (Page_turn_event const &brk)
124 {
125 vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], std::less<Rational> ());
126 vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], std::less<Rational> ());
127
128 if (start == breakable_moments_.size ())
129 return NULL;
130 if (end == 0)
131 return NULL;
132 end--;
133
134 for (vsize i = end + 1; i-- > start;)
135 if (special_barlines_[i])
136 return breakable_columns_[i];
137
138 return breakable_columns_[end];
139 }
140
141 Real
penalty(Rational rest_len)142 Page_turn_engraver::penalty (Rational rest_len)
143 {
144 Rational min_turn = robust_scm2moment (get_property (this, "minimumPageTurnLength"), Moment (1)).main_part_;
145
146 return (rest_len < min_turn) ? infinity_f : 0;
147 }
148
149 void
acknowledge_note_head(Grob_info gi)150 Page_turn_engraver::acknowledge_note_head (Grob_info gi)
151 {
152 Stream_event *cause = gi.event_cause ();
153
154 Duration *dur_ptr = cause
155 ? unsmob<Duration> (get_property (cause, "duration"))
156 : 0;
157
158 if (!dur_ptr)
159 return;
160
161 if (rest_begin_ < now_mom ())
162 {
163 Real pen = penalty ((now_mom () - rest_begin_).main_part_);
164 if (!std::isinf (pen))
165 automatic_breaks_.push_back (Page_turn_event (rest_begin_.main_part_,
166 now_mom ().main_part_,
167 ly_symbol2scm ("allow"), 0));
168 }
169
170 if (rest_begin_ <= repeat_begin_)
171 repeat_begin_rest_length_ = (now_mom () - repeat_begin_).main_part_;
172 note_end_ = now_mom () + dur_ptr->get_length ();
173 }
174
175 void
listen_break(Stream_event * ev)176 Page_turn_engraver::listen_break (Stream_event *ev)
177 {
178 string name = ly_symbol2string (scm_car (get_property (ev, "class")));
179
180 if (name == "page-turn-event")
181 {
182 SCM permission = get_property (ev, "break-permission");
183 Real penalty = from_scm<double> (get_property (ev, "break-penalty"), 0);
184 Rational now = now_mom ().main_part_;
185
186 forced_breaks_.push_back (Page_turn_event (now, now, permission, penalty));
187 }
188 }
189
190 void
start_translation_timestep()191 Page_turn_engraver::start_translation_timestep ()
192 {
193 /* What we want to do is to build a list of all the
194 breakable paper columns. In general, paper-columns won't be marked as
195 such until the Paper_column_engraver has done stop_translation_timestep.
196
197 Therefore, we just grab /all/ paper columns (in the
198 stop_translation_timestep, since they're not created here yet)
199 and remove the non-breakable ones at the beginning of the following
200 timestep.
201 */
202
203 if (breakable_columns_.size () && !Paper_column::is_breakable (breakable_columns_.back ()))
204 {
205 breakable_columns_.pop_back ();
206 breakable_moments_.pop_back ();
207 special_barlines_.pop_back ();
208 }
209 }
210
211 void
stop_translation_timestep()212 Page_turn_engraver::stop_translation_timestep ()
213 {
214 Grob *pc = unsmob<Grob> (get_property (this, "currentCommandColumn"));
215
216 if (pc)
217 {
218 breakable_columns_.push_back (pc);
219 breakable_moments_.push_back (now_mom ().main_part_);
220
221 SCM bar_scm = get_property (this, "whichBar");
222 string bar = robust_scm2string (bar_scm, "");
223
224 special_barlines_.push_back (bar != "" && bar != "|");
225 }
226
227 /* C&P from Repeat_acknowledge_engraver */
228 SCM cs = get_property (this, "repeatCommands");
229 bool start = false;
230 bool end = false;
231
232 for (; scm_is_pair (cs); cs = scm_cdr (cs))
233 {
234 SCM command = scm_car (cs);
235 if (scm_is_eq (command, ly_symbol2scm ("start-repeat")))
236 start = true;
237 else if (scm_is_eq (command, ly_symbol2scm ("end-repeat")))
238 end = true;
239 }
240
241 if (end && (repeat_begin_.main_part_ >= 0))
242 {
243 Rational now = now_mom ().main_part_;
244 Real pen = penalty ((now_mom () - rest_begin_).main_part_ + repeat_begin_rest_length_);
245 Moment *m = unsmob<Moment> (get_property (this, "minimumRepeatLengthForPageTurn"));
246 if (m && *m > (now_mom () - repeat_begin_))
247 pen = infinity_f;
248
249 if (pen == infinity_f)
250 repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, SCM_EOL, -infinity_f));
251 else
252 repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, ly_symbol2scm ("allow"), pen));
253
254 repeat_begin_ = Moment (-1);
255 }
256
257 if (start)
258 {
259 repeat_begin_ = now_mom ();
260 repeat_begin_rest_length_ = 0;
261 }
262 rest_begin_ = note_end_;
263 }
264
265 /* return the most permissive symbol (where force is the most permissive and
266 forbid is the least
267 */
268 SCM
max_permission(SCM perm1,SCM perm2)269 Page_turn_engraver::max_permission (SCM perm1, SCM perm2)
270 {
271 if (scm_is_null (perm1))
272 return perm2;
273 if (scm_is_eq (perm1, ly_symbol2scm ("allow"))
274 && scm_is_eq (perm2, ly_symbol2scm ("force")))
275 return perm2;
276 return perm1;
277 }
278
279 void
finalize()280 Page_turn_engraver::finalize ()
281 {
282 vsize rep_index = 0;
283 vector<Page_turn_event> auto_breaks;
284
285 /* filter the automatic breaks through the repeat penalties */
286 for (vsize i = 0; i < automatic_breaks_.size (); i++)
287 {
288 Page_turn_event &brk = automatic_breaks_[i];
289
290 /* find the next applicable repeat penalty */
291 for (;
292 rep_index < repeat_penalties_.size ()
293 && repeat_penalties_[rep_index].duration_[RIGHT] <= brk.duration_[LEFT];
294 rep_index++)
295 ;
296
297 if (rep_index >= repeat_penalties_.size ()
298 || brk.duration_[RIGHT] <= repeat_penalties_[rep_index].duration_[LEFT])
299 auto_breaks.push_back (brk);
300 else
301 {
302 vector<Page_turn_event> split = brk.penalize (repeat_penalties_[rep_index]);
303
304 /* it's possible that the last of my newly-split events overlaps the next repeat_penalty,
305 in which case we need to refilter that event */
306 if (rep_index + 1 < repeat_penalties_.size ()
307 && split.size ()
308 && split.back ().duration_[RIGHT] > repeat_penalties_[rep_index + 1].duration_[LEFT])
309 {
310 automatic_breaks_[i] = split.back ();
311 split.pop_back ();
312 i--;
313 }
314 auto_breaks.insert (auto_breaks.end (), split.begin (), split.end ());
315 }
316 }
317
318 /* apply the automatic breaks */
319 for (vsize i = 0; i < auto_breaks.size (); i++)
320 {
321 Page_turn_event const &brk = auto_breaks[i];
322 Grob *pc = breakable_column (auto_breaks[i]);
323 if (pc)
324 {
325 SCM perm = max_permission (get_property (pc, "page-turn-permission"), brk.permission_);
326 Real pen = std::min (from_scm<double> (get_property (pc, "page-turn-penalty"), infinity_f), brk.penalty_);
327 set_property (pc, "page-turn-permission", perm);
328 set_property (pc, "page-turn-penalty", to_scm (pen));
329 }
330 }
331
332 /* unless a manual break overrides it, allow a page turn at the end of the piece */
333 set_property (breakable_columns_.back (), "page-turn-permission", ly_symbol2scm ("allow"));
334
335 /* apply the manual breaks */
336 for (vsize i = 0; i < forced_breaks_.size (); i++)
337 {
338 Page_turn_event const &brk = forced_breaks_[i];
339 Grob *pc = breakable_column (forced_breaks_[i]);
340 if (pc)
341 {
342 set_property (pc, "page-turn-permission", brk.permission_);
343 set_property (pc, "page-turn-penalty", to_scm (brk.penalty_));
344 }
345 }
346 }
347
348 void
boot()349 Page_turn_engraver::boot ()
350 {
351 ADD_LISTENER (Page_turn_engraver, break);
352 ADD_ACKNOWLEDGER (Page_turn_engraver, note_head);
353 }
354
355 ADD_TRANSLATOR (Page_turn_engraver,
356 /* doc */
357 "Decide where page turns are allowed to go.",
358
359 /* create */
360 "",
361
362 /* read */
363 "minimumPageTurnLength "
364 "minimumRepeatLengthForPageTurn ",
365
366 /* write */
367 ""
368 );
369