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.
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);
466 dir = get_default_dir (me);
468 return scm_from_int (dir);
471 /* A separate function, since this is used elsewhere too. */
473 Stem::get_default_dir (Grob *me)
475 Direction dir = CENTER;
476 int staff_center = 0;
477 Interval hp = head_positions (me);
480 int udistance = (int) (UP *hp[UP] - staff_center);
481 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
483 if (sign (ddistance - udistance))
484 dir = Direction (sign (ddistance - udistance));
486 dir = to_dir (me->get_property ("neutral-direction"));
493 MAKE_SCHEME_CALLBACK (Stem, height, 1);
495 Stem::height (SCM smob)
497 Grob *me = unsmob_grob (smob);
499 Direction dir = get_grob_direction (me);
503 UGH. Should be automatic
505 Grob *beam = get_beam (me);
508 beam->get_property ("positions");
512 Can't get_stencil(), since that would cache stencils too early.
513 This causes problems with beams.
515 Stencil *stencil = unsmob_stencil (print (smob));
516 Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
521 programming_error ("no stem direction");
524 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
527 return ly_interval2scm (iv);
531 Stem::stem_end_position (Grob *me)
533 return robust_scm2double (me->get_property ("stem-end-position"), 0);
537 Stem::flag (Grob *me)
539 int log = duration_log (me);
541 || unsmob_grob (me->get_object ("beam")))
545 TODO: maybe property stroke-style should take different values,
546 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
550 SCM flag_style_scm = me->get_property ("flag-style");
551 if (scm_is_symbol (flag_style_scm))
552 flag_style = ly_symbol2string (flag_style_scm);
554 if (flag_style == "no-flag")
559 String staffline_offs;
560 if (String::compare (flag_style, "mensural") == 0)
561 /* Mensural notation: For notes on staff lines, use different
562 flags than for notes between staff lines. The idea is that
563 flags are always vertically aligned with the staff lines,
564 regardless if the note head is on a staff line or between two
565 staff lines. In other words, the inner end of a flag always
566 touches a staff line.
571 int p = (int) (rint (stem_end_position (me)));
573 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
576 staffline_offs = "2";
581 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
582 String font_char = flag_style
583 + to_string (dir) + staffline_offs + to_string (log);
584 Font_metric *fm = Font_interface::get_default_font (me);
585 Stencil flag = fm->find_by_name ("flags." + font_char);
586 if (flag.is_empty ())
587 me->warning (_f ("flag `%s' not found", font_char));
589 SCM stroke_style_scm = me->get_property ("stroke-style");
590 if (scm_is_string (stroke_style_scm))
592 String stroke_style = ly_scm2string (stroke_style_scm);
593 if (!stroke_style.is_empty ())
595 String font_char = to_string (dir) + stroke_style;
596 Stencil stroke = fm->find_by_name ("flags." + font_char);
597 if (stroke.is_empty ())
598 me->warning (_f ("flag stroke `%s' not found", font_char));
600 flag.add_stencil (stroke);
607 MAKE_SCHEME_CALLBACK (Stem, width, 1);
611 Grob *me = unsmob_grob (e);
615 if (is_invisible (me))
617 else if (unsmob_grob (me->get_object ("beam"))
618 || abs (duration_log (me)) <= 2)
620 r = Interval (-1, 1);
621 r *= thickness (me) / 2;
625 r = Interval (-1, 1) * thickness (me) * 0.5;
626 r.unite (flag (me).extent (X_AXIS));
628 return ly_interval2scm (r);
632 Stem::thickness (Grob *me)
634 return scm_to_double (me->get_property ("thickness"))
635 * Staff_symbol_referencer::line_thickness (me);
638 MAKE_SCHEME_CALLBACK (Stem, print, 1);
640 Stem::print (SCM smob)
642 Grob *me = unsmob_grob (smob);
644 Direction d = get_grob_direction (me);
646 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
648 bool stemlet = stemlet_length > 0.0;
650 /* TODO: make the stem start a direction ?
651 This is required to avoid stems passing in tablature chords. */
653 = to_boolean (me->get_property ("avoid-note-head"))
656 Grob *beam = get_beam (me);
661 if (!lh && stemlet && !beam)
664 if (is_invisible (me))
667 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
669 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
672 y2 = Staff_symbol_referencer::get_position (lh);
675 Real beam_translation = Beam::get_beam_translation (beam);
676 Real beam_thickness = Beam::get_thickness (beam);
677 int beam_count = beam_multiplicity (me).length () + 1;
680 * (0.5 * beam_thickness
681 + beam_translation * max (0, (beam_count - 1))
682 + stemlet_length) / half_space;
685 Interval stem_y (min (y1, y2), max (y2, y1));
687 if (Grob *head = support_head (me))
690 must not take ledgers into account.
692 Interval head_height = head->extent (head, Y_AXIS);
693 Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
695 y_attach = head_height.linear_combination (y_attach);
696 stem_y[Direction (-d)] += d * y_attach / half_space;
700 Real stem_width = thickness (me);
702 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
704 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
705 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
707 Stencil ss = Lookup::round_filled_box (b, blot);
708 mol.add_stencil (ss);
710 mol.add_stencil (get_translated_flag (me));
712 return mol.smobbed_copy ();
716 Stem::get_translated_flag (Grob *me)
718 Stencil fl = flag (me);
721 Direction d = get_grob_direction (me);
723 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
724 Real stem_width = thickness (me);
725 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
726 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
727 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
728 fl.translate_axis (stem_width / 2, X_AXIS);
735 move the stem to right of the notehead if it is up.
737 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
739 Stem::offset_callback (SCM smob)
741 Grob *me = unsmob_grob (smob);
744 if (Grob *f = first_head (me))
746 Interval head_wid = f->extent (f, X_AXIS);
749 if (is_invisible (me))
752 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
754 Direction d = get_grob_direction (me);
755 Real real_attach = head_wid.linear_combination (d * attach);
758 /* If not centered: correct for stem thickness. */
761 Real rule_thick = thickness (me);
762 r += -d * rule_thick * 0.5;
767 extract_grob_set (me, "rests", rests);
770 Grob *rest = rests.top ();
771 r = rest->extent (rest, X_AXIS).center ();
774 return scm_from_double (r);
778 Stem::get_beam (Grob *me)
780 SCM b = me->get_object ("beam");
781 return dynamic_cast<Spanner *> (unsmob_grob (b));
785 Stem::get_stem_info (Grob *me)
788 si.dir_ = get_grob_direction (me);
790 SCM scm_info = me->get_property ("stem-info");
791 si.ideal_y_ = scm_to_double (scm_car (scm_info));
792 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
796 /* TODO: add extra space for tremolos! */
797 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
799 Stem::calc_stem_info (SCM smob)
801 Grob *me = unsmob_grob (smob);
802 Direction my_dir = get_grob_direction (me);
806 programming_error ("no stem dir set");
810 Real staff_space = Staff_symbol_referencer::staff_space (me);
811 Grob *beam = get_beam (me);
812 Real beam_translation = Beam::get_beam_translation (beam);
813 Real beam_thickness = Beam::get_thickness (beam);
814 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
816 /* Simple standard stem length */
817 SCM details = me->get_property ("details");
818 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
822 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
825 /* stem only extends to center of beam
827 - 0.5 * beam_thickness;
829 /* Condition: sane minimum free stem length (chord to beams) */
830 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
832 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
834 Real ideal_minimum_free
835 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
836 * staff_space * length_fraction;
839 It seems that also for ideal minimum length, we must use
840 the maximum beam count (for this direction):
842 \score{ \notes\relative c''{ [a8 a32] }}
844 must be horizontal. */
845 Real height_of_my_beams = beam_thickness
846 + (beam_count - 1) * beam_translation;
848 Real ideal_minimum_length = ideal_minimum_free
850 /* stem only extends to center of beam */
851 - 0.5 * beam_thickness;
853 ideal_length = max (ideal_length, ideal_minimum_length);
855 /* Convert to Y position, calculate for dir == UP */
857 = /* staff positions */
858 head_positions (me)[my_dir] * 0.5
859 * my_dir * staff_space;
860 Real ideal_y = note_start + ideal_length;
862 /* Conditions for Y position */
864 /* Lowest beam of (UP) beam must never be lower than second staffline
868 Although this (additional) rule is probably correct,
869 I expect that highest beam (UP) should also never be lower
870 than middle staffline, just as normal stems.
874 Obviously not for grace beams.
876 Also, not for knees. Seems to be a good thing. */
877 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
878 bool is_knee = to_boolean (beam->get_property ("knee"));
879 if (!no_extend_b && !is_knee)
881 /* Highest beam of (UP) beam must never be lower than middle
883 ideal_y = max (ideal_y, 0.0);
884 /* Lowest beam of (UP) beam must never be lower than second staffline */
885 ideal_y = max (ideal_y, (-staff_space
886 - beam_thickness + height_of_my_beams));
889 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
891 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
895 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
898 Real minimum_length = minimum_free
900 /* stem only extends to center of beam */
901 - 0.5 * beam_thickness;
903 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
905 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
906 y_ext.widen (0.5); // FIXME. Should be tunable?
907 minimum_length = max (minimum_length, y_ext.length ());
911 Real minimum_y = note_start + minimum_length;
912 Real shortest_y = minimum_y * my_dir;
914 return scm_list_2 (scm_from_double (ideal_y),
915 scm_from_double (shortest_y));
919 Stem::beam_multiplicity (Grob *stem)
921 SCM beaming = stem->get_property ("beaming");
922 Slice le = int_list_to_slice (scm_car (beaming));
923 Slice ri = int_list_to_slice (scm_cdr (beaming));
928 /* FIXME: Too many properties */
929 ADD_INTERFACE (Stem, "stem-interface",
930 "The stem represent the graphical stem. "
931 "In addition, it internally connects note heads, beams and"
933 "Rests and whole notes have invisible stems."
935 "\n\nThe following properties may be set in the details list."
937 "@item beamed-lengths \n"
938 "list of stem lengths given beam multiplicity. \n"
939 "@item beamed-minimum-free-lengths \n"
940 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
941 "@item beamed-extreme-minimum-free-lengths\n"
942 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
944 "Default stem lengths. The list gives a length for each flag-count.\n"
945 "@item stem-shorten\n"
946 "How much a stem in a forced direction "
947 "should be shortened. The list gives an amount depending on the number "
976 /****************************************************************/
978 Stem_info::Stem_info ()
980 ideal_y_ = shortest_y_ = 0;
985 Stem_info::scale (Real x)