2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2006--2010 Joe Neeman <joeneeman@gmail.com>
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.
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.
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/>.
20 #include "engraver.hh"
23 #include "duration.hh"
25 #include "international.hh"
26 #include "paper-column.hh"
27 #include "stream-event.hh"
30 #include "translator.icc"
32 class Page_turn_event
{
36 Interval_t
<Rational
> duration_
;
38 Page_turn_event (Rational start
, Rational end
, SCM perm
, Real pen
)
40 duration_
[LEFT
] = start
;
41 duration_
[RIGHT
] = end
;
46 /* Suppose we have decided on a possible page turn, only to change
47 out mind later (for example, if there is a volta repeat and it
48 would be difficult to turn the page back). Then we need to
49 re-penalize a region of the piece. Depending on how the events
50 intersect, we may have to split it into as many as 3 pieces.
52 vector
<Page_turn_event
> penalize (Page_turn_event
const &penalty
)
54 Interval_t
<Rational
> intersect
= intersection (duration_
, penalty
.duration_
);
55 vector
<Page_turn_event
> ret
;
57 if (intersect
.is_empty ())
59 ret
.push_back (*this);
63 Real new_pen
= max (penalty_
, penalty
.penalty_
);
65 if (duration_
[LEFT
] < penalty
.duration_
[LEFT
])
66 ret
.push_back (Page_turn_event (duration_
[LEFT
], penalty
.duration_
[LEFT
], permission_
, penalty_
));
68 if (penalty
.permission_
!= SCM_EOL
)
69 ret
.push_back (Page_turn_event (intersect
[LEFT
], intersect
[RIGHT
], permission_
, new_pen
));
71 if (penalty
.duration_
[RIGHT
] < duration_
[RIGHT
])
72 ret
.push_back (Page_turn_event (penalty
.duration_
[RIGHT
], duration_
[RIGHT
], permission_
, penalty_
));
78 class Page_turn_engraver
: public Engraver
83 Rational repeat_begin_rest_length_
;
85 vector
<Page_turn_event
> forced_breaks_
;
86 vector
<Page_turn_event
> automatic_breaks_
;
87 vector
<Page_turn_event
> repeat_penalties_
;
89 /* the next 3 are in sync (ie. same number of elements, etc.) */
90 vector
<Rational
> breakable_moments_
;
91 vector
<Grob
*> breakable_columns_
;
92 vector
<bool> special_barlines_
;
94 SCM
max_permission (SCM perm1
, SCM perm2
);
95 Real
penalty (Rational rest_len
);
96 Grob
*breakable_column (Page_turn_event
const &);
99 DECLARE_TRANSLATOR_LISTENER (break);
100 DECLARE_ACKNOWLEDGER (note_head
);
103 TRANSLATOR_DECLARATIONS (Page_turn_engraver
);
104 void stop_translation_timestep ();
105 void start_translation_timestep ();
109 Page_turn_engraver::Page_turn_engraver ()
111 repeat_begin_
= Moment (-1);
112 repeat_begin_rest_length_
= 0;
118 Page_turn_engraver::breakable_column (Page_turn_event
const &brk
)
120 vsize start
= lower_bound (breakable_moments_
, brk
.duration_
[LEFT
], less
<Rational
> ());
121 vsize end
= upper_bound (breakable_moments_
, brk
.duration_
[RIGHT
], less
<Rational
> ());
123 if (start
== breakable_moments_
.size ())
129 for (vsize i
= end
+ 1; i
-- > start
;)
130 if (special_barlines_
[i
])
131 return breakable_columns_
[i
];
133 return breakable_columns_
[end
];
137 Page_turn_engraver::penalty (Rational rest_len
)
139 Rational min_turn
= robust_scm2moment (get_property ("minimumPageTurnLength"), Moment (1)).main_part_
;
141 return (rest_len
< min_turn
) ? infinity_f
: 0;
145 Page_turn_engraver::acknowledge_note_head (Grob_info gi
)
147 Stream_event
*cause
= gi
.event_cause ();
149 Duration
*dur_ptr
= cause
150 ? unsmob_duration (cause
->get_property ("duration"))
156 if (rest_begin_
< now_mom ())
158 Real pen
= penalty ((now_mom () - rest_begin_
).main_part_
);
160 automatic_breaks_
.push_back (Page_turn_event (rest_begin_
.main_part_
,
161 now_mom ().main_part_
,
162 ly_symbol2scm ("allow"), 0));
165 if (rest_begin_
<= repeat_begin_
)
166 repeat_begin_rest_length_
= (now_mom () - repeat_begin_
).main_part_
;
167 note_end_
= now_mom () + dur_ptr
->get_length ();
170 IMPLEMENT_TRANSLATOR_LISTENER (Page_turn_engraver
, break);
172 Page_turn_engraver::listen_break (Stream_event
*ev
)
174 string name
= ly_scm2string (scm_symbol_to_string (ev
->get_property ("class")));
176 if (name
== "page-turn-event")
178 SCM permission
= ev
->get_property ("break-permission");
179 Real penalty
= robust_scm2double (ev
->get_property ("break-penalty"), 0);
180 Rational now
= now_mom ().main_part_
;
182 forced_breaks_
.push_back (Page_turn_event (now
, now
, permission
, penalty
));
187 Page_turn_engraver::start_translation_timestep ()
189 /* What we want to do is to build a list of all the
190 breakable paper columns. In general, paper-columns won't be marked as
191 such until the Paper_column_engraver has done stop_translation_timestep.
193 Therefore, we just grab /all/ paper columns (in the
194 stop_translation_timestep, since they're not created here yet)
195 and remove the non-breakable ones at the beginning of the following
199 if (breakable_columns_
.size () && !Paper_column::is_breakable (breakable_columns_
.back ()))
201 breakable_columns_
.pop_back ();
202 breakable_moments_
.pop_back ();
203 special_barlines_
.pop_back ();
208 Page_turn_engraver::stop_translation_timestep ()
210 Grob
*pc
= unsmob_grob (get_property ("currentCommandColumn"));
214 breakable_columns_
.push_back (pc
);
215 breakable_moments_
.push_back (now_mom ().main_part_
);
217 SCM bar_scm
= get_property ("whichBar");
218 string bar
= robust_scm2string (bar_scm
, "");
220 special_barlines_
.push_back (bar
!= "" && bar
!= "|");
223 /* C&P from Repeat_acknowledge_engraver */
224 SCM cs
= get_property ("repeatCommands");
228 for (; scm_is_pair (cs
); cs
= scm_cdr (cs
))
230 SCM command
= scm_car (cs
);
231 if (command
== ly_symbol2scm ("start-repeat"))
233 else if (command
== ly_symbol2scm ("end-repeat"))
237 if (end
&& repeat_begin_
.main_part_
>= Moment (0))
239 Rational now
= now_mom ().main_part_
;
240 Real pen
= penalty ((now_mom () - rest_begin_
).main_part_
+ repeat_begin_rest_length_
);
241 Moment
*m
= unsmob_moment (get_property ("minimumRepeatLengthForPageTurn"));
242 if (m
&& *m
> (now_mom () - repeat_begin_
))
245 if (pen
== infinity_f
)
246 repeat_penalties_
.push_back (Page_turn_event (repeat_begin_
.main_part_
, now
, SCM_EOL
, -infinity_f
));
248 repeat_penalties_
.push_back (Page_turn_event (repeat_begin_
.main_part_
, now
, ly_symbol2scm ("allow"), pen
));
250 repeat_begin_
= Moment (-1);
255 repeat_begin_
= now_mom ();
256 repeat_begin_rest_length_
= 0;
258 rest_begin_
= note_end_
;
261 /* return the most permissive symbol (where force is the most permissive and
265 Page_turn_engraver::max_permission (SCM perm1
, SCM perm2
)
267 if (perm1
== SCM_EOL
)
269 if (perm1
== ly_symbol2scm ("allow") && perm2
== ly_symbol2scm ("force"))
275 Page_turn_engraver::finalize ()
278 vector
<Page_turn_event
> auto_breaks
;
280 /* filter the automatic breaks through the repeat penalties */
281 for (vsize i
= 0; i
< automatic_breaks_
.size (); i
++)
283 Page_turn_event
&brk
= automatic_breaks_
[i
];
285 /* find the next applicable repeat penalty */
287 rep_index
< repeat_penalties_
.size ()
288 && repeat_penalties_
[rep_index
].duration_
[RIGHT
] <= brk
.duration_
[LEFT
];
292 if (rep_index
>= repeat_penalties_
.size ()
293 || brk
.duration_
[RIGHT
] <= repeat_penalties_
[rep_index
].duration_
[LEFT
])
294 auto_breaks
.push_back (brk
);
297 vector
<Page_turn_event
> split
= brk
.penalize (repeat_penalties_
[rep_index
]);
299 /* it's possible that the last of my newly-split events overlaps the next repeat_penalty,
300 in which case we need to refilter that event */
301 if (rep_index
+ 1 < repeat_penalties_
.size ()
303 && split
.back ().duration_
[RIGHT
] > repeat_penalties_
[rep_index
+1].duration_
[LEFT
])
305 automatic_breaks_
[i
] = split
.back ();
309 auto_breaks
.insert (auto_breaks
.end (), split
.begin (), split
.end ());
313 /* apply the automatic breaks */
314 for (vsize i
= 0; i
< auto_breaks
.size (); i
++)
316 Page_turn_event
const &brk
= auto_breaks
[i
];
317 Grob
*pc
= breakable_column (auto_breaks
[i
]);
320 SCM perm
= max_permission (pc
->get_property ("page-turn-permission"), brk
.permission_
);
321 Real pen
= min (robust_scm2double (pc
->get_property ("page-turn-penalty"), infinity_f
), brk
.penalty_
);
322 pc
->set_property ("page-turn-permission", perm
);
323 pc
->set_property ("page-turn-penalty", scm_from_double (pen
));
327 /* unless a manual break overrides it, allow a page turn at the end of the piece */
328 breakable_columns_
.back ()->set_property ("page-turn-permission", ly_symbol2scm ("allow"));
330 /* apply the manual breaks */
331 for (vsize i
= 0; i
< forced_breaks_
.size (); i
++)
333 Page_turn_event
const &brk
= forced_breaks_
[i
];
334 Grob
*pc
= breakable_column (forced_breaks_
[i
]);
337 pc
->set_property ("page-turn-permission", brk
.permission_
);
338 pc
->set_property ("page-turn-penalty", scm_from_double (brk
.penalty_
));
343 ADD_ACKNOWLEDGER (Page_turn_engraver
, note_head
);
345 ADD_TRANSLATOR (Page_turn_engraver
,
347 "Decide where page turns are allowed to go.",
353 "minimumPageTurnLength "
354 "minimumRepeatLengthForPageTurn ",