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"
40 The reference stem is used to determine on which side of the beam to place
41 the tuplet number when it is positioned independently of a bracket. (The number
42 is always placed on the opposite side of this stem.)
45 Tuplet_number::select_reference_stem (Grob *me_grob, vector<Grob *> const &cols)
47 Spanner *me = dynamic_cast<Spanner *> (me_grob);
49 int col_count = cols.size ();
55 When we have an odd number of stems, we choose the middle stem as
58 Grob *ref_stem = Note_column::get_stem (cols[col_count / 2]);
60 if (col_count % 2 == 1)
64 When we have an even number of stems, we choose between the central
67 Direction me_dir = robust_scm2dir (me->get_property ("direction"), UP);
68 Drul_array<Item *> bounding_stems (Note_column::get_stem (cols[col_count / 2 - 1]),
69 Note_column::get_stem (cols[col_count / 2]));
71 for (LEFT_and_RIGHT (d))
72 if (!bounding_stems[d])
73 return bounding_stems[-d];
76 If the central stems point in opposite directions, the number may
77 be placed on either side unless there is a fractional beam, in which
78 case the number goes opposite to the partial beam.
80 When there is an option, we use the setting of TupletNumber.direction.
82 If the central stems are in the same direction, it doesn't matter
83 which is used as the reference. We use the one on the left.
85 Direction dir_left = get_grob_direction (bounding_stems[LEFT]);
86 Direction dir_right = get_grob_direction (bounding_stems[RIGHT]);
88 if (dir_left == dir_right)
89 ref_stem = bounding_stems[LEFT];
92 int beam_count_L_R = Stem::get_beaming (bounding_stems[LEFT], RIGHT);
93 int beam_count_R_L = Stem::get_beaming (bounding_stems[RIGHT], LEFT);
94 if (beam_count_L_R == beam_count_R_L)
95 ref_stem = (dir_left == me_dir) ? bounding_stems[LEFT] : bounding_stems[RIGHT];
97 ref_stem = (beam_count_L_R > beam_count_R_L)
98 ? bounding_stems[LEFT] : bounding_stems[RIGHT];
105 When we place the number close to the beam, we need to consider the note
106 columns adjoining the tuplet number on the same side of the beam. The
107 number may not fit in the available space, or may need to be shifted
108 horizontally out of the way of stems and ledger lines.
111 Tuplet_number::adjacent_note_columns (Grob *me_grob, Grob *ref_stem)
113 Spanner *me = dynamic_cast<Spanner *> (me_grob);
114 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
116 extract_grob_set (tuplet, "note-columns", columns);
117 Grob *ref_col = ref_stem->get_parent (X_AXIS); // X-parent of Stem = NoteColumn
118 Direction ref_stem_dir = get_grob_direction (ref_stem);
119 vector<Grob *> filtered_cols;
122 for (vsize i = 0, counter = 0; i < columns.size (); ++i)
124 Grob *stem = Note_column::get_stem (columns[i]);
125 if (stem && get_grob_direction (stem) == -ref_stem_dir)
127 filtered_cols.push_back (columns[i]);
130 if (columns[i] == ref_col)
132 filtered_cols.push_back (columns[i]);
137 Drul_array<Grob *> adj_cols (0, 0);
140 adj_cols[LEFT] = filtered_cols[ref_pos - 1];
141 if (ref_pos < filtered_cols.size () - 1)
142 adj_cols[RIGHT] = filtered_cols[ref_pos + 1];
148 We determine whether our tuplet number will be put next to the beam
149 independently of the positioning of the associated tuplet bracket.
151 Draw next to the beam if:
152 --bracket isn't visible, AND
153 --there is a beam above or below the number, AND
154 --this beam is kneed, AND
155 --the tuplet number will fit between adjoining note columns
158 Tuplet_number::knee_position_against_beam (Grob *me_grob, Grob *ref_stem)
160 Spanner *me = dynamic_cast<Spanner *> (me_grob);
161 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
163 bool bracket_visible = to_boolean (me->get_property ("bracket-visibility"))
164 || !tuplet->extent (tuplet, Y_AXIS).is_empty ();
166 if (bracket_visible || !to_boolean (me->get_property ("knee-to-beam")))
169 Grob *beam = Stem::get_beam (ref_stem);
171 if (!beam || !Beam::is_knee (beam))
174 Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
175 commonx = commonx->common_refpoint (me, X_AXIS);
177 Interval number_ext = me->extent (commonx, X_AXIS);
179 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
181 Item *left = me->get_bound (LEFT);
182 Item *right = me->get_bound (RIGHT);
187 Drul_array<Item *> bounds (left, right);
189 Interval available_ext;
190 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
193 If there is no note column on a given side of the tuplet number, we use
194 a paper column instead to determine the available space. Padding is only
195 considered in the case of a note column.
197 for (LEFT_and_RIGHT (d))
200 available_ext[d] = Axis_group_interface::generic_bound_extent (adj_cols[d], commonx, X_AXIS)[-d] + (-d * padding);
202 available_ext[d] = Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d];
205 if (number_ext.length () > available_ext.length ())
207 programming_error ("not enough space for tuplet number against beam");
214 MAKE_SCHEME_CALLBACK (Tuplet_number, print, 1);
216 Tuplet_number::print (SCM smob)
218 Spanner *me = unsmob<Spanner> (smob);
219 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
221 if (!tuplet || !tuplet->is_live ())
227 SCM stc_scm = Text_interface::print (smob);
228 Stencil *stc = unsmob<Stencil> (stc_scm);
230 stc->align_to (X_AXIS, CENTER);
231 stc->align_to (Y_AXIS, CENTER);
233 return stc->smobbed_copy ();
237 For a given horizontal displacement of the tuplet number, how much
238 vertical shift is necessary to keep it the same distance from the beam?
241 calc_beam_y_shift (Grob *ref_stem, Real dx)
243 Grob *beam = Stem::get_beam (ref_stem);
244 Interval x_pos = robust_scm2interval (beam->get_property ("X-positions"), Interval (0.0, 0.0));
245 Interval y_pos = robust_scm2interval (beam->get_property ("quantized-positions"), Interval (0.0, 0.0));
246 Real beam_dx = x_pos.length ();
247 Real beam_dy = y_pos[RIGHT] - y_pos[LEFT];
248 Real slope = beam_dx ? beam_dy / beam_dx : 0.0;
254 The X- and Y-offset of the tuplet number are calculated in relation either
255 to the bracket associated with it, or with the beam it is placed against.
258 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_x_offset, 1);
260 Tuplet_number::calc_x_offset (SCM smob)
262 Spanner *me = unsmob<Spanner> (smob);
264 Item *left_bound = me->get_bound (LEFT);
265 Item *right_bound = me->get_bound (RIGHT);
266 Drul_array<Item *> bounds (left_bound, right_bound);
268 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
270 Grob *commonx = Tuplet_bracket::get_common_x (tuplet);
271 commonx = commonx->common_refpoint (me, X_AXIS);
275 for (LEFT_and_RIGHT (d))
277 if (has_interface<Note_column> (bounds[d])
278 && Note_column::get_stem (bounds[d]))
279 bounds[d] = Note_column::get_stem (bounds[d]);
280 bound_poss[d] = Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d];
283 extract_grob_set (tuplet, "note-columns", cols);
284 Grob *ref_stem = select_reference_stem (me, cols);
287 Return bracket-based positioning.
290 || !knee_position_against_beam (me, ref_stem))
292 Interval x_positions;
293 x_positions = robust_scm2interval (tuplet->get_property ("X-positions"),
294 Interval (0.0, 0.0));
295 return scm_from_double (x_positions.center ());
299 Horizontally center the number on the beam.
301 Real col_pos = left_bound->relative_coordinate (commonx, X_AXIS);
302 Real x_offset = bound_poss.center () - col_pos;
305 Consider possible collisions with adjacent note columns.
307 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
308 Interval number_ext = me->extent (commonx, X_AXIS);
309 number_ext.translate (x_offset);
310 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
311 number_ext.widen (padding);
313 Interval cor (0.0, 0.0);
315 for (LEFT_and_RIGHT (d))
318 Interval nc_ext = adj_cols[d]->extent (commonx, X_AXIS);
319 Interval overlap (nc_ext);
320 overlap.intersect (number_ext);
321 if (!overlap.is_empty ())
322 cor[d] = overlap.length () * -d;
326 return scm_from_double (x_offset);
330 When a number is placed against the beam (independently of a bracket), the
331 Y-extent of a reference stem is used to determine the vertical placement of
332 the number. When French beams are used the stem may not reach all beams.
335 count_beams_not_touching_stem (SCM beaming)
339 for (SCM s = scm_car (beaming); scm_is_pair (s); s = scm_cdr (s))
341 if (scm_is_true (scm_c_memq (scm_car (s), scm_cdr (beaming))))
345 return max (0, count - 1);
348 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_y_offset, 1);
350 Tuplet_number::calc_y_offset (SCM smob)
352 Spanner *me = unsmob<Spanner> (smob);
353 Spanner *tuplet = unsmob<Spanner> (me->get_object ("bracket"));
354 Drul_array<Real> positions = robust_scm2drul (tuplet->get_property ("positions"),
355 Drul_array<Real> (0.0, 0.0));
356 SCM to_bracket = scm_from_double ((positions[LEFT] + positions[RIGHT]) / 2.0);
358 Grob *commonx = Tuplet_bracket::get_common_x (me);
359 commonx = commonx->common_refpoint (me, X_AXIS);
360 Real x_coord = me->relative_coordinate (commonx, X_AXIS);
361 extract_grob_set (tuplet, "note-columns", columns);
362 Grob *ref_stem = select_reference_stem (me, columns);
364 if (!ref_stem || !knee_position_against_beam (me, ref_stem))
368 First, we calculate the Y-offset of the tuplet number as if it
369 is positioned at the reference stem.
371 Grob *commony = common_refpoint_of_array (columns, tuplet, Y_AXIS);
372 commony = commony->common_refpoint (me, Y_AXIS);
373 extract_grob_set (me, "tuplets", tuplets);
374 commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
375 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
376 commony = st->common_refpoint (commony, Y_AXIS);
378 Interval ref_stem_ext = ref_stem->extent (commony, Y_AXIS);
379 Real tuplet_y = tuplet->relative_coordinate (commony, Y_AXIS);
380 Direction ref_stem_dir = get_grob_direction (ref_stem);
382 Real y_offset = ref_stem_ext[ref_stem_dir] - tuplet_y;
385 Additional displacement for French beaming.
387 if (to_boolean (ref_stem->get_property ("french-beaming")))
389 Grob *beam = Stem::get_beam (ref_stem);
390 Real beam_translation = Beam::get_beam_translation (beam);
391 SCM beaming = ref_stem->get_property ("beaming");
392 y_offset += ref_stem_dir
393 * count_beams_not_touching_stem (beaming)
397 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
398 Real num_height = me->extent (commony, Y_AXIS).length ();
400 y_offset += ref_stem_dir * (padding + num_height / 2.0);
403 Now we adjust the vertical position of the number to reflect
404 its actual horizontal placement along the beam.
406 Real ref_stem_x = ref_stem->relative_coordinate (commonx, X_AXIS);
407 y_offset += calc_beam_y_shift (ref_stem, x_coord - ref_stem_x);
410 Check if the number is between the beam and the staff. If so, it will collide
411 with ledger lines. Move it into the staff.
413 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (ref_stem))
415 Interval staff_ext_y = st->extent (commony, Y_AXIS);
416 bool move = ref_stem_dir == DOWN
417 ? ref_stem_ext[DOWN] > staff_ext_y[UP]
418 : staff_ext_y[DOWN] > ref_stem_ext[UP];
421 Interval ledger_domain = Interval (min (staff_ext_y[UP], ref_stem_ext[UP]),
422 max (staff_ext_y[DOWN], ref_stem_ext[DOWN]));
423 Interval num_y (me->extent (commony, Y_AXIS));
424 num_y.translate (y_offset);
425 Interval num_ledger_overlap (num_y);
426 num_ledger_overlap.intersect (ledger_domain);
427 Real line_thickness = Staff_symbol_referencer::line_thickness (st);
428 Real staff_space = Staff_symbol_referencer::staff_space (st);
429 // Number will touch outer staff line.
430 if (!num_ledger_overlap.is_empty ()
431 && num_ledger_overlap.length () > (staff_space / 2.0)
433 y_offset += staff_ext_y[-ref_stem_dir] - num_y[-ref_stem_dir]
434 + line_thickness * ref_stem_dir;
439 Now consider possible collisions with accidentals on the right. We
440 move the accidental away from the beam.
442 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
444 if (!adj_cols[RIGHT])
445 return scm_from_double (y_offset);
448 Collect Y-extents of accidentals that overlap the number
451 extract_grob_set (adj_cols[RIGHT], "note-heads", heads);
452 Interval colliding_acc_ext_y;
454 for (vsize i = 0; i < heads.size (); i++)
455 if (Grob *acc = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
457 commony = commony->common_refpoint (acc, Y_AXIS);
458 Interval acc_ext_y = acc->extent (commony, Y_AXIS);
460 commonx = commonx->common_refpoint (acc, X_AXIS);
461 Interval num_ext_x = me->extent (commonx, X_AXIS);
462 num_ext_x.widen (padding);
463 Interval overlap_x (num_ext_x);
464 Interval acc_x = acc->extent (commonx, X_AXIS);
465 overlap_x.intersect (acc_x);
467 if (!overlap_x.is_empty ())
468 colliding_acc_ext_y.unite (acc_ext_y);
471 Does our number intersect vertically with the accidental Y-extents we
472 combined above? If so, move it.
474 Interval overlap_acc_y (colliding_acc_ext_y);
475 Interval num_ext_y (me->extent (commony, Y_AXIS));
476 num_ext_y.translate (y_offset);
477 overlap_acc_y.intersect (num_ext_y);
479 if (!overlap_acc_y.is_empty ())
480 y_offset += colliding_acc_ext_y[ref_stem_dir] - num_ext_y[-ref_stem_dir] + padding * ref_stem_dir;
482 return scm_from_double (y_offset);
485 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_cross_staff, 1)
487 Tuplet_number::calc_cross_staff (SCM smob)
489 Grob *me = unsmob<Grob> (smob);
490 return unsmob<Grob> (me->get_object ("bracket"))->get_property ("cross-staff");
493 ADD_INTERFACE (Tuplet_number,
494 "The number for a bracket.",
497 "avoid-slur " // UGH.