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 ());
233 n->add_dependency (me);
235 if (Note_head::has_interface (n))
236 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
237 else if (Rest::has_interface (n))
238 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
242 Stem::is_invisible (Grob *me)
244 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
247 return !((head_count (me)
248 || stemlet_length > 0.0)
249 && scm_to_int (me->get_property ("duration-log")) >= 1);
253 Stem::get_default_dir (Grob *me)
255 int staff_center = 0;
256 Interval hp = head_positions (me);
260 int udistance = (int) (UP *hp[UP] - staff_center);
261 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
263 if (sign (ddistance - udistance))
264 return Direction (sign (ddistance - udistance));
266 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);
294 dir = get_default_dir (me);
295 set_grob_direction (me, dir);
298 /* Stems in unnatural (forced) direction should be shortened,
299 according to [Roush & Gourlay] */
300 Interval hp = head_positions (me);
301 if (dir && dir * hp[dir] >= 0)
303 SCM sshorten = me->get_property ("stem-shorten");
304 SCM scm_shorten = scm_is_pair (sshorten)
305 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
306 Real shorten = 2* robust_scm2double (scm_shorten, 0);
308 /* On boundary: shorten only half */
309 if (abs (head_positions (me)[dir]) <= 1)
316 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
317 if (t_flag && !unsmob_grob (me->get_object ("beam")))
319 /* Crude hack: add extra space if tremolo flag is there.
321 We can't do this for the beam, since we get into a loop
322 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
325 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
330 Interval flag_ext = flag (me).extent (Y_AXIS);
331 if (!flag_ext.is_empty ())
332 minlen += 2 * flag_ext.length () / ss;
334 /* The clash is smaller for down stems (since the tremolo is
339 length = max (length, minlen + 1.0);
342 Real st = dir ? hp[dir] + dir * length : 0;
344 /* TODO: change name to extend-stems to staff/center/'() */
345 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
346 if (!no_extend_b && dir * st < 0)
349 /* Make a little room if we have a upflag and there is a dot.
350 previous approach was to lengthen the stem. This is not
351 good typesetting practice. */
352 if (!get_beam (me) && dir == UP
355 Grob *closest_to_flag = extremal_heads (me)[dir];
356 Grob *dots = closest_to_flag
357 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
361 Real dp = Staff_symbol_referencer::get_position (dots);
362 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
364 /* Very gory: add myself to the X-support of the parent,
365 which should be a dot-column. */
366 if (dir * (st + flagy - dp) < 0.5)
368 Grob *par = dots->get_parent (X_AXIS);
370 if (Dot_column::has_interface (par))
372 Side_position_interface::add_support (par, me);
374 /* TODO: apply some better logic here. The flag is
375 curved inwards, so this will typically be too
384 /* The log of the duration (Number of hooks on the flag minus two) */
386 Stem::duration_log (Grob *me)
388 SCM s = me->get_property ("duration-log");
389 return (scm_is_number (s)) ? scm_to_int (s) : 2;
393 Stem::position_noteheads (Grob *me)
395 if (!head_count (me))
398 extract_grob_set (me, "note-heads", ro_heads);
399 Link_array<Grob> heads (ro_heads);
400 heads.sort (compare_position);
401 Direction dir = get_direction (me);
406 Real thick = thickness (me);
408 Grob *hed = support_head (me);
409 Real w = hed->extent (hed, X_AXIS)[dir];
410 for (int i = 0; i < heads.size (); i++)
411 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
415 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
416 for (int i = 1; i < heads.size (); i++)
418 Real p = Staff_symbol_referencer::get_position (heads[i]);
419 Real dy = fabs (lastpos- p);
422 dy should always be 0.5, 0.0, 1.0, but provide safety margin
429 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
431 Direction d = get_direction (me);
433 Reversed head should be shifted ell-thickness, but this
434 looks too crowded, so we only shift ell-0.5*thickness.
436 This leads to assymetry: Normal heads overlap the
437 stem 100% whereas reversed heads only overlaps the
441 Real reverse_overlap = 0.5;
442 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
445 if (is_invisible (me))
446 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
451 For some cases we should kern some more: when the
452 distance between the next or prev note is too large, we'd
453 get large white gaps, eg.
472 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
474 Stem::before_line_breaking (SCM smob)
476 Grob *me = unsmob_grob (smob);
479 Do the calculations for visible stems, but also for invisible stems
480 with note heads (i.e. half notes.)
484 stem_end_position (me); // ugh. Trigger direction calc.
485 position_noteheads (me);
488 return SCM_UNSPECIFIED;
493 When in a beam with tuplet brackets, brew_mol is called early,
494 caching a wrong value.
496 MAKE_SCHEME_CALLBACK (Stem, height, 2);
498 Stem::height (SCM smob, SCM ax)
500 Axis a = (Axis)scm_to_int (ax);
501 Grob *me = unsmob_grob (smob);
502 assert (a == Y_AXIS);
505 ugh. - this dependency should be automatic.
507 Grob *beam = get_beam (me);
509 Beam::after_line_breaking (beam->self_scm ());
511 SCM mol = me->get_uncached_stencil ();
514 iv = unsmob_stencil (mol)->extent (a);
515 if (Grob *b = get_beam (me))
517 Direction d = get_direction (me);
520 programming_error ("no stem direction");
523 iv[d] += d * Beam::get_thickness (b) * 0.5;
526 return ly_interval2scm (iv);
530 Stem::flag (Grob *me)
532 int log = duration_log (me);
534 || unsmob_grob (me->get_object ("beam")))
538 TODO: maybe property stroke-style should take different values,
539 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
543 SCM flag_style_scm = me->get_property ("flag-style");
544 if (scm_is_symbol (flag_style_scm))
545 flag_style = ly_symbol2string (flag_style_scm);
547 if (flag_style == "no-flag")
552 String staffline_offs;
553 if (String::compare (flag_style, "mensural") == 0)
554 /* Mensural notation: For notes on staff lines, use different
555 flags than for notes between staff lines. The idea is that
556 flags are always vertically aligned with the staff lines,
557 regardless if the note head is on a staff line or between two
558 staff lines. In other words, the inner end of a flag always
559 touches a staff line.
564 int p = (int) (rint (stem_end_position (me)));
566 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
569 staffline_offs = "2";
574 char dir = (get_direction (me) == UP) ? 'u' : 'd';
575 String font_char = flag_style
576 + to_string (dir) + staffline_offs + to_string (log);
577 Font_metric *fm = Font_interface::get_default_font (me);
578 Stencil flag = fm->find_by_name ("flags." + font_char);
579 if (flag.is_empty ())
580 me->warning (_f ("flag `%s' not found", font_char));
582 SCM stroke_style_scm = me->get_property ("stroke-style");
583 if (scm_is_string (stroke_style_scm))
585 String stroke_style = ly_scm2string (stroke_style_scm);
586 if (!stroke_style.is_empty ())
588 String font_char = to_string (dir) + stroke_style;
589 Stencil stroke = fm->find_by_name ("flags." + font_char);
590 if (stroke.is_empty ())
591 me->warning (_f ("flag stroke `%s' not found", font_char));
593 flag.add_stencil (stroke);
600 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
602 Stem::width_callback (SCM e, SCM ax)
605 assert (scm_to_int (ax) == X_AXIS);
606 Grob *me = unsmob_grob (e);
610 if (is_invisible (me))
612 else if (unsmob_grob (me->get_object ("beam"))
613 || abs (duration_log (me)) <= 2)
615 r = Interval (-1, 1);
616 r *= thickness (me) / 2;
620 r = Interval (-1, 1) * thickness (me) * 0.5;
621 r.unite (flag (me).extent (X_AXIS));
623 return ly_interval2scm (r);
627 Stem::thickness (Grob *me)
629 return scm_to_double (me->get_property ("thickness"))
630 * Staff_symbol_referencer::line_thickness (me);
633 MAKE_SCHEME_CALLBACK (Stem, print, 1);
635 Stem::print (SCM smob)
637 Grob *me = unsmob_grob (smob);
639 Direction d = get_direction (me);
641 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
643 bool stemlet = stemlet_length > 0.0;
645 /* TODO: make the stem start a direction ?
646 This is required to avoid stems passing in tablature chords. */
648 = to_boolean (me->get_property ("avoid-note-head"))
651 Grob *beam = get_beam (me);
656 if (!lh && stemlet && !beam)
659 if (is_invisible (me))
662 Real y2 = stem_end_position (me);
664 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
667 y2 = Staff_symbol_referencer::get_position (lh);
670 Real beam_translation = Beam::get_beam_translation (beam);
671 Real beam_thickness = Beam::get_thickness (beam);
672 int beam_count = beam_multiplicity (me).length () + 1;
675 * (0.5 * beam_thickness
676 + beam_translation * max (0, (beam_count - 1))
677 + stemlet_length) / half_space;
680 Interval stem_y (min (y1, y2), max (y2, y1));
682 if (Grob *hed = support_head (me))
685 must not take ledgers into account.
687 Interval head_height = hed->extent (hed, Y_AXIS);
688 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
690 y_attach = head_height.linear_combination (y_attach);
691 stem_y[Direction (-d)] += d * y_attach / half_space;
695 Real stem_width = thickness (me);
697 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
699 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
700 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
702 Stencil ss = Lookup::round_filled_box (b, blot);
703 mol.add_stencil (ss);
705 mol.add_stencil (get_translated_flag (me));
707 return mol.smobbed_copy ();
711 Stem::get_translated_flag (Grob *me)
713 Stencil fl = flag (me);
716 Direction d = get_direction (me);
718 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
719 Real stem_width = thickness (me);
720 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
721 Real y2 = stem_end_position (me);
722 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
723 fl.translate_axis (stem_width / 2, X_AXIS);
730 move the stem to right of the notehead if it is up.
732 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
734 Stem::offset_callback (SCM element_smob, SCM)
736 Grob *me = unsmob_grob (element_smob);
739 if (Grob *f = first_head (me))
741 Interval head_wid = f->extent (f, X_AXIS);
744 if (is_invisible (me))
747 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
749 Direction d = get_direction (me);
750 Real real_attach = head_wid.linear_combination (d * attach);
753 /* If not centered: correct for stem thickness. */
756 Real rule_thick = thickness (me);
757 r += -d * rule_thick * 0.5;
762 extract_grob_set (me, "rests", rests);
765 Grob *rest = rests.top ();
766 r = rest->extent (rest, X_AXIS).center ();
769 return scm_from_double (r);
773 Stem::get_beam (Grob *me)
775 SCM b = me->get_object ("beam");
776 return dynamic_cast<Spanner *> (unsmob_grob (b));
780 Stem::get_stem_info (Grob *me)
782 /* Return cached info if available */
783 SCM scm_info = me->get_property ("stem-info");
784 if (!scm_is_pair (scm_info))
787 scm_info = me->get_property ("stem-info");
791 si.dir_ = get_grob_direction (me);
792 si.ideal_y_ = scm_to_double (scm_car (scm_info));
793 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
797 /* TODO: add extra space for tremolos! */
799 Stem::calc_stem_info (Grob *me)
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 lengths = me->get_property ("beamed-lengths");
818 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
821 /* stem only extends to center of beam
823 - 0.5 * beam_thickness;
825 /* Condition: sane minimum free stem length (chord to beams) */
826 lengths = me->get_property ("beamed-minimum-free-lengths");
827 Real ideal_minimum_free
828 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
832 It seems that also for ideal minimum length, we must use
833 the maximum beam count (for this direction):
835 \score{ \notes\relative c''{ [a8 a32] }}
837 must be horizontal. */
838 Real height_of_my_beams = beam_thickness
839 + (beam_count - 1) * beam_translation;
841 Real ideal_minimum_length = ideal_minimum_free
843 /* stem only extends to center of beam */
844 - 0.5 * beam_thickness;
846 ideal_length = max (ideal_length, ideal_minimum_length);
848 /* Convert to Y position, calculate for dir == UP */
850 = /* staff positions */
851 head_positions (me)[my_dir] * 0.5
852 * my_dir * staff_space;
853 Real ideal_y = note_start + ideal_length;
855 /* Conditions for Y position */
857 /* Lowest beam of (UP) beam must never be lower than second staffline
861 Although this (additional) rule is probably correct,
862 I expect that highest beam (UP) should also never be lower
863 than middle staffline, just as normal stems.
867 Obviously not for grace beams.
869 Also, not for knees. Seems to be a good thing. */
870 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
871 bool is_knee = to_boolean (beam->get_property ("knee"));
872 if (!no_extend_b && !is_knee)
874 /* Highest beam of (UP) beam must never be lower than middle
876 ideal_y = max (ideal_y, 0.0);
877 /* Lowest beam of (UP) beam must never be lower than second staffline */
878 ideal_y = max (ideal_y, (-staff_space
879 - beam_thickness + height_of_my_beams));
882 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
885 = scm_to_double (robust_list_ref
888 ("beamed-extreme-minimum-free-lengths")))
891 Real minimum_length = minimum_free
893 /* stem only extends to center of beam */
894 - 0.5 * beam_thickness;
896 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
898 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
899 y_ext.widen (0.5); // FIXME. Should be tunable?
900 minimum_length = max (minimum_length, y_ext.length ());
904 Real minimum_y = note_start + minimum_length;
905 Real shortest_y = minimum_y * my_dir;
907 me->set_property ("stem-info",
908 scm_list_2 (scm_from_double (ideal_y),
909 scm_from_double (shortest_y)));
913 Stem::beam_multiplicity (Grob *stem)
915 SCM beaming = stem->get_property ("beaming");
916 Slice le = int_list_to_slice (scm_car (beaming));
917 Slice ri = int_list_to_slice (scm_cdr (beaming));
922 /* FIXME: Too many properties */
923 ADD_INTERFACE (Stem, "stem-interface",
924 "The stem represent the graphical stem. "
925 "In addition, it internally connects note heads, beams and"
927 "Rests and whole notes have invisible stems.",
928 "tremolo-flag french-beaming "
929 "avoid-note-head thickness "
930 "stemlet-length rests "
931 "stem-info beamed-lengths beamed-minimum-free-lengths "
932 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
933 "duration-log beaming neutral-direction stem-end-position "
934 "note-heads direction length flag-style "
935 "no-stem-extend stroke-style");
937 /****************************************************************/
939 Stem_info::Stem_info ()
941 ideal_y_ = shortest_y_ = 0;
946 Stem_info::scale (Real x)