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);
51 for (int i = 0; i < beam_count; i++)
52 lst = scm_cons (scm_from_int (i), lst);
53 index_set_cell (pair, d, lst);
57 Stem::get_beaming (Grob *me, Direction d)
59 SCM pair = me->get_property ("beaming");
60 if (!scm_is_pair (pair))
63 SCM lst = index_get_cell (pair, d);
64 return scm_ilength (lst);
68 Stem::head_positions (Grob *me)
72 Drul_array<Grob *> e (extremal_heads (me));
73 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
74 Staff_symbol_referencer::get_position (e[UP]));
80 Stem::chord_start_y (Grob *me)
82 Interval hp = head_positions (me);
84 return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
92 Stem::set_stemend (Grob *me, Real se)
95 Direction d = get_grob_direction (me);
97 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
98 me->warning (_ ("weird stem size, check for narrow beams"));
100 me->set_property ("stem-end-position", scm_from_double (se));
103 /* Note head that determines hshift for upstems
104 WARNING: triggers direction */
106 Stem::support_head (Grob *me)
108 extract_grob_set (me, "note-heads", heads);
109 if (heads.size () == 1)
112 return first_head (me);
116 Stem::head_count (Grob *me)
118 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
121 /* The note head which forms one end of the stem.
122 WARNING: triggers direction */
124 Stem::first_head (Grob *me)
126 Direction d = get_grob_direction (me);
128 return extremal_heads (me)[-d];
132 /* The note head opposite to the first head. */
134 Stem::last_head (Grob *me)
136 Direction d = get_grob_direction (me);
138 return extremal_heads (me)[d];
143 START is part where stem reaches `last' head.
145 This function returns a drul with (bottom-head, top-head).
148 Stem::extremal_heads (Grob *me)
150 const int inf = 1000000;
151 Drul_array<int> extpos;
155 Drul_array<Grob *> exthead (0, 0);
156 extract_grob_set (me, "note-heads", heads);
158 for (vsize i = heads.size (); i--;)
161 int p = Staff_symbol_referencer::get_rounded_position (n);
166 if (d * p > d * extpos[d])
172 while (flip (&d) != DOWN);
178 integer_compare (int const &a, int const &b)
183 /* The positions, in ascending order. */
185 Stem::note_head_positions (Grob *me)
188 extract_grob_set (me, "note-heads", heads);
190 for (vsize i = heads.size (); i--;)
193 int p = Staff_symbol_referencer::get_rounded_position (n);
198 vector_sort (ps, integer_compare);
203 Stem::add_head (Grob *me, Grob *n)
205 n->set_object ("stem", me->self_scm ());
207 if (Note_head::has_interface (n))
208 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
209 else if (Rest::has_interface (n))
210 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
214 Stem::is_invisible (Grob *me)
216 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
219 return !((head_count (me)
220 || stemlet_length > 0.0)
221 && scm_to_int (me->get_property ("duration-log")) >= 1);
224 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
226 Stem::calc_stem_end_position (SCM smob)
228 Grob *me = unsmob_grob (smob);
230 if (!head_count (me))
231 return scm_from_double (0.0);
234 Real ss = Staff_symbol_referencer::staff_space (me);
235 int durlog = duration_log (me);
238 /* WARNING: IN HALF SPACES */
239 Real length = robust_scm2double (me->get_property ("length"), 7);
241 Direction dir = get_grob_direction (me);
242 Interval hp = head_positions (me);
243 Real stem_end = dir ? hp[dir] + dir * length : 0;
245 /* TODO: change name to extend-stems to staff/center/'() */
246 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
247 if (!no_extend_b && dir * stem_end < 0)
251 /* Make a little room if we have a upflag and there is a dot.
252 previous approach was to lengthen the stem. This is not
253 good typesetting practice. */
254 if (!get_beam (me) && dir == UP
257 Grob *closest_to_flag = extremal_heads (me)[dir];
258 Grob *dots = closest_to_flag
259 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
263 Real dp = Staff_symbol_referencer::get_position (dots);
264 Interval flag_yext = flag (me).extent (Y_AXIS) * (2 / ss) + stem_end;
266 /* Very gory: add myself to the X-support of the parent,
267 which should be a dot-column. */
269 if (flag_yext.distance (dp) < 0.5)
271 Grob *par = dots->get_parent (X_AXIS);
273 if (Dot_column::has_interface (par))
275 Side_position_interface::add_support (par, me);
277 /* TODO: apply some better logic here. The flag is
278 curved inwards, so this will typically be too
285 return scm_from_double (stem_end);
289 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
291 Stem::calc_length (SCM smob)
293 Grob *me = unsmob_grob (smob);
295 SCM details = me->get_property ("details");
296 int durlog = duration_log (me);
298 Real ss = Staff_symbol_referencer::staff_space (me);
300 SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
302 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
304 Direction dir = get_grob_direction (me);
306 /* Stems in unnatural (forced) direction should be shortened,
307 according to [Roush & Gourlay] */
308 Interval hp = head_positions (me);
309 if (dir && dir * hp[dir] >= 0)
311 SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
312 SCM scm_shorten = scm_is_pair (sshorten)
313 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
314 Real shorten = 2* robust_scm2double (scm_shorten, 0);
316 /* On boundary: shorten only half */
317 if (abs (head_positions (me)[dir]) <= 1)
323 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
326 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
327 if (t_flag && !unsmob_grob (me->get_object ("beam")))
329 /* Crude hack: add extra space if tremolo flag is there.
331 We can't do this for the beam, since we get into a loop
332 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
335 + 2 * t_flag->extent (t_flag, Y_AXIS).length ()
340 Interval flag_ext = flag (me).extent (Y_AXIS);
341 if (!flag_ext.is_empty ())
342 minlen += 2 * flag_ext.length () / ss;
344 /* The clash is smaller for down stems (since the tremolo is
349 length = max (length, minlen + 1.0);
352 return scm_from_double (length);
354 /* The log of the duration (Number of hooks on the flag minus two) */
356 Stem::duration_log (Grob *me)
358 SCM s = me->get_property ("duration-log");
359 return (scm_is_number (s)) ? scm_to_int (s) : 2;
362 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
364 Stem::calc_positioning_done (SCM smob)
366 Grob *me = unsmob_grob (smob);
367 if (!head_count (me))
370 extract_grob_set (me, "note-heads", ro_heads);
371 vector<Grob*> heads (ro_heads);
372 vector_sort (heads, compare_position);
373 Direction dir = get_grob_direction (me);
378 Real thick = thickness (me);
380 Grob *hed = support_head (me);
383 programming_error ("Stem dir must be up or down.");
385 set_grob_direction (me, dir);
388 Real w = hed->extent (hed, X_AXIS)[dir];
389 for (vsize i = 0; i < heads.size (); i++)
390 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
394 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
395 for (vsize i = 1; i < heads.size (); i++)
397 Real p = Staff_symbol_referencer::get_position (heads[i]);
398 Real dy = fabs (lastpos- p);
401 dy should always be 0.5, 0.0, 1.0, but provide safety margin
408 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
410 Direction d = get_grob_direction (me);
412 Reversed head should be shifted ell-thickness, but this
413 looks too crowded, so we only shift ell-0.5*thickness.
415 This leads to assymetry: Normal heads overlap the
416 stem 100% whereas reversed heads only overlaps the
420 Real reverse_overlap = 0.5;
421 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
424 if (is_invisible (me))
425 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
430 For some cases we should kern some more: when the
431 distance between the next or prev note is too large, we'd
432 get large white gaps, eg.
453 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
455 Stem::calc_direction (SCM smob)
457 Grob *me = unsmob_grob (smob);
458 Direction dir = CENTER;
459 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
461 SCM ignore_me = beam->get_property ("direction");
463 dir = get_grob_direction (me);
467 SCM dd = me->get_property ("default-direction");
470 return me->get_property ("neutral-direction");
473 return scm_from_int (dir);
476 MAKE_SCHEME_CALLBACK(Stem, calc_default_direction, 1);
478 Stem::calc_default_direction (SCM smob)
480 Grob *me = unsmob_grob (smob);
482 Direction dir = CENTER;
483 int staff_center = 0;
484 Interval hp = head_positions (me);
487 int udistance = (int) (UP * hp[UP] - staff_center);
488 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
490 dir = Direction (sign (ddistance - udistance));
493 return scm_from_int (dir);
497 MAKE_SCHEME_CALLBACK (Stem, height, 1);
499 Stem::height (SCM smob)
501 Grob *me = unsmob_grob (smob);
503 Direction dir = get_grob_direction (me);
507 UGH. Should be automatic
509 Grob *beam = get_beam (me);
512 /* trigger set-stem-lengths. */
513 beam->get_property ("quantized-positions");
517 Can't get_stencil(), since that would cache stencils too early.
518 This causes problems with beams.
520 Stencil *stencil = unsmob_stencil (print (smob));
521 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
526 programming_error ("no stem direction");
529 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
532 return ly_interval2scm (iv);
536 Stem::stem_end_position (Grob *me)
538 return robust_scm2double (me->get_property ("stem-end-position"), 0);
542 Stem::flag (Grob *me)
544 int log = duration_log (me);
546 || unsmob_grob (me->get_object ("beam")))
550 TODO: maybe property stroke-style should take different values,
551 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
555 SCM flag_style_scm = me->get_property ("flag-style");
556 if (scm_is_symbol (flag_style_scm))
557 flag_style = ly_symbol2string (flag_style_scm);
559 if (flag_style == "no-flag")
564 string staffline_offs;
565 if (flag_style == "mensural")
566 /* Mensural notation: For notes on staff lines, use different
567 flags than for notes between staff lines. The idea is that
568 flags are always vertically aligned with the staff lines,
569 regardless if the note head is on a staff line or between two
570 staff lines. In other words, the inner end of a flag always
571 touches a staff line.
576 int p = (int) (rint (stem_end_position (me)));
578 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
581 staffline_offs = "2";
586 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
587 string font_char = flag_style
588 + to_string (dir) + staffline_offs + to_string (log);
589 Font_metric *fm = Font_interface::get_default_font (me);
590 Stencil flag = fm->find_by_name ("flags." + font_char);
591 if (flag.is_empty ())
592 me->warning (_f ("flag `%s' not found", font_char));
594 SCM stroke_style_scm = me->get_property ("stroke-style");
595 if (scm_is_string (stroke_style_scm))
597 string stroke_style = ly_scm2string (stroke_style_scm);
598 if (!stroke_style.empty ())
600 string font_char = to_string (dir) + stroke_style;
601 Stencil stroke = fm->find_by_name ("flags." + font_char);
602 if (stroke.is_empty ())
603 me->warning (_f ("flag stroke `%s' not found", font_char));
605 flag.add_stencil (stroke);
612 MAKE_SCHEME_CALLBACK (Stem, width, 1);
616 Grob *me = unsmob_grob (e);
620 if (is_invisible (me))
622 else if (unsmob_grob (me->get_object ("beam"))
623 || abs (duration_log (me)) <= 2)
625 r = Interval (-1, 1);
626 r *= thickness (me) / 2;
630 r = Interval (-1, 1) * thickness (me) * 0.5;
631 r.unite (flag (me).extent (X_AXIS));
633 return ly_interval2scm (r);
637 Stem::thickness (Grob *me)
639 return scm_to_double (me->get_property ("thickness"))
640 * Staff_symbol_referencer::line_thickness (me);
643 MAKE_SCHEME_CALLBACK (Stem, print, 1);
645 Stem::print (SCM smob)
647 Grob *me = unsmob_grob (smob);
649 Direction d = get_grob_direction (me);
651 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
653 bool stemlet = stemlet_length > 0.0;
655 /* TODO: make the stem start a direction ?
656 This is required to avoid stems passing in tablature chords. */
658 = to_boolean (me->get_property ("avoid-note-head"))
661 Grob *beam = get_beam (me);
666 if (!lh && stemlet && !beam)
669 if (is_invisible (me))
672 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
674 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
677 y2 = Staff_symbol_referencer::get_position (lh);
680 Real beam_translation = Beam::get_beam_translation (beam);
681 Real beam_thickness = Beam::get_thickness (beam);
682 int beam_count = beam_multiplicity (me).length () + 1;
685 * (0.5 * beam_thickness
686 + beam_translation * max (0, (beam_count - 1))
687 + stemlet_length) / half_space;
690 Interval stem_y (min (y1, y2), max (y2, y1));
692 if (Grob *head = support_head (me))
695 must not take ledgers into account.
697 Interval head_height = head->extent (head, Y_AXIS);
698 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
700 y_attach = head_height.linear_combination (y_attach);
701 stem_y[Direction (-d)] += d * y_attach / half_space;
705 Real stem_width = thickness (me);
707 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
709 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
710 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
712 Stencil ss = Lookup::round_filled_box (b, blot);
713 mol.add_stencil (ss);
715 mol.add_stencil (get_translated_flag (me));
717 return mol.smobbed_copy ();
721 Stem::get_translated_flag (Grob *me)
723 Stencil fl = flag (me);
726 Direction d = get_grob_direction (me);
728 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
729 Real stem_width = thickness (me);
730 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
731 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
732 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
733 fl.translate_axis (stem_width / 2, X_AXIS);
740 move the stem to right of the notehead if it is up.
742 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
744 Stem::offset_callback (SCM smob)
746 Grob *me = unsmob_grob (smob);
749 if (Grob *f = first_head (me))
751 Interval head_wid = f->extent (f, X_AXIS);
754 if (is_invisible (me))
757 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
759 Direction d = get_grob_direction (me);
760 Real real_attach = head_wid.linear_combination (d * attach);
763 /* If not centered: correct for stem thickness. */
766 Real rule_thick = thickness (me);
767 r += -d * rule_thick * 0.5;
772 extract_grob_set (me, "rests", rests);
775 Grob *rest = rests.back ();
776 r = rest->extent (rest, X_AXIS).center ();
779 return scm_from_double (r);
783 Stem::get_beam (Grob *me)
785 SCM b = me->get_object ("beam");
786 return dynamic_cast<Spanner *> (unsmob_grob (b));
790 Stem::get_stem_info (Grob *me)
793 si.dir_ = get_grob_direction (me);
795 SCM scm_info = me->get_property ("stem-info");
796 si.ideal_y_ = scm_to_double (scm_car (scm_info));
797 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
801 /* TODO: add extra space for tremolos! */
802 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
804 Stem::calc_stem_info (SCM smob)
806 Grob *me = unsmob_grob (smob);
807 Direction my_dir = get_grob_direction (me);
811 programming_error ("no stem dir set");
815 Real staff_space = Staff_symbol_referencer::staff_space (me);
816 Grob *beam = get_beam (me);
820 (void) beam->get_property ("beaming");
823 Real beam_translation = Beam::get_beam_translation (beam);
824 Real beam_thickness = Beam::get_thickness (beam);
825 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
827 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
829 /* Simple standard stem length */
830 SCM details = me->get_property ("details");
831 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
834 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
838 /* stem only extends to center of beam
840 - 0.5 * beam_thickness;
842 /* Condition: sane minimum free stem length (chord to beams) */
843 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
845 Real ideal_minimum_free
846 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
851 It seems that also for ideal minimum length, we must use
852 the maximum beam count (for this direction):
854 \score{ \notes\relative c''{ [a8 a32] }}
856 must be horizontal. */
857 Real height_of_my_beams = beam_thickness
858 + (beam_count - 1) * beam_translation;
860 Real ideal_minimum_length = ideal_minimum_free
862 /* stem only extends to center of beam */
863 - 0.5 * beam_thickness;
865 ideal_length = max (ideal_length, ideal_minimum_length);
867 /* Convert to Y position, calculate for dir == UP */
869 = /* staff positions */
870 head_positions (me)[my_dir] * 0.5
871 * my_dir * staff_space;
872 Real ideal_y = note_start + ideal_length;
874 /* Conditions for Y position */
876 /* Lowest beam of (UP) beam must never be lower than second staffline
880 Although this (additional) rule is probably correct,
881 I expect that highest beam (UP) should also never be lower
882 than middle staffline, just as normal stems.
886 Obviously not for grace beams.
888 Also, not for knees. Seems to be a good thing. */
889 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
890 bool is_knee = to_boolean (beam->get_property ("knee"));
891 if (!no_extend_b && !is_knee)
893 /* Highest beam of (UP) beam must never be lower than middle
895 ideal_y = max (ideal_y, 0.0);
896 /* Lowest beam of (UP) beam must never be lower than second staffline */
897 ideal_y = max (ideal_y, (-staff_space
898 - beam_thickness + height_of_my_beams));
901 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
903 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
907 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
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."
948 "\n\nThe following properties may be set in the details list."
950 "@item beamed-lengths \n"
951 "list of stem lengths given beam multiplicity. \n"
952 "@item beamed-minimum-free-lengths \n"
953 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
954 "@item beamed-extreme-minimum-free-lengths\n"
955 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
957 "Default stem lengths. The list gives a length for each flag-count.\n"
958 "@item stem-shorten\n"
959 "How much a stem in a forced direction "
960 "should be shortened. The list gives an amount depending on the number "
990 /****************************************************************/
992 Stem_info::Stem_info ()
994 ideal_y_ = shortest_y_ = 0;
999 Stem_info::scale (Real x)