2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
9 TODO: This is way too hairy
13 Stem-end, chord-start, etc. is all confusing naming.
18 #include <cmath> // rint
22 #include "directional-element-interface.hh"
23 #include "note-head.hh"
25 #include "output-def.hh"
26 #include "rhythmic-head.hh"
27 #include "font-interface.hh"
28 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #include "staff-symbol-referencer.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
36 #include "stem-tremolo.hh"
39 Stem::set_beaming (Grob *me, int beam_count, Direction d)
41 SCM pair = me->get_property ("beaming");
43 if (!scm_is_pair (pair))
45 pair = scm_cons (SCM_EOL, SCM_EOL);
46 me->set_property ("beaming", pair);
49 SCM lst = index_get_cell (pair, d);
50 for (int i = 0; i < beam_count; i++)
51 lst = scm_cons (scm_from_int (i), lst);
52 index_set_cell (pair, d, lst);
56 Stem::get_beaming (Grob *me, Direction d)
58 SCM pair = me->get_property ("beaming");
59 if (!scm_is_pair (pair))
62 SCM lst = index_get_cell (pair, d);
63 return scm_ilength (lst);
67 Stem::head_positions (Grob *me)
71 Drul_array<Grob *> e (extremal_heads (me));
72 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
73 Staff_symbol_referencer::get_position (e[UP]));
79 Stem::chord_start_y (Grob *me)
81 Interval hp = head_positions (me);
83 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
91 Stem::set_stemend (Grob *me, Real se)
94 Direction d = get_grob_direction (me);
96 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
97 me->warning (_ ("weird stem size, check for narrow beams"));
99 me->set_property ("stem-end-position", scm_from_double (se));
102 /* Note head that determines hshift for upstems
103 WARNING: triggers direction */
105 Stem::support_head (Grob *me)
107 extract_grob_set (me, "note-heads", heads);
108 if (heads.size () == 1)
111 return first_head (me);
115 Stem::head_count (Grob *me)
117 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
120 /* The note head which forms one end of the stem.
121 WARNING: triggers direction */
123 Stem::first_head (Grob *me)
125 Direction d = get_grob_direction (me);
127 return extremal_heads (me)[-d];
131 /* The note head opposite to the first head. */
133 Stem::last_head (Grob *me)
135 Direction d = get_grob_direction (me);
137 return extremal_heads (me)[d];
142 START is part where stem reaches `last' head.
144 This function returns a drul with (bottom-head, top-head).
147 Stem::extremal_heads (Grob *me)
149 const int inf = 1000000;
150 Drul_array<int> extpos;
154 Drul_array<Grob *> exthead (0, 0);
155 extract_grob_set (me, "note-heads", heads);
157 for (int i = heads.size (); i--;)
160 int p = Staff_symbol_referencer::get_rounded_position (n);
165 if (d * p > d * extpos[d])
171 while (flip (&d) != DOWN);
177 integer_compare (int const &a, int const &b)
182 /* The positions, in ascending order. */
184 Stem::note_head_positions (Grob *me)
187 extract_grob_set (me, "note-heads", heads);
189 for (int i = heads.size (); i--;)
192 int p = Staff_symbol_referencer::get_rounded_position (n);
197 ps.sort (integer_compare);
202 Stem::add_head (Grob *me, Grob *n)
204 n->set_object ("stem", me->self_scm ());
206 if (Note_head::has_interface (n))
207 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
208 else if (Rest::has_interface (n))
209 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
213 Stem::is_invisible (Grob *me)
215 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
218 return !((head_count (me)
219 || stemlet_length > 0.0)
220 && scm_to_int (me->get_property ("duration-log")) >= 1);
223 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
225 Stem::calc_stem_end_position (SCM smob)
227 Grob *me = unsmob_grob (smob);
229 if (!head_count (me))
230 return scm_from_double (0.0);
233 Real ss = Staff_symbol_referencer::staff_space (me);
234 int durlog = duration_log (me);
237 /* WARNING: IN HALF SPACES */
238 Real length = robust_scm2double (me->get_property ("length"), 7);
240 Direction dir = get_grob_direction (me);
241 Interval hp = head_positions (me);
242 Real st = dir ? hp[dir] + dir * length : 0;
244 /* TODO: change name to extend-stems to staff/center/'() */
245 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
246 if (!no_extend_b && dir * st < 0)
249 /* Make a little room if we have a upflag and there is a dot.
250 previous approach was to lengthen the stem. This is not
251 good typesetting practice. */
252 if (!get_beam (me) && dir == UP
255 Grob *closest_to_flag = extremal_heads (me)[dir];
256 Grob *dots = closest_to_flag
257 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
261 Real dp = Staff_symbol_referencer::get_position (dots);
262 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
264 /* Very gory: add myself to the X-support of the parent,
265 which should be a dot-column. */
266 if (dir * (st + flagy - dp) < 0.5)
268 Grob *par = dots->get_parent (X_AXIS);
270 if (Dot_column::has_interface (par))
272 Side_position_interface::add_support (par, me);
274 /* TODO: apply some better logic here. The flag is
275 curved inwards, so this will typically be too
282 return scm_from_double (st);
286 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
288 Stem::calc_length (SCM smob)
290 Grob *me = unsmob_grob (smob);
292 SCM details = me->get_property ("details");
293 int durlog = duration_log (me);
295 Real ss = Staff_symbol_referencer::staff_space (me);
297 SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
299 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
301 Direction dir = get_grob_direction (me);
303 /* Stems in unnatural (forced) direction should be shortened,
304 according to [Roush & Gourlay] */
305 Interval hp = head_positions (me);
306 if (dir && dir * hp[dir] >= 0)
308 SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
309 SCM scm_shorten = scm_is_pair (sshorten)
310 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
311 Real shorten = 2* robust_scm2double (scm_shorten, 0);
313 /* On boundary: shorten only half */
314 if (abs (head_positions (me)[dir]) <= 1)
320 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
323 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
324 if (t_flag && !unsmob_grob (me->get_object ("beam")))
326 /* Crude hack: add extra space if tremolo flag is there.
328 We can't do this for the beam, since we get into a loop
329 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
332 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
337 Interval flag_ext = flag (me).extent (Y_AXIS);
338 if (!flag_ext.is_empty ())
339 minlen += 2 * flag_ext.length () / ss;
341 /* The clash is smaller for down stems (since the tremolo is
346 length = max (length, minlen + 1.0);
349 return scm_from_double (length);
351 /* The log of the duration (Number of hooks on the flag minus two) */
353 Stem::duration_log (Grob *me)
355 SCM s = me->get_property ("duration-log");
356 return (scm_is_number (s)) ? scm_to_int (s) : 2;
359 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
361 Stem::calc_positioning_done (SCM smob)
363 Grob *me = unsmob_grob (smob);
364 if (!head_count (me))
367 extract_grob_set (me, "note-heads", ro_heads);
368 Link_array<Grob> heads (ro_heads);
369 heads.sort (compare_position);
370 Direction dir = get_grob_direction (me);
375 Real thick = thickness (me);
377 Grob *hed = support_head (me);
380 programming_error ("Stem dir must be up or down.");
382 set_grob_direction (me, dir);
385 Real w = hed->extent (hed, X_AXIS)[dir];
386 for (int i = 0; i < heads.size (); i++)
387 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
391 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
392 for (int i = 1; i < heads.size (); i++)
394 Real p = Staff_symbol_referencer::get_position (heads[i]);
395 Real dy = fabs (lastpos- p);
398 dy should always be 0.5, 0.0, 1.0, but provide safety margin
405 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
407 Direction d = get_grob_direction (me);
409 Reversed head should be shifted ell-thickness, but this
410 looks too crowded, so we only shift ell-0.5*thickness.
412 This leads to assymetry: Normal heads overlap the
413 stem 100% whereas reversed heads only overlaps the
417 Real reverse_overlap = 0.5;
418 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
421 if (is_invisible (me))
422 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
427 For some cases we should kern some more: when the
428 distance between the next or prev note is too large, we'd
429 get large white gaps, eg.
452 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
454 Stem::calc_direction (SCM smob)
456 Grob *me = unsmob_grob (smob);
457 Direction dir = CENTER;
458 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
460 SCM ignore_me = beam->get_property ("direction");
462 dir = get_grob_direction (me);
465 dir = get_default_dir (me);
467 return scm_from_int (dir);
471 Stem::get_default_dir (Grob *me)
473 Direction dir = CENTER;
474 int staff_center = 0;
475 Interval hp = head_positions (me);
478 int udistance = (int) (UP *hp[UP] - staff_center);
479 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
481 if (sign (ddistance - udistance))
482 dir = Direction (sign (ddistance - udistance));
484 dir = to_dir (me->get_property ("neutral-direction"));
491 MAKE_SCHEME_CALLBACK (Stem, height, 2);
493 Stem::height (SCM smob, SCM ax)
495 Axis a = (Axis)scm_to_int (ax);
496 Grob *me = unsmob_grob (smob);
497 assert (a == Y_AXIS);
499 Direction dir = get_grob_direction (me);
503 UGH. Should be automatic
505 Grob *beam = get_beam (me);
508 beam->get_property ("positions");
511 /* FIXME uncached? */
512 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
517 programming_error ("no stem direction");
520 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
523 return ly_interval2scm (iv);
527 Stem::stem_end_position (Grob *me)
529 return robust_scm2double (me->get_property ("stem-end-position"), 0);
533 Stem::flag (Grob *me)
535 int log = duration_log (me);
537 || unsmob_grob (me->get_object ("beam")))
541 TODO: maybe property stroke-style should take different values,
542 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
546 SCM flag_style_scm = me->get_property ("flag-style");
547 if (scm_is_symbol (flag_style_scm))
548 flag_style = ly_symbol2string (flag_style_scm);
550 if (flag_style == "no-flag")
555 String staffline_offs;
556 if (String::compare (flag_style, "mensural") == 0)
557 /* Mensural notation: For notes on staff lines, use different
558 flags than for notes between staff lines. The idea is that
559 flags are always vertically aligned with the staff lines,
560 regardless if the note head is on a staff line or between two
561 staff lines. In other words, the inner end of a flag always
562 touches a staff line.
567 int p = (int) (rint (stem_end_position (me)));
569 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
572 staffline_offs = "2";
577 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
578 String font_char = flag_style
579 + to_string (dir) + staffline_offs + to_string (log);
580 Font_metric *fm = Font_interface::get_default_font (me);
581 Stencil flag = fm->find_by_name ("flags." + font_char);
582 if (flag.is_empty ())
583 me->warning (_f ("flag `%s' not found", font_char));
585 SCM stroke_style_scm = me->get_property ("stroke-style");
586 if (scm_is_string (stroke_style_scm))
588 String stroke_style = ly_scm2string (stroke_style_scm);
589 if (!stroke_style.is_empty ())
591 String font_char = to_string (dir) + stroke_style;
592 Stencil stroke = fm->find_by_name ("flags." + font_char);
593 if (stroke.is_empty ())
594 me->warning (_f ("flag stroke `%s' not found", font_char));
596 flag.add_stencil (stroke);
603 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
605 Stem::width_callback (SCM e, SCM ax)
608 assert (scm_to_int (ax) == X_AXIS);
609 Grob *me = unsmob_grob (e);
613 if (is_invisible (me))
615 else if (unsmob_grob (me->get_object ("beam"))
616 || abs (duration_log (me)) <= 2)
618 r = Interval (-1, 1);
619 r *= thickness (me) / 2;
623 r = Interval (-1, 1) * thickness (me) * 0.5;
624 r.unite (flag (me).extent (X_AXIS));
626 return ly_interval2scm (r);
630 Stem::thickness (Grob *me)
632 return scm_to_double (me->get_property ("thickness"))
633 * Staff_symbol_referencer::line_thickness (me);
636 MAKE_SCHEME_CALLBACK (Stem, print, 1);
638 Stem::print (SCM smob)
640 Grob *me = unsmob_grob (smob);
642 Direction d = get_grob_direction (me);
644 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
646 bool stemlet = stemlet_length > 0.0;
648 /* TODO: make the stem start a direction ?
649 This is required to avoid stems passing in tablature chords. */
651 = to_boolean (me->get_property ("avoid-note-head"))
654 Grob *beam = get_beam (me);
659 if (!lh && stemlet && !beam)
662 if (is_invisible (me))
665 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
667 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
670 y2 = Staff_symbol_referencer::get_position (lh);
673 Real beam_translation = Beam::get_beam_translation (beam);
674 Real beam_thickness = Beam::get_thickness (beam);
675 int beam_count = beam_multiplicity (me).length () + 1;
678 * (0.5 * beam_thickness
679 + beam_translation * max (0, (beam_count - 1))
680 + stemlet_length) / half_space;
683 Interval stem_y (min (y1, y2), max (y2, y1));
685 if (Grob *hed = support_head (me))
688 must not take ledgers into account.
690 Interval head_height = hed->extent (hed, Y_AXIS);
691 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
693 y_attach = head_height.linear_combination (y_attach);
694 stem_y[Direction (-d)] += d * y_attach / half_space;
698 Real stem_width = thickness (me);
700 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
702 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
703 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
705 Stencil ss = Lookup::round_filled_box (b, blot);
706 mol.add_stencil (ss);
708 mol.add_stencil (get_translated_flag (me));
710 return mol.smobbed_copy ();
714 Stem::get_translated_flag (Grob *me)
716 Stencil fl = flag (me);
719 Direction d = get_grob_direction (me);
721 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
722 Real stem_width = thickness (me);
723 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
724 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
725 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
726 fl.translate_axis (stem_width / 2, X_AXIS);
733 move the stem to right of the notehead if it is up.
735 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
737 Stem::offset_callback (SCM element_smob, SCM)
739 Grob *me = unsmob_grob (element_smob);
742 if (Grob *f = first_head (me))
744 Interval head_wid = f->extent (f, X_AXIS);
747 if (is_invisible (me))
750 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
752 Direction d = get_grob_direction (me);
753 Real real_attach = head_wid.linear_combination (d * attach);
756 /* If not centered: correct for stem thickness. */
759 Real rule_thick = thickness (me);
760 r += -d * rule_thick * 0.5;
765 extract_grob_set (me, "rests", rests);
768 Grob *rest = rests.top ();
769 r = rest->extent (rest, X_AXIS).center ();
772 return scm_from_double (r);
776 Stem::get_beam (Grob *me)
778 SCM b = me->get_object ("beam");
779 return dynamic_cast<Spanner *> (unsmob_grob (b));
783 Stem::get_stem_info (Grob *me)
786 si.dir_ = get_grob_direction (me);
788 SCM scm_info = me->get_property ("stem-info");
789 si.ideal_y_ = scm_to_double (scm_car (scm_info));
790 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
794 /* TODO: add extra space for tremolos! */
795 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
797 Stem::calc_stem_info (SCM smob)
799 Grob *me = unsmob_grob (smob);
800 Direction my_dir = get_grob_direction (me);
804 programming_error ("no stem dir set");
808 Real staff_space = Staff_symbol_referencer::staff_space (me);
809 Grob *beam = get_beam (me);
810 Real beam_translation = Beam::get_beam_translation (beam);
811 Real beam_thickness = Beam::get_thickness (beam);
812 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
814 /* Simple standard stem length */
815 SCM details = me->get_property ("details");
816 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
820 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
823 /* stem only extends to center of beam
825 - 0.5 * beam_thickness;
827 /* Condition: sane minimum free stem length (chord to beams) */
828 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
830 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
832 Real ideal_minimum_free
833 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
834 * staff_space * length_fraction;
837 It seems that also for ideal minimum length, we must use
838 the maximum beam count (for this direction):
840 \score{ \notes\relative c''{ [a8 a32] }}
842 must be horizontal. */
843 Real height_of_my_beams = beam_thickness
844 + (beam_count - 1) * beam_translation;
846 Real ideal_minimum_length = ideal_minimum_free
848 /* stem only extends to center of beam */
849 - 0.5 * beam_thickness;
851 ideal_length = max (ideal_length, ideal_minimum_length);
853 /* Convert to Y position, calculate for dir == UP */
855 = /* staff positions */
856 head_positions (me)[my_dir] * 0.5
857 * my_dir * staff_space;
858 Real ideal_y = note_start + ideal_length;
860 /* Conditions for Y position */
862 /* Lowest beam of (UP) beam must never be lower than second staffline
866 Although this (additional) rule is probably correct,
867 I expect that highest beam (UP) should also never be lower
868 than middle staffline, just as normal stems.
872 Obviously not for grace beams.
874 Also, not for knees. Seems to be a good thing. */
875 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
876 bool is_knee = to_boolean (beam->get_property ("knee"));
877 if (!no_extend_b && !is_knee)
879 /* Highest beam of (UP) beam must never be lower than middle
881 ideal_y = max (ideal_y, 0.0);
882 /* Lowest beam of (UP) beam must never be lower than second staffline */
883 ideal_y = max (ideal_y, (-staff_space
884 - beam_thickness + height_of_my_beams));
887 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
889 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
893 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
896 Real minimum_length = minimum_free
898 /* stem only extends to center of beam */
899 - 0.5 * beam_thickness;
901 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
903 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
904 y_ext.widen (0.5); // FIXME. Should be tunable?
905 minimum_length = max (minimum_length, y_ext.length ());
909 Real minimum_y = note_start + minimum_length;
910 Real shortest_y = minimum_y * my_dir;
912 return scm_list_2 (scm_from_double (ideal_y),
913 scm_from_double (shortest_y));
917 Stem::beam_multiplicity (Grob *stem)
919 SCM beaming = stem->get_property ("beaming");
920 Slice le = int_list_to_slice (scm_car (beaming));
921 Slice ri = int_list_to_slice (scm_cdr (beaming));
926 /* FIXME: Too many properties */
927 ADD_INTERFACE (Stem, "stem-interface",
928 "The stem represent the graphical stem. "
929 "In addition, it internally connects note heads, beams and"
931 "Rests and whole notes have invisible stems."
933 "\n\nThe following properties may be set in the details list."
935 "@item beamed-lengths \n"
936 "list of stem lengths given beam multiplicity. \n"
937 "@item beamed-minimum-free-lengths \n"
938 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
939 "@item beamed-extreme-minimum-free-lengths\n"
940 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
942 "Default stem lengths. The list gives a length for each flag-count.\n"
943 "@item stem-shorten\n"
944 "How much a stem in a forced direction "
945 "should be shortened. The list gives an amount depending on the number "
976 /****************************************************************/
978 Stem_info::Stem_info ()
980 ideal_y_ = shortest_y_ = 0;
985 Stem_info::scale (Real x)