]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
f6dea51ba32d241cbd7fb2fa372500530340b803
[lilypond.git] / lily / tuplet-bracket.cc
1 /*
2   tuplet-bracket.cc -- implement Tuplet_bracket
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2007 Jan Nieuwenhuizen <janneke@gnu.org>
7   Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
9
10 /*
11   TODO:
12
13   - tuplet bracket should probably be subject to the same rules as
14   beam sloping/quanting.
15
16   - There is no support for kneed brackets, or nested brackets.
17
18   - number placement for parallel beams should be much more advanced:
19   for sloped beams some extra horizontal offset must be introduced.
20
21   - number placement is usually done over the center note, not the
22   graphical center.
23 */
24
25 /*
26   TODO: quantise, we don't want to collide with staff lines.
27   (or should we be above staff?)
28
29   todo: handle breaking elegantly.
30 */
31
32
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
35 #include "beam.hh"
36 #include "warn.hh"
37 #include "output-def.hh"
38 #include "font-interface.hh"
39 #include "text-interface.hh"
40 #include "stem.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
44 #include "spanner.hh"
45 #include "staff-symbol-referencer.hh"
46 #include "lookup.hh"
47 #include "paper-column.hh"
48 #include "moment.hh"
49
50 static Item *
51 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
52 {
53   Spanner *me = dynamic_cast<Spanner *> (me_grob);
54   Item *g = me->get_bound (hdir);
55   if (Note_column::has_interface (g)
56       && Note_column::get_stem (g)
57       && Note_column::dir (g) == my_dir)
58     g = Note_column::get_stem (g);
59
60   return g;
61 }
62
63
64 void
65 flatten_number_pair_property (Grob *me,
66                               Direction xdir,  SCM sym)
67 {
68   Drul_array<Real> zero (0, 0);
69   Drul_array<Real> pair
70     = robust_scm2drul (me->internal_get_property (sym), zero);
71   pair[xdir] = 0.0;
72   
73   me->set_property (sym, ly_interval2scm (pair));
74 }
75
76
77 /*
78   Return beam that encompasses the span of the tuplet bracket.
79 */
80 Grob *
81 Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols,
82                                bool *equally_long)
83 {
84   Spanner *me = dynamic_cast<Spanner *> (me_grob);
85
86   if (me->get_bound (LEFT)->break_status_dir ()
87       || me->get_bound (RIGHT)->break_status_dir ())
88     return 0;
89
90   Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
91                            Note_column::get_stem (cols.back ()));
92
93   if (!stems[RIGHT]
94       || !stems[LEFT]
95       || (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
96           != me->get_bound (RIGHT)->get_column ()))
97     return 0;
98
99   Drul_array<Grob*> beams;
100   Direction d = LEFT;
101   do {
102     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
103   } while (flip (&d) != LEFT);
104   
105   *equally_long = false;
106   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
107     return 0;
108
109   extract_grob_set (beams[LEFT], "stems", beam_stems);
110   if (beam_stems.size () == 0)
111     {
112       programming_error ("beam under tuplet bracket has no stems");
113       *equally_long = 0;
114       return 0;
115     }
116
117   *equally_long =
118     (beam_stems[0] == stems[LEFT]
119      && beam_stems.back () == stems[RIGHT]);
120   return beams[LEFT];
121 }
122
123
124 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_connect_to_neighbors,1);
125 SCM
126 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
127 {
128   Spanner *me = unsmob_spanner (smob);
129
130   Direction dir = get_grob_direction (me); 
131   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
132                              get_x_bound_item (me, RIGHT, dir));
133   
134   Drul_array<bool> connect_to_other (false, false);
135   Direction d = LEFT;
136   do
137     {
138       Direction break_dir = bounds[d]->break_status_dir ();
139       Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
140       vsize neighbor_idx = me->get_break_index () - break_dir;
141       if (break_dir
142           && d == RIGHT
143           && neighbor_idx < orig_spanner->broken_intos_.size ())
144         {
145           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
146
147           /* trigger possible suicide*/
148           (void) neighbor->get_property ("positions");
149         }
150
151       connect_to_other[d]
152         = (break_dir
153            && neighbor_idx < orig_spanner->broken_intos_.size ()
154            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
155     }
156   while (flip (&d) != LEFT);
157
158
159   if (connect_to_other[LEFT] || connect_to_other[RIGHT])
160     return scm_cons (scm_from_bool (connect_to_other[LEFT]),
161                      scm_from_bool (connect_to_other[RIGHT]));
162                      
163   return SCM_EOL;
164 }
165
166 Grob* 
167 Tuplet_bracket::get_common_x (Spanner *me)
168 {
169   extract_grob_set (me, "note-columns", columns);
170
171   Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
172   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
173   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
174
175   return commonx;
176 }
177   
178 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_control_points,1)
179 SCM
180 Tuplet_bracket::calc_control_points (SCM smob)
181 {
182   Spanner *me = unsmob_spanner (smob);
183
184   extract_grob_set (me, "note-columns", columns);
185
186   SCM scm_positions = me->get_property ("positions");
187   if (!me->is_live ())
188     return SCM_EOL;
189   
190   if (!scm_is_pair (scm_positions))
191     programming_error ("Positions should be number pair");
192     
193   Drul_array<Real> positions
194     = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
195
196   Grob *commonx = get_common_x (me);
197   Direction dir = get_grob_direction (me);
198
199   Drul_array<Item *> bounds;
200   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
201   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
202
203   Drul_array<bool> connect_to_other =
204     robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
205                          Drul_array<bool> (false, false));
206   
207     
208   Interval x_span;
209   Direction d = LEFT;
210   do
211     {
212       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
213
214       if (connect_to_other[d])
215         {
216           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
217                                                Interval (-0.5, 0.0)));
218
219           if (d == RIGHT)
220             x_span[d] += d * overshoot[d];
221           else
222             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
223               - overshoot[LEFT];
224         }
225       
226       else if (d == RIGHT
227                && (columns.empty ()
228                    || (bounds[d]->get_column ()
229                        != dynamic_cast<Item *> (columns.back ())->get_column ())))
230         {
231           /*
232             We're connecting to a column, for the last bit of a broken
233             fullLength bracket.
234           */
235           Real padding =
236             robust_scm2double(me->get_property("full-length-padding"), 1.0);
237
238           if (bounds[d]->break_status_dir ())
239             padding = 0.0;
240
241           x_span[d] = bounds[d]->relative_coordinate(commonx, X_AXIS) - padding;
242         }
243     }
244   while (flip (&d) != LEFT);
245
246   
247   
248   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
249   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
250                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
251 }
252
253 /*
254   TODO:
255
256   in the case that there is no bracket, but there is a (single) beam,
257   follow beam precisely for determining tuplet number location.
258 */
259 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
260 SCM
261 Tuplet_bracket::print (SCM smob)
262 {
263   Spanner *me = unsmob_spanner (smob);
264   Stencil mol;
265
266   extract_grob_set (me, "note-columns", columns);
267   bool equally_long = false;
268   Grob *par_beam = parallel_beam (me, columns, &equally_long);
269   
270   bool bracket_visibility = !(par_beam && equally_long);
271   /*
272     Fixme: the type of this prop is sucky.
273   */
274   SCM bracket = me->get_property ("bracket-visibility");
275   if (scm_is_bool (bracket))
276     bracket_visibility = ly_scm2bool (bracket);
277   else if (bracket == ly_symbol2scm ("if-no-beam"))
278     bracket_visibility = !par_beam;
279   
280   /* Don't print a tuplet bracket and number if no control-points were calculated */
281   SCM cpoints =  me->get_property ("control-points");
282   if (scm_ilength (cpoints) < 2)
283     {
284       me->suicide ();
285       return SCM_EOL;
286     }
287   /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
288       the bracket, but still let the number be displayed */
289   if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
290       == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
291   {
292       bracket_visibility = false;
293   }
294   
295   Drul_array<Offset> points;
296   points[LEFT] = ly_scm2offset (scm_car (cpoints));
297   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
298   
299   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
300   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
301
302   Output_def *pap = me->layout ();
303
304   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
305   
306   /*
307     No bracket when it would be smaller than the number.
308   */
309   Real gap = 0.;
310   if (bracket_visibility && number_grob)
311     {
312       Interval ext = number_grob->extent (number_grob, X_AXIS);
313       if (!ext.is_empty ())
314         {
315           gap = ext.length () + 1.0;
316       
317           if (0.75 * x_span.length () < gap)
318             bracket_visibility = false;
319         }
320     }
321
322   if (bracket_visibility)
323     {
324       Drul_array<Real> zero (0, 0);
325       Real ss = Staff_symbol_referencer::staff_space (me);
326       Drul_array<Real> height
327         = robust_scm2drul (me->get_property ("edge-height"), zero);
328       Drul_array<Real> flare
329         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
330       Drul_array<Real> shorten
331         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
332       Drul_array<Stencil> edge_stencils;
333
334       Direction dir = get_grob_direction (me);
335       
336       scale_drul (&height, -ss * dir);
337       scale_drul (&flare, ss);
338       scale_drul (&shorten, ss);
339
340       Drul_array<bool> connect_to_other =
341         robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
342                              Drul_array<bool> (false, false));
343
344       Direction d = LEFT;
345       do
346         {
347           if (connect_to_other[d])
348             {
349               height[d] = 0.0;
350               flare[d] = 0.0;
351               shorten[d] = 0.0;
352
353               SCM edge_text = me->get_property ("edge-text");
354
355               if (scm_is_pair (edge_text))
356                 {
357                   SCM properties = Font_interface::text_font_alist_chain (me);
358                   SCM text = index_get_cell (edge_text, d);
359                   if (Text_interface::is_markup (text))
360                     {
361                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
362                                                                 properties, text);
363
364                       Stencil *edge_text = unsmob_stencil (t);
365                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
366                       edge_stencils[d] = *edge_text;
367                     }
368                 }
369             }
370         }
371       while (flip (&d) != LEFT);
372
373       Stencil brack = make_bracket (me, Y_AXIS,
374                                     points[RIGHT] - points[LEFT],
375                                     height,
376                                     /*
377                                       0.1 = more space at right due to italics
378                                       TODO: use italic correction of font.
379                                     */
380                                     Interval (-0.5, 0.5) * gap + 0.1,
381                                     flare, shorten);
382
383       do
384         {
385           if (!edge_stencils[d].is_empty ())
386             brack.add_stencil (edge_stencils[d]);
387         }
388       while (flip (&d) != LEFT);
389
390       mol.add_stencil (brack);
391     }
392
393   mol.translate (points[LEFT]);
394   return mol.smobbed_copy ();
395 }
396
397 /*
398   should move to lookup?
399
400   TODO: this will fail for very short (shorter than the flare)
401   brackets.
402 */
403 Stencil
404 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
405                               Axis protusion_axis,
406                               Offset dz,
407                               Drul_array<Real> height,
408                               Interval gap,
409                               Drul_array<Real> flare,
410                               Drul_array<Real> shorten)
411 {
412   Drul_array<Offset> corners (Offset (0, 0), dz);
413
414   Real length = dz.length ();
415   Drul_array<Offset> gap_corners;
416
417   Axis bracket_axis = other_axis (protusion_axis);
418
419   Drul_array<Offset> straight_corners = corners;
420
421   Direction d = LEFT;
422   do
423     straight_corners[d] += -d * shorten[d] / length * dz;
424   while (flip (&d) != LEFT);
425
426   if (!gap.is_empty ())
427     {
428       do
429         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
430       while (flip (&d) != LEFT);
431     }
432
433   Drul_array<Offset> flare_corners = straight_corners;
434   do
435     {
436       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
437       flare_corners[d][protusion_axis] += height[d];
438       straight_corners[d][bracket_axis] += -d * flare[d];
439     }
440   while (flip (&d) != LEFT);
441
442   Stencil m;
443   do
444     {
445       if (!gap.is_empty ())
446         m.add_stencil (Line_interface::line (me, straight_corners[d],
447                                              gap_corners[d]));
448
449       m.add_stencil (Line_interface::line (me, straight_corners[d],
450                                            flare_corners[d]));
451     }
452
453   while (flip (&d) != LEFT);
454
455   if (gap.is_empty ())
456     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
457                                          straight_corners[RIGHT]));
458   
459   return m;
460 }
461
462 void
463 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
464 {
465   extract_grob_set (me, "note-columns", columns);
466   vsize l = 0;
467   while (l < columns.size () && Note_column::has_rests (columns[l]))
468     l++;
469
470   vsize r = columns.size ();
471   while (r > l && Note_column::has_rests (columns[r-1]))
472     r--;
473
474   *left = *right = 0;
475
476   if (l < r)
477     {
478       *left = columns[l];
479       *right = columns[r-1];
480     }
481 }
482
483 /*
484   use first -> last note for slope, and then correct for disturbing
485   notes in between.  */
486 void
487 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
488 {
489   Spanner *me = dynamic_cast<Spanner *> (me_grob);
490
491   extract_grob_set (me, "note-columns", columns);
492   extract_grob_set (me, "tuplets", tuplets);
493
494   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
495   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
496   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
497     commony = st->common_refpoint (commony, Y_AXIS);
498   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
499
500   Grob *commonx = get_common_x (me);
501   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
502
503   Interval staff;
504   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
505
506   /* staff-padding doesn't work correctly on cross-staff tuplets
507      because it only considers one staff symbol. Until this works,
508      disable it. */
509   if (st && !to_boolean (me->get_property ("cross-staff")))
510     {
511       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
512       if  (pad >= 0.0)
513         {
514           staff = st->extent (commony, Y_AXIS) - my_offset;
515           staff.widen (pad);
516         }
517     }
518   
519   Direction dir = get_grob_direction (me);
520
521   bool equally_long = false;
522   Grob *par_beam = parallel_beam (me, columns, &equally_long);
523   
524   Item *lgr = get_x_bound_item (me, LEFT, dir);
525   Item *rgr = get_x_bound_item (me, RIGHT, dir);
526   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
527   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
528   bool follow_beam = par_beam
529     && get_grob_direction (par_beam) == dir
530     && ! to_boolean (par_beam->get_property ("knee"));
531
532   vector<Offset> points;
533   if (columns.size ()
534       && follow_beam
535       && Note_column::get_stem (columns[0])
536       && Note_column::get_stem (columns.back ()))
537     {
538       /*
539         trigger set_stem_ends
540        */
541       (void) par_beam->get_property ("quantized-positions");
542
543       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
544                                 Note_column::get_stem (columns.back ()));
545
546       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
547       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
548         + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
549       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
550         + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
551
552       *dy = rp - lp;
553       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
554       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
555     }
556   else
557     {
558       /*
559         Use outer non-rest columns to determine slope
560       */
561       Grob *left_col = 0;
562       Grob *right_col = 0;
563       get_bounds (me, &left_col, &right_col);
564       if (left_col && right_col)
565         {
566           Interval rv = Note_column::cross_staff_extent (right_col, commony);
567           Interval lv = Note_column::cross_staff_extent (left_col, commony);
568           rv.unite (staff);
569           lv.unite (staff);
570
571           Real graphical_dy = rv[dir] - lv[dir];
572
573           Slice ls = Note_column::head_positions_interval (left_col);
574           Slice rs = Note_column::head_positions_interval (right_col);
575
576           Interval musical_dy;
577           musical_dy[UP] = rs[UP] - ls[UP];
578           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
579           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
580             *dy = 0.0;
581           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
582             *dy = 0.0;
583           else
584             *dy = graphical_dy;
585         }
586       else
587         *dy = 0;
588
589       for (vsize i = 0; i < columns.size (); i++)
590         {
591           Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
592           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
593
594           points.push_back (Offset (x, note_ext[dir]));
595         }
596     }
597
598   if (!follow_beam)
599     {
600       points.push_back (Offset (x0 - x0, staff[dir]));
601       points.push_back (Offset (x1 - x0, staff[dir]));
602     }
603   
604   /*
605     This is a slight hack. We compute two encompass points from the
606     bbox of the smaller tuplets.
607
608     We assume that the smaller bracket is 1.0 space high.
609   */
610   Real ss = Staff_symbol_referencer::staff_space (me);
611   for (vsize i = 0; i < tuplets.size (); i++)
612     {
613       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
614       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
615
616       if (!tuplets[i]->is_live ())
617         continue;
618       
619       Direction d = LEFT;
620       Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
621                                                         Interval (0,0));
622
623       
624       Real other_dy = positions[RIGHT] - positions[LEFT];
625
626       do
627         {
628           Real y
629             = tuplet_y.linear_combination (d * sign (other_dy));
630
631           /*
632             We don't take padding into account for nested tuplets.
633             the edges can come very close to the stems, likewise for
634             nested tuplets?
635           */
636
637           points.push_back (Offset (tuplet_x[d] - x0, y));
638         }
639       while (flip (&d) != LEFT);
640     }
641
642   *offset = -dir * infinity_f;
643   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
644   for (vsize i = 0; i < points.size (); i++)
645     {
646       Real x = points[i][X_AXIS];
647       Real tuplety = (*dy) * x * factor + my_offset;
648
649       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
650         *offset = points[i][Y_AXIS] - tuplety;
651     }
652
653   *offset += scm_to_double (me->get_property ("padding")) * dir;
654
655   /*
656     horizontal brackets should not collide with staff lines.
657
658     Kind of pointless since we put them outside the staff anyway, but
659     let's leave code for the future when possibly allow them to move
660     into the staff once again.
661   
662     This doesn't seem to support cross-staff tuplets atm.
663   */
664   if (*dy == 0
665       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
666     {
667       // quantize, then do collision check.
668       *offset *= 2 / ss;
669
670       *offset = rint (*offset);
671       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
672         *offset += dir;
673
674       *offset *= 0.5 * ss;
675     }
676 }
677
678
679 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
680 SCM
681 Tuplet_bracket::calc_direction (SCM smob)
682 {
683   Grob *me = unsmob_grob (smob);
684   Direction dir = Tuplet_bracket::get_default_dir (me);
685   return scm_from_int (dir);
686 }
687
688 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
689 SCM
690 Tuplet_bracket::calc_positions (SCM smob)
691 {
692   Spanner *me = unsmob_spanner (smob);
693
694   Real dy = 0.0;
695   Real offset = 0.0;
696   calc_position_and_height (me, &offset, &dy);
697   
698   SCM x = scm_cons (scm_from_double (offset),
699                     scm_from_double (offset + dy));
700   
701   return x;
702 }
703
704 /*
705   similar to beam ?
706 */
707 Direction
708 Tuplet_bracket::get_default_dir (Grob *me)
709 {
710   Drul_array<int> dirs (0, 0);
711   extract_grob_set (me, "note-columns", columns);
712   for (vsize i = 0; i < columns.size (); i++)
713     {
714       Grob *nc = columns[i];
715       Direction d = Note_column::dir (nc);
716       if (d)
717         dirs[d]++;
718     }
719
720   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
721 }
722
723 void
724 Tuplet_bracket::add_column (Grob *me, Item *n)
725 {
726   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
727   add_bound_item (dynamic_cast<Spanner *> (me), n);
728 }
729
730 void
731 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
732 {
733   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
734 }
735
736 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
737 SCM
738 Tuplet_bracket::calc_cross_staff (SCM smob)
739 {
740   Grob *me = unsmob_grob (smob);
741   Grob *staff_symbol = 0;
742   extract_grob_set (me, "note-columns", cols);
743   bool equally_long = false;
744   Grob *par_beam = parallel_beam (me, cols, &equally_long);
745
746   if (par_beam)
747     return par_beam->get_property ("cross-staff");
748
749   for (vsize i = 0; i < cols.size (); i++)
750     {
751       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
752       if (!stem)
753         continue;
754       
755       if (to_boolean (stem->get_property ("cross-staff")))
756         return SCM_BOOL_T;
757
758       Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
759       if (staff_symbol && (stem_staff != staff_symbol))
760         return SCM_BOOL_T;
761       staff_symbol = stem_staff;
762     }
763   return SCM_BOOL_F;
764 }
765
766 ADD_INTERFACE (Tuplet_bracket,
767                "A bracket with a number in the middle, used for tuplets."
768                "  When the bracket spans a line break, the value of"
769                " @code{break-overshoot} determines how far it extends"
770                " beyond the staff.  At a line break, the markups in the"
771                " @code{edge-text} are printed at the edges.",
772
773                /* properties */
774                "bracket-flare "
775                "bracket-visibility "
776                "break-overshoot "
777                "connect-to-neighbor "
778                "control-points "
779                "direction "
780                "edge-height "
781                "edge-text "
782                "full-length-padding "
783                "gap "
784                "positions "
785                "note-columns "
786                "padding "
787                "tuplet-number "
788                "shorten-pair "
789                "staff-padding "
790                "thickness "
791                "tuplets "
792                );
793
794