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);
378 Real w = hed->extent (hed, X_AXIS)[dir];
379 for (int i = 0; i < heads.size (); i++)
380 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
384 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
385 for (int i = 1; i < heads.size (); i++)
387 Real p = Staff_symbol_referencer::get_position (heads[i]);
388 Real dy = fabs (lastpos- p);
391 dy should always be 0.5, 0.0, 1.0, but provide safety margin
398 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
400 Direction d = get_grob_direction (me);
402 Reversed head should be shifted ell-thickness, but this
403 looks too crowded, so we only shift ell-0.5*thickness.
405 This leads to assymetry: Normal heads overlap the
406 stem 100% whereas reversed heads only overlaps the
410 Real reverse_overlap = 0.5;
411 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
414 if (is_invisible (me))
415 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
420 For some cases we should kern some more: when the
421 distance between the next or prev note is too large, we'd
422 get large white gaps, eg.
445 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
447 Stem::calc_direction (SCM smob)
449 Grob *me = unsmob_grob (smob);
450 Direction dir = CENTER;
451 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
453 SCM ignore_me = beam->get_property ("direction");
455 dir = get_grob_direction (me);
458 dir = get_default_dir (me);
460 return scm_from_int (dir);
464 Stem::get_default_dir (Grob *me)
466 Direction dir = CENTER;
467 int staff_center = 0;
468 Interval hp = head_positions (me);
471 int udistance = (int) (UP *hp[UP] - staff_center);
472 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
474 if (sign (ddistance - udistance))
475 dir = Direction (sign (ddistance - udistance));
477 dir = to_dir (me->get_property ("neutral-direction"));
484 MAKE_SCHEME_CALLBACK (Stem, height, 2);
486 Stem::height (SCM smob, SCM ax)
488 Axis a = (Axis)scm_to_int (ax);
489 Grob *me = unsmob_grob (smob);
490 assert (a == Y_AXIS);
492 Direction dir = get_grob_direction (me);
496 UGH. Should be automatic
498 Grob *beam = get_beam (me);
501 beam->get_property ("positions");
504 /* FIXME uncached? */
505 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
510 programming_error ("no stem direction");
513 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
516 return ly_interval2scm (iv);
520 Stem::stem_end_position (Grob *me)
522 return robust_scm2double (me->get_property ("stem-end-position"), 0);
526 Stem::flag (Grob *me)
528 int log = duration_log (me);
530 || unsmob_grob (me->get_object ("beam")))
534 TODO: maybe property stroke-style should take different values,
535 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
539 SCM flag_style_scm = me->get_property ("flag-style");
540 if (scm_is_symbol (flag_style_scm))
541 flag_style = ly_symbol2string (flag_style_scm);
543 if (flag_style == "no-flag")
548 String staffline_offs;
549 if (String::compare (flag_style, "mensural") == 0)
550 /* Mensural notation: For notes on staff lines, use different
551 flags than for notes between staff lines. The idea is that
552 flags are always vertically aligned with the staff lines,
553 regardless if the note head is on a staff line or between two
554 staff lines. In other words, the inner end of a flag always
555 touches a staff line.
560 int p = (int) (rint (stem_end_position (me)));
562 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
565 staffline_offs = "2";
570 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
571 String font_char = flag_style
572 + to_string (dir) + staffline_offs + to_string (log);
573 Font_metric *fm = Font_interface::get_default_font (me);
574 Stencil flag = fm->find_by_name ("flags." + font_char);
575 if (flag.is_empty ())
576 me->warning (_f ("flag `%s' not found", font_char));
578 SCM stroke_style_scm = me->get_property ("stroke-style");
579 if (scm_is_string (stroke_style_scm))
581 String stroke_style = ly_scm2string (stroke_style_scm);
582 if (!stroke_style.is_empty ())
584 String font_char = to_string (dir) + stroke_style;
585 Stencil stroke = fm->find_by_name ("flags." + font_char);
586 if (stroke.is_empty ())
587 me->warning (_f ("flag stroke `%s' not found", font_char));
589 flag.add_stencil (stroke);
596 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
598 Stem::width_callback (SCM e, SCM ax)
601 assert (scm_to_int (ax) == X_AXIS);
602 Grob *me = unsmob_grob (e);
606 if (is_invisible (me))
608 else if (unsmob_grob (me->get_object ("beam"))
609 || abs (duration_log (me)) <= 2)
611 r = Interval (-1, 1);
612 r *= thickness (me) / 2;
616 r = Interval (-1, 1) * thickness (me) * 0.5;
617 r.unite (flag (me).extent (X_AXIS));
619 return ly_interval2scm (r);
623 Stem::thickness (Grob *me)
625 return scm_to_double (me->get_property ("thickness"))
626 * Staff_symbol_referencer::line_thickness (me);
629 MAKE_SCHEME_CALLBACK (Stem, print, 1);
631 Stem::print (SCM smob)
633 Grob *me = unsmob_grob (smob);
635 Direction d = get_grob_direction (me);
637 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
639 bool stemlet = stemlet_length > 0.0;
641 /* TODO: make the stem start a direction ?
642 This is required to avoid stems passing in tablature chords. */
644 = to_boolean (me->get_property ("avoid-note-head"))
647 Grob *beam = get_beam (me);
652 if (!lh && stemlet && !beam)
655 if (is_invisible (me))
658 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
660 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
663 y2 = Staff_symbol_referencer::get_position (lh);
666 Real beam_translation = Beam::get_beam_translation (beam);
667 Real beam_thickness = Beam::get_thickness (beam);
668 int beam_count = beam_multiplicity (me).length () + 1;
671 * (0.5 * beam_thickness
672 + beam_translation * max (0, (beam_count - 1))
673 + stemlet_length) / half_space;
676 Interval stem_y (min (y1, y2), max (y2, y1));
678 if (Grob *hed = support_head (me))
681 must not take ledgers into account.
683 Interval head_height = hed->extent (hed, Y_AXIS);
684 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
686 y_attach = head_height.linear_combination (y_attach);
687 stem_y[Direction (-d)] += d * y_attach / half_space;
691 Real stem_width = thickness (me);
693 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
695 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
696 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
698 Stencil ss = Lookup::round_filled_box (b, blot);
699 mol.add_stencil (ss);
701 mol.add_stencil (get_translated_flag (me));
703 return mol.smobbed_copy ();
707 Stem::get_translated_flag (Grob *me)
709 Stencil fl = flag (me);
712 Direction d = get_grob_direction (me);
714 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
715 Real stem_width = thickness (me);
716 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
717 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
718 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
719 fl.translate_axis (stem_width / 2, X_AXIS);
726 move the stem to right of the notehead if it is up.
728 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
730 Stem::offset_callback (SCM element_smob, SCM)
732 Grob *me = unsmob_grob (element_smob);
735 if (Grob *f = first_head (me))
737 Interval head_wid = f->extent (f, X_AXIS);
740 if (is_invisible (me))
743 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
745 Direction d = get_grob_direction (me);
746 Real real_attach = head_wid.linear_combination (d * attach);
749 /* If not centered: correct for stem thickness. */
752 Real rule_thick = thickness (me);
753 r += -d * rule_thick * 0.5;
758 extract_grob_set (me, "rests", rests);
761 Grob *rest = rests.top ();
762 r = rest->extent (rest, X_AXIS).center ();
765 return scm_from_double (r);
769 Stem::get_beam (Grob *me)
771 SCM b = me->get_object ("beam");
772 return dynamic_cast<Spanner *> (unsmob_grob (b));
776 Stem::get_stem_info (Grob *me)
779 si.dir_ = get_grob_direction (me);
781 SCM scm_info = me->get_property ("stem-info");
782 si.ideal_y_ = scm_to_double (scm_car (scm_info));
783 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
787 /* TODO: add extra space for tremolos! */
788 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
790 Stem::calc_stem_info (SCM smob)
792 Grob *me = unsmob_grob (smob);
793 Direction my_dir = get_grob_direction (me);
797 programming_error ("no stem dir set");
801 Real staff_space = Staff_symbol_referencer::staff_space (me);
802 Grob *beam = get_beam (me);
803 Real beam_translation = Beam::get_beam_translation (beam);
804 Real beam_thickness = Beam::get_thickness (beam);
805 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
807 /* Simple standard stem length */
808 SCM details = me->get_property ("details");
809 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
813 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
816 /* stem only extends to center of beam
818 - 0.5 * beam_thickness;
820 /* Condition: sane minimum free stem length (chord to beams) */
821 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
823 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
825 Real ideal_minimum_free
826 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
827 * staff_space * length_fraction;
830 It seems that also for ideal minimum length, we must use
831 the maximum beam count (for this direction):
833 \score{ \notes\relative c''{ [a8 a32] }}
835 must be horizontal. */
836 Real height_of_my_beams = beam_thickness
837 + (beam_count - 1) * beam_translation;
839 Real ideal_minimum_length = ideal_minimum_free
841 /* stem only extends to center of beam */
842 - 0.5 * beam_thickness;
844 ideal_length = max (ideal_length, ideal_minimum_length);
846 /* Convert to Y position, calculate for dir == UP */
848 = /* staff positions */
849 head_positions (me)[my_dir] * 0.5
850 * my_dir * staff_space;
851 Real ideal_y = note_start + ideal_length;
853 /* Conditions for Y position */
855 /* Lowest beam of (UP) beam must never be lower than second staffline
859 Although this (additional) rule is probably correct,
860 I expect that highest beam (UP) should also never be lower
861 than middle staffline, just as normal stems.
865 Obviously not for grace beams.
867 Also, not for knees. Seems to be a good thing. */
868 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
869 bool is_knee = to_boolean (beam->get_property ("knee"));
870 if (!no_extend_b && !is_knee)
872 /* Highest beam of (UP) beam must never be lower than middle
874 ideal_y = max (ideal_y, 0.0);
875 /* Lowest beam of (UP) beam must never be lower than second staffline */
876 ideal_y = max (ideal_y, (-staff_space
877 - beam_thickness + height_of_my_beams));
880 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
882 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
886 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
889 Real minimum_length = minimum_free
891 /* stem only extends to center of beam */
892 - 0.5 * beam_thickness;
894 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
896 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
897 y_ext.widen (0.5); // FIXME. Should be tunable?
898 minimum_length = max (minimum_length, y_ext.length ());
902 Real minimum_y = note_start + minimum_length;
903 Real shortest_y = minimum_y * my_dir;
905 return scm_list_2 (scm_from_double (ideal_y),
906 scm_from_double (shortest_y));
910 Stem::beam_multiplicity (Grob *stem)
912 SCM beaming = stem->get_property ("beaming");
913 Slice le = int_list_to_slice (scm_car (beaming));
914 Slice ri = int_list_to_slice (scm_cdr (beaming));
919 /* FIXME: Too many properties */
920 ADD_INTERFACE (Stem, "stem-interface",
921 "The stem represent the graphical stem. "
922 "In addition, it internally connects note heads, beams and"
924 "Rests and whole notes have invisible stems."
926 "\n\nThe following properties may be set in the details list."
928 "@item beamed-lengths \n"
929 "list of stem lengths given beam multiplicity. \n"
930 "@item beamed-minimum-free-lengths \n"
931 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
932 "@item beamed-extreme-minimum-free-lengths\n"
933 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
935 "Default stem lengths. The list gives a length for each flag-count.\n"
936 "@item stem-shorten\n"
937 "How much a stem in a forced direction "
938 "should be shortened. The list gives an amount depending on the number "
969 /****************************************************************/
971 Stem_info::Stem_info ()
973 ideal_y_ = shortest_y_ = 0;
978 Stem_info::scale (Real x)