2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2006 Han-Wen Nienhuys <hanwen@xs4all.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 "dot-column.hh"
24 #include "font-interface.hh"
25 #include "international.hh"
28 #include "note-head.hh"
29 #include "output-def.hh"
30 #include "paper-column.hh"
31 #include "pointer-group-interface.hh"
33 #include "rhythmic-head.hh"
34 #include "side-position-interface.hh"
35 #include "staff-symbol-referencer.hh"
36 #include "stem-tremolo.hh"
40 Stem::set_beaming (Grob *me, int beam_count, Direction d)
42 SCM pair = me->get_property ("beaming");
44 if (!scm_is_pair (pair))
46 pair = scm_cons (SCM_EOL, SCM_EOL);
47 me->set_property ("beaming", pair);
50 SCM lst = index_get_cell (pair, d);
52 for (int i = 0; i < beam_count; i++)
53 lst = scm_cons (scm_from_int (i), lst);
57 index_set_cell (pair, d, lst);
61 Stem::get_beaming (Grob *me, Direction d)
63 SCM pair = me->get_property ("beaming");
64 if (!scm_is_pair (pair))
67 SCM lst = index_get_cell (pair, d);
68 return scm_ilength (lst);
72 Stem::head_positions (Grob *me)
76 Drul_array<Grob *> e (extremal_heads (me));
77 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
78 Staff_symbol_referencer::get_position (e[UP]));
84 Stem::chord_start_y (Grob *me)
86 Interval hp = head_positions (me);
88 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
96 Stem::set_stemend (Grob *me, Real se)
99 Direction d = get_grob_direction (me);
101 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
102 me->warning (_ ("weird stem size, check for narrow beams"));
104 me->set_property ("stem-end-position", scm_from_double (se));
107 /* Note head that determines hshift for upstems
108 WARNING: triggers direction */
110 Stem::support_head (Grob *me)
112 extract_grob_set (me, "note-heads", heads);
113 if (heads.size () == 1)
116 return first_head (me);
120 Stem::head_count (Grob *me)
122 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
125 /* The note head which forms one end of the stem.
126 WARNING: triggers direction */
128 Stem::first_head (Grob *me)
130 Direction d = get_grob_direction (me);
132 return extremal_heads (me)[-d];
136 /* The note head opposite to the first head. */
138 Stem::last_head (Grob *me)
140 Direction d = get_grob_direction (me);
142 return extremal_heads (me)[d];
147 START is part where stem reaches `last' head.
149 This function returns a drul with (bottom-head, top-head).
152 Stem::extremal_heads (Grob *me)
154 const int inf = 1000000;
155 Drul_array<int> extpos;
159 Drul_array<Grob *> exthead (0, 0);
160 extract_grob_set (me, "note-heads", heads);
162 for (vsize i = heads.size (); i--;)
165 int p = Staff_symbol_referencer::get_rounded_position (n);
170 if (d * p > d * extpos[d])
176 while (flip (&d) != DOWN);
182 integer_compare (int const &a, int const &b)
187 /* The positions, in ascending order. */
189 Stem::note_head_positions (Grob *me)
192 extract_grob_set (me, "note-heads", heads);
194 for (vsize i = heads.size (); i--;)
197 int p = Staff_symbol_referencer::get_rounded_position (n);
202 vector_sort (ps, integer_compare);
207 Stem::add_head (Grob *me, Grob *n)
209 n->set_object ("stem", me->self_scm ());
211 if (Note_head::has_interface (n))
212 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
213 else if (Rest::has_interface (n))
214 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
218 Stem::is_invisible (Grob *me)
220 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
223 return !((head_count (me)
224 || stemlet_length > 0.0)
225 && scm_to_int (me->get_property ("duration-log")) >= 1);
228 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
230 Stem::pure_height (SCM smob, SCM start, SCM end)
232 Grob *me = unsmob_grob (smob);
233 Real ss = Staff_symbol_referencer::staff_space (me);
234 Real len = scm_to_double (calc_length (smob)) * ss / 2;
235 Direction dir = get_grob_direction (me);
238 Interval hp = head_positions (me);
240 iv = Interval (0, len);
242 iv = Interval (-len, 0);
245 iv.translate (hp[dir] * ss / 2);
247 return ly_interval2scm (iv);
250 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
252 Stem::calc_stem_end_position (SCM smob)
254 Grob *me = unsmob_grob (smob);
256 if (!head_count (me))
257 return scm_from_double (0.0);
260 Real ss = Staff_symbol_referencer::staff_space (me);
261 int durlog = duration_log (me);
264 /* WARNING: IN HALF SPACES */
265 Real length = robust_scm2double (me->get_property ("length"), 7);
267 Direction dir = get_grob_direction (me);
268 Interval hp = head_positions (me);
269 Real stem_end = dir ? hp[dir] + dir * length : 0;
271 /* TODO: change name to extend-stems to staff/center/'() */
272 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
273 if (!no_extend_b && dir * stem_end < 0)
277 /* Make a little room if we have a upflag and there is a dot.
278 previous approach was to lengthen the stem. This is not
279 good typesetting practice. */
280 if (!get_beam (me) && dir == UP
283 Grob *closest_to_flag = extremal_heads (me)[dir];
284 Grob *dots = closest_to_flag
285 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
289 Real dp = Staff_symbol_referencer::get_position (dots);
290 Interval flag_yext = flag (me).extent (Y_AXIS) * (2 / ss) + stem_end;
292 /* Very gory: add myself to the X-support of the parent,
293 which should be a dot-column. */
295 if (flag_yext.distance (dp) < 0.5)
297 Grob *par = dots->get_parent (X_AXIS);
299 if (Dot_column::has_interface (par))
301 Side_position_interface::add_support (par, me);
303 /* TODO: apply some better logic here. The flag is
304 curved inwards, so this will typically be too
311 return scm_from_double (stem_end);
315 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
317 Stem::calc_length (SCM smob)
319 Grob *me = unsmob_grob (smob);
321 SCM details = me->get_property ("details");
322 int durlog = duration_log (me);
324 Real ss = Staff_symbol_referencer::staff_space (me);
326 SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
328 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
330 Direction dir = get_grob_direction (me);
332 /* Stems in unnatural (forced) direction should be shortened,
333 according to [Roush & Gourlay] */
334 Interval hp = head_positions (me);
335 if (dir && dir * hp[dir] >= 0)
337 SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
338 SCM scm_shorten = scm_is_pair (sshorten)
339 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
340 Real shorten = 2* robust_scm2double (scm_shorten, 0);
342 /* On boundary: shorten only half */
343 if (abs (head_positions (me)[dir]) <= 1)
349 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
352 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
353 if (t_flag && !unsmob_grob (me->get_object ("beam")))
355 /* Crude hack: add extra space if tremolo flag is there.
357 We can't do this for the beam, since we get into a loop
358 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
361 + 2 * t_flag->extent (t_flag, Y_AXIS).length ()
364 /* We don't want to add the whole extent of the flag because the trem
365 and the flag can overlap partly. beam_translation gives a good
369 Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
370 /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
371 minlen += 2 * (durlog - 1.5) * beam_trans;
373 /* up-stems need even a little more space to avoid collisions. This
374 needs to be in sync with the tremolo positioning code in
375 Stem_tremolo::print */
377 minlen += beam_trans;
379 length = max (length, minlen + 1.0);
382 return scm_from_double (length);
384 /* The log of the duration (Number of hooks on the flag minus two) */
386 Stem::duration_log (Grob *me)
388 SCM s = me->get_property ("duration-log");
389 return (scm_is_number (s)) ? scm_to_int (s) : 2;
392 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
394 Stem::calc_positioning_done (SCM smob)
396 Grob *me = unsmob_grob (smob);
397 if (!head_count (me))
400 extract_grob_set (me, "note-heads", ro_heads);
401 vector<Grob*> heads (ro_heads);
402 vector_sort (heads, compare_position);
403 Direction dir = get_grob_direction (me);
408 Real thick = thickness (me);
410 Grob *hed = support_head (me);
413 programming_error ("Stem dir must be up or down.");
415 set_grob_direction (me, dir);
418 Real w = hed->extent (hed, X_AXIS)[dir];
419 for (vsize i = 0; i < heads.size (); i++)
420 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
424 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
425 for (vsize i = 1; i < heads.size (); i++)
427 Real p = Staff_symbol_referencer::get_position (heads[i]);
428 Real dy = fabs (lastpos- p);
431 dy should always be 0.5, 0.0, 1.0, but provide safety margin
438 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
440 Direction d = get_grob_direction (me);
442 Reversed head should be shifted ell-thickness, but this
443 looks too crowded, so we only shift ell-0.5*thickness.
445 This leads to assymetry: Normal heads overlap the
446 stem 100% whereas reversed heads only overlaps the
450 Real reverse_overlap = 0.5;
451 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
454 if (is_invisible (me))
455 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
460 For some cases we should kern some more: when the
461 distance between the next or prev note is too large, we'd
462 get large white gaps, eg.
483 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
485 Stem::calc_direction (SCM smob)
487 Grob *me = unsmob_grob (smob);
488 Direction dir = CENTER;
489 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
491 SCM ignore_me = beam->get_property ("direction");
493 dir = get_grob_direction (me);
497 SCM dd = me->get_property ("default-direction");
500 return me->get_property ("neutral-direction");
503 return scm_from_int (dir);
506 MAKE_SCHEME_CALLBACK(Stem, calc_default_direction, 1);
508 Stem::calc_default_direction (SCM smob)
510 Grob *me = unsmob_grob (smob);
512 Direction dir = CENTER;
513 int staff_center = 0;
514 Interval hp = head_positions (me);
517 int udistance = (int) (UP * hp[UP] - staff_center);
518 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
520 dir = Direction (sign (ddistance - udistance));
523 return scm_from_int (dir);
527 MAKE_SCHEME_CALLBACK (Stem, height, 1);
529 Stem::height (SCM smob)
531 Grob *me = unsmob_grob (smob);
533 Direction dir = get_grob_direction (me);
537 UGH. Should be automatic
539 Grob *beam = get_beam (me);
542 /* trigger set-stem-lengths. */
543 beam->get_property ("quantized-positions");
547 Can't get_stencil(), since that would cache stencils too early.
548 This causes problems with beams.
550 Stencil *stencil = unsmob_stencil (print (smob));
551 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
556 programming_error ("no stem direction");
559 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
562 return ly_interval2scm (iv);
566 Stem::stem_end_position (Grob *me)
568 return robust_scm2double (me->get_property ("stem-end-position"), 0);
572 Stem::flag (Grob *me)
574 int log = duration_log (me);
576 || unsmob_grob (me->get_object ("beam")))
580 TODO: maybe property stroke-style should take different values,
581 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
585 SCM flag_style_scm = me->get_property ("flag-style");
586 if (scm_is_symbol (flag_style_scm))
587 flag_style = ly_symbol2string (flag_style_scm);
589 if (flag_style == "no-flag")
594 string staffline_offs;
595 if (flag_style == "mensural")
596 /* Mensural notation: For notes on staff lines, use different
597 flags than for notes between staff lines. The idea is that
598 flags are always vertically aligned with the staff lines,
599 regardless if the note head is on a staff line or between two
600 staff lines. In other words, the inner end of a flag always
601 touches a staff line.
606 int p = (int) (rint (stem_end_position (me)));
608 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
611 staffline_offs = "2";
616 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
617 string font_char = flag_style
618 + to_string (dir) + staffline_offs + to_string (log);
619 Font_metric *fm = Font_interface::get_default_font (me);
620 Stencil flag = fm->find_by_name ("flags." + font_char);
621 if (flag.is_empty ())
622 me->warning (_f ("flag `%s' not found", font_char));
624 SCM stroke_style_scm = me->get_property ("stroke-style");
625 if (scm_is_string (stroke_style_scm))
627 string stroke_style = ly_scm2string (stroke_style_scm);
628 if (!stroke_style.empty ())
630 string font_char = to_string (dir) + stroke_style;
631 Stencil stroke = fm->find_by_name ("flags." + font_char);
632 if (stroke.is_empty ())
633 me->warning (_f ("flag stroke `%s' not found", font_char));
635 flag.add_stencil (stroke);
642 MAKE_SCHEME_CALLBACK (Stem, width, 1);
646 Grob *me = unsmob_grob (e);
650 if (is_invisible (me))
652 else if (unsmob_grob (me->get_object ("beam"))
653 || abs (duration_log (me)) <= 2)
655 r = Interval (-1, 1);
656 r *= thickness (me) / 2;
660 r = Interval (-1, 1) * thickness (me) * 0.5;
661 r.unite (flag (me).extent (X_AXIS));
663 return ly_interval2scm (r);
667 Stem::thickness (Grob *me)
669 return scm_to_double (me->get_property ("thickness"))
670 * Staff_symbol_referencer::line_thickness (me);
673 MAKE_SCHEME_CALLBACK (Stem, print, 1);
675 Stem::print (SCM smob)
677 Grob *me = unsmob_grob (smob);
679 Direction d = get_grob_direction (me);
681 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
683 bool stemlet = stemlet_length > 0.0;
685 /* TODO: make the stem start a direction ?
686 This is required to avoid stems passing in tablature chords. */
688 = to_boolean (me->get_property ("avoid-note-head"))
691 Grob *beam = get_beam (me);
696 if (!lh && stemlet && !beam)
699 if (is_invisible (me))
702 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
704 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
707 y2 = Staff_symbol_referencer::get_position (lh);
710 Real beam_translation = Beam::get_beam_translation (beam);
711 Real beam_thickness = Beam::get_thickness (beam);
712 int beam_count = beam_multiplicity (me).length () + 1;
715 * (0.5 * beam_thickness
716 + beam_translation * max (0, (beam_count - 1))
717 + stemlet_length) / half_space;
720 Interval stem_y (min (y1, y2), max (y2, y1));
722 if (Grob *head = support_head (me))
725 must not take ledgers into account.
727 Interval head_height = head->extent (head, Y_AXIS);
728 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
730 y_attach = head_height.linear_combination (y_attach);
731 stem_y[Direction (-d)] += d * y_attach / half_space;
735 Real stem_width = thickness (me);
737 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
739 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
740 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
742 Stencil ss = Lookup::round_filled_box (b, blot);
743 mol.add_stencil (ss);
745 mol.add_stencil (get_translated_flag (me));
747 return mol.smobbed_copy ();
751 Stem::get_translated_flag (Grob *me)
753 Stencil fl = flag (me);
756 Direction d = get_grob_direction (me);
758 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
759 Real stem_width = thickness (me);
760 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
761 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
762 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
763 fl.translate_axis (stem_width / 2, X_AXIS);
770 move the stem to right of the notehead if it is up.
772 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
774 Stem::offset_callback (SCM smob)
776 Grob *me = unsmob_grob (smob);
779 if (Grob *f = first_head (me))
781 Interval head_wid = f->extent (f, X_AXIS);
784 if (is_invisible (me))
787 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
789 Direction d = get_grob_direction (me);
790 Real real_attach = head_wid.linear_combination (d * attach);
793 /* If not centered: correct for stem thickness. */
796 Real rule_thick = thickness (me);
797 r += -d * rule_thick * 0.5;
802 extract_grob_set (me, "rests", rests);
805 Grob *rest = rests.back ();
806 r = rest->extent (rest, X_AXIS).center ();
809 return scm_from_double (r);
813 Stem::get_beam (Grob *me)
815 SCM b = me->get_object ("beam");
816 return dynamic_cast<Spanner *> (unsmob_grob (b));
820 Stem::get_stem_info (Grob *me)
823 si.dir_ = get_grob_direction (me);
825 SCM scm_info = me->get_property ("stem-info");
826 si.ideal_y_ = scm_to_double (scm_car (scm_info));
827 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
831 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
833 Stem::calc_stem_info (SCM smob)
835 Grob *me = unsmob_grob (smob);
836 Direction my_dir = get_grob_direction (me);
840 programming_error ("no stem dir set");
844 Real staff_space = Staff_symbol_referencer::staff_space (me);
845 Grob *beam = get_beam (me);
849 (void) beam->get_property ("beaming");
852 Real beam_translation = Beam::get_beam_translation (beam);
853 Real beam_thickness = Beam::get_thickness (beam);
854 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
856 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
858 /* Simple standard stem length */
859 SCM details = me->get_property ("details");
860 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
863 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
867 /* stem only extends to center of beam
869 - 0.5 * beam_thickness;
871 /* Condition: sane minimum free stem length (chord to beams) */
872 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
874 Real ideal_minimum_free
875 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
879 Real height_of_my_trem = 0.0;
880 Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
882 height_of_my_trem = trem->extent (trem, Y_AXIS).length ()
883 /* hack a bit of space around the trem. */
887 It seems that also for ideal minimum length, we must use
888 the maximum beam count (for this direction):
890 \score{ \notes\relative c''{ [a8 a32] }}
892 must be horizontal. */
893 Real height_of_my_beams = beam_thickness
894 + (beam_count - 1) * beam_translation;
896 Real ideal_minimum_length = ideal_minimum_free
899 /* stem only extends to center of beam */
900 - 0.5 * beam_thickness;
902 ideal_length = max (ideal_length, ideal_minimum_length);
904 /* Convert to Y position, calculate for dir == UP */
906 = /* staff positions */
907 head_positions (me)[my_dir] * 0.5
908 * my_dir * staff_space;
909 Real ideal_y = note_start + ideal_length;
911 /* Conditions for Y position */
913 /* Lowest beam of (UP) beam must never be lower than second staffline
917 Although this (additional) rule is probably correct,
918 I expect that highest beam (UP) should also never be lower
919 than middle staffline, just as normal stems.
923 Obviously not for grace beams.
925 Also, not for knees. Seems to be a good thing. */
926 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
927 bool is_knee = to_boolean (beam->get_property ("knee"));
928 if (!no_extend_b && !is_knee)
930 /* Highest beam of (UP) beam must never be lower than middle
932 ideal_y = max (ideal_y, 0.0);
933 /* Lowest beam of (UP) beam must never be lower than second staffline */
934 ideal_y = max (ideal_y, (-staff_space
935 - beam_thickness + height_of_my_beams));
938 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
940 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
944 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
948 Real minimum_length = max (minimum_free, height_of_my_trem)
950 /* stem only extends to center of beam */
951 - 0.5 * beam_thickness;
954 Real minimum_y = note_start + minimum_length;
955 Real shortest_y = minimum_y * my_dir;
957 return scm_list_2 (scm_from_double (ideal_y),
958 scm_from_double (shortest_y));
962 Stem::beam_multiplicity (Grob *stem)
964 SCM beaming = stem->get_property ("beaming");
965 Slice le = int_list_to_slice (scm_car (beaming));
966 Slice ri = int_list_to_slice (scm_cdr (beaming));
971 /* FIXME: Too many properties */
972 ADD_INTERFACE (Stem, "stem-interface",
973 "The stem represent the graphical stem. "
974 "In addition, it internally connects note heads, beams and"
976 "Rests and whole notes have invisible stems."
978 "\n\nThe following properties may be set in the details list."
980 "@item beamed-lengths \n"
981 "list of stem lengths given beam multiplicity. \n"
982 "@item beamed-minimum-free-lengths \n"
983 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
984 "@item beamed-extreme-minimum-free-lengths\n"
985 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
987 "Default stem lengths. The list gives a length for each flag-count.\n"
988 "@item stem-shorten\n"
989 "How much a stem in a forced direction "
990 "should be shortened. The list gives an amount depending on the number "
1008 "neutral-direction "
1013 "stem-end-position "
1021 /****************************************************************/
1023 Stem_info::Stem_info ()
1025 ideal_y_ = shortest_y_ = 0;
1030 Stem_info::scale (Real x)