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);
380 programming_error ("Stem dir must be up or down.");
382 set_grob_direction (me, dir);
385 Real w = hed->extent (hed, X_AXIS)[dir];
386 for (int i = 0; i < heads.size (); i++)
387 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
391 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
392 for (int i = 1; i < heads.size (); i++)
394 Real p = Staff_symbol_referencer::get_position (heads[i]);
395 Real dy = fabs (lastpos- p);
398 dy should always be 0.5, 0.0, 1.0, but provide safety margin
405 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
407 Direction d = get_grob_direction (me);
409 Reversed head should be shifted ell-thickness, but this
410 looks too crowded, so we only shift ell-0.5*thickness.
412 This leads to assymetry: Normal heads overlap the
413 stem 100% whereas reversed heads only overlaps the
417 Real reverse_overlap = 0.5;
418 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
421 if (is_invisible (me))
422 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
427 For some cases we should kern some more: when the
428 distance between the next or prev note is too large, we'd
429 get large white gaps, eg.
452 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
454 Stem::calc_direction (SCM smob)
456 Grob *me = unsmob_grob (smob);
457 Direction dir = CENTER;
458 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
460 SCM ignore_me = beam->get_property ("direction");
462 dir = get_grob_direction (me);
465 dir = get_default_dir (me);
467 return scm_from_int (dir);
470 /* A separate function, since this is used elsewhere too. */
472 Stem::get_default_dir (Grob *me)
474 Direction dir = CENTER;
475 int staff_center = 0;
476 Interval hp = head_positions (me);
479 int udistance = (int) (UP *hp[UP] - staff_center);
480 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
482 if (sign (ddistance - udistance))
483 dir = Direction (sign (ddistance - udistance));
485 dir = to_dir (me->get_property ("neutral-direction"));
492 MAKE_SCHEME_CALLBACK (Stem, height, 2);
494 Stem::height (SCM smob, SCM ax)
496 Axis a = (Axis)scm_to_int (ax);
497 Grob *me = unsmob_grob (smob);
498 assert (a == Y_AXIS);
500 Direction dir = get_grob_direction (me);
504 UGH. Should be automatic
506 Grob *beam = get_beam (me);
509 beam->get_property ("positions");
512 /* FIXME uncached? */
513 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
518 programming_error ("no stem direction");
521 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
524 return ly_interval2scm (iv);
528 Stem::stem_end_position (Grob *me)
530 return robust_scm2double (me->get_property ("stem-end-position"), 0);
534 Stem::flag (Grob *me)
536 int log = duration_log (me);
538 || unsmob_grob (me->get_object ("beam")))
542 TODO: maybe property stroke-style should take different values,
543 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
547 SCM flag_style_scm = me->get_property ("flag-style");
548 if (scm_is_symbol (flag_style_scm))
549 flag_style = ly_symbol2string (flag_style_scm);
551 if (flag_style == "no-flag")
556 String staffline_offs;
557 if (String::compare (flag_style, "mensural") == 0)
558 /* Mensural notation: For notes on staff lines, use different
559 flags than for notes between staff lines. The idea is that
560 flags are always vertically aligned with the staff lines,
561 regardless if the note head is on a staff line or between two
562 staff lines. In other words, the inner end of a flag always
563 touches a staff line.
568 int p = (int) (rint (stem_end_position (me)));
570 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
573 staffline_offs = "2";
578 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
579 String font_char = flag_style
580 + to_string (dir) + staffline_offs + to_string (log);
581 Font_metric *fm = Font_interface::get_default_font (me);
582 Stencil flag = fm->find_by_name ("flags." + font_char);
583 if (flag.is_empty ())
584 me->warning (_f ("flag `%s' not found", font_char));
586 SCM stroke_style_scm = me->get_property ("stroke-style");
587 if (scm_is_string (stroke_style_scm))
589 String stroke_style = ly_scm2string (stroke_style_scm);
590 if (!stroke_style.is_empty ())
592 String font_char = to_string (dir) + stroke_style;
593 Stencil stroke = fm->find_by_name ("flags." + font_char);
594 if (stroke.is_empty ())
595 me->warning (_f ("flag stroke `%s' not found", font_char));
597 flag.add_stencil (stroke);
604 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
606 Stem::width_callback (SCM e, SCM ax)
609 assert (scm_to_int (ax) == X_AXIS);
610 Grob *me = unsmob_grob (e);
614 if (is_invisible (me))
616 else if (unsmob_grob (me->get_object ("beam"))
617 || abs (duration_log (me)) <= 2)
619 r = Interval (-1, 1);
620 r *= thickness (me) / 2;
624 r = Interval (-1, 1) * thickness (me) * 0.5;
625 r.unite (flag (me).extent (X_AXIS));
627 return ly_interval2scm (r);
631 Stem::thickness (Grob *me)
633 return scm_to_double (me->get_property ("thickness"))
634 * Staff_symbol_referencer::line_thickness (me);
637 MAKE_SCHEME_CALLBACK (Stem, print, 1);
639 Stem::print (SCM smob)
641 Grob *me = unsmob_grob (smob);
643 Direction d = get_grob_direction (me);
645 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
647 bool stemlet = stemlet_length > 0.0;
649 /* TODO: make the stem start a direction ?
650 This is required to avoid stems passing in tablature chords. */
652 = to_boolean (me->get_property ("avoid-note-head"))
655 Grob *beam = get_beam (me);
660 if (!lh && stemlet && !beam)
663 if (is_invisible (me))
666 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
668 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
671 y2 = Staff_symbol_referencer::get_position (lh);
674 Real beam_translation = Beam::get_beam_translation (beam);
675 Real beam_thickness = Beam::get_thickness (beam);
676 int beam_count = beam_multiplicity (me).length () + 1;
679 * (0.5 * beam_thickness
680 + beam_translation * max (0, (beam_count - 1))
681 + stemlet_length) / half_space;
684 Interval stem_y (min (y1, y2), max (y2, y1));
686 if (Grob *hed = support_head (me))
689 must not take ledgers into account.
691 Interval head_height = hed->extent (hed, Y_AXIS);
692 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
694 y_attach = head_height.linear_combination (y_attach);
695 stem_y[Direction (-d)] += d * y_attach / half_space;
699 Real stem_width = thickness (me);
701 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
703 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
704 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
706 Stencil ss = Lookup::round_filled_box (b, blot);
707 mol.add_stencil (ss);
709 mol.add_stencil (get_translated_flag (me));
711 return mol.smobbed_copy ();
715 Stem::get_translated_flag (Grob *me)
717 Stencil fl = flag (me);
720 Direction d = get_grob_direction (me);
722 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
723 Real stem_width = thickness (me);
724 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
725 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
726 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
727 fl.translate_axis (stem_width / 2, X_AXIS);
734 move the stem to right of the notehead if it is up.
736 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
738 Stem::offset_callback (SCM element_smob, SCM)
740 Grob *me = unsmob_grob (element_smob);
743 if (Grob *f = first_head (me))
745 Interval head_wid = f->extent (f, X_AXIS);
748 if (is_invisible (me))
751 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
753 Direction d = get_grob_direction (me);
754 Real real_attach = head_wid.linear_combination (d * attach);
757 /* If not centered: correct for stem thickness. */
760 Real rule_thick = thickness (me);
761 r += -d * rule_thick * 0.5;
766 extract_grob_set (me, "rests", rests);
769 Grob *rest = rests.top ();
770 r = rest->extent (rest, X_AXIS).center ();
773 return scm_from_double (r);
777 Stem::get_beam (Grob *me)
779 SCM b = me->get_object ("beam");
780 return dynamic_cast<Spanner *> (unsmob_grob (b));
784 Stem::get_stem_info (Grob *me)
787 si.dir_ = get_grob_direction (me);
789 SCM scm_info = me->get_property ("stem-info");
790 si.ideal_y_ = scm_to_double (scm_car (scm_info));
791 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
795 /* TODO: add extra space for tremolos! */
796 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
798 Stem::calc_stem_info (SCM smob)
800 Grob *me = unsmob_grob (smob);
801 Direction my_dir = get_grob_direction (me);
805 programming_error ("no stem dir set");
809 Real staff_space = Staff_symbol_referencer::staff_space (me);
810 Grob *beam = get_beam (me);
811 Real beam_translation = Beam::get_beam_translation (beam);
812 Real beam_thickness = Beam::get_thickness (beam);
813 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
815 /* Simple standard stem length */
816 SCM details = me->get_property ("details");
817 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
821 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
824 /* stem only extends to center of beam
826 - 0.5 * beam_thickness;
828 /* Condition: sane minimum free stem length (chord to beams) */
829 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
831 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
833 Real ideal_minimum_free
834 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
835 * staff_space * length_fraction;
838 It seems that also for ideal minimum length, we must use
839 the maximum beam count (for this direction):
841 \score{ \notes\relative c''{ [a8 a32] }}
843 must be horizontal. */
844 Real height_of_my_beams = beam_thickness
845 + (beam_count - 1) * beam_translation;
847 Real ideal_minimum_length = ideal_minimum_free
849 /* stem only extends to center of beam */
850 - 0.5 * beam_thickness;
852 ideal_length = max (ideal_length, ideal_minimum_length);
854 /* Convert to Y position, calculate for dir == UP */
856 = /* staff positions */
857 head_positions (me)[my_dir] * 0.5
858 * my_dir * staff_space;
859 Real ideal_y = note_start + ideal_length;
861 /* Conditions for Y position */
863 /* Lowest beam of (UP) beam must never be lower than second staffline
867 Although this (additional) rule is probably correct,
868 I expect that highest beam (UP) should also never be lower
869 than middle staffline, just as normal stems.
873 Obviously not for grace beams.
875 Also, not for knees. Seems to be a good thing. */
876 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
877 bool is_knee = to_boolean (beam->get_property ("knee"));
878 if (!no_extend_b && !is_knee)
880 /* Highest beam of (UP) beam must never be lower than middle
882 ideal_y = max (ideal_y, 0.0);
883 /* Lowest beam of (UP) beam must never be lower than second staffline */
884 ideal_y = max (ideal_y, (-staff_space
885 - beam_thickness + height_of_my_beams));
888 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
890 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
894 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
897 Real minimum_length = minimum_free
899 /* stem only extends to center of beam */
900 - 0.5 * beam_thickness;
902 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
904 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
905 y_ext.widen (0.5); // FIXME. Should be tunable?
906 minimum_length = max (minimum_length, y_ext.length ());
910 Real minimum_y = note_start + minimum_length;
911 Real shortest_y = minimum_y * my_dir;
913 return scm_list_2 (scm_from_double (ideal_y),
914 scm_from_double (shortest_y));
918 Stem::beam_multiplicity (Grob *stem)
920 SCM beaming = stem->get_property ("beaming");
921 Slice le = int_list_to_slice (scm_car (beaming));
922 Slice ri = int_list_to_slice (scm_cdr (beaming));
927 /* FIXME: Too many properties */
928 ADD_INTERFACE (Stem, "stem-interface",
929 "The stem represent the graphical stem. "
930 "In addition, it internally connects note heads, beams and"
932 "Rests and whole notes have invisible stems."
934 "\n\nThe following properties may be set in the details list."
936 "@item beamed-lengths \n"
937 "list of stem lengths given beam multiplicity. \n"
938 "@item beamed-minimum-free-lengths \n"
939 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
940 "@item beamed-extreme-minimum-free-lengths\n"
941 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
943 "Default stem lengths. The list gives a length for each flag-count.\n"
944 "@item stem-shorten\n"
945 "How much a stem in a forced direction "
946 "should be shortened. The list gives an amount depending on the number "
977 /****************************************************************/
979 Stem_info::Stem_info ()
981 ideal_y_ = shortest_y_ = 0;
986 Stem_info::scale (Real x)