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);
464 dir = get_default_dir (me);
466 return scm_from_int (dir);
469 /* A separate function, since this is used elsewhere too. */
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, 1);
493 Stem::height (SCM smob)
495 Grob *me = unsmob_grob (smob);
497 Direction dir = get_grob_direction (me);
501 UGH. Should be automatic
503 Grob *beam = get_beam (me);
506 beam->get_property ("positions");
510 Can't get_stencil(), since that would cache stencils too early.
511 This causes problems with beams.
513 Stencil *stencil = unsmob_stencil (print (smob));
514 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
519 programming_error ("no stem direction");
522 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
525 return ly_interval2scm (iv);
529 Stem::stem_end_position (Grob *me)
531 return robust_scm2double (me->get_property ("stem-end-position"), 0);
535 Stem::flag (Grob *me)
537 int log = duration_log (me);
539 || unsmob_grob (me->get_object ("beam")))
543 TODO: maybe property stroke-style should take different values,
544 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
548 SCM flag_style_scm = me->get_property ("flag-style");
549 if (scm_is_symbol (flag_style_scm))
550 flag_style = ly_symbol2string (flag_style_scm);
552 if (flag_style == "no-flag")
557 String staffline_offs;
558 if (String::compare (flag_style, "mensural") == 0)
559 /* Mensural notation: For notes on staff lines, use different
560 flags than for notes between staff lines. The idea is that
561 flags are always vertically aligned with the staff lines,
562 regardless if the note head is on a staff line or between two
563 staff lines. In other words, the inner end of a flag always
564 touches a staff line.
569 int p = (int) (rint (stem_end_position (me)));
571 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
574 staffline_offs = "2";
579 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
580 String font_char = flag_style
581 + to_string (dir) + staffline_offs + to_string (log);
582 Font_metric *fm = Font_interface::get_default_font (me);
583 Stencil flag = fm->find_by_name ("flags." + font_char);
584 if (flag.is_empty ())
585 me->warning (_f ("flag `%s' not found", font_char));
587 SCM stroke_style_scm = me->get_property ("stroke-style");
588 if (scm_is_string (stroke_style_scm))
590 String stroke_style = ly_scm2string (stroke_style_scm);
591 if (!stroke_style.is_empty ())
593 String font_char = to_string (dir) + stroke_style;
594 Stencil stroke = fm->find_by_name ("flags." + font_char);
595 if (stroke.is_empty ())
596 me->warning (_f ("flag stroke `%s' not found", font_char));
598 flag.add_stencil (stroke);
605 MAKE_SCHEME_CALLBACK (Stem, width, 1);
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 *head = support_head (me))
688 must not take ledgers into account.
690 Interval head_height = head->extent (head, Y_AXIS);
691 Real y_attach = Note_head::stem_attachment_coordinate (head, 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->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->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, 1);
737 Stem::offset_callback (SCM smob)
739 Grob *me = unsmob_grob (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);
813 (void) beam->get_property ("beaming");
816 Real beam_translation = Beam::get_beam_translation (beam);
817 Real beam_thickness = Beam::get_thickness (beam);
818 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
820 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
822 /* Simple standard stem length */
823 SCM details = me->get_property ("details");
824 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
827 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
831 /* stem only extends to center of beam
833 - 0.5 * beam_thickness;
835 /* Condition: sane minimum free stem length (chord to beams) */
836 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
838 Real ideal_minimum_free
839 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
844 It seems that also for ideal minimum length, we must use
845 the maximum beam count (for this direction):
847 \score{ \notes\relative c''{ [a8 a32] }}
849 must be horizontal. */
850 Real height_of_my_beams = beam_thickness
851 + (beam_count - 1) * beam_translation;
853 Real ideal_minimum_length = ideal_minimum_free
855 /* stem only extends to center of beam */
856 - 0.5 * beam_thickness;
858 ideal_length = max (ideal_length, ideal_minimum_length);
860 /* Convert to Y position, calculate for dir == UP */
862 = /* staff positions */
863 head_positions (me)[my_dir] * 0.5
864 * my_dir * staff_space;
865 Real ideal_y = note_start + ideal_length;
867 /* Conditions for Y position */
869 /* Lowest beam of (UP) beam must never be lower than second staffline
873 Although this (additional) rule is probably correct,
874 I expect that highest beam (UP) should also never be lower
875 than middle staffline, just as normal stems.
879 Obviously not for grace beams.
881 Also, not for knees. Seems to be a good thing. */
882 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
883 bool is_knee = to_boolean (beam->get_property ("knee"));
884 if (!no_extend_b && !is_knee)
886 /* Highest beam of (UP) beam must never be lower than middle
888 ideal_y = max (ideal_y, 0.0);
889 /* Lowest beam of (UP) beam must never be lower than second staffline */
890 ideal_y = max (ideal_y, (-staff_space
891 - beam_thickness + height_of_my_beams));
894 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
896 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
900 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
904 Real minimum_length = minimum_free
906 /* stem only extends to center of beam */
907 - 0.5 * beam_thickness;
909 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
911 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
912 y_ext.widen (0.5); // FIXME. Should be tunable?
913 minimum_length = max (minimum_length, y_ext.length ());
917 Real minimum_y = note_start + minimum_length;
918 Real shortest_y = minimum_y * my_dir;
920 return scm_list_2 (scm_from_double (ideal_y),
921 scm_from_double (shortest_y));
925 Stem::beam_multiplicity (Grob *stem)
927 SCM beaming = stem->get_property ("beaming");
928 Slice le = int_list_to_slice (scm_car (beaming));
929 Slice ri = int_list_to_slice (scm_cdr (beaming));
934 /* FIXME: Too many properties */
935 ADD_INTERFACE (Stem, "stem-interface",
936 "The stem represent the graphical stem. "
937 "In addition, it internally connects note heads, beams and"
939 "Rests and whole notes have invisible stems."
941 "\n\nThe following properties may be set in the details list."
943 "@item beamed-lengths \n"
944 "list of stem lengths given beam multiplicity. \n"
945 "@item beamed-minimum-free-lengths \n"
946 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
947 "@item beamed-extreme-minimum-free-lengths\n"
948 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
950 "Default stem lengths. The list gives a length for each flag-count.\n"
951 "@item stem-shorten\n"
952 "How much a stem in a forced direction "
953 "should be shortened. The list gives an amount depending on the number "
982 /****************************************************************/
984 Stem_info::Stem_info ()
986 ideal_y_ = shortest_y_ = 0;
991 Stem_info::scale (Real x)