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