]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
* Documentation/topdocs/NEWS.tely (Top): document new feature.
[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--2005 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
48 static Item *
49 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
50 {
51   Spanner *me = dynamic_cast<Spanner *> (me_grob);
52   Item *g = me->get_bound (hdir);
53   if (Note_column::has_interface (g)
54       && Note_column::get_stem (g)
55       && Note_column::dir (g) == my_dir)
56     g = Note_column::get_stem (g);
57
58   return g;
59 }
60
61
62 void
63 flatten_number_pair_property (Grob *me,
64                               Direction xdir,  SCM sym)
65 {
66   Drul_array<Real> zero (0, 0);
67   Drul_array<Real> pair = robust_scm2drul (me->internal_get_property (sym), zero);
68   pair[xdir] = 0.0;
69   
70   me->internal_set_property (sym, ly_interval2scm (pair));
71 }
72
73
74 Grob *
75 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
76 {
77   Spanner *me = dynamic_cast<Spanner *> (me_grob);
78
79   if (me->get_bound (LEFT)->break_status_dir ()
80       || me->get_bound (RIGHT)->break_status_dir ())
81     return 0;
82
83   Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
84                            Note_column::get_stem (cols.top ()));
85
86   if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
87       != me->get_bound (RIGHT)->get_column())
88     return 0;
89
90   Drul_array<Grob*> beams;
91   Direction d = LEFT;
92   do {
93     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
94   } while (flip (&d) != LEFT);
95   
96   *equally_long = false;
97   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
98     return 0;
99
100   extract_grob_set (beams[LEFT], "stems", beam_stems);
101   if (beam_stems.size () == 0)
102     {
103       programming_error ("beam under tuplet bracket has no stems");
104       *equally_long = 0;
105       return 0;
106     }
107
108   *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
109   return beams[LEFT];
110 }
111
112
113 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
114 SCM
115 Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
116 {
117   Spanner *me = unsmob_spanner (smob);
118
119   Direction dir = get_grob_direction (me); 
120   Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
121                              get_x_bound_item (me, RIGHT, dir));
122   
123   Drul_array<bool> connect_to_other (false, false);
124   Direction d = LEFT;
125   do
126     {
127       Direction break_dir = bounds[d]->break_status_dir ();
128       
129       Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
130
131       int neighbor_idx = me->get_break_index () - break_dir;
132       if (break_dir
133           && d == RIGHT
134           && neighbor_idx < orig_spanner->broken_intos_.size ())
135         {
136           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
137
138           /* trigger possible suicide*/
139           (void) neighbor->get_property ("positions");
140         }
141
142       connect_to_other[d]
143         = (break_dir
144            && (neighbor_idx < orig_spanner->broken_intos_.size ()
145                && neighbor_idx >= 0)
146            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
147     }
148   while (flip (&d) != LEFT);
149
150
151   if (connect_to_other[LEFT] || connect_to_other[RIGHT])
152     return scm_cons (scm_from_bool (connect_to_other[LEFT]),
153                      scm_from_bool (connect_to_other[RIGHT]));
154                      
155   return SCM_EOL;
156 }
157
158 Grob* 
159 Tuplet_bracket::get_common_x (Spanner *me)
160 {
161   extract_grob_set (me, "note-columns", columns);
162
163   Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
164   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
165   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
166
167   return commonx;
168 }
169   
170 MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
171 SCM
172 Tuplet_bracket::calc_control_points (SCM smob)
173 {
174   Spanner *me = unsmob_spanner (smob);
175
176   extract_grob_set (me, "note-columns", columns);
177
178   Drul_array<Real> positions
179     = ly_scm2realdrul (me->get_property ("positions"));
180
181   Grob *commonx = get_common_x (me);
182   Direction dir = get_grob_direction (me);
183
184   Drul_array<Item *> bounds;
185   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
186   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
187
188   Drul_array<bool> connect_to_other =
189     robust_scm2booldrul (me->get_property ("connect-to-other"),
190                          Drul_array<bool> (false, false));
191   
192     
193   Interval x_span;
194   Direction d = LEFT;
195   do
196     {
197       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
198
199       if (connect_to_other[d])
200         {
201           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
202                                                Interval (-0.5, 0.0)));
203
204           if (d == RIGHT)
205             x_span[d] += d * overshoot[d];
206           else
207             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
208               - overshoot[LEFT];
209         }
210       else if (d == RIGHT
211                && (columns.is_empty ()
212                    || (bounds[d]->get_column ()
213                        != dynamic_cast<Item *> (columns.top ())->get_column ())))
214         {
215           /*
216             TODO: make padding tunable?
217           */
218           Real padding = 1.0;
219
220           if (bounds[d]->break_status_dir ())
221             padding = 0.0;
222           
223           x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
224         }
225     }
226   while (flip (&d) != LEFT);
227
228   
229   
230   x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
231   return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
232                      ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
233 }
234
235 /*
236   TODO:
237
238   in the case that there is no bracket, but there is a (single) beam,
239   follow beam precisely for determining tuplet number location.
240 */
241 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
242 SCM
243 Tuplet_bracket::print (SCM smob)
244 {
245   Spanner *me = unsmob_spanner (smob);
246   Stencil mol;
247
248   extract_grob_set (me, "note-columns", columns);
249   bool equally_long = false;
250   Grob *par_beam = parallel_beam (me, columns, &equally_long);
251   
252   bool bracket_visibility = !(par_beam && equally_long);
253   /*
254     Fixme: the type of this prop is sucky.
255   */
256   SCM bracket = me->get_property ("bracket-visibility");
257   if (scm_is_bool (bracket))
258     bracket_visibility = ly_scm2bool (bracket);
259   else if (bracket == ly_symbol2scm ("if-no-beam"))
260     bracket_visibility = !par_beam;
261
262   
263   SCM cpoints =  me->get_property ("control-points");
264   if (scm_ilength (cpoints) < 2)
265     {
266       me->suicide ();
267       return SCM_EOL;
268     }
269   
270   Drul_array<Offset> points;
271   points[LEFT] = ly_scm2offset (scm_car (cpoints));
272   points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
273   
274   Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
275   Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
276
277
278
279   Output_def *pap = me->layout ();
280
281   Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
282   
283   /*
284     No bracket when it would be smaller than the number.
285   */
286   Real gap = 0.;
287   if (bracket_visibility && number_grob)
288     {
289       Interval ext = number_grob->extent (number_grob, X_AXIS);
290       if (!ext.is_empty ())
291         {
292           gap = ext.length () + 1.0;
293       
294           if (0.75 * x_span.length () < gap)
295             bracket_visibility = false;
296         }
297     }
298
299   if (bracket_visibility)
300     {
301       Drul_array<Real> zero (0, 0);
302       Real ss = Staff_symbol_referencer::staff_space (me);
303       Drul_array<Real> height
304         = robust_scm2drul (me->get_property ("edge-height"), zero);
305       Drul_array<Real> flare
306         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
307       Drul_array<Real> shorten
308         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
309       Drul_array<Stencil> edge_stencils;
310
311       Direction dir = get_grob_direction (me);
312       
313       scale_drul (&height, -ss * dir);
314       scale_drul (&flare, ss);
315       scale_drul (&shorten, ss);
316
317       Drul_array<bool> connect_to_other =
318         robust_scm2booldrul (me->get_property ("connect-to-other"),
319                              Drul_array<bool> (false, false));
320
321       Direction d = LEFT;
322       do
323         {
324           if (connect_to_other[d])
325             {
326               height[d] = 0.0;
327               flare[d] = 0.0;
328               shorten[d] = 0.0;
329
330               SCM edge_text = me->get_property ("edge-text");
331
332               if (scm_is_pair (edge_text))
333                 {
334                   SCM properties = Font_interface::text_font_alist_chain (me);
335                   SCM text = index_get_cell (edge_text, d);
336                   if (Text_interface::is_markup (text))
337                     {
338                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
339                                                                 properties, text);
340
341                       Stencil *edge_text = unsmob_stencil (t);
342                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
343                       edge_stencils[d] = *edge_text;
344                     }
345                 }
346             }
347         }
348       while (flip (&d) != LEFT);
349
350       Stencil brack = make_bracket (me, Y_AXIS,
351                                     points[RIGHT] - points[LEFT],
352                                     height,
353                                     /*
354                                       0.1 = more space at right due to italics
355                                       TODO: use italic correction of font.
356                                     */
357                                     Interval (-0.5, 0.5) * gap + 0.1,
358                                     flare, shorten);
359
360       do
361         {
362           if (!edge_stencils[d].is_empty ())
363             brack.add_stencil (edge_stencils[d]);
364         }
365       while (flip (&d) != LEFT);
366
367       mol.add_stencil (brack);
368     }
369
370   mol.translate (points[LEFT]);
371   return mol.smobbed_copy ();
372 }
373
374 /*
375   should move to lookup?
376
377   TODO: this will fail for very short (shorter than the flare)
378   brackets.
379 */
380 Stencil
381 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
382                               Axis protusion_axis,
383                               Offset dz,
384                               Drul_array<Real> height,
385                               Interval gap,
386                               Drul_array<Real> flare,
387                               Drul_array<Real> shorten)
388 {
389   Drul_array<Offset> corners (Offset (0, 0), dz);
390
391   Real length = dz.length ();
392   Drul_array<Offset> gap_corners;
393
394   Axis bracket_axis = other_axis (protusion_axis);
395
396   Drul_array<Offset> straight_corners = corners;
397
398   Direction d = LEFT;
399   do
400     straight_corners[d] += -d * shorten[d] / length * dz;
401   while (flip (&d) != LEFT)
402     ;
403
404   if (gap.is_empty ())
405     gap = Interval (0, 0);
406   do
407     gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
408   while (flip (&d) != LEFT)
409     ;
410
411   Drul_array<Offset> flare_corners = straight_corners;
412   do
413     {
414       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
415       flare_corners[d][protusion_axis] += height[d];
416       straight_corners[d][bracket_axis] += -d * flare[d];
417     }
418   while (flip (&d) != LEFT);
419
420   Stencil m;
421   do
422     {
423       m.add_stencil (Line_interface::line (me, straight_corners[d],
424                                            gap_corners[d]));
425
426       m.add_stencil (Line_interface::line (me, straight_corners[d],
427                                            flare_corners[d]));
428     }
429   while (flip (&d) != LEFT);
430
431   return m;
432 }
433
434 void
435 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
436 {
437   extract_grob_set (me, "note-columns", columns);
438   int l = 0;
439   while (l < columns.size () && Note_column::has_rests (columns[l]))
440     l++;
441
442   int r = columns.size ()- 1;
443   while (r >= l && Note_column::has_rests (columns[r]))
444     r--;
445
446   *left = *right = 0;
447
448   if (l <= r)
449     {
450       *left = columns[l];
451       *right = columns[r];
452     }
453 }
454
455 /*
456   use first -> last note for slope, and then correct for disturbing
457   notes in between.  */
458 void
459 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
460 {
461   Spanner *me = dynamic_cast<Spanner *> (me_grob);
462
463   extract_grob_set (me, "note-columns", columns);
464   extract_grob_set (me, "tuplets", tuplets);
465
466   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
467   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
468   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
469     commony = st->common_refpoint (commony, Y_AXIS);
470
471   Grob *commonx = get_common_x (me);
472   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
473
474   Interval staff;
475   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
476     {
477       Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
478       if  (pad >= 0.0)
479         {
480           staff = st->extent (commony, Y_AXIS);
481           staff.widen (pad);
482         }
483     }
484   
485   Direction dir = get_grob_direction (me);
486
487   /*
488     Use outer non-rest columns to determine slope
489   */
490   Grob *left_col = 0;
491   Grob *right_col = 0;
492   get_bounds (me, &left_col, &right_col);
493   if (left_col && right_col)
494     {
495       Interval rv = right_col->extent (commony, Y_AXIS);
496       Interval lv = left_col->extent (commony, Y_AXIS);
497       rv.unite (staff);
498       lv.unite (staff);
499       Real graphical_dy = rv[dir] - lv[dir];
500
501       Slice ls = Note_column::head_positions_interval (left_col);
502       Slice rs = Note_column::head_positions_interval (right_col);
503
504       Interval musical_dy;
505       musical_dy[UP] = rs[UP] - ls[UP];
506       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
507       if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
508         *dy = 0.0;
509       else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
510         *dy = 0.0;
511       else
512         *dy = graphical_dy;
513     }
514   else
515     *dy = 0;
516
517   *offset = -dir * infinity_f;
518
519   Item *lgr = get_x_bound_item (me, LEFT, dir);
520   Item *rgr = get_x_bound_item (me, RIGHT, dir);
521   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
522   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
523
524   Array<Offset> points;
525   points.push (Offset (x0 - x0, staff[dir]));
526   points.push (Offset (x1 - x0, staff[dir]));
527
528   for (int i = 0; i < columns.size (); i++)
529     {
530       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
531       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
532
533       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
534       points.push (Offset (x, notey));
535     }
536
537   /*
538     This is a slight hack. We compute two encompass points from the
539     bbox of the smaller tuplets.
540
541     We assume that the smaller bracket is 1.0 space high.
542   */
543   Real ss = Staff_symbol_referencer::staff_space (me);
544   for (int i = 0; i < tuplets.size (); i++)
545     {
546       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
547       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
548
549       Direction d = LEFT;
550       Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
551
552       
553       Real other_dy = positions[RIGHT] - positions[LEFT];
554
555       do
556         {
557           Real y
558             = tuplet_y.linear_combination (d * sign (other_dy));
559
560 #if 0
561           /*
562             Let's not take padding into account for nested tuplets.
563             the edges can come very close to the stems, likewise for
564             nested tuplets?
565           */
566           Drul_array<Real> my_height
567             = robust_scm2drul (me->get_property ("edge-height"),
568                                Interval (0, 0));
569           if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
570               == me->get_bound (d))
571             y += dir * my_height[d];
572 #endif
573
574           points.push (Offset (tuplet_x[d] - x0, y));
575         }
576       while (flip (&d) != LEFT);
577     }
578
579   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
580   for (int i = 0; i < points.size (); i++)
581     {
582       Real x = points[i][X_AXIS];
583       Real tuplety = (*dy) * x * factor;
584
585       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
586         *offset = points[i][Y_AXIS] - tuplety;
587     }
588
589   *offset += scm_to_double (me->get_property ("padding")) * dir;
590
591   /*
592     horizontal brackets should not collide with staff lines.
593
594     Kind of pointless since we put them outside the staff anyway, but
595     let's leave code for the future when possibly allow them to move
596     into the staff once again.
597   */
598   if (*dy == 0
599       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
600     {
601       // quantize, then do collision check.
602       *offset *= 2 / ss;
603
604       *offset = rint (*offset);
605       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
606         *offset += dir;
607
608       *offset *= 0.5 * ss;
609     }
610 }
611
612
613 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
614 SCM
615 Tuplet_bracket::calc_direction (SCM smob)
616 {
617   Grob *me = unsmob_grob (smob);
618   Direction dir = Tuplet_bracket::get_default_dir (me);
619   return scm_from_int (dir);
620 }
621
622 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
623 SCM
624 Tuplet_bracket::calc_positions (SCM smob)
625 {
626   Grob *me = unsmob_grob (smob);
627   extract_grob_set (me, "note-columns", columns);
628
629   Direction dir = get_grob_direction (me);
630   bool equally_long = false;
631   Grob *par_beam = parallel_beam (me, columns, &equally_long);
632
633   /*
634     We follow the beam only if there is one, and we are next to it.
635   */
636   Real dy = 0.0;
637   Real offset = 0.0;
638   if (!par_beam
639       || get_grob_direction (par_beam) != dir)
640     calc_position_and_height (me, &offset, &dy);
641   else
642     {
643       SCM ps = par_beam->get_property ("positions");
644
645       Real lp = scm_to_double (scm_car (ps));
646       Real rp = scm_to_double (scm_cdr (ps));
647
648       /*
649         duh. magic.
650       */
651       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
652       dy = rp - lp;
653     }
654
655   
656   SCM x = scm_cons (scm_from_double (offset),
657                     scm_from_double (offset + dy));
658   
659   return x;
660 }
661
662 /*
663   similar to beam ?
664 */
665 Direction
666 Tuplet_bracket::get_default_dir (Grob *me)
667 {
668   Drul_array<int> dirs (0, 0);
669   extract_grob_set (me, "note-columns", columns);
670   for (int i = 0; i < columns.size (); i++)
671     {
672       Grob *nc = columns[i];
673       Direction d = Note_column::dir (nc);
674       if (d)
675         dirs[d]++;
676     }
677
678   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
679 }
680
681 void
682 Tuplet_bracket::add_column (Grob *me, Item *n)
683 {
684   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
685   add_bound_item (dynamic_cast<Spanner *> (me), n);
686 }
687
688 void
689 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
690 {
691   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
692 }
693
694 ADD_INTERFACE (Tuplet_bracket,
695                "tuplet-bracket-interface",
696                "A bracket with a number in the middle, used for tuplets. "
697                "When the bracket spans  a line break, the value of "
698                "@code{break-overshoot} determines how far it extends "
699                "beyond the staff. "
700                "At a line break, the markups in the @code{edge-text} are printed "
701                "at the edges. ",
702
703
704                /* properties */
705                "bracket-flare "
706                "bracket-visibility "
707                "break-overshoot "
708                "connect-to-neighbor "
709                "control-points "
710                "direction "
711                "edge-height "
712                "edge-text "
713                "gap "
714                "positions "
715                "note-columns "
716                "padding "
717                "tuplet-number "
718                "shorten-pair "
719                "staff-padding "
720                "thickness "
721                "tuplets ");
722
723