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_direction (me)] * Staff_symbol_referencer::staff_space (me)
89 Stem::stem_end_position (Grob *me)
91 SCM p = me->get_property ("stem-end-position");
93 if (!scm_is_number (p))
95 pos = get_default_stem_end_position (me);
96 me->set_property ("stem-end-position", scm_from_double (pos));
99 pos = scm_to_double (p);
105 Stem::get_direction (Grob *me)
107 Direction d = get_grob_direction (me);
111 d = get_default_dir (me);
113 set_grob_direction (me, d);
119 Stem::set_stemend (Grob *me, Real se)
122 Direction d = get_direction (me);
124 if (d && d * head_positions (me)[get_direction (me)] >= se * d)
125 me->warning (_ ("weird stem size, check for narrow beams"));
127 me->set_property ("stem-end-position", scm_from_double (se));
130 /* Note head that determines hshift for upstems
131 WARNING: triggers direction */
133 Stem::support_head (Grob *me)
135 extract_grob_set (me, "note-heads", heads);
136 if (heads.size () == 1)
139 return first_head (me);
143 Stem::head_count (Grob *me)
145 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
148 /* The note head which forms one end of the stem.
149 WARNING: triggers direction */
151 Stem::first_head (Grob *me)
153 Direction d = get_direction (me);
155 return extremal_heads (me)[-d];
159 /* The note head opposite to the first head. */
161 Stem::last_head (Grob *me)
163 Direction d = get_direction (me);
165 return extremal_heads (me)[d];
170 START is part where stem reaches `last' head.
172 This function returns a drul with (bottom-head, top-head).
175 Stem::extremal_heads (Grob *me)
177 const int inf = 1000000;
178 Drul_array<int> extpos;
182 Drul_array<Grob *> exthead (0, 0);
183 extract_grob_set (me, "note-heads", heads);
185 for (int i = heads.size (); i--;)
188 int p = Staff_symbol_referencer::get_rounded_position (n);
193 if (d * p > d * extpos[d])
199 while (flip (&d) != DOWN);
205 integer_compare (int const &a, int const &b)
210 /* The positions, in ascending order. */
212 Stem::note_head_positions (Grob *me)
215 extract_grob_set (me, "note-heads", heads);
217 for (int i = heads.size (); i--;)
220 int p = Staff_symbol_referencer::get_rounded_position (n);
225 ps.sort (integer_compare);
230 Stem::add_head (Grob *me, Grob *n)
232 n->set_object ("stem", me->self_scm ());
234 if (Note_head::has_interface (n))
235 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
236 else if (Rest::has_interface (n))
237 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
241 Stem::is_invisible (Grob *me)
243 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
246 return !((head_count (me)
247 || stemlet_length > 0.0)
248 && scm_to_int (me->get_property ("duration-log")) >= 1);
252 Stem::get_default_dir (Grob *me)
254 int staff_center = 0;
255 Interval hp = head_positions (me);
259 int udistance = (int) (UP *hp[UP] - staff_center);
260 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
262 if (sign (ddistance - udistance))
263 return Direction (sign (ddistance - udistance));
265 return to_dir (me->get_property ("neutral-direction"));
270 Stem::get_default_stem_end_position (Grob *me)
272 Real ss = Staff_symbol_referencer::staff_space (me);
273 int durlog = duration_log (me);
277 /* WARNING: IN HALF SPACES */
279 SCM scm_len = me->get_property ("length");
280 if (scm_is_number (scm_len))
281 length = scm_to_double (scm_len);
284 s = me->get_property ("lengths");
286 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
290 'set-default-stemlen' sets direction too. */
291 Direction dir = get_direction (me);
293 /* Stems in unnatural (forced) direction should be shortened,
294 according to [Roush & Gourlay] */
295 Interval hp = head_positions (me);
296 if (dir && dir * hp[dir] >= 0)
298 SCM sshorten = me->get_property ("stem-shorten");
299 SCM scm_shorten = scm_is_pair (sshorten)
300 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
301 Real shorten = 2* robust_scm2double (scm_shorten, 0);
303 /* On boundary: shorten only half */
304 if (abs (head_positions (me)[dir]) <= 1)
311 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
312 if (t_flag && !unsmob_grob (me->get_object ("beam")))
314 /* Crude hack: add extra space if tremolo flag is there.
316 We can't do this for the beam, since we get into a loop
317 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
320 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
325 Interval flag_ext = flag (me).extent (Y_AXIS);
326 if (!flag_ext.is_empty ())
327 minlen += 2 * flag_ext.length () / ss;
329 /* The clash is smaller for down stems (since the tremolo is
334 length = max (length, minlen + 1.0);
337 Real st = dir ? hp[dir] + dir * length : 0;
339 /* TODO: change name to extend-stems to staff/center/'() */
340 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
341 if (!no_extend_b && dir * st < 0)
344 /* Make a little room if we have a upflag and there is a dot.
345 previous approach was to lengthen the stem. This is not
346 good typesetting practice. */
347 if (!get_beam (me) && dir == UP
350 Grob *closest_to_flag = extremal_heads (me)[dir];
351 Grob *dots = closest_to_flag
352 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
356 Real dp = Staff_symbol_referencer::get_position (dots);
357 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
359 /* Very gory: add myself to the X-support of the parent,
360 which should be a dot-column. */
361 if (dir * (st + flagy - dp) < 0.5)
363 Grob *par = dots->get_parent (X_AXIS);
365 if (Dot_column::has_interface (par))
367 Side_position_interface::add_support (par, me);
369 /* TODO: apply some better logic here. The flag is
370 curved inwards, so this will typically be too
379 /* The log of the duration (Number of hooks on the flag minus two) */
381 Stem::duration_log (Grob *me)
383 SCM s = me->get_property ("duration-log");
384 return (scm_is_number (s)) ? scm_to_int (s) : 2;
387 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
389 Stem::calc_positioning_done (SCM smob)
391 Grob *me = unsmob_grob (smob);
392 if (!head_count (me))
395 extract_grob_set (me, "note-heads", ro_heads);
396 Link_array<Grob> heads (ro_heads);
397 heads.sort (compare_position);
398 Direction dir = get_direction (me);
403 Real thick = thickness (me);
405 Grob *hed = support_head (me);
406 Real w = hed->extent (hed, X_AXIS)[dir];
407 for (int i = 0; i < heads.size (); i++)
408 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
412 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
413 for (int i = 1; i < heads.size (); i++)
415 Real p = Staff_symbol_referencer::get_position (heads[i]);
416 Real dy = fabs (lastpos- p);
419 dy should always be 0.5, 0.0, 1.0, but provide safety margin
426 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
428 Direction d = get_direction (me);
430 Reversed head should be shifted ell-thickness, but this
431 looks too crowded, so we only shift ell-0.5*thickness.
433 This leads to assymetry: Normal heads overlap the
434 stem 100% whereas reversed heads only overlaps the
438 Real reverse_overlap = 0.5;
439 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
442 if (is_invisible (me))
443 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
448 For some cases we should kern some more: when the
449 distance between the next or prev note is too large, we'd
450 get large white gaps, eg.
473 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
475 Stem::calc_direction (SCM smob)
477 Grob *me = unsmob_grob (smob);
478 Direction dir = CENTER;
479 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
481 SCM ignore_me = beam->get_property ("direction");
483 dir = get_grob_direction (me);
486 dir = get_default_dir (me);
488 return scm_from_int (dir);
491 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1);
493 Stem::calc_stem_end_position (SCM smob)
495 Grob *me = unsmob_grob (smob);
498 Do the calculations for visible stems, but also for invisible stems
499 with note heads (i.e. half notes.)
504 pos = stem_end_position (me); // ugh. Trigger direction calc.
507 return scm_from_double (pos);
512 When in a beam with tuplet brackets, brew_mol is called early,
513 caching a wrong value.
515 MAKE_SCHEME_CALLBACK (Stem, height, 2);
517 Stem::height (SCM smob, SCM ax)
519 Axis a = (Axis)scm_to_int (ax);
520 Grob *me = unsmob_grob (smob);
521 assert (a == Y_AXIS);
523 Direction dir = get_grob_direction (me);
527 UGH. Should be automatic
529 Grob *beam = get_beam (me);
532 beam->get_property ("positions");
535 SCM mol = me->get_uncached_stencil ();
538 iv = unsmob_stencil (mol)->extent (a);
544 programming_error ("no stem direction");
547 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
550 return ly_interval2scm (iv);
554 Stem::flag (Grob *me)
556 int log = duration_log (me);
558 || unsmob_grob (me->get_object ("beam")))
562 TODO: maybe property stroke-style should take different values,
563 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
567 SCM flag_style_scm = me->get_property ("flag-style");
568 if (scm_is_symbol (flag_style_scm))
569 flag_style = ly_symbol2string (flag_style_scm);
571 if (flag_style == "no-flag")
576 String staffline_offs;
577 if (String::compare (flag_style, "mensural") == 0)
578 /* Mensural notation: For notes on staff lines, use different
579 flags than for notes between staff lines. The idea is that
580 flags are always vertically aligned with the staff lines,
581 regardless if the note head is on a staff line or between two
582 staff lines. In other words, the inner end of a flag always
583 touches a staff line.
588 int p = (int) (rint (stem_end_position (me)));
590 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
593 staffline_offs = "2";
598 char dir = (get_direction (me) == UP) ? 'u' : 'd';
599 String font_char = flag_style
600 + to_string (dir) + staffline_offs + to_string (log);
601 Font_metric *fm = Font_interface::get_default_font (me);
602 Stencil flag = fm->find_by_name ("flags." + font_char);
603 if (flag.is_empty ())
604 me->warning (_f ("flag `%s' not found", font_char));
606 SCM stroke_style_scm = me->get_property ("stroke-style");
607 if (scm_is_string (stroke_style_scm))
609 String stroke_style = ly_scm2string (stroke_style_scm);
610 if (!stroke_style.is_empty ())
612 String font_char = to_string (dir) + stroke_style;
613 Stencil stroke = fm->find_by_name ("flags." + font_char);
614 if (stroke.is_empty ())
615 me->warning (_f ("flag stroke `%s' not found", font_char));
617 flag.add_stencil (stroke);
624 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
626 Stem::width_callback (SCM e, SCM ax)
629 assert (scm_to_int (ax) == X_AXIS);
630 Grob *me = unsmob_grob (e);
634 if (is_invisible (me))
636 else if (unsmob_grob (me->get_object ("beam"))
637 || abs (duration_log (me)) <= 2)
639 r = Interval (-1, 1);
640 r *= thickness (me) / 2;
644 r = Interval (-1, 1) * thickness (me) * 0.5;
645 r.unite (flag (me).extent (X_AXIS));
647 return ly_interval2scm (r);
651 Stem::thickness (Grob *me)
653 return scm_to_double (me->get_property ("thickness"))
654 * Staff_symbol_referencer::line_thickness (me);
657 MAKE_SCHEME_CALLBACK (Stem, print, 1);
659 Stem::print (SCM smob)
661 Grob *me = unsmob_grob (smob);
663 Direction d = get_direction (me);
665 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
667 bool stemlet = stemlet_length > 0.0;
669 /* TODO: make the stem start a direction ?
670 This is required to avoid stems passing in tablature chords. */
672 = to_boolean (me->get_property ("avoid-note-head"))
675 Grob *beam = get_beam (me);
680 if (!lh && stemlet && !beam)
683 if (is_invisible (me))
686 Real y2 = stem_end_position (me);
688 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
691 y2 = Staff_symbol_referencer::get_position (lh);
694 Real beam_translation = Beam::get_beam_translation (beam);
695 Real beam_thickness = Beam::get_thickness (beam);
696 int beam_count = beam_multiplicity (me).length () + 1;
699 * (0.5 * beam_thickness
700 + beam_translation * max (0, (beam_count - 1))
701 + stemlet_length) / half_space;
704 Interval stem_y (min (y1, y2), max (y2, y1));
706 if (Grob *hed = support_head (me))
709 must not take ledgers into account.
711 Interval head_height = hed->extent (hed, Y_AXIS);
712 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
714 y_attach = head_height.linear_combination (y_attach);
715 stem_y[Direction (-d)] += d * y_attach / half_space;
719 Real stem_width = thickness (me);
721 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
723 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
724 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
726 Stencil ss = Lookup::round_filled_box (b, blot);
727 mol.add_stencil (ss);
729 mol.add_stencil (get_translated_flag (me));
731 return mol.smobbed_copy ();
735 Stem::get_translated_flag (Grob *me)
737 Stencil fl = flag (me);
740 Direction d = get_direction (me);
742 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
743 Real stem_width = thickness (me);
744 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
745 Real y2 = stem_end_position (me);
746 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
747 fl.translate_axis (stem_width / 2, X_AXIS);
754 move the stem to right of the notehead if it is up.
756 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
758 Stem::offset_callback (SCM element_smob, SCM)
760 Grob *me = unsmob_grob (element_smob);
763 if (Grob *f = first_head (me))
765 Interval head_wid = f->extent (f, X_AXIS);
768 if (is_invisible (me))
771 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
773 Direction d = get_direction (me);
774 Real real_attach = head_wid.linear_combination (d * attach);
777 /* If not centered: correct for stem thickness. */
780 Real rule_thick = thickness (me);
781 r += -d * rule_thick * 0.5;
786 extract_grob_set (me, "rests", rests);
789 Grob *rest = rests.top ();
790 r = rest->extent (rest, X_AXIS).center ();
793 return scm_from_double (r);
797 Stem::get_beam (Grob *me)
799 SCM b = me->get_object ("beam");
800 return dynamic_cast<Spanner *> (unsmob_grob (b));
804 Stem::get_stem_info (Grob *me)
807 si.dir_ = get_grob_direction (me);
809 SCM scm_info = me->get_property ("stem-info");
810 si.ideal_y_ = scm_to_double (scm_car (scm_info));
811 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
815 /* TODO: add extra space for tremolos! */
816 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
818 Stem::calc_stem_info (SCM smob)
820 Grob *me = unsmob_grob (smob);
821 Direction my_dir = get_grob_direction (me);
825 programming_error ("no stem dir set");
829 Real staff_space = Staff_symbol_referencer::staff_space (me);
830 Grob *beam = get_beam (me);
831 Real beam_translation = Beam::get_beam_translation (beam);
832 Real beam_thickness = Beam::get_thickness (beam);
833 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
835 /* Simple standard stem length */
836 SCM lengths = me->get_property ("beamed-lengths");
838 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
841 /* stem only extends to center of beam
843 - 0.5 * beam_thickness;
845 /* Condition: sane minimum free stem length (chord to beams) */
846 lengths = me->get_property ("beamed-minimum-free-lengths");
847 Real ideal_minimum_free
848 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
852 It seems that also for ideal minimum length, we must use
853 the maximum beam count (for this direction):
855 \score{ \notes\relative c''{ [a8 a32] }}
857 must be horizontal. */
858 Real height_of_my_beams = beam_thickness
859 + (beam_count - 1) * beam_translation;
861 Real ideal_minimum_length = ideal_minimum_free
863 /* stem only extends to center of beam */
864 - 0.5 * beam_thickness;
866 ideal_length = max (ideal_length, ideal_minimum_length);
868 /* Convert to Y position, calculate for dir == UP */
870 = /* staff positions */
871 head_positions (me)[my_dir] * 0.5
872 * my_dir * staff_space;
873 Real ideal_y = note_start + ideal_length;
875 /* Conditions for Y position */
877 /* Lowest beam of (UP) beam must never be lower than second staffline
881 Although this (additional) rule is probably correct,
882 I expect that highest beam (UP) should also never be lower
883 than middle staffline, just as normal stems.
887 Obviously not for grace beams.
889 Also, not for knees. Seems to be a good thing. */
890 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
891 bool is_knee = to_boolean (beam->get_property ("knee"));
892 if (!no_extend_b && !is_knee)
894 /* Highest beam of (UP) beam must never be lower than middle
896 ideal_y = max (ideal_y, 0.0);
897 /* Lowest beam of (UP) beam must never be lower than second staffline */
898 ideal_y = max (ideal_y, (-staff_space
899 - beam_thickness + height_of_my_beams));
902 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
905 = scm_to_double (robust_list_ref
908 ("beamed-extreme-minimum-free-lengths")))
911 Real minimum_length = minimum_free
913 /* stem only extends to center of beam */
914 - 0.5 * beam_thickness;
916 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
918 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
919 y_ext.widen (0.5); // FIXME. Should be tunable?
920 minimum_length = max (minimum_length, y_ext.length ());
924 Real minimum_y = note_start + minimum_length;
925 Real shortest_y = minimum_y * my_dir;
927 return scm_list_2 (scm_from_double (ideal_y),
928 scm_from_double (shortest_y));
932 Stem::beam_multiplicity (Grob *stem)
934 SCM beaming = stem->get_property ("beaming");
935 Slice le = int_list_to_slice (scm_car (beaming));
936 Slice ri = int_list_to_slice (scm_cdr (beaming));
941 /* FIXME: Too many properties */
942 ADD_INTERFACE (Stem, "stem-interface",
943 "The stem represent the graphical stem. "
944 "In addition, it internally connects note heads, beams and"
946 "Rests and whole notes have invisible stems.",
952 "beamed-extreme-minimum-free-lengths "
954 "beamed-minimum-free-lengths "
976 /****************************************************************/
978 Stem_info::Stem_info ()
980 ideal_y_ = shortest_y_ = 0;
985 Stem_info::scale (Real x)