2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2005--2014 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 || !to_boolean (beam->get_property ("knee")))
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_c_memq (scm_car (s), scm_cdr (beaming)) != SCM_BOOL_F)
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))
365 Grob *beam = Stem::get_beam (ref_stem);
366 if (!beam || !to_boolean (beam->get_property ("knee")))
370 First, we calculate the Y-offset of the tuplet number as if it
371 is positioned at the reference stem.
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);
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);
384 Real y_offset = ref_stem_ext[ref_stem_dir] - tuplet_y;
387 Additional displacement for French beaming.
389 if (to_boolean (ref_stem->get_property ("french-beaming")))
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)
398 Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
399 Real num_height = me->extent (commony, Y_AXIS).length ();
401 y_offset += ref_stem_dir * (padding + num_height / 2.0);
404 Now we adjust the vertical position of the number to reflect
405 its actual horizontal placement along the beam.
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);
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.
414 if (Grob *st = Staff_symbol_referencer::get_staff_symbol (ref_stem))
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];
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)
434 y_offset += staff_ext_y[-ref_stem_dir] - num_y[-ref_stem_dir]
435 + line_thickness * ref_stem_dir;
440 Now consider possible collisions with accidentals on the right. We
441 move the accidental away from the beam.
443 Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
445 if (!adj_cols[RIGHT])
446 return scm_from_double (y_offset);
449 Collect Y-extents of accidentals that overlap the number
452 extract_grob_set (adj_cols[RIGHT], "note-heads", heads);
453 Interval colliding_acc_ext_y;
455 for (vsize i = 0; i < heads.size (); i++)
456 if (Grob *acc = unsmob_grob (heads[i]->get_object ("accidental-grob")))
458 commony = commony->common_refpoint (acc, Y_AXIS);
459 Interval acc_ext_y = acc->extent (commony, Y_AXIS);
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);
468 if (!overlap_x.is_empty ())
469 colliding_acc_ext_y.unite (acc_ext_y);
472 Does our number intersect vertically with the accidental Y-extents we
473 combined above? If so, move it.
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);
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;
483 return scm_from_double (y_offset);
486 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_cross_staff, 1)
488 Tuplet_number::calc_cross_staff (SCM smob)
490 Grob *me = unsmob_grob (smob);
491 return unsmob_grob (me->get_object ("bracket"))->get_property ("cross-staff");
494 ADD_INTERFACE (Tuplet_number,
495 "The number for a bracket.",
498 "avoid-slur " // UGH.