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