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 <math.h> // rint
21 #include "directional-element-interface.hh"
22 #include "note-head.hh"
24 #include "output-def.hh"
25 #include "rhythmic-head.hh"
26 #include "font-interface.hh"
27 #include "paper-column.hh"
31 #include "pointer-group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "side-position-interface.hh"
34 #include "dot-column.hh"
35 #include "stem-tremolo.hh"
38 Stem::set_beaming (Grob *me, int beam_count, Direction d)
40 SCM pair = me->get_property ("beaming");
42 if (!scm_is_pair (pair))
44 pair = scm_cons (SCM_EOL, SCM_EOL);
45 me->set_property ("beaming", pair);
48 SCM lst = index_get_cell (pair, d);
49 for (int i = 0; i < beam_count; i++)
50 lst = scm_cons (scm_int2num (i), lst);
51 index_set_cell (pair, d, lst);
55 Stem::get_beaming (Grob *me, Direction d)
57 SCM pair = me->get_property ("beaming");
58 if (!scm_is_pair (pair))
61 SCM lst = index_get_cell (pair, d);
62 return scm_ilength (lst);
66 Stem::head_positions (Grob *me)
70 Drul_array<Grob *> e (extremal_heads (me));
71 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
72 Staff_symbol_referencer::get_position (e[UP]));
78 Stem::chord_start_y (Grob *me)
80 Interval hp = head_positions (me);
82 return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
88 Stem::stem_end_position (Grob *me)
90 SCM p = me->get_property ("stem-end-position");
92 if (!scm_is_number (p))
94 pos = get_default_stem_end_position (me);
95 me->set_property ("stem-end-position", scm_make_real (pos));
98 pos = scm_to_double (p);
104 Stem::get_direction (Grob *me)
106 Direction d = get_grob_direction (me);
110 d = get_default_dir (me);
112 set_grob_direction (me, d);
118 Stem::set_stemend (Grob *me, Real se)
121 Direction d = get_direction (me);
123 if (d && d * head_positions (me)[get_direction (me)] >= se * d)
124 me->warning (_ ("weird stem size, check for narrow beams"));
126 me->set_property ("stem-end-position", scm_make_real (se));
129 /* Note head that determines hshift for upstems
130 WARNING: triggers direction */
132 Stem::support_head (Grob *me)
134 extract_grob_set (me, "note-heads", heads);
135 if (heads.size () == 1)
138 return first_head (me);
142 Stem::head_count (Grob *me)
144 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
147 /* The note head which forms one end of the stem.
148 WARNING: triggers direction */
150 Stem::first_head (Grob *me)
152 Direction d = get_direction (me);
154 return extremal_heads (me)[-d];
158 /* The note head opposite to the first head. */
160 Stem::last_head (Grob *me)
162 Direction d = get_direction (me);
164 return extremal_heads (me)[d];
169 START is part where stem reaches `last' head.
171 This function returns a drul with (bottom-head, top-head).
174 Stem::extremal_heads (Grob *me)
176 const int inf = 1000000;
177 Drul_array<int> extpos;
181 Drul_array<Grob *> exthead (0, 0);
182 extract_grob_set (me, "note-heads", heads);
184 for (int i = heads.size (); i--;)
187 int p = Staff_symbol_referencer::get_rounded_position (n);
192 if (d * p > d * extpos[d])
198 while (flip (&d) != DOWN);
204 integer_compare (int const &a, int const &b)
209 /* The positions, in ascending order. */
211 Stem::note_head_positions (Grob *me)
214 extract_grob_set (me, "note-heads", heads);
216 for (int i = heads.size (); i--;)
219 int p = Staff_symbol_referencer::get_rounded_position (n);
224 ps.sort (integer_compare);
229 Stem::add_head (Grob *me, Grob *n)
231 n->set_object ("stem", me->self_scm ());
232 n->add_dependency (me);
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"));
269 Stem::get_default_stem_end_position (Grob *me)
271 Real ss = Staff_symbol_referencer::staff_space (me);
272 int durlog = duration_log (me);
276 /* WARNING: IN HALF SPACES */
278 SCM scm_len = me->get_property ("length");
279 if (scm_is_number (scm_len))
280 length = scm_to_double (scm_len);
283 s = me->get_property ("lengths");
285 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
289 'set-default-stemlen' sets direction too. */
290 Direction dir = get_direction (me);
293 dir = get_default_dir (me);
294 set_grob_direction (me, dir);
297 /* Stems in unnatural (forced) direction should be shortened,
298 according to [Roush & Gourlay] */
299 Interval hp = head_positions (me);
300 if (dir && dir * hp[dir] >= 0)
302 SCM sshorten = me->get_property ("stem-shorten");
303 SCM scm_shorten = scm_is_pair (sshorten)
304 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
305 Real shorten = 2* robust_scm2double (scm_shorten, 0);
307 /* On boundary: shorten only half */
308 if (abs (head_positions (me)[dir]) <= 1)
315 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
316 if (t_flag && !unsmob_grob (me->get_object ("beam")))
318 /* Crude hack: add extra space if tremolo flag is there.
320 We can't do this for the beam, since we get into a loop
321 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
324 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
329 Interval flag_ext = flag (me).extent (Y_AXIS);
330 if (!flag_ext.is_empty ())
331 minlen += 2 * flag_ext.length () / ss;
333 /* The clash is smaller for down stems (since the tremolo is
338 length = max (length, minlen + 1.0);
341 Real st = dir ? hp[dir] + dir * length : 0;
343 /* TODO: change name to extend-stems to staff/center/'() */
344 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
345 if (!no_extend_b && dir * st < 0)
348 /* Make a little room if we have a upflag and there is a dot.
349 previous approach was to lengthen the stem. This is not
350 good typesetting practice. */
351 if (!get_beam (me) && dir == UP
354 Grob *closest_to_flag = extremal_heads (me)[dir];
355 Grob *dots = closest_to_flag
356 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
360 Real dp = Staff_symbol_referencer::get_position (dots);
361 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
363 /* Very gory: add myself to the X-support of the parent,
364 which should be a dot-column. */
365 if (dir * (st + flagy - dp) < 0.5)
367 Grob *par = dots->get_parent (X_AXIS);
369 if (Dot_column::has_interface (par))
371 Side_position_interface::add_support (par, me);
373 /* TODO: apply some better logic here. The flag is
374 curved inwards, so this will typically be too
383 /* The log of the duration (Number of hooks on the flag minus two) */
385 Stem::duration_log (Grob *me)
387 SCM s = me->get_property ("duration-log");
388 return (scm_is_number (s)) ? scm_to_int (s) : 2;
392 Stem::position_noteheads (Grob *me)
394 if (!head_count (me))
397 extract_grob_set (me, "note-heads", ro_heads);
398 Link_array<Grob> heads (ro_heads);
399 heads.sort (compare_position);
400 Direction dir = get_direction (me);
405 Real thick = thickness (me);
407 Grob *hed = support_head (me);
408 Real w = hed->extent (hed, X_AXIS)[dir];
409 for (int i = 0; i < heads.size (); i++)
410 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
414 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
415 for (int i = 1; i < heads.size (); i++)
417 Real p = Staff_symbol_referencer::get_position (heads[i]);
418 Real dy = fabs (lastpos- p);
421 dy should always be 0.5, 0.0, 1.0, but provide safety margin
428 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
430 Direction d = get_direction (me);
432 Reversed head should be shifted ell-thickness, but this
433 looks too crowded, so we only shift ell-0.5*thickness.
435 This leads to assymetry: Normal heads overlap the
436 stem 100% whereas reversed heads only overlaps the
440 Real reverse_overlap = 0.5;
441 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
444 if (is_invisible (me))
445 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
450 For some cases we should kern some more: when the
451 distance between the next or prev note is too large, we'd
452 get large white gaps, eg.
471 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
473 Stem::before_line_breaking (SCM smob)
475 Grob *me = unsmob_grob (smob);
478 Do the calculations for visible stems, but also for invisible stems
479 with note heads (i.e. half notes.)
483 stem_end_position (me); // ugh. Trigger direction calc.
484 position_noteheads (me);
487 return SCM_UNSPECIFIED;
492 When in a beam with tuplet brackets, brew_mol is called early,
493 caching a wrong value.
495 MAKE_SCHEME_CALLBACK (Stem, height, 2);
497 Stem::height (SCM smob, SCM ax)
499 Axis a = (Axis)scm_to_int (ax);
500 Grob *me = unsmob_grob (smob);
501 assert (a == Y_AXIS);
504 ugh. - this dependency should be automatic.
506 Grob *beam = get_beam (me);
509 Beam::after_line_breaking (beam->self_scm ());
512 SCM mol = me->get_uncached_stencil ();
515 iv = unsmob_stencil (mol)->extent (a);
516 if (Grob *b = get_beam (me))
518 Direction d = get_direction (me);
521 programming_error ("no stem direction");
524 iv[d] += d * Beam::get_thickness (b) * 0.5;
527 return ly_interval2scm (iv);
531 Stem::flag (Grob *me)
533 /* TODO: maybe property stroke-style should take different values,
534 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
538 SCM flag_style_scm = me->get_property ("flag-style");
539 if (scm_is_symbol (flag_style_scm))
540 flag_style = ly_symbol2string (flag_style_scm);
542 if (flag_style == "no-flag")
547 String staffline_offs;
548 if (String::compare (flag_style, "mensural") == 0)
549 /* Mensural notation: For notes on staff lines, use different
550 flags than for notes between staff lines. The idea is that
551 flags are always vertically aligned with the staff lines,
552 regardless if the note head is on a staff line or between two
553 staff lines. In other words, the inner end of a flag always
554 touches a staff line.
559 int p = (int) (rint (stem_end_position (me)));
561 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
565 staffline_offs = "2";
573 char dir = (get_direction (me) == UP) ? 'u' : 'd';
574 String font_char = flag_style
575 + to_string (dir) + staffline_offs + to_string (duration_log (me));
576 Font_metric *fm = Font_interface::get_default_font (me);
577 Stencil flag = fm->find_by_name ("flags." + font_char);
578 if (flag.is_empty ())
579 me->warning (_f ("flag `%s' not found", font_char));
581 SCM stroke_style_scm = me->get_property ("stroke-style");
582 if (scm_is_string (stroke_style_scm))
584 String stroke_style = ly_scm2string (stroke_style_scm);
585 if (!stroke_style.is_empty ())
587 String font_char = to_string (dir) + stroke_style;
588 Stencil stroke = fm->find_by_name ("flags." + font_char);
589 if (stroke.is_empty ())
590 me->warning (_f ("flag stroke `%s' not found", font_char));
592 flag.add_stencil (stroke);
599 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
601 Stem::width_callback (SCM e, SCM ax)
604 assert (scm_to_int (ax) == X_AXIS);
605 Grob *me = unsmob_grob (e);
609 if (is_invisible (me))
613 else if (unsmob_grob (me->get_object ("beam")) || 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 if (!get_beam (me) && abs (duration_log (me)) > 2)
707 Stencil fl = flag (me);
708 fl.translate_axis (stem_y[d] * half_space - d * blot / 2, Y_AXIS);
709 fl.translate_axis (stem_width / 2, X_AXIS);
710 mol.add_stencil (fl);
713 return mol.smobbed_copy ();
717 move the stem to right of the notehead if it is up.
719 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
721 Stem::offset_callback (SCM element_smob, SCM)
723 Grob *me = unsmob_grob (element_smob);
726 if (Grob *f = first_head (me))
728 Interval head_wid = f->extent (f, X_AXIS);
731 if (is_invisible (me))
734 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
736 Direction d = get_direction (me);
737 Real real_attach = head_wid.linear_combination (d * attach);
740 /* If not centered: correct for stem thickness. */
743 Real rule_thick = thickness (me);
744 r += -d * rule_thick * 0.5;
749 extract_grob_set (me, "rests", rests);
752 Grob *rest = rests.top ();
753 r = rest->extent (rest, X_AXIS).center ();
756 return scm_make_real (r);
760 Stem::get_beam (Grob *me)
762 SCM b = me->get_object ("beam");
763 return dynamic_cast<Spanner *> (unsmob_grob (b));
767 Stem::get_stem_info (Grob *me)
769 /* Return cached info if available */
770 SCM scm_info = me->get_property ("stem-info");
771 if (!scm_is_pair (scm_info))
774 scm_info = me->get_property ("stem-info");
778 si.dir_ = get_grob_direction (me);
779 si.ideal_y_ = scm_to_double (scm_car (scm_info));
780 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
784 /* TODO: add extra space for tremolos! */
786 Stem::calc_stem_info (Grob *me)
788 Direction my_dir = get_grob_direction (me);
792 programming_error ("no stem dir set");
796 Real staff_space = Staff_symbol_referencer::staff_space (me);
797 Grob *beam = get_beam (me);
798 Real beam_translation = Beam::get_beam_translation (beam);
799 Real beam_thickness = Beam::get_thickness (beam);
800 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
802 /* Simple standard stem length */
803 SCM lengths = me->get_property ("beamed-lengths");
805 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
808 /* stem only extends to center of beam
810 - 0.5 * beam_thickness;
812 /* Condition: sane minimum free stem length (chord to beams) */
813 lengths = me->get_property ("beamed-minimum-free-lengths");
814 Real ideal_minimum_free
815 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
819 It seems that also for ideal minimum length, we must use
820 the maximum beam count (for this direction):
822 \score{ \notes\relative c''{ [a8 a32] }}
824 must be horizontal. */
825 Real height_of_my_beams = beam_thickness
826 + (beam_count - 1) * beam_translation;
828 Real ideal_minimum_length = ideal_minimum_free
830 /* stem only extends to center of beam */
831 - 0.5 * beam_thickness;
833 ideal_length = max (ideal_length, ideal_minimum_length);
835 /* Convert to Y position, calculate for dir == UP */
837 = /* staff positions */
838 head_positions (me)[my_dir] * 0.5
839 * my_dir * staff_space;
840 Real ideal_y = note_start + ideal_length;
842 /* Conditions for Y position */
844 /* Lowest beam of (UP) beam must never be lower than second staffline
848 Although this (additional) rule is probably correct,
849 I expect that highest beam (UP) should also never be lower
850 than middle staffline, just as normal stems.
854 Obviously not for grace beams.
856 Also, not for knees. Seems to be a good thing. */
857 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
858 bool is_knee = to_boolean (beam->get_property ("knee"));
859 if (!no_extend_b && !is_knee)
861 /* Highest beam of (UP) beam must never be lower than middle
863 ideal_y = max (ideal_y, 0.0);
864 /* Lowest beam of (UP) beam must never be lower than second staffline */
865 ideal_y = max (ideal_y, (-staff_space
866 - beam_thickness + height_of_my_beams));
869 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
872 = scm_to_double (robust_list_ref
875 ("beamed-extreme-minimum-free-lengths")))
878 Real minimum_length = minimum_free
880 /* stem only extends to center of beam */
881 - 0.5 * beam_thickness;
883 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
885 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
886 y_ext.widen (0.5); // FIXME. Should be tunable?
887 minimum_length = max (minimum_length, y_ext.length ());
891 Real minimum_y = note_start + minimum_length;
892 Real shortest_y = minimum_y * my_dir;
894 me->set_property ("stem-info",
895 scm_list_2 (scm_make_real (ideal_y),
896 scm_make_real (shortest_y)));
900 Stem::beam_multiplicity (Grob *stem)
902 SCM beaming = stem->get_property ("beaming");
903 Slice le = int_list_to_slice (scm_car (beaming));
904 Slice ri = int_list_to_slice (scm_cdr (beaming));
909 /* FIXME: Too many properties */
910 ADD_INTERFACE (Stem, "stem-interface",
911 "The stem represent the graphical stem. "
912 "In addition, it internally connects note heads, beams and"
914 "Rests and whole notes have invisible stems.",
915 "tremolo-flag french-beaming "
916 "avoid-note-head thickness "
917 "stemlet-length rests "
918 "stem-info beamed-lengths beamed-minimum-free-lengths "
919 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
920 "duration-log beaming neutral-direction stem-end-position "
921 "note-heads direction length flag-style "
922 "no-stem-extend stroke-style");
924 /****************************************************************/
926 Stem_info::Stem_info ()
928 ideal_y_ = shortest_y_ = 0;
933 Stem_info::scale (Real x)