2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2005--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
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.
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.
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/>.
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"
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"
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.)
43 Tuplet_number::select_reference_stem (Grob *me_grob, vector<Grob *> const &cols)
45 Spanner *me = dynamic_cast<Spanner *> (me_grob);
47 int col_count = cols.size ();
53 When we have an odd number of stems, we choose the middle stem as
56 Grob *ref_stem = Note_column::get_stem (cols[col_count / 2]);
58 if (col_count % 2 == 1)
62 When we have an even number of stems, we choose between the central
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]));
69 for (LEFT_and_RIGHT (d))
70 if (!bounding_stems[d])
71 return bounding_stems[-d];
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.
78 When there is an option, we use the setting of TupletNumber.direction.
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.
83 Direction dir_left = get_grob_direction (bounding_stems[LEFT]);
84 Direction dir_right = get_grob_direction (bounding_stems[RIGHT]);
86 if (dir_left == dir_right)
87 ref_stem = bounding_stems[LEFT];
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];
95 ref_stem = (beam_count_L_R > beam_count_R_L)
96 ? bounding_stems[LEFT] : bounding_stems[RIGHT];
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.
109 Tuplet_number::adjacent_note_columns (Grob *me_grob, Grob *ref_stem)
111 Spanner *me = dynamic_cast<Spanner *> (me_grob);
112 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
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;
120 for (vsize i = 0, counter = 0; i < columns.size (); ++i)
122 Grob *stem = Note_column::get_stem (columns[i]);
123 if (stem && get_grob_direction (stem) == -ref_stem_dir)
125 filtered_cols.push_back (columns[i]);
128 if (columns[i] == ref_col)
130 filtered_cols.push_back (columns[i]);
135 Drul_array<Grob *> adj_cols (0, 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];
146 We determine whether our tuplet number will be put next to the beam
147 independently of the positioning of the associated tuplet bracket.
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
156 Tuplet_number::knee_position_against_beam (Grob *me_grob, Grob *ref_stem)
158 Spanner *me = dynamic_cast<Spanner *> (me_grob);
159 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
161 bool bracket_visible = to_boolean (me->get_property ("bracket-visibility"))
162 || !tuplet->extent (tuplet, Y_AXIS).is_empty ();
164 if (bracket_visible || !to_boolean (me->get_property ("knee-to-beam")))
167 Grob *beam = Stem::get_beam (ref_stem);
169 if (!beam || !Beam::is_knee (beam))
172 Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
173 commonx = commonx->common_refpoint (me, X_AXIS);
175 Interval number_ext = me->extent (commonx, X_AXIS);
177 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
179 Item *left = me->get_bound (LEFT);
180 Item *right = me->get_bound (RIGHT);
185 Drul_array<Item *> bounds (left, right);
187 Interval available_ext;
188 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
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.
195 for (LEFT_and_RIGHT (d))
198 available_ext[d] = Axis_group_interface::generic_bound_extent (adj_cols[d], commonx, X_AXIS)[-d] + (-d * padding);
200 available_ext[d] = Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d];
203 if (number_ext.length () > available_ext.length ())
205 programming_error ("not enough space for tuplet number against beam");
212 MAKE_SCHEME_CALLBACK (Tuplet_number, print, 1);
214 Tuplet_number::print (SCM smob)
216 Spanner *me = unsmob<Spanner> (smob);
217 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
219 if (!tuplet || !tuplet->is_live ())
225 SCM stc_scm = Text_interface::print (smob);
226 Stencil *stc = unsmob<Stencil> (stc_scm);
228 stc->align_to (X_AXIS, CENTER);
229 stc->align_to (Y_AXIS, CENTER);
231 return stc->smobbed_copy ();
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?
239 calc_beam_y_shift (Grob *ref_stem, Real dx)
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;
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.
256 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_x_offset, 1);
258 Tuplet_number::calc_x_offset (SCM smob)
260 Spanner *me = unsmob<Spanner> (smob);
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);
266 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
268 Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
269 commonx = commonx->common_refpoint (me, X_AXIS);
273 for (LEFT_and_RIGHT (d))
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];
281 extract_grob_set (tuplet, "note-columns", cols);
282 Grob *ref_stem = select_reference_stem (me, cols);
285 Return bracket-based positioning.
288 || !knee_position_against_beam (me, ref_stem))
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 ());
297 Horizontally center the number on the beam.
299 Real col_pos = left_bound->relative_coordinate (commonx, X_AXIS);
300 Real x_offset = bound_poss.center () - col_pos;
303 Consider possible collisions with adjacent note columns.
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);
311 Interval cor (0.0, 0.0);
313 for (LEFT_and_RIGHT (d))
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;
324 return scm_from_double (x_offset);
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.
333 count_beams_not_touching_stem (SCM beaming)
337 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
339 if (scm_is_true (scm_c_memq (scm_car (s), scm_cdr (beaming))))
343 return max (0, count - 1);
346 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_y_offset, 1);
348 Tuplet_number::calc_y_offset (SCM smob)
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);
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);
362 if (!ref_stem || !knee_position_against_beam (me, ref_stem))
366 First, we calculate the Y-offset of the tuplet number as if it
367 is positioned at the reference stem.
369 Grob *commony = common_refpoint_of_array (columns, tuplet, Y_AXIS);
370 commony = commony->common_refpoint (me, Y_AXIS);
371 extract_grob_set (me, "tuplets", tuplets);
372 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
373 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
374 commony = st->common_refpoint (commony, Y_AXIS);
376 Interval ref_stem_ext = ref_stem->extent (commony, Y_AXIS);
377 Real tuplet_y = tuplet->relative_coordinate (commony, Y_AXIS);
378 Direction ref_stem_dir = get_grob_direction (ref_stem);
380 Real y_offset = ref_stem_ext[ref_stem_dir] - tuplet_y;
383 Additional displacement for French beaming.
385 if (to_boolean (ref_stem->get_property ("french-beaming")))
387 Grob *beam = Stem::get_beam (ref_stem);
388 Real beam_translation = Beam::get_beam_translation (beam);
389 SCM beaming = ref_stem->get_property ("beaming");
390 y_offset += ref_stem_dir
391 * count_beams_not_touching_stem (beaming)
395 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
396 Real num_height = me->extent (commony, Y_AXIS).length ();
398 y_offset += ref_stem_dir * (padding + num_height / 2.0);
401 Now we adjust the vertical position of the number to reflect
402 its actual horizontal placement along the beam.
404 Real ref_stem_x = ref_stem->relative_coordinate (commonx, X_AXIS);
405 y_offset += calc_beam_y_shift (ref_stem, x_coord - ref_stem_x);
408 Check if the number is between the beam and the staff. If so, it will collide
409 with ledger lines. Move it into the staff.
411 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (ref_stem))
413 Interval staff_ext_y = st->extent (commony, Y_AXIS);
414 bool move = ref_stem_dir == DOWN
415 ? ref_stem_ext[DOWN] > staff_ext_y[UP]
416 : staff_ext_y[DOWN] > ref_stem_ext[UP];
419 Interval ledger_domain = Interval (min (staff_ext_y[UP], ref_stem_ext[UP]),
420 max (staff_ext_y[DOWN], ref_stem_ext[DOWN]));
421 Interval num_y (me->extent (commony, Y_AXIS));
422 num_y.translate (y_offset);
423 Interval num_ledger_overlap (num_y);
424 num_ledger_overlap.intersect (ledger_domain);
425 Real line_thickness = Staff_symbol_referencer::line_thickness (st);
426 Real staff_space = Staff_symbol_referencer::staff_space (st);
427 // Number will touch outer staff line.
428 if (!num_ledger_overlap.is_empty ()
429 && num_ledger_overlap.length () > (staff_space / 2.0)
431 y_offset += staff_ext_y[-ref_stem_dir] - num_y[-ref_stem_dir]
432 + line_thickness * ref_stem_dir;
437 Now consider possible collisions with accidentals on the right. We
438 move the accidental away from the beam.
440 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
442 if (!adj_cols[RIGHT])
443 return scm_from_double (y_offset);
446 Collect Y-extents of accidentals that overlap the number
449 extract_grob_set (adj_cols[RIGHT], "note-heads", heads);
450 Interval colliding_acc_ext_y;
452 for (vsize i = 0; i < heads.size (); i++)
453 if (Grob *acc = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
455 commony = commony->common_refpoint (acc, Y_AXIS);
456 Interval acc_ext_y = acc->extent (commony, Y_AXIS);
458 commonx = commonx->common_refpoint (acc, X_AXIS);
459 Interval num_ext_x = me->extent (commonx, X_AXIS);
460 num_ext_x.widen (padding);
461 Interval overlap_x (num_ext_x);
462 Interval acc_x = acc->extent (commonx, X_AXIS);
463 overlap_x.intersect (acc_x);
465 if (!overlap_x.is_empty ())
466 colliding_acc_ext_y.unite (acc_ext_y);
469 Does our number intersect vertically with the accidental Y-extents we
470 combined above? If so, move it.
472 Interval overlap_acc_y (colliding_acc_ext_y);
473 Interval num_ext_y (me->extent (commony, Y_AXIS));
474 num_ext_y.translate (y_offset);
475 overlap_acc_y.intersect (num_ext_y);
477 if (!overlap_acc_y.is_empty ())
478 y_offset += colliding_acc_ext_y[ref_stem_dir] - num_ext_y[-ref_stem_dir] + padding * ref_stem_dir;
480 return scm_from_double (y_offset);
483 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_cross_staff, 1)
485 Tuplet_number::calc_cross_staff (SCM smob)
487 Grob *me = unsmob<Grob> (smob);
488 return unsmob<Grob> (me->get_object ("bracket"))->get_property ("cross-staff");
491 ADD_INTERFACE (Tuplet_number,
492 "The number for a bracket.",
495 "avoid-slur " // UGH.