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)
321 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
322 if (t_flag && !unsmob_grob (me->get_object ("beam")))
324 /* Crude hack: add extra space if tremolo flag is there.
326 We can't do this for the beam, since we get into a loop
327 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
330 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
335 Interval flag_ext = flag (me).extent (Y_AXIS);
336 if (!flag_ext.is_empty ())
337 minlen += 2 * flag_ext.length () / ss;
339 /* The clash is smaller for down stems (since the tremolo is
344 length = max (length, minlen + 1.0);
347 return scm_from_double (length);
349 /* The log of the duration (Number of hooks on the flag minus two) */
351 Stem::duration_log (Grob *me)
353 SCM s = me->get_property ("duration-log");
354 return (scm_is_number (s)) ? scm_to_int (s) : 2;
357 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
359 Stem::calc_positioning_done (SCM smob)
361 Grob *me = unsmob_grob (smob);
362 if (!head_count (me))
365 extract_grob_set (me, "note-heads", ro_heads);
366 Link_array<Grob> heads (ro_heads);
367 heads.sort (compare_position);
368 Direction dir = get_grob_direction (me);
373 Real thick = thickness (me);
375 Grob *hed = support_head (me);
376 Real w = hed->extent (hed, X_AXIS)[dir];
377 for (int i = 0; i < heads.size (); i++)
378 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
382 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
383 for (int i = 1; i < heads.size (); i++)
385 Real p = Staff_symbol_referencer::get_position (heads[i]);
386 Real dy = fabs (lastpos- p);
389 dy should always be 0.5, 0.0, 1.0, but provide safety margin
396 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
398 Direction d = get_grob_direction (me);
400 Reversed head should be shifted ell-thickness, but this
401 looks too crowded, so we only shift ell-0.5*thickness.
403 This leads to assymetry: Normal heads overlap the
404 stem 100% whereas reversed heads only overlaps the
408 Real reverse_overlap = 0.5;
409 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
412 if (is_invisible (me))
413 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
418 For some cases we should kern some more: when the
419 distance between the next or prev note is too large, we'd
420 get large white gaps, eg.
443 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
445 Stem::calc_direction (SCM smob)
447 Grob *me = unsmob_grob (smob);
448 Direction dir = CENTER;
449 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
451 SCM ignore_me = beam->get_property ("direction");
453 dir = get_grob_direction (me);
456 dir = get_default_dir (me);
458 return scm_from_int (dir);
462 Stem::get_default_dir (Grob *me)
464 Direction dir = CENTER;
465 int staff_center = 0;
466 Interval hp = head_positions (me);
469 int udistance = (int) (UP *hp[UP] - staff_center);
470 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
472 if (sign (ddistance - udistance))
473 dir = Direction (sign (ddistance - udistance));
475 dir = to_dir (me->get_property ("neutral-direction"));
482 MAKE_SCHEME_CALLBACK (Stem, height, 2);
484 Stem::height (SCM smob, SCM ax)
486 Axis a = (Axis)scm_to_int (ax);
487 Grob *me = unsmob_grob (smob);
488 assert (a == Y_AXIS);
490 Direction dir = get_grob_direction (me);
494 UGH. Should be automatic
496 Grob *beam = get_beam (me);
499 beam->get_property ("positions");
502 /* FIXME uncached? */
503 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
508 programming_error ("no stem direction");
511 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
514 return ly_interval2scm (iv);
518 Stem::stem_end_position (Grob *me)
520 return robust_scm2double (me->get_property ("stem-end-position"), 0);
524 Stem::flag (Grob *me)
526 int log = duration_log (me);
528 || unsmob_grob (me->get_object ("beam")))
532 TODO: maybe property stroke-style should take different values,
533 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
537 SCM flag_style_scm = me->get_property ("flag-style");
538 if (scm_is_symbol (flag_style_scm))
539 flag_style = ly_symbol2string (flag_style_scm);
541 if (flag_style == "no-flag")
546 String staffline_offs;
547 if (String::compare (flag_style, "mensural") == 0)
548 /* Mensural notation: For notes on staff lines, use different
549 flags than for notes between staff lines. The idea is that
550 flags are always vertically aligned with the staff lines,
551 regardless if the note head is on a staff line or between two
552 staff lines. In other words, the inner end of a flag always
553 touches a staff line.
558 int p = (int) (rint (stem_end_position (me)));
560 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
563 staffline_offs = "2";
568 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
569 String font_char = flag_style
570 + to_string (dir) + staffline_offs + to_string (log);
571 Font_metric *fm = Font_interface::get_default_font (me);
572 Stencil flag = fm->find_by_name ("flags." + font_char);
573 if (flag.is_empty ())
574 me->warning (_f ("flag `%s' not found", font_char));
576 SCM stroke_style_scm = me->get_property ("stroke-style");
577 if (scm_is_string (stroke_style_scm))
579 String stroke_style = ly_scm2string (stroke_style_scm);
580 if (!stroke_style.is_empty ())
582 String font_char = to_string (dir) + stroke_style;
583 Stencil stroke = fm->find_by_name ("flags." + font_char);
584 if (stroke.is_empty ())
585 me->warning (_f ("flag stroke `%s' not found", font_char));
587 flag.add_stencil (stroke);
594 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
596 Stem::width_callback (SCM e, SCM ax)
599 assert (scm_to_int (ax) == X_AXIS);
600 Grob *me = unsmob_grob (e);
604 if (is_invisible (me))
606 else if (unsmob_grob (me->get_object ("beam"))
607 || abs (duration_log (me)) <= 2)
609 r = Interval (-1, 1);
610 r *= thickness (me) / 2;
614 r = Interval (-1, 1) * thickness (me) * 0.5;
615 r.unite (flag (me).extent (X_AXIS));
617 return ly_interval2scm (r);
621 Stem::thickness (Grob *me)
623 return scm_to_double (me->get_property ("thickness"))
624 * Staff_symbol_referencer::line_thickness (me);
627 MAKE_SCHEME_CALLBACK (Stem, print, 1);
629 Stem::print (SCM smob)
631 Grob *me = unsmob_grob (smob);
633 Direction d = get_grob_direction (me);
635 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
637 bool stemlet = stemlet_length > 0.0;
639 /* TODO: make the stem start a direction ?
640 This is required to avoid stems passing in tablature chords. */
642 = to_boolean (me->get_property ("avoid-note-head"))
645 Grob *beam = get_beam (me);
650 if (!lh && stemlet && !beam)
653 if (is_invisible (me))
656 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
658 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
661 y2 = Staff_symbol_referencer::get_position (lh);
664 Real beam_translation = Beam::get_beam_translation (beam);
665 Real beam_thickness = Beam::get_thickness (beam);
666 int beam_count = beam_multiplicity (me).length () + 1;
669 * (0.5 * beam_thickness
670 + beam_translation * max (0, (beam_count - 1))
671 + stemlet_length) / half_space;
674 Interval stem_y (min (y1, y2), max (y2, y1));
676 if (Grob *hed = support_head (me))
679 must not take ledgers into account.
681 Interval head_height = hed->extent (hed, Y_AXIS);
682 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
684 y_attach = head_height.linear_combination (y_attach);
685 stem_y[Direction (-d)] += d * y_attach / half_space;
689 Real stem_width = thickness (me);
691 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
693 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
694 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
696 Stencil ss = Lookup::round_filled_box (b, blot);
697 mol.add_stencil (ss);
699 mol.add_stencil (get_translated_flag (me));
701 return mol.smobbed_copy ();
705 Stem::get_translated_flag (Grob *me)
707 Stencil fl = flag (me);
710 Direction d = get_grob_direction (me);
712 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
713 Real stem_width = thickness (me);
714 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
715 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
716 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
717 fl.translate_axis (stem_width / 2, X_AXIS);
724 move the stem to right of the notehead if it is up.
726 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
728 Stem::offset_callback (SCM element_smob, SCM)
730 Grob *me = unsmob_grob (element_smob);
733 if (Grob *f = first_head (me))
735 Interval head_wid = f->extent (f, X_AXIS);
738 if (is_invisible (me))
741 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
743 Direction d = get_grob_direction (me);
744 Real real_attach = head_wid.linear_combination (d * attach);
747 /* If not centered: correct for stem thickness. */
750 Real rule_thick = thickness (me);
751 r += -d * rule_thick * 0.5;
756 extract_grob_set (me, "rests", rests);
759 Grob *rest = rests.top ();
760 r = rest->extent (rest, X_AXIS).center ();
763 return scm_from_double (r);
767 Stem::get_beam (Grob *me)
769 SCM b = me->get_object ("beam");
770 return dynamic_cast<Spanner *> (unsmob_grob (b));
774 Stem::get_stem_info (Grob *me)
777 si.dir_ = get_grob_direction (me);
779 SCM scm_info = me->get_property ("stem-info");
780 si.ideal_y_ = scm_to_double (scm_car (scm_info));
781 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
785 /* TODO: add extra space for tremolos! */
786 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
788 Stem::calc_stem_info (SCM smob)
790 Grob *me = unsmob_grob (smob);
791 Direction my_dir = get_grob_direction (me);
795 programming_error ("no stem dir set");
799 Real staff_space = Staff_symbol_referencer::staff_space (me);
800 Grob *beam = get_beam (me);
801 Real beam_translation = Beam::get_beam_translation (beam);
802 Real beam_thickness = Beam::get_thickness (beam);
803 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
805 /* Simple standard stem length */
806 SCM details = me->get_property ("details");
807 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
809 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
812 /* stem only extends to center of beam
814 - 0.5 * beam_thickness;
816 /* Condition: sane minimum free stem length (chord to beams) */
817 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
818 Real ideal_minimum_free
819 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
823 It seems that also for ideal minimum length, we must use
824 the maximum beam count (for this direction):
826 \score{ \notes\relative c''{ [a8 a32] }}
828 must be horizontal. */
829 Real height_of_my_beams = beam_thickness
830 + (beam_count - 1) * beam_translation;
832 Real ideal_minimum_length = ideal_minimum_free
834 /* stem only extends to center of beam */
835 - 0.5 * beam_thickness;
837 ideal_length = max (ideal_length, ideal_minimum_length);
839 /* Convert to Y position, calculate for dir == UP */
841 = /* staff positions */
842 head_positions (me)[my_dir] * 0.5
843 * my_dir * staff_space;
844 Real ideal_y = note_start + ideal_length;
846 /* Conditions for Y position */
848 /* Lowest beam of (UP) beam must never be lower than second staffline
852 Although this (additional) rule is probably correct,
853 I expect that highest beam (UP) should also never be lower
854 than middle staffline, just as normal stems.
858 Obviously not for grace beams.
860 Also, not for knees. Seems to be a good thing. */
861 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
862 bool is_knee = to_boolean (beam->get_property ("knee"));
863 if (!no_extend_b && !is_knee)
865 /* Highest beam of (UP) beam must never be lower than middle
867 ideal_y = max (ideal_y, 0.0);
868 /* Lowest beam of (UP) beam must never be lower than second staffline */
869 ideal_y = max (ideal_y, (-staff_space
870 - beam_thickness + height_of_my_beams));
873 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
875 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"), details));
878 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
881 Real minimum_length = minimum_free
883 /* stem only extends to center of beam */
884 - 0.5 * beam_thickness;
886 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
888 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
889 y_ext.widen (0.5); // FIXME. Should be tunable?
890 minimum_length = max (minimum_length, y_ext.length ());
894 Real minimum_y = note_start + minimum_length;
895 Real shortest_y = minimum_y * my_dir;
897 return scm_list_2 (scm_from_double (ideal_y),
898 scm_from_double (shortest_y));
902 Stem::beam_multiplicity (Grob *stem)
904 SCM beaming = stem->get_property ("beaming");
905 Slice le = int_list_to_slice (scm_car (beaming));
906 Slice ri = int_list_to_slice (scm_cdr (beaming));
911 /* FIXME: Too many properties */
912 ADD_INTERFACE (Stem, "stem-interface",
913 "The stem represent the graphical stem. "
914 "In addition, it internally connects note heads, beams and"
916 "Rests and whole notes have invisible stems."
918 "\n\nThe following properties may be set in the details list."
920 "@item beamed-lengths \n"
921 "list of stem lengths given beam multiplicity. \n"
922 "@item beamed-minimum-free-lengths \n"
923 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
924 "@item beamed-extreme-minimum-free-lengths\n"
925 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
927 "Default stem lengths. The list gives a length for each flag-count.\n"
928 "@item stem-shorten\n"
929 "How much a stem in a forced direction "
930 "should be shortened. The list gives an amount depending on the number "
960 /****************************************************************/
962 Stem_info::Stem_info ()
964 ideal_y_ = shortest_y_ = 0;
969 Stem_info::scale (Real x)