2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2005 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 "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 stem_end = 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 * stem_end < 0)
250 /* Make a little room if we have a upflag and there is a dot.
251 previous approach was to lengthen the stem. This is not
252 good typesetting practice. */
253 if (!get_beam (me) && dir == UP
256 Grob *closest_to_flag = extremal_heads (me)[dir];
257 Grob *dots = closest_to_flag
258 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
262 Real dp = Staff_symbol_referencer::get_position (dots);
263 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
265 /* Very gory: add myself to the X-support of the parent,
266 which should be a dot-column. */
267 if (dir * (stem_end + flagy - dp) < 0.5)
269 Grob *par = dots->get_parent (X_AXIS);
271 if (Dot_column::has_interface (par))
273 Side_position_interface::add_support (par, me);
275 /* TODO: apply some better logic here. The flag is
276 curved inwards, so this will typically be too
283 return scm_from_double (stem_end);
287 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
289 Stem::calc_length (SCM smob)
291 Grob *me = unsmob_grob (smob);
293 SCM details = me->get_property ("details");
294 int durlog = duration_log (me);
296 Real ss = Staff_symbol_referencer::staff_space (me);
298 SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
300 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
302 Direction dir = get_grob_direction (me);
304 /* Stems in unnatural (forced) direction should be shortened,
305 according to [Roush & Gourlay] */
306 Interval hp = head_positions (me);
307 if (dir && dir * hp[dir] >= 0)
309 SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
310 SCM scm_shorten = scm_is_pair (sshorten)
311 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
312 Real shorten = 2* robust_scm2double (scm_shorten, 0);
314 /* On boundary: shorten only half */
315 if (abs (head_positions (me)[dir]) <= 1)
321 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
324 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
325 if (t_flag && !unsmob_grob (me->get_object ("beam")))
327 /* Crude hack: add extra space if tremolo flag is there.
329 We can't do this for the beam, since we get into a loop
330 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
333 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
338 Interval flag_ext = flag (me).extent (Y_AXIS);
339 if (!flag_ext.is_empty ())
340 minlen += 2 * flag_ext.length () / ss;
342 /* The clash is smaller for down stems (since the tremolo is
347 length = max (length, minlen + 1.0);
350 return scm_from_double (length);
352 /* The log of the duration (Number of hooks on the flag minus two) */
354 Stem::duration_log (Grob *me)
356 SCM s = me->get_property ("duration-log");
357 return (scm_is_number (s)) ? scm_to_int (s) : 2;
360 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
362 Stem::calc_positioning_done (SCM smob)
364 Grob *me = unsmob_grob (smob);
365 if (!head_count (me))
368 extract_grob_set (me, "note-heads", ro_heads);
369 Link_array<Grob> heads (ro_heads);
370 heads.sort (compare_position);
371 Direction dir = get_grob_direction (me);
376 Real thick = thickness (me);
378 Grob *hed = support_head (me);
381 programming_error ("Stem dir must be up or down.");
383 set_grob_direction (me, dir);
386 Real w = hed->extent (hed, X_AXIS)[dir];
387 for (int i = 0; i < heads.size (); i++)
388 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
392 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
393 for (int i = 1; i < heads.size (); i++)
395 Real p = Staff_symbol_referencer::get_position (heads[i]);
396 Real dy = fabs (lastpos- p);
399 dy should always be 0.5, 0.0, 1.0, but provide safety margin
406 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
408 Direction d = get_grob_direction (me);
410 Reversed head should be shifted ell-thickness, but this
411 looks too crowded, so we only shift ell-0.5*thickness.
413 This leads to assymetry: Normal heads overlap the
414 stem 100% whereas reversed heads only overlaps the
418 Real reverse_overlap = 0.5;
419 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
422 if (is_invisible (me))
423 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
428 For some cases we should kern some more: when the
429 distance between the next or prev note is too large, we'd
430 get large white gaps, eg.
451 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
453 Stem::calc_direction (SCM smob)
455 Grob *me = unsmob_grob (smob);
456 Direction dir = CENTER;
457 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
459 SCM ignore_me = beam->get_property ("direction");
461 dir = get_grob_direction (me);
465 SCM dd = me->get_property ("default-direction");
468 return me->get_property ("neutral-direction");
471 return scm_from_int (dir);
474 MAKE_SCHEME_CALLBACK(Stem, calc_default_direction, 1);
476 Stem::calc_default_direction (SCM smob)
478 Grob *me = unsmob_grob (smob);
480 Direction dir = CENTER;
481 int staff_center = 0;
482 Interval hp = head_positions (me);
485 int udistance = (int) (UP * hp[UP] - staff_center);
486 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
488 dir = Direction (sign (ddistance - udistance));
491 return scm_from_int (dir);
495 MAKE_SCHEME_CALLBACK (Stem, height, 1);
497 Stem::height (SCM smob)
499 Grob *me = unsmob_grob (smob);
501 Direction dir = get_grob_direction (me);
505 UGH. Should be automatic
507 Grob *beam = get_beam (me);
510 beam->get_property ("positions");
514 Can't get_stencil(), since that would cache stencils too early.
515 This causes problems with beams.
517 Stencil *stencil = unsmob_stencil (print (smob));
518 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
523 programming_error ("no stem direction");
526 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
529 return ly_interval2scm (iv);
533 Stem::stem_end_position (Grob *me)
535 return robust_scm2double (me->get_property ("stem-end-position"), 0);
539 Stem::flag (Grob *me)
541 int log = duration_log (me);
543 || unsmob_grob (me->get_object ("beam")))
547 TODO: maybe property stroke-style should take different values,
548 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
552 SCM flag_style_scm = me->get_property ("flag-style");
553 if (scm_is_symbol (flag_style_scm))
554 flag_style = ly_symbol2string (flag_style_scm);
556 if (flag_style == "no-flag")
561 String staffline_offs;
562 if (String::compare (flag_style, "mensural") == 0)
563 /* Mensural notation: For notes on staff lines, use different
564 flags than for notes between staff lines. The idea is that
565 flags are always vertically aligned with the staff lines,
566 regardless if the note head is on a staff line or between two
567 staff lines. In other words, the inner end of a flag always
568 touches a staff line.
573 int p = (int) (rint (stem_end_position (me)));
575 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
578 staffline_offs = "2";
583 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
584 String font_char = flag_style
585 + to_string (dir) + staffline_offs + to_string (log);
586 Font_metric *fm = Font_interface::get_default_font (me);
587 Stencil flag = fm->find_by_name ("flags." + font_char);
588 if (flag.is_empty ())
589 me->warning (_f ("flag `%s' not found", font_char));
591 SCM stroke_style_scm = me->get_property ("stroke-style");
592 if (scm_is_string (stroke_style_scm))
594 String stroke_style = ly_scm2string (stroke_style_scm);
595 if (!stroke_style.is_empty ())
597 String font_char = to_string (dir) + stroke_style;
598 Stencil stroke = fm->find_by_name ("flags." + font_char);
599 if (stroke.is_empty ())
600 me->warning (_f ("flag stroke `%s' not found", font_char));
602 flag.add_stencil (stroke);
609 MAKE_SCHEME_CALLBACK (Stem, width, 1);
613 Grob *me = unsmob_grob (e);
617 if (is_invisible (me))
619 else if (unsmob_grob (me->get_object ("beam"))
620 || abs (duration_log (me)) <= 2)
622 r = Interval (-1, 1);
623 r *= thickness (me) / 2;
627 r = Interval (-1, 1) * thickness (me) * 0.5;
628 r.unite (flag (me).extent (X_AXIS));
630 return ly_interval2scm (r);
634 Stem::thickness (Grob *me)
636 return scm_to_double (me->get_property ("thickness"))
637 * Staff_symbol_referencer::line_thickness (me);
640 MAKE_SCHEME_CALLBACK (Stem, print, 1);
642 Stem::print (SCM smob)
644 Grob *me = unsmob_grob (smob);
646 Direction d = get_grob_direction (me);
648 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
650 bool stemlet = stemlet_length > 0.0;
652 /* TODO: make the stem start a direction ?
653 This is required to avoid stems passing in tablature chords. */
655 = to_boolean (me->get_property ("avoid-note-head"))
658 Grob *beam = get_beam (me);
663 if (!lh && stemlet && !beam)
666 if (is_invisible (me))
669 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
671 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
674 y2 = Staff_symbol_referencer::get_position (lh);
677 Real beam_translation = Beam::get_beam_translation (beam);
678 Real beam_thickness = Beam::get_thickness (beam);
679 int beam_count = beam_multiplicity (me).length () + 1;
682 * (0.5 * beam_thickness
683 + beam_translation * max (0, (beam_count - 1))
684 + stemlet_length) / half_space;
687 Interval stem_y (min (y1, y2), max (y2, y1));
689 if (Grob *head = support_head (me))
692 must not take ledgers into account.
694 Interval head_height = head->extent (head, Y_AXIS);
695 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
697 y_attach = head_height.linear_combination (y_attach);
698 stem_y[Direction (-d)] += d * y_attach / half_space;
702 Real stem_width = thickness (me);
704 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
706 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
707 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
709 Stencil ss = Lookup::round_filled_box (b, blot);
710 mol.add_stencil (ss);
712 mol.add_stencil (get_translated_flag (me));
714 return mol.smobbed_copy ();
718 Stem::get_translated_flag (Grob *me)
720 Stencil fl = flag (me);
723 Direction d = get_grob_direction (me);
725 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
726 Real stem_width = thickness (me);
727 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
728 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
729 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
730 fl.translate_axis (stem_width / 2, X_AXIS);
737 move the stem to right of the notehead if it is up.
739 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
741 Stem::offset_callback (SCM smob)
743 Grob *me = unsmob_grob (smob);
746 if (Grob *f = first_head (me))
748 Interval head_wid = f->extent (f, X_AXIS);
751 if (is_invisible (me))
754 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
756 Direction d = get_grob_direction (me);
757 Real real_attach = head_wid.linear_combination (d * attach);
760 /* If not centered: correct for stem thickness. */
763 Real rule_thick = thickness (me);
764 r += -d * rule_thick * 0.5;
769 extract_grob_set (me, "rests", rests);
772 Grob *rest = rests.top ();
773 r = rest->extent (rest, X_AXIS).center ();
776 return scm_from_double (r);
780 Stem::get_beam (Grob *me)
782 SCM b = me->get_object ("beam");
783 return dynamic_cast<Spanner *> (unsmob_grob (b));
787 Stem::get_stem_info (Grob *me)
790 si.dir_ = get_grob_direction (me);
792 SCM scm_info = me->get_property ("stem-info");
793 si.ideal_y_ = scm_to_double (scm_car (scm_info));
794 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
798 /* TODO: add extra space for tremolos! */
799 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
801 Stem::calc_stem_info (SCM smob)
803 Grob *me = unsmob_grob (smob);
804 Direction my_dir = get_grob_direction (me);
808 programming_error ("no stem dir set");
812 Real staff_space = Staff_symbol_referencer::staff_space (me);
813 Grob *beam = get_beam (me);
817 (void) beam->get_property ("beaming");
820 Real beam_translation = Beam::get_beam_translation (beam);
821 Real beam_thickness = Beam::get_thickness (beam);
822 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
824 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
826 /* Simple standard stem length */
827 SCM details = me->get_property ("details");
828 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
831 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
835 /* stem only extends to center of beam
837 - 0.5 * beam_thickness;
839 /* Condition: sane minimum free stem length (chord to beams) */
840 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
842 Real ideal_minimum_free
843 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
848 It seems that also for ideal minimum length, we must use
849 the maximum beam count (for this direction):
851 \score{ \notes\relative c''{ [a8 a32] }}
853 must be horizontal. */
854 Real height_of_my_beams = beam_thickness
855 + (beam_count - 1) * beam_translation;
857 Real ideal_minimum_length = ideal_minimum_free
859 /* stem only extends to center of beam */
860 - 0.5 * beam_thickness;
862 ideal_length = max (ideal_length, ideal_minimum_length);
864 /* Convert to Y position, calculate for dir == UP */
866 = /* staff positions */
867 head_positions (me)[my_dir] * 0.5
868 * my_dir * staff_space;
869 Real ideal_y = note_start + ideal_length;
871 /* Conditions for Y position */
873 /* Lowest beam of (UP) beam must never be lower than second staffline
877 Although this (additional) rule is probably correct,
878 I expect that highest beam (UP) should also never be lower
879 than middle staffline, just as normal stems.
883 Obviously not for grace beams.
885 Also, not for knees. Seems to be a good thing. */
886 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
887 bool is_knee = to_boolean (beam->get_property ("knee"));
888 if (!no_extend_b && !is_knee)
890 /* Highest beam of (UP) beam must never be lower than middle
892 ideal_y = max (ideal_y, 0.0);
893 /* Lowest beam of (UP) beam must never be lower than second staffline */
894 ideal_y = max (ideal_y, (-staff_space
895 - beam_thickness + height_of_my_beams));
898 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
900 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
904 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
908 Real minimum_length = minimum_free
910 /* stem only extends to center of beam */
911 - 0.5 * beam_thickness;
913 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
915 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
916 y_ext.widen (0.5); // FIXME. Should be tunable?
917 minimum_length = max (minimum_length, y_ext.length ());
921 Real minimum_y = note_start + minimum_length;
922 Real shortest_y = minimum_y * my_dir;
924 return scm_list_2 (scm_from_double (ideal_y),
925 scm_from_double (shortest_y));
929 Stem::beam_multiplicity (Grob *stem)
931 SCM beaming = stem->get_property ("beaming");
932 Slice le = int_list_to_slice (scm_car (beaming));
933 Slice ri = int_list_to_slice (scm_cdr (beaming));
938 /* FIXME: Too many properties */
939 ADD_INTERFACE (Stem, "stem-interface",
940 "The stem represent the graphical stem. "
941 "In addition, it internally connects note heads, beams and"
943 "Rests and whole notes have invisible stems."
945 "\n\nThe following properties may be set in the details list."
947 "@item beamed-lengths \n"
948 "list of stem lengths given beam multiplicity. \n"
949 "@item beamed-minimum-free-lengths \n"
950 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
951 "@item beamed-extreme-minimum-free-lengths\n"
952 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
954 "Default stem lengths. The list gives a length for each flag-count.\n"
955 "@item stem-shorten\n"
956 "How much a stem in a forced direction "
957 "should be shortened. The list gives an amount depending on the number "
987 /****************************************************************/
989 Stem_info::Stem_info ()
991 ideal_y_ = shortest_y_ = 0;
996 Stem_info::scale (Real x)