]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
* Documentation/user/basic-notation.itely (Tuplets): add note
[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 #include <math.h>
33
34 #include "tuplet-bracket.hh"
35 #include "line-interface.hh"
36 #include "beam.hh"
37 #include "warn.hh"
38 #include "font-interface.hh"
39 #include "output-def.hh"
40 #include "text-interface.hh"
41 #include "stem.hh"
42 #include "note-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "directional-element-interface.hh"
45 #include "spanner.hh"
46 #include "staff-symbol-referencer.hh"
47 #include "lookup.hh"
48
49 static Grob *
50 get_x_bound_grob (Grob *g, Direction my_dir)
51 {
52   if (Note_column::get_stem (g)
53       && Note_column::dir (g) == my_dir)
54     {
55       g = Note_column::get_stem (g);
56     }
57   return g;
58 }
59
60 Grob *
61 Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> const &cols, bool *equally_long)
62 {
63   /*
64     ugh: code dup.
65   */
66   Grob *s1 = Note_column::get_stem (cols[0]);
67   Grob *s2 = Note_column::get_stem (cols.top ());
68
69   Grob *b1 = s1 ? Stem::get_beam (s1) : 0;
70   Grob *b2 = s2 ? Stem::get_beam (s2) : 0;
71
72   Spanner *sp = dynamic_cast<Spanner *> (me);
73
74   *equally_long = false;
75   if (! (b1 && (b1 == b2) && !sp->is_broken ()))
76     return 0;
77
78   extract_grob_set (b1, "stems", beam_stems);
79   if (beam_stems.size () == 0)
80     {
81       programming_error ("beam under tuplet bracket has no stems");
82       *equally_long = 0;
83       return 0;
84     }
85
86   *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
87   return b1;
88 }
89
90 /*
91   TODO:
92
93   in the case that there is no bracket, but there is a (single) beam,
94   follow beam precisely for determining tuplet number location.
95 */
96 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
97 SCM
98 Tuplet_bracket::print (SCM smob)
99 {
100   Grob *me = unsmob_grob (smob);
101   Stencil mol;
102   extract_grob_set (me, "note-columns", columns);
103
104   if (!columns.size ())
105     return mol.smobbed_copy ();
106
107   {
108     SCM lp = me->get_property ("left-position");
109     SCM rp = me->get_property ("right-position");
110
111     if (!scm_is_number (rp) || !scm_is_number (lp))
112       {
113         /*
114           UGH. dependency tracking!
115          */
116         extract_grob_set (me, "tuplets", tuplets);
117         for (int i = 0; i < tuplets.size (); i++)
118           Tuplet_bracket::print (tuplets[i]->self_scm());
119
120         after_line_breaking (smob);
121       }
122   }
123
124   Real ly = robust_scm2double (me->get_property ("left-position"), 0);
125   Real ry = robust_scm2double (me->get_property ("right-position"), 0);
126
127   bool equally_long = false;
128   Grob *par_beam = parallel_beam (me, columns, &equally_long);
129
130   Spanner *sp = dynamic_cast<Spanner *> (me);
131
132   bool bracket_visibility = !(par_beam && equally_long);
133   bool number_visibility = true;
134
135   /*
136     Fixme: the type of this prop is sucky.
137   */
138   SCM bracket = me->get_property ("bracket-visibility");
139   if (scm_is_bool (bracket))
140     {
141       bracket_visibility = ly_scm2bool (bracket);
142     }
143   else if (bracket == ly_symbol2scm ("if-no-beam"))
144     bracket_visibility = !par_beam;
145
146   SCM numb = me->get_property ("number-visibility");
147   if (scm_is_bool (numb))
148     {
149       number_visibility = ly_scm2bool (numb);
150     }
151   else if (numb == ly_symbol2scm ("if-no-beam"))
152     number_visibility = !par_beam;
153
154   Grob *commonx = columns[0]->common_refpoint (columns.top (), X_AXIS);
155
156   /*
157     Tuplet brackets are normally not broken, but we shouldn't crash if
158     they are.
159   */
160   commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
161   commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
162
163   Direction dir = get_grob_direction (me);
164
165   Grob *lgr = get_x_bound_grob (columns[0], dir);
166   Grob *rgr = get_x_bound_grob (columns.top (), dir);
167
168   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
169   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
170   Real w = x1 -x0;
171
172   SCM number = me->get_property ("text");
173
174   Output_def *pap = me->get_layout ();
175   Stencil num;
176   if (scm_is_string (number) && number_visibility)
177     {
178       SCM properties = Font_interface::text_font_alist_chain (me);
179       SCM snum = Text_interface::interpret_markup (pap->self_scm (), properties, number);
180       num = *unsmob_stencil (snum);
181       num.align_to (X_AXIS, CENTER);
182       num.translate_axis (w / 2, X_AXIS);
183       num.align_to (Y_AXIS, CENTER);
184
185       num.translate_axis ((ry - ly) / 2, Y_AXIS);
186
187       mol.add_stencil (num);
188     }
189
190   /*
191     No bracket when it would be smaller than the number.
192
193     TODO: should use GAP in calculation too.
194   */
195   if (bracket_visibility && number_visibility
196       && mol.extent (X_AXIS).length () > w)
197     {
198       bracket_visibility = false;
199     }
200
201   if (bracket_visibility)
202     {
203       Real gap = 0.;
204
205       if (!num.extent (X_AXIS).is_empty ())
206         gap = num.extent (X_AXIS).length () + 1.0;
207
208       Drul_array<Real> zero (0,0);
209       Real ss = Staff_symbol_referencer::staff_space (me);
210       Drul_array<Real> height
211         = robust_scm2drul (me->get_property ("edge-height"), zero);
212       Drul_array<Real> flare
213         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
214       Drul_array<Real> shorten
215         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
216
217       scale_drul (&height, -ss * dir);
218       scale_drul (&flare, ss);
219       scale_drul (&shorten, ss);
220       
221       Stencil brack = make_bracket (me, Y_AXIS,
222                                     Offset (w, ry - ly),
223                                     height,
224                                     /*
225                                       0.1 = more space at right due to italics
226                                       TODO: use italic correction of font.
227                                     */
228                                     Interval (-0.5, 0.5) * gap + 0.1,
229                                     flare, shorten);
230       mol.add_stencil (brack);
231     }
232
233   mol.translate_axis (ly, Y_AXIS);
234   mol.translate_axis (x0 - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
235   return mol.smobbed_copy ();
236 }
237
238 /*
239   should move to lookup?
240
241   TODO: this will fail for very short (shorter than the flare)
242   brackets.
243 */
244 Stencil
245 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
246                               Axis protusion_axis,
247                               Offset dz,
248                               Drul_array<Real> height,
249                               Interval gap,
250                               Drul_array<Real> flare,
251                               Drul_array<Real> shorten)
252 {
253   Drul_array<Offset> corners (Offset (0, 0), dz);
254
255   Real length = dz.length ();
256   Drul_array<Offset> gap_corners;
257
258   Axis bracket_axis = other_axis (protusion_axis);
259
260   Drul_array<Offset> straight_corners = corners;
261
262   Direction d = LEFT;
263   do
264     {
265       straight_corners[d] += -d * shorten[d] / length * dz;
266     }
267   while (flip (&d) != LEFT);
268
269   if (gap.is_empty ())
270     gap = Interval (0, 0);
271   do
272     {
273       gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
274     }
275   while (flip (&d) != LEFT);
276
277   Drul_array<Offset> flare_corners = straight_corners;
278   do
279     {
280       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
281       flare_corners[d][protusion_axis] += height[d];
282       straight_corners[d][bracket_axis] += -d * flare[d];
283     }
284   while (flip (&d) != LEFT);
285
286   Stencil m;
287   do
288     {
289       m.add_stencil (Line_interface::line (me, straight_corners[d],
290                                            gap_corners[d]));
291
292       m.add_stencil (Line_interface::line (me, straight_corners[d],
293                                            flare_corners[d]));
294     }
295   while (flip (&d) != LEFT);
296
297   return m;
298 }
299
300 void
301 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
302 {
303   extract_grob_set (me, "note-columns", columns);
304   int l = 0;
305   while (l < columns.size () && Note_column::has_rests (columns[l]))
306     l++;
307
308   int r = columns.size ()- 1;
309   while (r >= l && Note_column::has_rests (columns[r]))
310     r--;
311
312   *left = *right = 0;
313
314   if (l <= r)
315     {
316       *left = columns[l];
317       *right = columns[r];
318     }
319 }
320
321
322 /*
323   use first -> last note for slope, and then correct for disturbing
324   notes in between.  */
325 void
326 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
327 {
328   Spanner *me = dynamic_cast<Spanner*> (me_grob);
329   
330   extract_grob_set (me, "note-columns", columns);
331   extract_grob_set (me, "tuplets", tuplets);
332   
333   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
334   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
335   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
336   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
337
338   Interval staff;
339   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
340     staff = st->extent (commony, Y_AXIS);
341
342   Direction dir = get_grob_direction (me);
343
344   /*
345     Use outer non-rest columns to determine slope
346   */
347   Grob *left_col = 0;
348   Grob *right_col = 0;
349   get_bounds (me, &left_col, &right_col);
350   if (left_col && right_col)
351     {
352       Interval rv = right_col->extent (commony, Y_AXIS);
353       Interval lv = left_col->extent (commony, Y_AXIS);
354       rv.unite (staff);
355       lv.unite (staff);
356       Real graphical_dy = rv[dir] - lv[dir];
357
358       Slice ls = Note_column::head_positions_interval (left_col);
359       Slice rs = Note_column::head_positions_interval (right_col);
360
361       Interval musical_dy;
362       musical_dy[UP] = rs[UP] - ls[UP];
363       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
364       if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
365         *dy = 0.0;
366       else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
367         *dy = 0.0;
368       else
369         *dy = graphical_dy;
370     }
371   else
372     *dy = 0;
373
374   *offset = -dir * infinity_f;
375
376   if (!columns.size ())
377     return;
378
379   Grob *lgr = get_x_bound_grob (columns[0], dir);
380   Grob *rgr = get_x_bound_grob (columns.top (), dir);
381   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
382   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
383
384   /*
385     offset
386   */
387   Real factor = columns.size () > 1 ? 1 / (x1 - x0) : 1.0;
388
389   Array<Offset> points;
390   for (int i = 0; i < columns.size (); i++)
391     {
392       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
393       note_ext.unite (staff);
394       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
395
396       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
397       points.push (Offset (x, notey));
398     }
399   
400   /*
401     This is a slight hack. We compute two encompass points from the
402     bbox of the smaller tuplets.
403     
404     We assume that the smaller bracket is 1.0 space high.
405   */
406   
407   Real ss = Staff_symbol_referencer::staff_space (me);
408   for (int i = 0; i < tuplets.size (); i++)
409     {
410       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
411       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
412
413       Direction d = LEFT;
414       Real lp = scm_to_double (tuplets[i]->get_property ("left-position"));
415       Real rp = scm_to_double (tuplets[i]->get_property ("right-position"));
416       Real other_dy = rp - lp;
417
418       do
419         {
420           Real y =
421             tuplet_y.linear_combination (d * sign (other_dy));
422
423 #if 0
424           /*
425             Let's not take padding into account for nested tuplets.
426             the edges can come very close to the stems, likewise for
427             nested tuplets?
428            */
429           Drul_array<Real> my_height
430             = robust_scm2drul (me->get_property ("edge-height"), Interval (0,0));
431           if (dynamic_cast<Spanner*> (tuplets[i])->get_bound (d)
432               ==  me->get_bound (d))
433             {
434               y += dir * my_height[d];
435             }
436 #endif
437           
438           points.push (Offset (tuplet_x[d] - x0, y));
439         }
440       while (flip (&d) != LEFT);
441     }
442
443   for (int i = 0; i < points.size (); i++)
444     {
445       Real x = points[i][X_AXIS];
446       Real tuplety = *dy * x * factor;
447
448       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
449         *offset = points[i][Y_AXIS] - tuplety;
450     }
451                   
452   *offset += scm_to_double (me->get_property ("padding")) * dir;
453
454   /*
455     horizontal brackets should not collide with staff lines.
456
457     Kind of pointless since we put them outside the staff anyway, but
458     let's leave code for the future when possibly allow them to move
459     into the staff once again.
460   */
461   if (*dy == 0 &&
462       fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
463     {
464       // quantize, then do collision check.
465       *offset *= 2 / ss;
466
467       *offset = rint (*offset);
468       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
469         *offset += dir;
470
471       *offset *= 0.5 * ss;
472     }
473 }
474
475 /*
476   We depend on the beams if there are any.
477 */
478 MAKE_SCHEME_CALLBACK (Tuplet_bracket, before_line_breaking, 1);
479 SCM
480 Tuplet_bracket::before_line_breaking (SCM smob)
481 {
482   Grob *me = unsmob_grob (smob);
483   extract_grob_set (me, "note-columns", columns);
484
485   for (int i = columns.size (); i--;)
486     {
487       Grob *s = Note_column::get_stem (columns[i]);
488       Grob *b = s ? Stem::get_beam (s) : 0;
489       if (b)
490         me->add_dependency (b);
491     }
492   return SCM_UNSPECIFIED;
493 }
494
495 MAKE_SCHEME_CALLBACK (Tuplet_bracket, after_line_breaking, 1);
496
497 SCM
498 Tuplet_bracket::after_line_breaking (SCM smob)
499 {
500   Grob *me = unsmob_grob (smob);
501   extract_grob_set (me, "note-columns", columns);
502
503   if (!columns.size ())
504     {
505       me->suicide ();
506       return SCM_UNSPECIFIED;
507     }
508   if (dynamic_cast<Spanner *> (me)->is_broken ())
509     {
510       me->warning (_ ("removing tuplet bracket across linebreak"));
511       me->suicide ();
512       return SCM_UNSPECIFIED;
513     }
514
515   Direction dir = get_grob_direction (me);
516   if (!dir)
517     {
518       dir = Tuplet_bracket::get_default_dir (me);
519       set_grob_direction (me, dir);
520     }
521
522   bool equally_long = false;
523   Grob *par_beam = parallel_beam (me, columns, &equally_long);
524
525   /*
526     We follow the beam only if there is one, and we are next to it.
527   */
528   Real dy = 0.0;
529   Real offset = 0.0;
530   if (!par_beam
531       || get_grob_direction (par_beam) != dir)
532     {
533       calc_position_and_height (me, &offset, &dy);
534     }
535   else
536     {
537       SCM ps = par_beam->get_property ("positions");
538
539       Real lp = scm_to_double (scm_car (ps));
540       Real rp = scm_to_double (scm_cdr (ps));
541
542       /*
543         duh. magic.
544       */
545       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
546       dy = rp- lp;
547     }
548
549   SCM lp = me->get_property ("left-position");
550   SCM rp = me->get_property ("right-position");
551
552   if (scm_is_number (lp) && !scm_is_number (rp))
553     {
554       rp = scm_from_double (scm_to_double (lp) + dy);
555     }
556   else if (scm_is_number (rp) && !scm_is_number (lp))
557     {
558       lp = scm_from_double (scm_to_double (rp) - dy);
559     }
560   else if (!scm_is_number (rp) && !scm_is_number (lp))
561     {
562       lp = scm_from_double (offset);
563       rp = scm_from_double (offset + dy);
564     }
565
566   me->set_property ("left-position", lp);
567   me->set_property ("right-position", rp);
568
569   return SCM_UNSPECIFIED;
570 }
571
572 /*
573   similar to beam ?
574 */
575 Direction
576 Tuplet_bracket::get_default_dir (Grob *me)
577 {
578   Drul_array<int> dirs (0, 0);
579   extract_grob_set (me, "note-columns", columns);
580   for (int i = 0 ; i < columns.size (); i++)
581     {
582       Grob *nc = columns[i];
583       Direction d = Note_column::dir (nc);
584       if (d)
585         dirs[d]++;
586     }
587
588   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
589 }
590
591 void
592 Tuplet_bracket::add_column (Grob *me, Item *n)
593 {
594   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
595   me->add_dependency (n);
596
597   add_bound_item (dynamic_cast<Spanner *> (me), n);
598 }
599
600 void
601 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
602 {
603   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
604   me->add_dependency (bracket);
605 }
606
607
608
609 ADD_INTERFACE (Tuplet_bracket,
610                "tuplet-bracket-interface",
611                "A bracket with a number in the middle, used for tuplets.",
612
613                "note-columns bracket-flare edge-height shorten-pair "
614                "tuplets "
615                "padding left-position right-position bracket-visibility "
616                "number-visibility thickness direction");
617