]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-turn-engraver.cc
02a437d863a60a366b9e2ce0875f31fd30878d12
[lilypond.git] / lily / page-turn-engraver.cc
1 /*
2   page-turn-engraver.cc -- implement Page_turn_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2006--2007 Joe Neeman <joeneeman@gmail.com>
7 */
8
9 #include "engraver.hh"
10
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"
18
19 #include "translator.icc"
20
21 class Page_turn_event {
22 public:
23   SCM permission_;
24   Real penalty_;
25   Interval_t<Rational> duration_;
26
27   Page_turn_event (Rational start, Rational end, SCM perm, Real pen)
28   {
29     duration_[LEFT] = start;
30     duration_[RIGHT] = end;
31     permission_ = perm;
32     penalty_ = pen;
33   }
34
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.
40   */
41   vector<Page_turn_event> penalize (Page_turn_event const &penalty)
42   {
43     Interval_t<Rational> intersect = intersection (duration_, penalty.duration_);
44     vector<Page_turn_event> ret;
45
46     if (intersect.is_empty ())
47       {
48         ret.push_back (*this);
49         return ret;
50       }
51
52     Real new_pen = max (penalty_, penalty.penalty_);
53
54     if (duration_[LEFT] < penalty.duration_[LEFT])
55       ret.push_back (Page_turn_event (duration_[LEFT], penalty.duration_[LEFT], permission_, penalty_));
56
57     if (penalty.permission_ != SCM_EOL)
58       ret.push_back (Page_turn_event (intersect[LEFT], intersect[RIGHT], permission_, new_pen));
59
60     if (penalty.duration_[RIGHT] < duration_[RIGHT])
61       ret.push_back (Page_turn_event (penalty.duration_[RIGHT], duration_[RIGHT], permission_, penalty_));
62
63     return ret;
64   }
65 };
66
67 class Page_turn_engraver : public Engraver
68 {
69   Moment rest_begin_;
70   Moment repeat_begin_;
71   Moment note_end_;
72   Rational repeat_begin_rest_length_;
73
74   vector<Page_turn_event> forced_breaks_;
75   vector<Page_turn_event> automatic_breaks_;
76   vector<Page_turn_event> repeat_penalties_;
77
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_;
82
83   SCM max_permission (SCM perm1, SCM perm2);
84   Real penalty (Rational rest_len);
85   Grob *breakable_column (Page_turn_event const &);
86
87 protected:
88   DECLARE_TRANSLATOR_LISTENER (break);
89   DECLARE_ACKNOWLEDGER (note_head);
90
91 public:
92   TRANSLATOR_DECLARATIONS (Page_turn_engraver);
93   void stop_translation_timestep ();
94   void start_translation_timestep ();
95   void finalize ();
96 };
97
98 Page_turn_engraver::Page_turn_engraver ()
99 {
100   repeat_begin_ = Moment (-1);
101   repeat_begin_rest_length_ = 0;
102   rest_begin_ = 0;
103   note_end_ = 0;
104 }
105
106 Grob*
107 Page_turn_engraver::breakable_column (Page_turn_event const &brk)
108 {
109   vsize start = lower_bound (breakable_moments_, brk.duration_[LEFT], less<Rational> ());
110   vsize end = upper_bound (breakable_moments_, brk.duration_[RIGHT], less<Rational> ());
111
112   if (start == breakable_moments_.size ())
113     return NULL;
114   if (end == 0)
115     return NULL;
116   end--;
117
118   for (vsize i = end + 1; i-- > start;)
119     if (special_barlines_[i])
120       return breakable_columns_[i];
121
122   return breakable_columns_[end];
123 }
124
125 Real
126 Page_turn_engraver::penalty (Rational rest_len)
127 {
128   Rational min_turn = robust_scm2moment (get_property ("minimumPageTurnLength"), Moment (1)).main_part_;
129
130   return (rest_len < min_turn) ? infinity_f : 0;
131 }
132
133 void
134 Page_turn_engraver::acknowledge_note_head (Grob_info gi)
135 {
136   Stream_event *cause = gi.event_cause ();
137
138   Duration *dur_ptr = cause
139     ? unsmob_duration (cause->get_property ("duration"))
140     : 0;
141   
142   if (!dur_ptr)
143     return;
144
145   if (rest_begin_ < now_mom ())
146     {
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));
152     }
153
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 ();
157 }
158
159 IMPLEMENT_TRANSLATOR_LISTENER (Page_turn_engraver, break);
160 void
161 Page_turn_engraver::listen_break (Stream_event *ev)
162 {
163   string name = ly_scm2string (scm_symbol_to_string (ev->get_property ("class")));
164
165   if (name == "page-turn-event")
166     {
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_;
170
171       forced_breaks_.push_back (Page_turn_event (now, now, permission, penalty));
172     }
173 }
174
175 void
176 Page_turn_engraver::start_translation_timestep ()
177 {
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.
181
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.
186   */
187
188   if (breakable_columns_.size () && !Paper_column::is_breakable (breakable_columns_.back ()))
189     {
190       breakable_columns_.pop_back ();
191       breakable_moments_.pop_back ();
192       special_barlines_.pop_back ();
193     }
194 }
195
196 void
197 Page_turn_engraver::stop_translation_timestep ()
198 {
199   Grob *pc = unsmob_grob (get_property ("currentCommandColumn"));
200
201   if (pc)
202     {
203       breakable_columns_.push_back (pc);
204       breakable_moments_.push_back (now_mom ().main_part_);
205
206       SCM bar_scm = get_property ("whichBar");
207       string bar = robust_scm2string (bar_scm, "");
208
209       special_barlines_.push_back (bar != "" && bar != "|");
210     }
211
212   /* C&P from Repeat_acknowledge_engraver */
213   SCM cs = get_property ("repeatCommands");
214   bool start = false;
215   bool end = false;
216
217   for (; scm_is_pair (cs); cs = scm_cdr (cs))
218     {
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;
224     }
225
226   if (end && repeat_begin_.main_part_ >= Moment (0))
227     {
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;
233
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));
238
239       repeat_begin_ = Moment (-1);
240     }
241
242   if (start)
243     {
244       repeat_begin_ = now_mom ();
245       repeat_begin_rest_length_ = 0;
246     }
247   rest_begin_ = note_end_;
248 }
249
250 /* return the most permissive symbol (where force is the most permissive and
251    forbid is the least
252 */
253 SCM
254 Page_turn_engraver::max_permission (SCM perm1, SCM perm2)
255 {
256   if (perm1 == SCM_EOL)
257     return perm2;
258   if (perm1 == ly_symbol2scm ("allow") && perm2 == ly_symbol2scm ("force"))
259     return perm2;
260   return perm1;
261 }
262
263 void
264 Page_turn_engraver::finalize ()
265 {
266   vsize rep_index = 0;
267   vector<Page_turn_event> auto_breaks;
268
269   /* filter the automatic breaks through the repeat penalties */
270   for (vsize i = 0; i < automatic_breaks_.size (); i++)
271     {
272       Page_turn_event &brk = automatic_breaks_[i];
273
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++)
279         ;
280
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
285         {
286           vector<Page_turn_event> split = brk.penalize (repeat_penalties_[rep_index]);
287
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])
293             {
294               automatic_breaks_[i] = split.back ();
295               split.pop_back ();
296               i--;
297             }
298           auto_breaks.insert (auto_breaks.end (), split.begin (), split.end ());
299         }
300     }
301
302   /* apply the automatic breaks */
303   for (vsize i = 0; i < auto_breaks.size (); i++)
304     {
305       Page_turn_event const &brk = auto_breaks[i];
306       Grob *pc = breakable_column (auto_breaks[i]);
307       if (pc)
308         {
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));
313         }
314     }
315
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"));
318
319   /* apply the manual breaks */
320   for (vsize i = 0; i < forced_breaks_.size (); i++)
321     {
322       Page_turn_event const &brk = forced_breaks_[i];
323       Grob *pc = breakable_column (forced_breaks_[i]);
324       if (pc)
325         {
326           pc->set_property ("page-turn-permission", brk.permission_);
327           pc->set_property ("page-turn-penalty", scm_from_double (brk.penalty_));
328         }
329     }
330 }
331
332 ADD_ACKNOWLEDGER (Page_turn_engraver, note_head);
333
334 ADD_TRANSLATOR (Page_turn_engraver,
335                 /* doc */
336                 "Decide where page turns are allowed to go.",
337
338                 /* create */
339                 "",
340
341                 /* read */
342                 "minimumPageTurnLength "
343                 "minimumRepeatLengthForPageTurn ",
344
345                 /* write */
346                 ""
347                 );