Consider accidentals in optical spacing correction.
[lilypond.git] / lily / page-turn-engraver.cc
blob82cb4dd1a9a54bb71ec612ce0ce9014d4de1bf26
1 /*
2 page-turn-engraver.cc -- implement Page_turn_engraver
4 source file of the GNU LilyPond music typesetter
6 (c) 2006--2009 Joe Neeman <joeneeman@gmail.com>
7 */
9 #include "engraver.hh"
11 #include "context.hh"
12 #include "duration.hh"
13 #include "grob.hh"
14 #include "international.hh"
15 #include "paper-column.hh"
16 #include "stream-event.hh"
17 #include "warn.hh"
19 #include "translator.icc"
21 class Page_turn_event {
22 public:
23 SCM permission_;
24 Real penalty_;
25 Interval_t<Rational> duration_;
27 Page_turn_event (Rational start, Rational end, SCM perm, Real pen)
29 duration_[LEFT] = start;
30 duration_[RIGHT] = end;
31 permission_ = perm;
32 penalty_ = pen;
35 /* Suppose we have decided on a possible page turn, only to change
36 out mind later (for example, if there is a volta repeat and it
37 would be difficult to turn the page back). Then we need to
38 re-penalize a region of the piece. Depending on how the events
39 intersect, we may have to split it into as many as 3 pieces.
41 vector<Page_turn_event> penalize (Page_turn_event const &penalty)
43 Interval_t<Rational> intersect = intersection (duration_, penalty.duration_);
44 vector<Page_turn_event> ret;
46 if (intersect.is_empty ())
48 ret.push_back (*this);
49 return ret;
52 Real new_pen = max (penalty_, penalty.penalty_);
54 if (duration_[LEFT] < penalty.duration_[LEFT])
55 ret.push_back (Page_turn_event (duration_[LEFT], penalty.duration_[LEFT], permission_, penalty_));
57 if (penalty.permission_ != SCM_EOL)
58 ret.push_back (Page_turn_event (intersect[LEFT], intersect[RIGHT], permission_, new_pen));
60 if (penalty.duration_[RIGHT] < duration_[RIGHT])
61 ret.push_back (Page_turn_event (penalty.duration_[RIGHT], duration_[RIGHT], permission_, penalty_));
63 return ret;
67 class Page_turn_engraver : public Engraver
69 Moment rest_begin_;
70 Moment repeat_begin_;
71 Moment note_end_;
72 Rational repeat_begin_rest_length_;
74 vector<Page_turn_event> forced_breaks_;
75 vector<Page_turn_event> automatic_breaks_;
76 vector<Page_turn_event> repeat_penalties_;
78 /* the next 3 are in sync (ie. same number of elements, etc.) */
79 vector<Rational> breakable_moments_;
80 vector<Grob*> breakable_columns_;
81 vector<bool> special_barlines_;
83 SCM max_permission (SCM perm1, SCM perm2);
84 Real penalty (Rational rest_len);
85 Grob *breakable_column (Page_turn_event const &);
87 protected:
88 DECLARE_TRANSLATOR_LISTENER (break);
89 DECLARE_ACKNOWLEDGER (note_head);
91 public:
92 TRANSLATOR_DECLARATIONS (Page_turn_engraver);
93 void stop_translation_timestep ();
94 void start_translation_timestep ();
95 void finalize ();
98 Page_turn_engraver::Page_turn_engraver ()
100 repeat_begin_ = Moment (-1);
101 repeat_begin_rest_length_ = 0;
102 rest_begin_ = 0;
103 note_end_ = 0;
106 Grob*
107 Page_turn_engraver::breakable_column (Page_turn_event const &brk)
109 vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], less<Rational> ());
110 vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], less<Rational> ());
112 if (start == breakable_moments_.size ())
113 return NULL;
114 if (end == 0)
115 return NULL;
116 end--;
118 for (vsize i = end + 1; i-- > start;)
119 if (special_barlines_[i])
120 return breakable_columns_[i];
122 return breakable_columns_[end];
125 Real
126 Page_turn_engraver::penalty (Rational rest_len)
128 Rational min_turn = robust_scm2moment (get_property ("minimumPageTurnLength"), Moment (1)).main_part_;
130 return (rest_len < min_turn) ? infinity_f : 0;
133 void
134 Page_turn_engraver::acknowledge_note_head (Grob_info gi)
136 Stream_event *cause = gi.event_cause ();
138 Duration *dur_ptr = cause
139 ? unsmob_duration (cause->get_property ("duration"))
140 : 0;
142 if (!dur_ptr)
143 return;
145 if (rest_begin_ < now_mom ())
147 Real pen = penalty ((now_mom () - rest_begin_).main_part_);
148 if (!isinf (pen))
149 automatic_breaks_.push_back (Page_turn_event (rest_begin_.main_part_,
150 now_mom ().main_part_,
151 ly_symbol2scm ("allow"), 0));
154 if (rest_begin_ <= repeat_begin_)
155 repeat_begin_rest_length_ = (now_mom () - repeat_begin_).main_part_;
156 note_end_ = now_mom () + dur_ptr->get_length ();
159 IMPLEMENT_TRANSLATOR_LISTENER (Page_turn_engraver, break);
160 void
161 Page_turn_engraver::listen_break (Stream_event *ev)
163 string name = ly_scm2string (scm_symbol_to_string (ev->get_property ("class")));
165 if (name == "page-turn-event")
167 SCM permission = ev->get_property ("break-permission");
168 Real penalty = robust_scm2double (ev->get_property ("break-penalty"), 0);
169 Rational now = now_mom ().main_part_;
171 forced_breaks_.push_back (Page_turn_event (now, now, permission, penalty));
175 void
176 Page_turn_engraver::start_translation_timestep ()
178 /* What we want to do is to build a list of all the
179 breakable paper columns. In general, paper-columns won't be marked as
180 such until the Paper_column_engraver has done stop_translation_timestep.
182 Therefore, we just grab /all/ paper columns (in the
183 stop_translation_timestep, since they're not created here yet)
184 and remove the non-breakable ones at the beginning of the following
185 timestep.
188 if (breakable_columns_.size () && !Paper_column::is_breakable (breakable_columns_.back ()))
190 breakable_columns_.pop_back ();
191 breakable_moments_.pop_back ();
192 special_barlines_.pop_back ();
196 void
197 Page_turn_engraver::stop_translation_timestep ()
199 Grob *pc = unsmob_grob (get_property ("currentCommandColumn"));
201 if (pc)
203 breakable_columns_.push_back (pc);
204 breakable_moments_.push_back (now_mom ().main_part_);
206 SCM bar_scm = get_property ("whichBar");
207 string bar = robust_scm2string (bar_scm, "");
209 special_barlines_.push_back (bar != "" && bar != "|");
212 /* C&P from Repeat_acknowledge_engraver */
213 SCM cs = get_property ("repeatCommands");
214 bool start = false;
215 bool end = false;
217 for (; scm_is_pair (cs); cs = scm_cdr (cs))
219 SCM command = scm_car (cs);
220 if (command == ly_symbol2scm ("start-repeat"))
221 start = true;
222 else if (command == ly_symbol2scm ("end-repeat"))
223 end = true;
226 if (end && repeat_begin_.main_part_ >= Moment (0))
228 Rational now = now_mom ().main_part_;
229 Real pen = penalty ((now_mom () - rest_begin_).main_part_ + repeat_begin_rest_length_);
230 Moment *m = unsmob_moment (get_property ("minimumRepeatLengthForPageTurn"));
231 if (m && *m > (now_mom () - repeat_begin_))
232 pen = infinity_f;
234 if (pen == infinity_f)
235 repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, SCM_EOL, -infinity_f));
236 else
237 repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, ly_symbol2scm ("allow"), pen));
239 repeat_begin_ = Moment (-1);
242 if (start)
244 repeat_begin_ = now_mom ();
245 repeat_begin_rest_length_ = 0;
247 rest_begin_ = note_end_;
250 /* return the most permissive symbol (where force is the most permissive and
251 forbid is the least
254 Page_turn_engraver::max_permission (SCM perm1, SCM perm2)
256 if (perm1 == SCM_EOL)
257 return perm2;
258 if (perm1 == ly_symbol2scm ("allow") && perm2 == ly_symbol2scm ("force"))
259 return perm2;
260 return perm1;
263 void
264 Page_turn_engraver::finalize ()
266 vsize rep_index = 0;
267 vector<Page_turn_event> auto_breaks;
269 /* filter the automatic breaks through the repeat penalties */
270 for (vsize i = 0; i < automatic_breaks_.size (); i++)
272 Page_turn_event &brk = automatic_breaks_[i];
274 /* find the next applicable repeat penalty */
275 for (;
276 rep_index < repeat_penalties_.size ()
277 && repeat_penalties_[rep_index].duration_[RIGHT] <= brk.duration_[LEFT];
278 rep_index++)
281 if (rep_index >= repeat_penalties_.size ()
282 || brk.duration_[RIGHT] <= repeat_penalties_[rep_index].duration_[LEFT])
283 auto_breaks.push_back (brk);
284 else
286 vector<Page_turn_event> split = brk.penalize (repeat_penalties_[rep_index]);
288 /* it's possible that the last of my newly-split events overlaps the next repeat_penalty,
289 in which case we need to refilter that event */
290 if (rep_index + 1 < repeat_penalties_.size ()
291 && split.size ()
292 && split.back ().duration_[RIGHT] > repeat_penalties_[rep_index+1].duration_[LEFT])
294 automatic_breaks_[i] = split.back ();
295 split.pop_back ();
296 i--;
298 auto_breaks.insert (auto_breaks.end (), split.begin (), split.end ());
302 /* apply the automatic breaks */
303 for (vsize i = 0; i < auto_breaks.size (); i++)
305 Page_turn_event const &brk = auto_breaks[i];
306 Grob *pc = breakable_column (auto_breaks[i]);
307 if (pc)
309 SCM perm = max_permission (pc->get_property ("page-turn-permission"), brk.permission_);
310 Real pen = min (robust_scm2double (pc->get_property ("page-turn-penalty"), infinity_f), brk.penalty_);
311 pc->set_property ("page-turn-permission", perm);
312 pc->set_property ("page-turn-penalty", scm_from_double (pen));
316 /* unless a manual break overrides it, allow a page turn at the end of the piece */
317 breakable_columns_.back ()->set_property ("page-turn-permission", ly_symbol2scm ("allow"));
319 /* apply the manual breaks */
320 for (vsize i = 0; i < forced_breaks_.size (); i++)
322 Page_turn_event const &brk = forced_breaks_[i];
323 Grob *pc = breakable_column (forced_breaks_[i]);
324 if (pc)
326 pc->set_property ("page-turn-permission", brk.permission_);
327 pc->set_property ("page-turn-penalty", scm_from_double (brk.penalty_));
332 ADD_ACKNOWLEDGER (Page_turn_engraver, note_head);
334 ADD_TRANSLATOR (Page_turn_engraver,
335 /* doc */
336 "Decide where page turns are allowed to go.",
338 /* create */
341 /* read */
342 "minimumPageTurnLength "
343 "minimumRepeatLengthForPageTurn ",
345 /* write */