]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-number.cc
Web-ja: update introduction
[lilypond.git] / lily / tuplet-number.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2005--2015 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 || !Beam::is_knee (beam))
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 (has_interface<Note_column> (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_is_true (ly_memv (scm_car (s), scm_cdr (beaming))))
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   /*
366     First, we calculate the Y-offset of the tuplet number as if it
367     is positioned at the reference stem.
368   */
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);
375
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);
379
380   Real y_offset = ref_stem_ext[ref_stem_dir] - tuplet_y;
381
382   /*
383     Additional displacement for French beaming.
384   */
385   if (to_boolean (ref_stem->get_property ("french-beaming")))
386     {
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)
392                   * beam_translation;
393     }
394
395   Real padding = robust_scm2double (me->get_property ("padding"), 0.5);
396   Real num_height = me->extent (commony, Y_AXIS).length ();
397
398   y_offset += ref_stem_dir * (padding + num_height / 2.0);
399
400   /*
401     Now we adjust the vertical position of the number to reflect
402     its actual horizontal placement along the beam.
403   */
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);
406
407   /*
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.
410   */
411   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (ref_stem))
412     {
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];
417       if (move)
418         {
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)
430               && move)
431             y_offset += staff_ext_y[-ref_stem_dir] - num_y[-ref_stem_dir]
432                         + line_thickness * ref_stem_dir;
433         }
434     }
435
436   /*
437     Now consider possible collisions with accidentals on the right.  We
438     move the accidental away from the beam.
439   */
440   Drul_array<Grob *> adj_cols = adjacent_note_columns (me, ref_stem);
441
442   if (!adj_cols[RIGHT])
443     return scm_from_double (y_offset);
444
445   /*
446     Collect Y-extents of accidentals that overlap the number
447     along the X-axis.
448   */
449   extract_grob_set (adj_cols[RIGHT], "note-heads", heads);
450   Interval colliding_acc_ext_y;
451
452   for (vsize i = 0; i < heads.size (); i++)
453     if (Grob *acc = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
454       {
455         commony = commony->common_refpoint (acc, Y_AXIS);
456         Interval acc_ext_y = acc->extent (commony, Y_AXIS);
457
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);
464
465         if (!overlap_x.is_empty ())
466           colliding_acc_ext_y.unite (acc_ext_y);
467       }
468   /*
469     Does our number intersect vertically with the accidental Y-extents we
470     combined above?  If so, move it.
471   */
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);
476
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;
479
480   return scm_from_double (y_offset);
481 }
482
483 MAKE_SCHEME_CALLBACK (Tuplet_number, calc_cross_staff, 1)
484 SCM
485 Tuplet_number::calc_cross_staff (SCM smob)
486 {
487   Grob *me = unsmob<Grob> (smob);
488   return unsmob<Grob> (me->get_object ("bracket"))->get_property ("cross-staff");
489 }
490
491 ADD_INTERFACE (Tuplet_number,
492                "The number for a bracket.",
493
494                /* properties */
495                "avoid-slur "    // UGH.
496                "bracket "
497                "direction "
498                "knee-to-beam "
499               );
500