]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-number.cc
Improve positioning of tuplet numbers for kneed beams.
[lilypond.git] / lily / tuplet-number.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2005--2014 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "tuplet-number.hh"
22 #include "tuplet-bracket.hh"
23 #include "moment.hh"      // needed?
24 #include "paper-column.hh"
25 #include "text-interface.hh"
26 #include "spanner.hh"
27 #include "lookup.hh"
28 #include "pointer-group-interface.hh"
29 #include "staff-symbol-referencer.hh"
30 #include "axis-group-interface.hh"
31 #include "directional-element-interface.hh"
32 #include "note-column.hh"
33 #include "beam.hh"
34 #include "stem.hh"
35 #include "warn.hh"
36
37 /*
38   The reference stem is used to determine on which side of the beam to place
39   the tuplet number when it is positioned independently of a bracket.  (The number
40   is always placed on the opposite side of this stem.)
41 */
42 Grob *
43 Tuplet_number::select_reference_stem (Grob *me_grob, vector<Grob *> const &cols)
44 {
45   Spanner *me = dynamic_cast<Spanner *> (me_grob);
46
47   int col_count = cols.size ();
48
49   if (!col_count)
50     return 0;
51
52   /*
53     When we have an odd number of stems, we choose the middle stem as
54     our reference.
55   */
56   Grob *ref_stem = Note_column::get_stem (cols[col_count / 2]);
57
58   if (col_count % 2 == 1)
59     return ref_stem;
60
61   /*
62     When we have an even number of stems, we choose between the central
63     two stems.
64   */
65   Direction me_dir = robust_scm2dir (me->get_property ("direction"), UP);
66   Drul_array<Item *> bounding_stems (Note_column::get_stem (cols[col_count / 2 - 1]),
67                                      Note_column::get_stem (cols[col_count / 2]));
68
69   for (LEFT_and_RIGHT (d))
70     if (!bounding_stems[d])
71       return bounding_stems[-d];
72
73   /*
74     If the central stems point in opposite directions, the number may
75     be placed on either side unless there is a fractional beam, in which
76     case the number goes opposite to the partial beam.
77
78     When there is an option, we use the setting of TupletNumber.direction.
79
80     If the central stems are in the same direction, it doesn't matter
81     which is used as the reference.  We use the one on the left.
82   */
83   Direction dir_left = get_grob_direction (bounding_stems[LEFT]);
84   Direction dir_right = get_grob_direction (bounding_stems[RIGHT]);
85
86   if (dir_left == dir_right)
87     ref_stem = bounding_stems[LEFT];
88   else
89     {
90       int beam_count_L_R = Stem::get_beaming (bounding_stems[LEFT], RIGHT);
91       int beam_count_R_L = Stem::get_beaming (bounding_stems[RIGHT], LEFT);
92       if (beam_count_L_R == beam_count_R_L)
93         ref_stem = (dir_left == me_dir) ? bounding_stems[LEFT] : bounding_stems[RIGHT];
94       else
95         ref_stem = (beam_count_L_R > beam_count_R_L)
96                    ? bounding_stems[LEFT] : bounding_stems[RIGHT];
97     }
98
99   return ref_stem;
100 }
101
102 /*
103   When we place the number close to the beam, we need to consider the note
104   columns adjoining the tuplet number on the same side of the beam.  The
105   number may not fit in the available space, or may need to be shifted
106   horizontally out of the way of stems and ledger lines.
107 */
108 Drul_array<Grob *>
109 Tuplet_number::adjacent_note_columns (Grob *me_grob, Grob *ref_stem)
110 {
111   Spanner *me = dynamic_cast<Spanner *> (me_grob);
112   Spanner *tuplet = unsmob_spanner (me->get_object ("bracket"));
113
114   extract_grob_set (tuplet, "note-columns", columns);
115   Grob *ref_col = ref_stem->get_parent (X_AXIS); // X-parent of Stem = NoteColumn
116   Direction ref_stem_dir = get_grob_direction (ref_stem);
117   vector<Grob *> filtered_cols;
118   vsize ref_pos = 0;
119
120   for (vsize i = 0, counter = 0; i < columns.size (); ++i)
121     {
122       Grob *stem = Note_column::get_stem (columns[i]);
123       if (stem && get_grob_direction (stem) == -ref_stem_dir)
124         {
125           filtered_cols.push_back (columns[i]);
126           ++counter;
127         }
128       if (columns[i] == ref_col)
129         {
130           filtered_cols.push_back (columns[i]);
131           ref_pos = counter;
132         }
133     }
134
135   Drul_array<Grob *> adj_cols (0, 0);
136
137   if (ref_pos > 0)
138     adj_cols[LEFT] = filtered_cols[ref_pos - 1];
139   if (ref_pos < filtered_cols.size () - 1)
140     adj_cols[RIGHT] = filtered_cols[ref_pos + 1];
141
142   return adj_cols;
143 }
144
145 /*
146   We determine whether our tuplet number will be put next to the beam
147   independently of the positioning of the associated tuplet bracket.
148
149   Draw next to the beam if:
150   --bracket isn't visible, AND
151   --there is a beam above or below the number, AND
152   --this beam is kneed, AND
153   --the tuplet number will fit between adjoining note columns
154 */
155 bool
156 Tuplet_number::knee_position_against_beam (Grob *me_grob, Grob *ref_stem)
157 {
158   Spanner *me = dynamic_cast<Spanner *> (me_grob);
159   Spanner *tuplet = unsmob_spanner (me->get_object ("bracket"));
160
161   bool bracket_visible = to_boolean (me->get_property ("bracket-visibility"))
162                          || !tuplet->extent (tuplet, Y_AXIS).is_empty ();
163
164   if (bracket_visible || !to_boolean (me->get_property ("knee-to-beam")))
165     return false;
166
167   Grob *beam = Stem::get_beam (ref_stem);
168
169   if (!beam || !to_boolean (beam->get_property ("knee")))
170     return false;
171
172   Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
173   commonx = commonx->common_refpoint (me, X_AXIS);
174
175   Interval number_ext = me->extent (commonx, X_AXIS);
176
177   Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
178
179   Item *left = me->get_bound (LEFT);
180   Item *right = me->get_bound (RIGHT);
181
182   if (!left || !right)
183     return false;
184
185   Drul_array<Item *> bounds (left, right);
186
187   Interval available_ext;
188   Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
189
190   /*
191      If there is no note column on a given side of the tuplet number, we use
192      a paper column instead to determine the available space.  Padding is only
193      considered in the case of a note column.
194   */
195   for (LEFT_and_RIGHT (d))
196     {
197       if (adj_cols[d])
198         available_ext[d] = Axis_group_interface::generic_bound_extent (adj_cols[d], commonx, X_AXIS)[-d] + (-d * padding);
199       else
200         available_ext[d] = Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d];
201     }
202
203   if (number_ext.length () > available_ext.length ())
204     {
205       programming_error ("not enough space for tuplet number against beam");
206       return false;
207     }
208
209   return true;
210 }
211
212 MAKE_SCHEME_CALLBACK (Tuplet_number, print, 1);
213 SCM
214 Tuplet_number::print (SCM smob)
215 {
216   Spanner *me = unsmob_spanner (smob);
217   Spanner *tuplet = unsmob_spanner (me->get_object ("bracket"));
218
219   if (!tuplet || !tuplet->is_live ())
220     {
221       me->suicide ();
222       return SCM_EOL;
223     }
224
225   SCM stc_scm = Text_interface::print (smob);
226   Stencil *stc = unsmob_stencil (stc_scm);
227
228   stc->align_to (X_AXIS, CENTER);
229   stc->align_to (Y_AXIS, CENTER);
230
231   return stc->smobbed_copy ();
232 }
233
234 /*
235   For a given horizontal displacement of the tuplet number, how much
236   vertical shift is necessary to keep it the same distance from the beam?
237 */
238 Real
239 calc_beam_y_shift (Grob *ref_stem, Real dx)
240 {
241   Grob *beam = Stem::get_beam (ref_stem);
242   Interval x_pos = robust_scm2interval (beam->get_property ("X-positions"), Interval (0.0, 0.0));
243   Interval y_pos = robust_scm2interval (beam->get_property ("quantized-positions"), Interval (0.0, 0.0));
244   Real beam_dx = x_pos.length ();
245   Real beam_dy = y_pos[RIGHT] - y_pos[LEFT];
246   Real slope = beam_dx ? beam_dy / beam_dx : 0.0;
247
248   return (slope * dx);
249 }
250
251 /*
252   The X- and Y-offset of the tuplet number are calculated in relation either
253   to the bracket associated with it, or with the beam it is placed against.
254 */
255
256 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_x_offset, 1);
257 SCM
258 Tuplet_number::calc_x_offset (SCM smob)
259 {
260   Spanner *me = unsmob_spanner (smob);
261
262   Item *left_bound = me->get_bound (LEFT);
263   Item *right_bound = me->get_bound (RIGHT);
264   Drul_array<Item *> bounds (left_bound, right_bound);
265
266   Spanner *tuplet = unsmob_spanner (me->get_object ("bracket"));
267
268   Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
269   commonx = commonx->common_refpoint (me, X_AXIS);
270
271   Interval bound_poss;
272
273   for (LEFT_and_RIGHT (d))
274     {
275       if (Note_column::has_interface (bounds[d])
276           && Note_column::get_stem (bounds[d]))
277         bounds[d] = Note_column::get_stem (bounds[d]);
278       bound_poss[d] = Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d];
279     }
280
281   extract_grob_set (tuplet, "note-columns", cols);
282   Grob *ref_stem = select_reference_stem (me, cols);
283
284   /*
285     Return bracket-based positioning.
286   */
287   if (!ref_stem
288       || !knee_position_against_beam (me, ref_stem))
289     {
290       Interval x_positions;
291       x_positions = robust_scm2interval (tuplet->get_property ("X-positions"),
292                                          Interval (0.0, 0.0));
293       return scm_from_double (x_positions.center ());
294     }
295
296   /*
297      Horizontally center the number on the beam.
298   */
299   Real col_pos = left_bound->relative_coordinate (commonx, X_AXIS);
300   Real x_offset = bound_poss.center () - col_pos;
301
302   /*
303     Consider possible collisions with adjacent note columns.
304   */
305   Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
306   Interval number_ext = me->extent (commonx, X_AXIS);
307   number_ext.translate (x_offset);
308   Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
309   number_ext.widen (padding);
310
311   Interval cor (0.0, 0.0);
312
313   for (LEFT_and_RIGHT (d))
314     if (adj_cols[d])
315       {
316         Interval nc_ext = adj_cols[d]->extent (commonx, X_AXIS);
317         Interval overlap (nc_ext);
318         overlap.intersect (number_ext);
319         if (!overlap.is_empty ())
320           cor[d] = overlap.length () * -d;
321         x_offset += cor[d];
322       }
323
324   return scm_from_double (x_offset);
325 }
326
327 /*
328   When a number is placed against the beam (independently of a bracket), the
329   Y-extent of a reference stem is used to determine the vertical placement of
330   the number.  When French beams are used the stem may not reach all beams.
331 */
332 int
333 count_beams_not_touching_stem (SCM beaming)
334 {
335   int count = 0;
336
337   for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
338     {
339       if (scm_c_memq (scm_car (s), scm_cdr (beaming)) != SCM_BOOL_F)
340         ++count;
341     }
342
343   return max (0, count - 1);
344 }
345
346 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_y_offset, 1);
347 SCM
348 Tuplet_number::calc_y_offset (SCM smob)
349 {
350   Spanner *me = unsmob_spanner (smob);
351   Spanner *tuplet = unsmob_spanner (me->get_object ("bracket"));
352   Drul_array<Real> positions = robust_scm2drul (tuplet->get_property ("positions"),
353                                                 Drul_array<Real> (0.0, 0.0));
354   SCM to_bracket = scm_from_double ((positions[LEFT] + positions[RIGHT]) / 2.0);
355
356   Grob *commonx = Tuplet_bracket::get_common_x (me);
357   commonx = commonx->common_refpoint (me, X_AXIS);
358   Real x_coord = me->relative_coordinate (commonx, X_AXIS);
359   extract_grob_set (tuplet, "note-columns", columns);
360   Grob *ref_stem = select_reference_stem (me, columns);
361
362   if (!ref_stem || !knee_position_against_beam (me, ref_stem))
363     return to_bracket;
364
365   Grob *beam = Stem::get_beam (ref_stem);
366   if (!beam || !to_boolean (beam->get_property ("knee")))
367     return to_bracket;
368
369   /*
370     First, we calculate the Y-offset of the tuplet number as if it
371     is positioned at the reference stem.
372   */
373   Grob *commony = common_refpoint_of_array (columns, tuplet, Y_AXIS);
374   commony = commony->common_refpoint (me, Y_AXIS);
375   extract_grob_set (me, "tuplets", tuplets);
376   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
377   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
378     commony = st->common_refpoint (commony, Y_AXIS);
379
380   Interval ref_stem_ext = ref_stem->extent (commony, Y_AXIS);
381   Real tuplet_y = tuplet->relative_coordinate (commony, Y_AXIS);
382   Direction ref_stem_dir = get_grob_direction (ref_stem);
383
384   Real y_offset = ref_stem_ext[ref_stem_dir] - tuplet_y;
385
386   /*
387     Additional displacement for French beaming.
388   */
389   if (to_boolean (ref_stem->get_property ("french-beaming")))
390     {
391       Real beam_translation = Beam::get_beam_translation (beam);
392       SCM beaming = ref_stem->get_property ("beaming");
393       y_offset += ref_stem_dir
394                   * count_beams_not_touching_stem (beaming)
395                   * beam_translation;
396     }
397
398   Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
399   Real num_height = me->extent (commony, Y_AXIS).length ();
400
401   y_offset += ref_stem_dir * (padding + num_height / 2.0);
402
403   /*
404     Now we adjust the vertical position of the number to reflect
405     its actual horizontal placement along the beam.
406   */
407   Real ref_stem_x = ref_stem->relative_coordinate (commonx, X_AXIS);
408   y_offset += calc_beam_y_shift (ref_stem, x_coord - ref_stem_x);
409
410   /*
411     Check if the number is between the beam and the staff.  If so, it will collide
412     with ledger lines.  Move it into the staff.
413   */
414   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (ref_stem))
415     {
416       Interval staff_ext_y = st->extent (commony, Y_AXIS);
417       bool move = ref_stem_dir == DOWN
418                   ? ref_stem_ext[DOWN] > staff_ext_y[UP]
419                   : staff_ext_y[DOWN] > ref_stem_ext[UP];
420       if (move)
421         {
422           Interval ledger_domain = Interval (min (staff_ext_y[UP], ref_stem_ext[UP]),
423                                              max (staff_ext_y[DOWN], ref_stem_ext[DOWN]));
424           Interval num_y (me->extent (commony, Y_AXIS));
425           num_y.translate (y_offset);
426           Interval num_ledger_overlap (num_y);
427           num_ledger_overlap.intersect (ledger_domain);
428           Real line_thickness = Staff_symbol_referencer::line_thickness (st);
429           Real staff_space = Staff_symbol_referencer::staff_space (st);
430           // Number will touch outer staff line.
431           if (!num_ledger_overlap.is_empty ()
432               && num_ledger_overlap.length () > (staff_space / 2.0)
433               && move)
434             y_offset += staff_ext_y[-ref_stem_dir] - num_y[-ref_stem_dir]
435                         + line_thickness * ref_stem_dir;
436         }
437     }
438
439   /*
440     Now consider possible collisions with accidentals on the right.  We
441     move the accidental away from the beam.
442   */
443   Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
444
445   if (!adj_cols[RIGHT])
446     return scm_from_double (y_offset);
447
448   /*
449     Collect Y-extents of accidentals that overlap the number
450     along the X-axis.
451   */
452   extract_grob_set (adj_cols[RIGHT], "note-heads", heads);
453   Interval colliding_acc_ext_y;
454
455   for (vsize i = 0; i < heads.size (); i++)
456     if (Grob *acc = unsmob_grob (heads[i]->get_object ("accidental-grob")))
457       {
458         commony = commony->common_refpoint (acc, Y_AXIS);
459         Interval acc_ext_y = acc->extent (commony, Y_AXIS);
460
461         commonx = commonx->common_refpoint (acc, X_AXIS);
462         Interval num_ext_x = me->extent (commonx, X_AXIS);
463         num_ext_x.widen (padding);
464         Interval overlap_x (num_ext_x);
465         Interval acc_x = acc->extent (commonx, X_AXIS);
466         overlap_x.intersect (acc_x);
467
468         if (!overlap_x.is_empty ())
469           colliding_acc_ext_y.unite (acc_ext_y);
470       }
471   /*
472     Does our number intersect vertically with the accidental Y-extents we
473     combined above?  If so, move it.
474   */
475   Interval overlap_acc_y (colliding_acc_ext_y);
476   Interval num_ext_y (me->extent (commony, Y_AXIS));
477   num_ext_y.translate (y_offset);
478   overlap_acc_y.intersect (num_ext_y);
479
480   if (!overlap_acc_y.is_empty ())
481     y_offset += colliding_acc_ext_y[ref_stem_dir] - num_ext_y[-ref_stem_dir] + padding * ref_stem_dir;
482
483   return scm_from_double (y_offset);
484 }
485
486 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_cross_staff, 1)
487 SCM
488 Tuplet_number::calc_cross_staff (SCM smob)
489 {
490   Grob *me = unsmob_grob (smob);
491   return unsmob_grob (me->get_object ("bracket"))->get_property ("cross-staff");
492 }
493
494 ADD_INTERFACE (Tuplet_number,
495                "The number for a bracket.",
496
497                /* properties */
498                "avoid-slur "    // UGH.
499                "bracket "
500                "direction "
501                "knee-to-beam "
502               );
503