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 beam->get_property ("positions");
516 Can't get_stencil(), since that would cache stencils too early.
517 This causes problems with beams.
519 Stencil *stencil = unsmob_stencil (print (smob));
520 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
525 programming_error ("no stem direction");
528 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
531 return ly_interval2scm (iv);
535 Stem::stem_end_position (Grob *me)
537 return robust_scm2double (me->get_property ("stem-end-position"), 0);
541 Stem::flag (Grob *me)
543 int log = duration_log (me);
545 || unsmob_grob (me->get_object ("beam")))
549 TODO: maybe property stroke-style should take different values,
550 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
554 SCM flag_style_scm = me->get_property ("flag-style");
555 if (scm_is_symbol (flag_style_scm))
556 flag_style = ly_symbol2string (flag_style_scm);
558 if (flag_style == "no-flag")
563 string staffline_offs;
564 if (flag_style == "mensural")
565 /* Mensural notation: For notes on staff lines, use different
566 flags than for notes between staff lines. The idea is that
567 flags are always vertically aligned with the staff lines,
568 regardless if the note head is on a staff line or between two
569 staff lines. In other words, the inner end of a flag always
570 touches a staff line.
575 int p = (int) (rint (stem_end_position (me)));
577 = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
580 staffline_offs = "2";
585 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
586 string font_char = flag_style
587 + to_string (dir) + staffline_offs + to_string (log);
588 Font_metric *fm = Font_interface::get_default_font (me);
589 Stencil flag = fm->find_by_name ("flags." + font_char);
590 if (flag.is_empty ())
591 me->warning (_f ("flag `%s' not found", font_char));
593 SCM stroke_style_scm = me->get_property ("stroke-style");
594 if (scm_is_string (stroke_style_scm))
596 string stroke_style = ly_scm2string (stroke_style_scm);
597 if (!stroke_style.empty ())
599 string font_char = to_string (dir) + stroke_style;
600 Stencil stroke = fm->find_by_name ("flags." + font_char);
601 if (stroke.is_empty ())
602 me->warning (_f ("flag stroke `%s' not found", font_char));
604 flag.add_stencil (stroke);
611 MAKE_SCHEME_CALLBACK (Stem, width, 1);
615 Grob *me = unsmob_grob (e);
619 if (is_invisible (me))
621 else if (unsmob_grob (me->get_object ("beam"))
622 || abs (duration_log (me)) <= 2)
624 r = Interval (-1, 1);
625 r *= thickness (me) / 2;
629 r = Interval (-1, 1) * thickness (me) * 0.5;
630 r.unite (flag (me).extent (X_AXIS));
632 return ly_interval2scm (r);
636 Stem::thickness (Grob *me)
638 return scm_to_double (me->get_property ("thickness"))
639 * Staff_symbol_referencer::line_thickness (me);
642 MAKE_SCHEME_CALLBACK (Stem, print, 1);
644 Stem::print (SCM smob)
646 Grob *me = unsmob_grob (smob);
648 Direction d = get_grob_direction (me);
650 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
652 bool stemlet = stemlet_length > 0.0;
654 /* TODO: make the stem start a direction ?
655 This is required to avoid stems passing in tablature chords. */
657 = to_boolean (me->get_property ("avoid-note-head"))
660 Grob *beam = get_beam (me);
665 if (!lh && stemlet && !beam)
668 if (is_invisible (me))
671 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
673 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
676 y2 = Staff_symbol_referencer::get_position (lh);
679 Real beam_translation = Beam::get_beam_translation (beam);
680 Real beam_thickness = Beam::get_thickness (beam);
681 int beam_count = beam_multiplicity (me).length () + 1;
684 * (0.5 * beam_thickness
685 + beam_translation * max (0, (beam_count - 1))
686 + stemlet_length) / half_space;
689 Interval stem_y (min (y1, y2), max (y2, y1));
691 if (Grob *head = support_head (me))
694 must not take ledgers into account.
696 Interval head_height = head->extent (head, Y_AXIS);
697 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
699 y_attach = head_height.linear_combination (y_attach);
700 stem_y[Direction (-d)] += d * y_attach / half_space;
704 Real stem_width = thickness (me);
706 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
708 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
709 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
711 Stencil ss = Lookup::round_filled_box (b, blot);
712 mol.add_stencil (ss);
714 mol.add_stencil (get_translated_flag (me));
716 return mol.smobbed_copy ();
720 Stem::get_translated_flag (Grob *me)
722 Stencil fl = flag (me);
725 Direction d = get_grob_direction (me);
727 = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
728 Real stem_width = thickness (me);
729 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
730 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
731 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
732 fl.translate_axis (stem_width / 2, X_AXIS);
739 move the stem to right of the notehead if it is up.
741 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
743 Stem::offset_callback (SCM smob)
745 Grob *me = unsmob_grob (smob);
748 if (Grob *f = first_head (me))
750 Interval head_wid = f->extent (f, X_AXIS);
753 if (is_invisible (me))
756 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
758 Direction d = get_grob_direction (me);
759 Real real_attach = head_wid.linear_combination (d * attach);
762 /* If not centered: correct for stem thickness. */
765 Real rule_thick = thickness (me);
766 r += -d * rule_thick * 0.5;
771 extract_grob_set (me, "rests", rests);
774 Grob *rest = rests.back ();
775 r = rest->extent (rest, X_AXIS).center ();
778 return scm_from_double (r);
782 Stem::get_beam (Grob *me)
784 SCM b = me->get_object ("beam");
785 return dynamic_cast<Spanner *> (unsmob_grob (b));
789 Stem::get_stem_info (Grob *me)
792 si.dir_ = get_grob_direction (me);
794 SCM scm_info = me->get_property ("stem-info");
795 si.ideal_y_ = scm_to_double (scm_car (scm_info));
796 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
800 /* TODO: add extra space for tremolos! */
801 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
803 Stem::calc_stem_info (SCM smob)
805 Grob *me = unsmob_grob (smob);
806 Direction my_dir = get_grob_direction (me);
810 programming_error ("no stem dir set");
814 Real staff_space = Staff_symbol_referencer::staff_space (me);
815 Grob *beam = get_beam (me);
819 (void) beam->get_property ("beaming");
822 Real beam_translation = Beam::get_beam_translation (beam);
823 Real beam_thickness = Beam::get_thickness (beam);
824 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
826 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
828 /* Simple standard stem length */
829 SCM details = me->get_property ("details");
830 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
833 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
837 /* stem only extends to center of beam
839 - 0.5 * beam_thickness;
841 /* Condition: sane minimum free stem length (chord to beams) */
842 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
844 Real ideal_minimum_free
845 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
850 It seems that also for ideal minimum length, we must use
851 the maximum beam count (for this direction):
853 \score{ \notes\relative c''{ [a8 a32] }}
855 must be horizontal. */
856 Real height_of_my_beams = beam_thickness
857 + (beam_count - 1) * beam_translation;
859 Real ideal_minimum_length = ideal_minimum_free
861 /* stem only extends to center of beam */
862 - 0.5 * beam_thickness;
864 ideal_length = max (ideal_length, ideal_minimum_length);
866 /* Convert to Y position, calculate for dir == UP */
868 = /* staff positions */
869 head_positions (me)[my_dir] * 0.5
870 * my_dir * staff_space;
871 Real ideal_y = note_start + ideal_length;
873 /* Conditions for Y position */
875 /* Lowest beam of (UP) beam must never be lower than second staffline
879 Although this (additional) rule is probably correct,
880 I expect that highest beam (UP) should also never be lower
881 than middle staffline, just as normal stems.
885 Obviously not for grace beams.
887 Also, not for knees. Seems to be a good thing. */
888 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
889 bool is_knee = to_boolean (beam->get_property ("knee"));
890 if (!no_extend_b && !is_knee)
892 /* Highest beam of (UP) beam must never be lower than middle
894 ideal_y = max (ideal_y, 0.0);
895 /* Lowest beam of (UP) beam must never be lower than second staffline */
896 ideal_y = max (ideal_y, (-staff_space
897 - beam_thickness + height_of_my_beams));
900 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
902 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
906 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
910 Real minimum_length = minimum_free
912 /* stem only extends to center of beam */
913 - 0.5 * beam_thickness;
915 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
917 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
918 y_ext.widen (0.5); // FIXME. Should be tunable?
919 minimum_length = max (minimum_length, y_ext.length ());
923 Real minimum_y = note_start + minimum_length;
924 Real shortest_y = minimum_y * my_dir;
926 return scm_list_2 (scm_from_double (ideal_y),
927 scm_from_double (shortest_y));
931 Stem::beam_multiplicity (Grob *stem)
933 SCM beaming = stem->get_property ("beaming");
934 Slice le = int_list_to_slice (scm_car (beaming));
935 Slice ri = int_list_to_slice (scm_cdr (beaming));
940 /* FIXME: Too many properties */
941 ADD_INTERFACE (Stem, "stem-interface",
942 "The stem represent the graphical stem. "
943 "In addition, it internally connects note heads, beams and"
945 "Rests and whole notes have invisible stems."
947 "\n\nThe following properties may be set in the details list."
949 "@item beamed-lengths \n"
950 "list of stem lengths given beam multiplicity. \n"
951 "@item beamed-minimum-free-lengths \n"
952 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
953 "@item beamed-extreme-minimum-free-lengths\n"
954 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
956 "Default stem lengths. The list gives a length for each flag-count.\n"
957 "@item stem-shorten\n"
958 "How much a stem in a forced direction "
959 "should be shortened. The list gives an amount depending on the number "
989 /****************************************************************/
991 Stem_info::Stem_info ()
993 ideal_y_ = shortest_y_ = 0;
998 Stem_info::scale (Real x)