/* This file is part of LilyPond, the GNU music typesetter. Copyright (C) 2006--2020 Joe Neeman LilyPond is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. LilyPond is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with LilyPond. If not, see . */ #include "engraver.hh" #include "context.hh" #include "duration.hh" #include "grob.hh" #include "international.hh" #include "paper-column.hh" #include "stream-event.hh" #include "warn.hh" #include "translator.icc" using std::string; using std::vector; class Page_turn_event { public: SCM permission_; Real penalty_; Interval_t duration_; Page_turn_event (Rational start, Rational end, SCM perm, Real pen) { duration_[LEFT] = start; duration_[RIGHT] = end; permission_ = perm; penalty_ = pen; } /* Suppose we have decided on a possible page turn, only to change out mind later (for example, if there is a volta repeat and it would be difficult to turn the page back). Then we need to re-penalize a region of the piece. Depending on how the events intersect, we may have to split it into as many as 3 pieces. */ vector penalize (Page_turn_event const &penalty) { Interval_t intersect = intersection (duration_, penalty.duration_); vector ret; if (intersect.is_empty ()) { ret.push_back (*this); return ret; } Real new_pen = std::max (penalty_, penalty.penalty_); if (duration_[LEFT] < penalty.duration_[LEFT]) ret.push_back (Page_turn_event (duration_[LEFT], penalty.duration_[LEFT], permission_, penalty_)); if (!scm_is_null (penalty.permission_)) ret.push_back (Page_turn_event (intersect[LEFT], intersect[RIGHT], permission_, new_pen)); if (penalty.duration_[RIGHT] < duration_[RIGHT]) ret.push_back (Page_turn_event (penalty.duration_[RIGHT], duration_[RIGHT], permission_, penalty_)); return ret; } }; class Page_turn_engraver : public Engraver { Moment rest_begin_; Moment repeat_begin_; Moment note_end_; Rational repeat_begin_rest_length_; vector forced_breaks_; vector automatic_breaks_; vector repeat_penalties_; /* the next 3 are in sync (ie. same number of elements, etc.) */ vector breakable_moments_; vector breakable_columns_; vector special_barlines_; SCM max_permission (SCM perm1, SCM perm2); Real penalty (Rational rest_len); Grob *breakable_column (Page_turn_event const &); protected: void listen_break (Stream_event *); void acknowledge_note_head (Grob_info); public: TRANSLATOR_DECLARATIONS (Page_turn_engraver); void stop_translation_timestep (); void start_translation_timestep (); void finalize () override; }; Page_turn_engraver::Page_turn_engraver (Context *c) : Engraver (c) { repeat_begin_ = Moment (-1); repeat_begin_rest_length_ = 0; rest_begin_ = 0; note_end_ = 0; } Grob * Page_turn_engraver::breakable_column (Page_turn_event const &brk) { vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], std::less ()); vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], std::less ()); if (start == breakable_moments_.size ()) return NULL; if (end == 0) return NULL; end--; for (vsize i = end + 1; i-- > start;) if (special_barlines_[i]) return breakable_columns_[i]; return breakable_columns_[end]; } Real Page_turn_engraver::penalty (Rational rest_len) { Rational min_turn = robust_scm2moment (get_property (this, "minimumPageTurnLength"), Moment (1)).main_part_; return (rest_len < min_turn) ? infinity_f : 0; } void Page_turn_engraver::acknowledge_note_head (Grob_info gi) { Stream_event *cause = gi.event_cause (); Duration *dur_ptr = cause ? unsmob (get_property (cause, "duration")) : 0; if (!dur_ptr) return; if (rest_begin_ < now_mom ()) { Real pen = penalty ((now_mom () - rest_begin_).main_part_); if (!std::isinf (pen)) automatic_breaks_.push_back (Page_turn_event (rest_begin_.main_part_, now_mom ().main_part_, ly_symbol2scm ("allow"), 0)); } if (rest_begin_ <= repeat_begin_) repeat_begin_rest_length_ = (now_mom () - repeat_begin_).main_part_; note_end_ = now_mom () + dur_ptr->get_length (); } void Page_turn_engraver::listen_break (Stream_event *ev) { string name = ly_symbol2string (scm_car (get_property (ev, "class"))); if (name == "page-turn-event") { SCM permission = get_property (ev, "break-permission"); Real penalty = from_scm (get_property (ev, "break-penalty"), 0); Rational now = now_mom ().main_part_; forced_breaks_.push_back (Page_turn_event (now, now, permission, penalty)); } } void Page_turn_engraver::start_translation_timestep () { /* What we want to do is to build a list of all the breakable paper columns. In general, paper-columns won't be marked as such until the Paper_column_engraver has done stop_translation_timestep. Therefore, we just grab /all/ paper columns (in the stop_translation_timestep, since they're not created here yet) and remove the non-breakable ones at the beginning of the following timestep. */ if (breakable_columns_.size () && !Paper_column::is_breakable (breakable_columns_.back ())) { breakable_columns_.pop_back (); breakable_moments_.pop_back (); special_barlines_.pop_back (); } } void Page_turn_engraver::stop_translation_timestep () { Grob *pc = unsmob (get_property (this, "currentCommandColumn")); if (pc) { breakable_columns_.push_back (pc); breakable_moments_.push_back (now_mom ().main_part_); SCM bar_scm = get_property (this, "whichBar"); string bar = robust_scm2string (bar_scm, ""); special_barlines_.push_back (bar != "" && bar != "|"); } /* C&P from Repeat_acknowledge_engraver */ SCM cs = get_property (this, "repeatCommands"); bool start = false; bool end = false; for (; scm_is_pair (cs); cs = scm_cdr (cs)) { SCM command = scm_car (cs); if (scm_is_eq (command, ly_symbol2scm ("start-repeat"))) start = true; else if (scm_is_eq (command, ly_symbol2scm ("end-repeat"))) end = true; } if (end && (repeat_begin_.main_part_ >= 0)) { Rational now = now_mom ().main_part_; Real pen = penalty ((now_mom () - rest_begin_).main_part_ + repeat_begin_rest_length_); Moment *m = unsmob (get_property (this, "minimumRepeatLengthForPageTurn")); if (m && *m > (now_mom () - repeat_begin_)) pen = infinity_f; if (pen == infinity_f) repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, SCM_EOL, -infinity_f)); else repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, ly_symbol2scm ("allow"), pen)); repeat_begin_ = Moment (-1); } if (start) { repeat_begin_ = now_mom (); repeat_begin_rest_length_ = 0; } rest_begin_ = note_end_; } /* return the most permissive symbol (where force is the most permissive and forbid is the least */ SCM Page_turn_engraver::max_permission (SCM perm1, SCM perm2) { if (scm_is_null (perm1)) return perm2; if (scm_is_eq (perm1, ly_symbol2scm ("allow")) && scm_is_eq (perm2, ly_symbol2scm ("force"))) return perm2; return perm1; } void Page_turn_engraver::finalize () { vsize rep_index = 0; vector auto_breaks; /* filter the automatic breaks through the repeat penalties */ for (vsize i = 0; i < automatic_breaks_.size (); i++) { Page_turn_event &brk = automatic_breaks_[i]; /* find the next applicable repeat penalty */ for (; rep_index < repeat_penalties_.size () && repeat_penalties_[rep_index].duration_[RIGHT] <= brk.duration_[LEFT]; rep_index++) ; if (rep_index >= repeat_penalties_.size () || brk.duration_[RIGHT] <= repeat_penalties_[rep_index].duration_[LEFT]) auto_breaks.push_back (brk); else { vector split = brk.penalize (repeat_penalties_[rep_index]); /* it's possible that the last of my newly-split events overlaps the next repeat_penalty, in which case we need to refilter that event */ if (rep_index + 1 < repeat_penalties_.size () && split.size () && split.back ().duration_[RIGHT] > repeat_penalties_[rep_index + 1].duration_[LEFT]) { automatic_breaks_[i] = split.back (); split.pop_back (); i--; } auto_breaks.insert (auto_breaks.end (), split.begin (), split.end ()); } } /* apply the automatic breaks */ for (vsize i = 0; i < auto_breaks.size (); i++) { Page_turn_event const &brk = auto_breaks[i]; Grob *pc = breakable_column (auto_breaks[i]); if (pc) { SCM perm = max_permission (get_property (pc, "page-turn-permission"), brk.permission_); Real pen = std::min (from_scm (get_property (pc, "page-turn-penalty"), infinity_f), brk.penalty_); set_property (pc, "page-turn-permission", perm); set_property (pc, "page-turn-penalty", to_scm (pen)); } } /* unless a manual break overrides it, allow a page turn at the end of the piece */ set_property (breakable_columns_.back (), "page-turn-permission", ly_symbol2scm ("allow")); /* apply the manual breaks */ for (vsize i = 0; i < forced_breaks_.size (); i++) { Page_turn_event const &brk = forced_breaks_[i]; Grob *pc = breakable_column (forced_breaks_[i]); if (pc) { set_property (pc, "page-turn-permission", brk.permission_); set_property (pc, "page-turn-penalty", to_scm (brk.penalty_)); } } } void Page_turn_engraver::boot () { ADD_LISTENER (Page_turn_engraver, break); ADD_ACKNOWLEDGER (Page_turn_engraver, note_head); } ADD_TRANSLATOR (Page_turn_engraver, /* doc */ "Decide where page turns are allowed to go.", /* create */ "", /* read */ "minimumPageTurnLength " "minimumRepeatLengthForPageTurn ", /* write */ "" );