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