]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-turn-engraver.cc
Issue 4550 (2/2) Avoid "using namespace std;" in included files
[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 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
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   */
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   DECLARE_TRANSLATOR_LISTENER (break);
104   DECLARE_ACKNOWLEDGER (note_head);
105
106 public:
107   TRANSLATOR_DECLARATIONS (Page_turn_engraver);
108   void stop_translation_timestep ();
109   void start_translation_timestep ();
110   void finalize ();
111 };
112
113 Page_turn_engraver::Page_turn_engraver ()
114 {
115   repeat_begin_ = Moment (-1);
116   repeat_begin_rest_length_ = 0;
117   rest_begin_ = 0;
118   note_end_ = 0;
119 }
120
121 Grob *
122 Page_turn_engraver::breakable_column (Page_turn_event const &brk)
123 {
124   vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], std::less<Rational> ());
125   vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], std::less<Rational> ());
126
127   if (start == breakable_moments_.size ())
128     return NULL;
129   if (end == 0)
130     return NULL;
131   end--;
132
133   for (vsize i = end + 1; i-- > start;)
134     if (special_barlines_[i])
135       return breakable_columns_[i];
136
137   return breakable_columns_[end];
138 }
139
140 Real
141 Page_turn_engraver::penalty (Rational rest_len)
142 {
143   Rational min_turn = robust_scm2moment (get_property ("minimumPageTurnLength"), Moment (1)).main_part_;
144
145   return (rest_len < min_turn) ? infinity_f : 0;
146 }
147
148 void
149 Page_turn_engraver::acknowledge_note_head (Grob_info gi)
150 {
151   Stream_event *cause = gi.event_cause ();
152
153   Duration *dur_ptr = cause
154                       ? unsmob<Duration> (cause->get_property ("duration"))
155                       : 0;
156
157   if (!dur_ptr)
158     return;
159
160   if (rest_begin_ < now_mom ())
161     {
162       Real pen = penalty ((now_mom () - rest_begin_).main_part_);
163       if (!isinf (pen))
164         automatic_breaks_.push_back (Page_turn_event (rest_begin_.main_part_,
165                                                       now_mom ().main_part_,
166                                                       ly_symbol2scm ("allow"), 0));
167     }
168
169   if (rest_begin_ <= repeat_begin_)
170     repeat_begin_rest_length_ = (now_mom () - repeat_begin_).main_part_;
171   note_end_ = now_mom () + dur_ptr->get_length ();
172 }
173
174 IMPLEMENT_TRANSLATOR_LISTENER (Page_turn_engraver, break);
175 void
176 Page_turn_engraver::listen_break (Stream_event *ev)
177 {
178   string name = ly_symbol2string (scm_car (ev->get_property ("class")));
179
180   if (name == "page-turn-event")
181     {
182       SCM permission = ev->get_property ("break-permission");
183       Real penalty = robust_scm2double (ev->get_property ("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
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
212 Page_turn_engraver::stop_translation_timestep ()
213 {
214   Grob *pc = unsmob<Grob> (get_property ("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 ("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 ("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_ >= Moment (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 ("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
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
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 (pc->get_property ("page-turn-permission"), brk.permission_);
326           Real pen = std::min (robust_scm2double (pc->get_property ("page-turn-penalty"), infinity_f), brk.penalty_);
327           pc->set_property ("page-turn-permission", perm);
328           pc->set_property ("page-turn-penalty", scm_from_double (pen));
329         }
330     }
331
332   /* unless a manual break overrides it, allow a page turn at the end of the piece */
333   breakable_columns_.back ()->set_property ("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           pc->set_property ("page-turn-permission", brk.permission_);
343           pc->set_property ("page-turn-penalty", scm_from_double (brk.penalty_));
344         }
345     }
346 }
347
348 ADD_ACKNOWLEDGER (Page_turn_engraver, note_head);
349
350 ADD_TRANSLATOR (Page_turn_engraver,
351                 /* doc */
352                 "Decide where page turns are allowed to go.",
353
354                 /* create */
355                 "",
356
357                 /* read */
358                 "minimumPageTurnLength "
359                 "minimumRepeatLengthForPageTurn ",
360
361                 /* write */
362                 ""
363                );