]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-turn-engraver.cc
Doc-de: Update to the Learning manual
[lilypond.git] / lily / page-turn-engraver.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2006--2009 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 public:
34   SCM permission_;
35   Real penalty_;
36   Interval_t<Rational> duration_;
37
38   Page_turn_event (Rational start, Rational end, SCM perm, Real pen)
39   {
40     duration_[LEFT] = start;
41     duration_[RIGHT] = end;
42     permission_ = perm;
43     penalty_ = pen;
44   }
45
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.
51   */
52   vector<Page_turn_event> penalize (Page_turn_event const &penalty)
53   {
54     Interval_t<Rational> intersect = intersection (duration_, penalty.duration_);
55     vector<Page_turn_event> ret;
56
57     if (intersect.is_empty ())
58       {
59         ret.push_back (*this);
60         return ret;
61       }
62
63     Real new_pen = max (penalty_, penalty.penalty_);
64
65     if (duration_[LEFT] < penalty.duration_[LEFT])
66       ret.push_back (Page_turn_event (duration_[LEFT], penalty.duration_[LEFT], permission_, penalty_));
67
68     if (penalty.permission_ != SCM_EOL)
69       ret.push_back (Page_turn_event (intersect[LEFT], intersect[RIGHT], permission_, new_pen));
70
71     if (penalty.duration_[RIGHT] < duration_[RIGHT])
72       ret.push_back (Page_turn_event (penalty.duration_[RIGHT], duration_[RIGHT], permission_, penalty_));
73
74     return ret;
75   }
76 };
77
78 class Page_turn_engraver : public Engraver
79 {
80   Moment rest_begin_;
81   Moment repeat_begin_;
82   Moment note_end_;
83   Rational repeat_begin_rest_length_;
84
85   vector<Page_turn_event> forced_breaks_;
86   vector<Page_turn_event> automatic_breaks_;
87   vector<Page_turn_event> repeat_penalties_;
88
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_;
93
94   SCM max_permission (SCM perm1, SCM perm2);
95   Real penalty (Rational rest_len);
96   Grob *breakable_column (Page_turn_event const &);
97
98 protected:
99   DECLARE_TRANSLATOR_LISTENER (break);
100   DECLARE_ACKNOWLEDGER (note_head);
101
102 public:
103   TRANSLATOR_DECLARATIONS (Page_turn_engraver);
104   void stop_translation_timestep ();
105   void start_translation_timestep ();
106   void finalize ();
107 };
108
109 Page_turn_engraver::Page_turn_engraver ()
110 {
111   repeat_begin_ = Moment (-1);
112   repeat_begin_rest_length_ = 0;
113   rest_begin_ = 0;
114   note_end_ = 0;
115 }
116
117 Grob*
118 Page_turn_engraver::breakable_column (Page_turn_event const &brk)
119 {
120   vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], less<Rational> ());
121   vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], less<Rational> ());
122
123   if (start == breakable_moments_.size ())
124     return NULL;
125   if (end == 0)
126     return NULL;
127   end--;
128
129   for (vsize i = end + 1; i-- > start;)
130     if (special_barlines_[i])
131       return breakable_columns_[i];
132
133   return breakable_columns_[end];
134 }
135
136 Real
137 Page_turn_engraver::penalty (Rational rest_len)
138 {
139   Rational min_turn = robust_scm2moment (get_property ("minimumPageTurnLength"), Moment (1)).main_part_;
140
141   return (rest_len < min_turn) ? infinity_f : 0;
142 }
143
144 void
145 Page_turn_engraver::acknowledge_note_head (Grob_info gi)
146 {
147   Stream_event *cause = gi.event_cause ();
148
149   Duration *dur_ptr = cause
150     ? unsmob_duration (cause->get_property ("duration"))
151     : 0;
152   
153   if (!dur_ptr)
154     return;
155
156   if (rest_begin_ < now_mom ())
157     {
158       Real pen = penalty ((now_mom () - rest_begin_).main_part_);
159       if (!isinf (pen))
160           automatic_breaks_.push_back (Page_turn_event (rest_begin_.main_part_,
161                                                         now_mom ().main_part_,
162                                                         ly_symbol2scm ("allow"), 0));
163     }
164
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 ();
168 }
169
170 IMPLEMENT_TRANSLATOR_LISTENER (Page_turn_engraver, break);
171 void
172 Page_turn_engraver::listen_break (Stream_event *ev)
173 {
174   string name = ly_scm2string (scm_symbol_to_string (ev->get_property ("class")));
175
176   if (name == "page-turn-event")
177     {
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_;
181
182       forced_breaks_.push_back (Page_turn_event (now, now, permission, penalty));
183     }
184 }
185
186 void
187 Page_turn_engraver::start_translation_timestep ()
188 {
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.
192
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
196      timestep.
197   */
198
199   if (breakable_columns_.size () && !Paper_column::is_breakable (breakable_columns_.back ()))
200     {
201       breakable_columns_.pop_back ();
202       breakable_moments_.pop_back ();
203       special_barlines_.pop_back ();
204     }
205 }
206
207 void
208 Page_turn_engraver::stop_translation_timestep ()
209 {
210   Grob *pc = unsmob_grob (get_property ("currentCommandColumn"));
211
212   if (pc)
213     {
214       breakable_columns_.push_back (pc);
215       breakable_moments_.push_back (now_mom ().main_part_);
216
217       SCM bar_scm = get_property ("whichBar");
218       string bar = robust_scm2string (bar_scm, "");
219
220       special_barlines_.push_back (bar != "" && bar != "|");
221     }
222
223   /* C&P from Repeat_acknowledge_engraver */
224   SCM cs = get_property ("repeatCommands");
225   bool start = false;
226   bool end = false;
227
228   for (; scm_is_pair (cs); cs = scm_cdr (cs))
229     {
230       SCM command = scm_car (cs);
231       if (command == ly_symbol2scm ("start-repeat"))
232         start = true;
233       else if (command == ly_symbol2scm ("end-repeat"))
234         end = true;
235     }
236
237   if (end && repeat_begin_.main_part_ >= Moment (0))
238     {
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_))
243         pen = infinity_f;
244
245       if (pen == infinity_f)
246         repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, SCM_EOL, -infinity_f));
247       else
248         repeat_penalties_.push_back (Page_turn_event (repeat_begin_.main_part_, now, ly_symbol2scm ("allow"), pen));
249
250       repeat_begin_ = Moment (-1);
251     }
252
253   if (start)
254     {
255       repeat_begin_ = now_mom ();
256       repeat_begin_rest_length_ = 0;
257     }
258   rest_begin_ = note_end_;
259 }
260
261 /* return the most permissive symbol (where force is the most permissive and
262    forbid is the least
263 */
264 SCM
265 Page_turn_engraver::max_permission (SCM perm1, SCM perm2)
266 {
267   if (perm1 == SCM_EOL)
268     return perm2;
269   if (perm1 == ly_symbol2scm ("allow") && perm2 == ly_symbol2scm ("force"))
270     return perm2;
271   return perm1;
272 }
273
274 void
275 Page_turn_engraver::finalize ()
276 {
277   vsize rep_index = 0;
278   vector<Page_turn_event> auto_breaks;
279
280   /* filter the automatic breaks through the repeat penalties */
281   for (vsize i = 0; i < automatic_breaks_.size (); i++)
282     {
283       Page_turn_event &brk = automatic_breaks_[i];
284
285       /* find the next applicable repeat penalty */
286       for (;
287            rep_index < repeat_penalties_.size ()
288              && repeat_penalties_[rep_index].duration_[RIGHT] <= brk.duration_[LEFT];
289            rep_index++)
290         ;
291
292       if (rep_index >= repeat_penalties_.size ()
293           || brk.duration_[RIGHT] <= repeat_penalties_[rep_index].duration_[LEFT])
294         auto_breaks.push_back (brk);
295       else
296         {
297           vector<Page_turn_event> split = brk.penalize (repeat_penalties_[rep_index]);
298
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 ()
302               && split.size ()
303               && split.back ().duration_[RIGHT] > repeat_penalties_[rep_index+1].duration_[LEFT])
304             {
305               automatic_breaks_[i] = split.back ();
306               split.pop_back ();
307               i--;
308             }
309           auto_breaks.insert (auto_breaks.end (), split.begin (), split.end ());
310         }
311     }
312
313   /* apply the automatic breaks */
314   for (vsize i = 0; i < auto_breaks.size (); i++)
315     {
316       Page_turn_event const &brk = auto_breaks[i];
317       Grob *pc = breakable_column (auto_breaks[i]);
318       if (pc)
319         {
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));
324         }
325     }
326
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"));
329
330   /* apply the manual breaks */
331   for (vsize i = 0; i < forced_breaks_.size (); i++)
332     {
333       Page_turn_event const &brk = forced_breaks_[i];
334       Grob *pc = breakable_column (forced_breaks_[i]);
335       if (pc)
336         {
337           pc->set_property ("page-turn-permission", brk.permission_);
338           pc->set_property ("page-turn-penalty", scm_from_double (brk.penalty_));
339         }
340     }
341 }
342
343 ADD_ACKNOWLEDGER (Page_turn_engraver, note_head);
344
345 ADD_TRANSLATOR (Page_turn_engraver,
346                 /* doc */
347                 "Decide where page turns are allowed to go.",
348
349                 /* create */
350                 "",
351
352                 /* read */
353                 "minimumPageTurnLength "
354                 "minimumRepeatLengthForPageTurn ",
355
356                 /* write */
357                 ""
358                 );