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