]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
Second attempt to fix tuplet numbers for single-note tuplets
[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             TODO: make padding tunable?
236           */
237           Real padding = 1.0;
238
239           if (bounds[d]->break_status_dir ())
240             padding = 0.0;
241           
242           x_span[d]
243             = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
244             - padding;
245         }
246     }
247   while (flip (&d) != LEFT);
248
249   
250   
251   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
252   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
253                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
254 }
255
256 /*
257   TODO:
258
259   in the case that there is no bracket, but there is a (single) beam,
260   follow beam precisely for determining tuplet number location.
261 */
262 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
263 SCM
264 Tuplet_bracket::print (SCM smob)
265 {
266   Spanner *me = unsmob_spanner (smob);
267   Stencil mol;
268
269   extract_grob_set (me, "note-columns", columns);
270   bool equally_long = false;
271   Grob *par_beam = parallel_beam (me, columns, &equally_long);
272   
273   bool bracket_visibility = !(par_beam && equally_long);
274   /*
275     Fixme: the type of this prop is sucky.
276   */
277   SCM bracket = me->get_property ("bracket-visibility");
278   if (scm_is_bool (bracket))
279     bracket_visibility = ly_scm2bool (bracket);
280   else if (bracket == ly_symbol2scm ("if-no-beam"))
281     bracket_visibility = !par_beam;
282   
283   /* Don't print a tuplet bracket and number if no control-points were calculated */
284   SCM cpoints =  me->get_property ("control-points");
285   if (scm_ilength (cpoints) < 2 )
286     {
287       me->suicide ();
288       return SCM_EOL;
289     }
290   /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
291       the bracket, but still let the number be displayed */
292   if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
293       == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
294   {
295       bracket_visibility = false;
296   }
297   
298   Drul_array<Offset> points;
299   points[LEFT] = ly_scm2offset (scm_car (cpoints));
300   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
301   
302   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
303   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
304
305   Output_def *pap = me->layout ();
306
307   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
308   
309   /*
310     No bracket when it would be smaller than the number.
311   */
312   Real gap = 0.;
313   if (bracket_visibility && number_grob)
314     {
315       Interval ext = number_grob->extent (number_grob, X_AXIS);
316       if (!ext.is_empty ())
317         {
318           gap = ext.length () + 1.0;
319       
320           if (0.75 * x_span.length () < gap)
321             bracket_visibility = false;
322         }
323     }
324
325   if (bracket_visibility)
326     {
327       Drul_array<Real> zero (0, 0);
328       Real ss = Staff_symbol_referencer::staff_space (me);
329       Drul_array<Real> height
330         = robust_scm2drul (me->get_property ("edge-height"), zero);
331       Drul_array<Real> flare
332         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
333       Drul_array<Real> shorten
334         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
335       Drul_array<Stencil> edge_stencils;
336
337       Direction dir = get_grob_direction (me);
338       
339       scale_drul (&height, -ss * dir);
340       scale_drul (&flare, ss);
341       scale_drul (&shorten, ss);
342
343       Drul_array<bool> connect_to_other =
344         robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
345                              Drul_array<bool> (false, false));
346
347       Direction d = LEFT;
348       do
349         {
350           if (connect_to_other[d])
351             {
352               height[d] = 0.0;
353               flare[d] = 0.0;
354               shorten[d] = 0.0;
355
356               SCM edge_text = me->get_property ("edge-text");
357
358               if (scm_is_pair (edge_text))
359                 {
360                   SCM properties = Font_interface::text_font_alist_chain (me);
361                   SCM text = index_get_cell (edge_text, d);
362                   if (Text_interface::is_markup (text))
363                     {
364                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
365                                                                 properties, text);
366
367                       Stencil *edge_text = unsmob_stencil (t);
368                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
369                       edge_stencils[d] = *edge_text;
370                     }
371                 }
372             }
373         }
374       while (flip (&d) != LEFT);
375
376       Stencil brack = make_bracket (me, Y_AXIS,
377                                     points[RIGHT] - points[LEFT],
378                                     height,
379                                     /*
380                                       0.1 = more space at right due to italics
381                                       TODO: use italic correction of font.
382                                     */
383                                     Interval (-0.5, 0.5) * gap + 0.1,
384                                     flare, shorten);
385
386       do
387         {
388           if (!edge_stencils[d].is_empty ())
389             brack.add_stencil (edge_stencils[d]);
390         }
391       while (flip (&d) != LEFT);
392
393       mol.add_stencil (brack);
394     }
395
396   mol.translate (points[LEFT]);
397   return mol.smobbed_copy ();
398 }
399
400 /*
401   should move to lookup?
402
403   TODO: this will fail for very short (shorter than the flare)
404   brackets.
405 */
406 Stencil
407 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
408                               Axis protusion_axis,
409                               Offset dz,
410                               Drul_array<Real> height,
411                               Interval gap,
412                               Drul_array<Real> flare,
413                               Drul_array<Real> shorten)
414 {
415   Drul_array<Offset> corners (Offset (0, 0), dz);
416
417   Real length = dz.length ();
418   Drul_array<Offset> gap_corners;
419
420   Axis bracket_axis = other_axis (protusion_axis);
421
422   Drul_array<Offset> straight_corners = corners;
423
424   Direction d = LEFT;
425   do
426     straight_corners[d] += -d * shorten[d] / length * dz;
427   while (flip (&d) != LEFT);
428
429   if (!gap.is_empty ())
430     {
431       do
432         gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
433       while (flip (&d) != LEFT);
434     }
435
436   Drul_array<Offset> flare_corners = straight_corners;
437   do
438     {
439       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
440       flare_corners[d][protusion_axis] += height[d];
441       straight_corners[d][bracket_axis] += -d * flare[d];
442     }
443   while (flip (&d) != LEFT);
444
445   Stencil m;
446   do
447     {
448       if (!gap.is_empty ())
449         m.add_stencil (Line_interface::line (me, straight_corners[d],
450                                              gap_corners[d]));
451
452       m.add_stencil (Line_interface::line (me, straight_corners[d],
453                                            flare_corners[d]));
454     }
455
456   while (flip (&d) != LEFT);
457
458   if (gap.is_empty ())
459     m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
460                                          straight_corners[RIGHT]));
461   
462   return m;
463 }
464
465 void
466 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
467 {
468   extract_grob_set (me, "note-columns", columns);
469   vsize l = 0;
470   while (l < columns.size () && Note_column::has_rests (columns[l]))
471     l++;
472
473   vsize r = columns.size ();
474   while (r > l && Note_column::has_rests (columns[r-1]))
475     r--;
476
477   *left = *right = 0;
478
479   if (l < r)
480     {
481       *left = columns[l];
482       *right = columns[r-1];
483     }
484 }
485
486 /*
487   use first -> last note for slope, and then correct for disturbing
488   notes in between.  */
489 void
490 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
491 {
492   Spanner *me = dynamic_cast<Spanner *> (me_grob);
493
494   extract_grob_set (me, "note-columns", columns);
495   extract_grob_set (me, "tuplets", tuplets);
496
497   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
498   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
499   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
500     commony = st->common_refpoint (commony, Y_AXIS);
501   Real my_offset = me->relative_coordinate (commony, Y_AXIS);
502
503   Grob *commonx = get_common_x (me);
504   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
505
506   Interval staff;
507   Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
508
509   /* staff-padding doesn't work correctly on cross-staff tuplets
510      because it only considers one staff symbol. Until this works,
511      disable it. */
512   if (st && !to_boolean (me->get_property ("cross-staff")))
513     {
514       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
515       if  (pad >= 0.0)
516         {
517           staff = st->extent (commony, Y_AXIS) - my_offset;
518           staff.widen (pad);
519         }
520     }
521   
522   Direction dir = get_grob_direction (me);
523
524   bool equally_long = false;
525   Grob *par_beam = parallel_beam (me, columns, &equally_long);
526   
527   Item *lgr = get_x_bound_item (me, LEFT, dir);
528   Item *rgr = get_x_bound_item (me, RIGHT, dir);
529   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
530   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
531   bool follow_beam = par_beam
532     && get_grob_direction (par_beam) == dir
533     && ! to_boolean (par_beam->get_property ("knee"));
534
535   vector<Offset> points;
536   if (columns.size ()
537       && follow_beam
538       && Note_column::get_stem (columns[0])
539       && Note_column::get_stem (columns.back ()))
540     {
541       /*
542         trigger set_stem_ends
543        */
544       (void) par_beam->get_property ("quantized-positions");
545
546       Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
547                                 Note_column::get_stem (columns.back ()));
548
549       Real ss = 0.5 * Staff_symbol_referencer::staff_space (me);
550       Real lp = ss * robust_scm2double (stems[LEFT]->get_property ("stem-end-position"), 0.0)
551         + stems[LEFT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
552       Real rp = ss * robust_scm2double (stems[RIGHT]->get_property ("stem-end-position"), 0.0)
553         + stems[RIGHT]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
554
555       *dy = rp - lp;
556       points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, lp));
557       points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, rp));
558     }
559   else
560     {
561       /*
562         Use outer non-rest columns to determine slope
563       */
564       Grob *left_col = 0;
565       Grob *right_col = 0;
566       get_bounds (me, &left_col, &right_col);
567       if (left_col && right_col)
568         {
569           Interval rv = Note_column::cross_staff_extent (right_col, commony);
570           Interval lv = Note_column::cross_staff_extent (left_col, commony);
571           rv.unite (staff);
572           lv.unite (staff);
573
574           Real graphical_dy = rv[dir] - lv[dir];
575
576           Slice ls = Note_column::head_positions_interval (left_col);
577           Slice rs = Note_column::head_positions_interval (right_col);
578
579           Interval musical_dy;
580           musical_dy[UP] = rs[UP] - ls[UP];
581           musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
582           if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
583             *dy = 0.0;
584           else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
585             *dy = 0.0;
586           else
587             *dy = graphical_dy;
588         }
589       else
590         *dy = 0;
591
592       for (vsize i = 0; i < columns.size (); i++)
593         {
594           Interval note_ext = Note_column::cross_staff_extent (columns[i], commony);
595           Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
596
597           points.push_back (Offset (x, note_ext[dir]));
598         }
599     }
600
601   if (!follow_beam)
602     {
603       points.push_back (Offset (x0 - x0, staff[dir]));
604       points.push_back (Offset (x1 - x0, staff[dir]));
605     }
606   
607   /*
608     This is a slight hack. We compute two encompass points from the
609     bbox of the smaller tuplets.
610
611     We assume that the smaller bracket is 1.0 space high.
612   */
613   Real ss = Staff_symbol_referencer::staff_space (me);
614   for (vsize i = 0; i < tuplets.size (); i++)
615     {
616       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
617       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
618
619       if (!tuplets[i]->is_live ())
620         continue;
621       
622       Direction d = LEFT;
623       Drul_array<Real> positions = robust_scm2interval (tuplets[i]->get_property ("positions"),
624                                                         Interval (0,0));
625
626       
627       Real other_dy = positions[RIGHT] - positions[LEFT];
628
629       do
630         {
631           Real y
632             = tuplet_y.linear_combination (d * sign (other_dy));
633
634           /*
635             We don't take padding into account for nested tuplets.
636             the edges can come very close to the stems, likewise for
637             nested tuplets?
638           */
639
640           points.push_back (Offset (tuplet_x[d] - x0, y));
641         }
642       while (flip (&d) != LEFT);
643     }
644
645   *offset = -dir * infinity_f;
646   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
647   for (vsize i = 0; i < points.size (); i++)
648     {
649       Real x = points[i][X_AXIS];
650       Real tuplety = (*dy) * x * factor + my_offset;
651
652       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
653         *offset = points[i][Y_AXIS] - tuplety;
654     }
655
656   *offset += scm_to_double (me->get_property ("padding")) * dir;
657
658   /*
659     horizontal brackets should not collide with staff lines.
660
661     Kind of pointless since we put them outside the staff anyway, but
662     let's leave code for the future when possibly allow them to move
663     into the staff once again.
664   
665     This doesn't seem to support cross-staff tuplets atm.
666   */
667   if (*dy == 0
668       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
669     {
670       // quantize, then do collision check.
671       *offset *= 2 / ss;
672
673       *offset = rint (*offset);
674       if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
675         *offset += dir;
676
677       *offset *= 0.5 * ss;
678     }
679 }
680
681
682 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
683 SCM
684 Tuplet_bracket::calc_direction (SCM smob)
685 {
686   Grob *me = unsmob_grob (smob);
687   Direction dir = Tuplet_bracket::get_default_dir (me);
688   return scm_from_int (dir);
689 }
690
691 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
692 SCM
693 Tuplet_bracket::calc_positions (SCM smob)
694 {
695   Spanner *me = unsmob_spanner (smob);
696
697   Real dy = 0.0;
698   Real offset = 0.0;
699   calc_position_and_height (me, &offset, &dy);
700   
701   SCM x = scm_cons (scm_from_double (offset),
702                     scm_from_double (offset + dy));
703   
704   return x;
705 }
706
707 /*
708   similar to beam ?
709 */
710 Direction
711 Tuplet_bracket::get_default_dir (Grob *me)
712 {
713   Drul_array<int> dirs (0, 0);
714   extract_grob_set (me, "note-columns", columns);
715   for (vsize i = 0; i < columns.size (); i++)
716     {
717       Grob *nc = columns[i];
718       Direction d = Note_column::dir (nc);
719       if (d)
720         dirs[d]++;
721     }
722
723   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
724 }
725
726 void
727 Tuplet_bracket::add_column (Grob *me, Item *n)
728 {
729   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
730   add_bound_item (dynamic_cast<Spanner *> (me), n);
731 }
732
733 void
734 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
735 {
736   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
737 }
738
739 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
740 SCM
741 Tuplet_bracket::calc_cross_staff (SCM smob)
742 {
743   Grob *me = unsmob_grob (smob);
744   Grob *staff_symbol = 0;
745   extract_grob_set (me, "note-columns", cols);
746   bool equally_long = false;
747   Grob *par_beam = parallel_beam (me, cols, &equally_long);
748
749   if (par_beam)
750     return par_beam->get_property ("cross-staff");
751
752   for (vsize i = 0; i < cols.size (); i++)
753     {
754       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
755       if (!stem)
756         continue;
757       
758       if (to_boolean (stem->get_property ("cross-staff")))
759         return SCM_BOOL_T;
760
761       Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
762       if (staff_symbol && (stem_staff != staff_symbol))
763         return SCM_BOOL_T;
764       staff_symbol = stem_staff;
765     }
766   return SCM_BOOL_F;
767 }
768
769 ADD_INTERFACE (Tuplet_bracket,
770                "A bracket with a number in the middle, used for tuplets. "
771                "When the bracket spans  a line break, the value of "
772                "@code{break-overshoot} determines how far it extends "
773                "beyond the staff. "
774                "At a line break, the markups in the @code{edge-text} are printed "
775                "at the edges. ",
776
777
778                /* properties */
779                "bracket-flare "
780                "bracket-visibility "
781                "break-overshoot "
782                "connect-to-neighbor "
783                "control-points "
784                "direction "
785                "edge-height "
786                "edge-text "
787                "gap "
788                "positions "
789                "note-columns "
790                "padding "
791                "tuplet-number "
792                "shorten-pair "
793                "staff-padding "
794                "thickness "
795                "tuplets ");
796
797