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_direction (me)] * Staff_symbol_referencer::staff_space (me)
89 Stem::stem_end_position (Grob *me)
91 SCM p = me->get_property ("stem-end-position");
93 if (!scm_is_number (p))
95 pos = get_default_stem_end_position (me);
96 me->set_property ("stem-end-position", scm_from_double (pos));
99 pos = scm_to_double (p);
105 Stem::get_direction (Grob *me)
107 Direction d = get_grob_direction (me);
111 d = get_default_dir (me);
113 set_grob_direction (me, d);
119 Stem::set_stemend (Grob *me, Real se)
122 Direction d = get_direction (me);
124 if (d && d * head_positions (me)[get_direction (me)] >= se * d)
125 me->warning (_ ("weird stem size, check for narrow beams"));
127 me->set_property ("stem-end-position", scm_from_double (se));
130 /* Note head that determines hshift for upstems
131 WARNING: triggers direction */
133 Stem::support_head (Grob *me)
135 extract_grob_set (me, "note-heads", heads);
136 if (heads.size () == 1)
139 return first_head (me);
143 Stem::head_count (Grob *me)
145 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
148 /* The note head which forms one end of the stem.
149 WARNING: triggers direction */
151 Stem::first_head (Grob *me)
153 Direction d = get_direction (me);
155 return extremal_heads (me)[-d];
159 /* The note head opposite to the first head. */
161 Stem::last_head (Grob *me)
163 Direction d = get_direction (me);
165 return extremal_heads (me)[d];
170 START is part where stem reaches `last' head.
172 This function returns a drul with (bottom-head, top-head).
175 Stem::extremal_heads (Grob *me)
177 const int inf = 1000000;
178 Drul_array<int> extpos;
182 Drul_array<Grob *> exthead (0, 0);
183 extract_grob_set (me, "note-heads", heads);
185 for (int i = heads.size (); i--;)
188 int p = Staff_symbol_referencer::get_rounded_position (n);
193 if (d * p > d * extpos[d])
199 while (flip (&d) != DOWN);
205 integer_compare (int const &a, int const &b)
210 /* The positions, in ascending order. */
212 Stem::note_head_positions (Grob *me)
215 extract_grob_set (me, "note-heads", heads);
217 for (int i = heads.size (); i--;)
220 int p = Staff_symbol_referencer::get_rounded_position (n);
225 ps.sort (integer_compare);
230 Stem::add_head (Grob *me, Grob *n)
232 n->set_object ("stem", me->self_scm ());
234 if (Note_head::has_interface (n))
235 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
236 else if (Rest::has_interface (n))
237 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
241 Stem::is_invisible (Grob *me)
243 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
246 return !((head_count (me)
247 || stemlet_length > 0.0)
248 && scm_to_int (me->get_property ("duration-log")) >= 1);
252 Stem::get_default_dir (Grob *me)
254 int staff_center = 0;
255 Interval hp = head_positions (me);
259 int udistance = (int) (UP *hp[UP] - staff_center);
260 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
262 if (sign (ddistance - udistance))
263 return Direction (sign (ddistance - udistance));
265 return to_dir (me->get_property ("neutral-direction"));
270 Stem::get_default_stem_end_position (Grob *me)
272 Real ss = Staff_symbol_referencer::staff_space (me);
273 int durlog = duration_log (me);
277 /* WARNING: IN HALF SPACES */
279 SCM scm_len = me->get_property ("length");
280 if (scm_is_number (scm_len))
281 length = scm_to_double (scm_len);
284 s = me->get_property ("lengths");
286 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
290 'set-default-stemlen' sets direction too. */
291 Direction dir = get_direction (me);
293 /* Stems in unnatural (forced) direction should be shortened,
294 according to [Roush & Gourlay] */
295 Interval hp = head_positions (me);
296 if (dir && dir * hp[dir] >= 0)
298 SCM sshorten = me->get_property ("stem-shorten");
299 SCM scm_shorten = scm_is_pair (sshorten)
300 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
301 Real shorten = 2* robust_scm2double (scm_shorten, 0);
303 /* On boundary: shorten only half */
304 if (abs (head_positions (me)[dir]) <= 1)
311 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
312 if (t_flag && !unsmob_grob (me->get_object ("beam")))
314 /* Crude hack: add extra space if tremolo flag is there.
316 We can't do this for the beam, since we get into a loop
317 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
320 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
325 Interval flag_ext = flag (me).extent (Y_AXIS);
326 if (!flag_ext.is_empty ())
327 minlen += 2 * flag_ext.length () / ss;
329 /* The clash is smaller for down stems (since the tremolo is
334 length = max (length, minlen + 1.0);
337 Real st = dir ? hp[dir] + dir * length : 0;
339 /* TODO: change name to extend-stems to staff/center/'() */
340 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
341 if (!no_extend_b && dir * st < 0)
344 /* Make a little room if we have a upflag and there is a dot.
345 previous approach was to lengthen the stem. This is not
346 good typesetting practice. */
347 if (!get_beam (me) && dir == UP
350 Grob *closest_to_flag = extremal_heads (me)[dir];
351 Grob *dots = closest_to_flag
352 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
356 Real dp = Staff_symbol_referencer::get_position (dots);
357 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
359 /* Very gory: add myself to the X-support of the parent,
360 which should be a dot-column. */
361 if (dir * (st + flagy - dp) < 0.5)
363 Grob *par = dots->get_parent (X_AXIS);
365 if (Dot_column::has_interface (par))
367 Side_position_interface::add_support (par, me);
369 /* TODO: apply some better logic here. The flag is
370 curved inwards, so this will typically be too
379 /* The log of the duration (Number of hooks on the flag minus two) */
381 Stem::duration_log (Grob *me)
383 SCM s = me->get_property ("duration-log");
384 return (scm_is_number (s)) ? scm_to_int (s) : 2;
387 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
389 Stem::calc_positioning_done (SCM smob)
391 Grob *me = unsmob_grob (smob);
392 if (!head_count (me))
395 extract_grob_set (me, "note-heads", ro_heads);
396 Link_array<Grob> heads (ro_heads);
397 heads.sort (compare_position);
398 Direction dir = get_direction (me);
403 Real thick = thickness (me);
405 Grob *hed = support_head (me);
406 Real w = hed->extent (hed, X_AXIS)[dir];
407 for (int i = 0; i < heads.size (); i++)
408 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
412 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
413 for (int i = 1; i < heads.size (); i++)
415 Real p = Staff_symbol_referencer::get_position (heads[i]);
416 Real dy = fabs (lastpos- p);
419 dy should always be 0.5, 0.0, 1.0, but provide safety margin
426 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
428 Direction d = get_direction (me);
430 Reversed head should be shifted ell-thickness, but this
431 looks too crowded, so we only shift ell-0.5*thickness.
433 This leads to assymetry: Normal heads overlap the
434 stem 100% whereas reversed heads only overlaps the
438 Real reverse_overlap = 0.5;
439 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
442 if (is_invisible (me))
443 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
448 For some cases we should kern some more: when the
449 distance between the next or prev note is too large, we'd
450 get large white gaps, eg.
473 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
475 Stem::calc_direction (SCM smob)
477 Grob *me = unsmob_grob (smob);
478 Direction dir = CENTER;
479 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
481 SCM ignore_me = beam->get_property ("direction");
483 dir = get_grob_direction (me);
486 dir = get_default_dir (me);
488 return scm_from_int (dir);
491 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1);
493 Stem::calc_stem_end_position (SCM smob)
495 Grob *me = unsmob_grob (smob);
498 Do the calculations for visible stems, but also for invisible stems
499 with note heads (i.e. half notes.)
504 pos = stem_end_position (me); // ugh. Trigger direction calc.
507 return scm_from_double (pos);
510 MAKE_SCHEME_CALLBACK (Stem, height, 2);
512 Stem::height (SCM smob, SCM ax)
514 Axis a = (Axis)scm_to_int (ax);
515 Grob *me = unsmob_grob (smob);
516 assert (a == Y_AXIS);
518 Direction dir = get_grob_direction (me);
522 UGH. Should be automatic
524 Grob *beam = get_beam (me);
527 beam->get_property ("positions");
530 /* FIXME uncached? */
531 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
536 programming_error ("no stem direction");
539 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
542 return ly_interval2scm (iv);
546 Stem::flag (Grob *me)
548 int log = duration_log (me);
550 || unsmob_grob (me->get_object ("beam")))
554 TODO: maybe property stroke-style should take different values,
555 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
559 SCM flag_style_scm = me->get_property ("flag-style");
560 if (scm_is_symbol (flag_style_scm))
561 flag_style = ly_symbol2string (flag_style_scm);
563 if (flag_style == "no-flag")
568 String staffline_offs;
569 if (String::compare (flag_style, "mensural") == 0)
570 /* Mensural notation: For notes on staff lines, use different
571 flags than for notes between staff lines. The idea is that
572 flags are always vertically aligned with the staff lines,
573 regardless if the note head is on a staff line or between two
574 staff lines. In other words, the inner end of a flag always
575 touches a staff line.
580 int p = (int) (rint (stem_end_position (me)));
582 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
585 staffline_offs = "2";
590 char dir = (get_direction (me) == UP) ? 'u' : 'd';
591 String font_char = flag_style
592 + to_string (dir) + staffline_offs + to_string (log);
593 Font_metric *fm = Font_interface::get_default_font (me);
594 Stencil flag = fm->find_by_name ("flags." + font_char);
595 if (flag.is_empty ())
596 me->warning (_f ("flag `%s' not found", font_char));
598 SCM stroke_style_scm = me->get_property ("stroke-style");
599 if (scm_is_string (stroke_style_scm))
601 String stroke_style = ly_scm2string (stroke_style_scm);
602 if (!stroke_style.is_empty ())
604 String font_char = to_string (dir) + stroke_style;
605 Stencil stroke = fm->find_by_name ("flags." + font_char);
606 if (stroke.is_empty ())
607 me->warning (_f ("flag stroke `%s' not found", font_char));
609 flag.add_stencil (stroke);
616 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
618 Stem::width_callback (SCM e, SCM ax)
621 assert (scm_to_int (ax) == X_AXIS);
622 Grob *me = unsmob_grob (e);
626 if (is_invisible (me))
628 else if (unsmob_grob (me->get_object ("beam"))
629 || abs (duration_log (me)) <= 2)
631 r = Interval (-1, 1);
632 r *= thickness (me) / 2;
636 r = Interval (-1, 1) * thickness (me) * 0.5;
637 r.unite (flag (me).extent (X_AXIS));
639 return ly_interval2scm (r);
643 Stem::thickness (Grob *me)
645 return scm_to_double (me->get_property ("thickness"))
646 * Staff_symbol_referencer::line_thickness (me);
649 MAKE_SCHEME_CALLBACK (Stem, print, 1);
651 Stem::print (SCM smob)
653 Grob *me = unsmob_grob (smob);
655 Direction d = get_direction (me);
657 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
659 bool stemlet = stemlet_length > 0.0;
661 /* TODO: make the stem start a direction ?
662 This is required to avoid stems passing in tablature chords. */
664 = to_boolean (me->get_property ("avoid-note-head"))
667 Grob *beam = get_beam (me);
672 if (!lh && stemlet && !beam)
675 if (is_invisible (me))
678 Real y2 = stem_end_position (me);
680 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
683 y2 = Staff_symbol_referencer::get_position (lh);
686 Real beam_translation = Beam::get_beam_translation (beam);
687 Real beam_thickness = Beam::get_thickness (beam);
688 int beam_count = beam_multiplicity (me).length () + 1;
691 * (0.5 * beam_thickness
692 + beam_translation * max (0, (beam_count - 1))
693 + stemlet_length) / half_space;
696 Interval stem_y (min (y1, y2), max (y2, y1));
698 if (Grob *hed = support_head (me))
701 must not take ledgers into account.
703 Interval head_height = hed->extent (hed, Y_AXIS);
704 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
706 y_attach = head_height.linear_combination (y_attach);
707 stem_y[Direction (-d)] += d * y_attach / half_space;
711 Real stem_width = thickness (me);
713 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
715 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
716 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
718 Stencil ss = Lookup::round_filled_box (b, blot);
719 mol.add_stencil (ss);
721 mol.add_stencil (get_translated_flag (me));
723 return mol.smobbed_copy ();
727 Stem::get_translated_flag (Grob *me)
729 Stencil fl = flag (me);
732 Direction d = get_direction (me);
734 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
735 Real stem_width = thickness (me);
736 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
737 Real y2 = stem_end_position (me);
738 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
739 fl.translate_axis (stem_width / 2, X_AXIS);
746 move the stem to right of the notehead if it is up.
748 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
750 Stem::offset_callback (SCM element_smob, SCM)
752 Grob *me = unsmob_grob (element_smob);
755 if (Grob *f = first_head (me))
757 Interval head_wid = f->extent (f, X_AXIS);
760 if (is_invisible (me))
763 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
765 Direction d = get_direction (me);
766 Real real_attach = head_wid.linear_combination (d * attach);
769 /* If not centered: correct for stem thickness. */
772 Real rule_thick = thickness (me);
773 r += -d * rule_thick * 0.5;
778 extract_grob_set (me, "rests", rests);
781 Grob *rest = rests.top ();
782 r = rest->extent (rest, X_AXIS).center ();
785 return scm_from_double (r);
789 Stem::get_beam (Grob *me)
791 SCM b = me->get_object ("beam");
792 return dynamic_cast<Spanner *> (unsmob_grob (b));
796 Stem::get_stem_info (Grob *me)
799 si.dir_ = get_grob_direction (me);
801 SCM scm_info = me->get_property ("stem-info");
802 si.ideal_y_ = scm_to_double (scm_car (scm_info));
803 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
807 /* TODO: add extra space for tremolos! */
808 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
810 Stem::calc_stem_info (SCM smob)
812 Grob *me = unsmob_grob (smob);
813 Direction my_dir = get_grob_direction (me);
817 programming_error ("no stem dir set");
821 Real staff_space = Staff_symbol_referencer::staff_space (me);
822 Grob *beam = get_beam (me);
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 /* Simple standard stem length */
828 SCM lengths = me->get_property ("beamed-lengths");
830 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
833 /* stem only extends to center of beam
835 - 0.5 * beam_thickness;
837 /* Condition: sane minimum free stem length (chord to beams) */
838 lengths = me->get_property ("beamed-minimum-free-lengths");
839 Real ideal_minimum_free
840 = 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);
897 = scm_to_double (robust_list_ref
900 ("beamed-extreme-minimum-free-lengths")))
903 Real minimum_length = minimum_free
905 /* stem only extends to center of beam */
906 - 0.5 * beam_thickness;
908 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
910 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
911 y_ext.widen (0.5); // FIXME. Should be tunable?
912 minimum_length = max (minimum_length, y_ext.length ());
916 Real minimum_y = note_start + minimum_length;
917 Real shortest_y = minimum_y * my_dir;
919 return scm_list_2 (scm_from_double (ideal_y),
920 scm_from_double (shortest_y));
924 Stem::beam_multiplicity (Grob *stem)
926 SCM beaming = stem->get_property ("beaming");
927 Slice le = int_list_to_slice (scm_car (beaming));
928 Slice ri = int_list_to_slice (scm_cdr (beaming));
933 /* FIXME: Too many properties */
934 ADD_INTERFACE (Stem, "stem-interface",
935 "The stem represent the graphical stem. "
936 "In addition, it internally connects note heads, beams and"
938 "Rests and whole notes have invisible stems.",
944 "beamed-extreme-minimum-free-lengths "
946 "beamed-minimum-free-lengths "
968 /****************************************************************/
970 Stem_info::Stem_info ()
972 ideal_y_ = shortest_y_ = 0;
977 Stem_info::scale (Real x)