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 "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 if (head_count (me) == 1)
136 return unsmob_grob (scm_car (me->get_property ("note-heads")));
137 return first_head (me);
141 Stem::head_count (Grob *me)
143 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
146 /* The note head which forms one end of the stem.
147 WARNING: triggers direction */
149 Stem::first_head (Grob *me)
151 Direction d = get_direction (me);
153 return extremal_heads (me)[-d];
157 /* The note head opposite to the first head. */
159 Stem::last_head (Grob *me)
161 Direction d = get_direction (me);
163 return extremal_heads (me)[d];
168 START is part where stem reaches `last' head.
170 This function returns a drul with (bottom-head, top-head).
173 Stem::extremal_heads (Grob *me)
175 const int inf = 1000000;
176 Drul_array<int> extpos;
180 Drul_array<Grob *> exthead (0, 0);
181 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
184 Grob *n = unsmob_grob (scm_car (s));
185 int p = Staff_symbol_referencer::get_rounded_position (n);
190 if (d * p > d * extpos[d])
196 while (flip (&d) != DOWN);
203 integer_compare (int const &a, int const &b)
208 /* The positions, in ascending order. */
210 Stem::note_head_positions (Grob *me)
213 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
216 Grob *n = unsmob_grob (scm_car (s));
217 int p = Staff_symbol_referencer::get_rounded_position (n);
222 ps.sort (integer_compare);
227 Stem::add_head (Grob *me, Grob *n)
229 n->set_property ("stem", me->self_scm ());
230 n->add_dependency (me);
232 if (Note_head::has_interface (n))
233 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
234 else if (Rest::has_interface (n))
235 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
239 Stem::is_invisible (Grob *me)
241 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
244 return !((head_count (me)
245 || stemlet_length > 0.0)
246 && scm_to_int (me->get_property ("duration-log")) >= 1);
250 Stem::get_default_dir (Grob *me)
252 int staff_center = 0;
253 Interval hp = head_positions (me);
257 int udistance = (int) (UP *hp[UP] - staff_center);
258 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
260 if (sign (ddistance - udistance))
261 return Direction (sign (ddistance - udistance));
263 return to_dir (me->get_property ("neutral-direction"));
267 Stem::get_default_stem_end_position (Grob *me)
269 Real ss = Staff_symbol_referencer::staff_space (me);
270 int durlog = duration_log (me);
274 /* WARNING: IN HALF SPACES */
276 SCM scm_len = me->get_property ("length");
277 if (scm_is_number (scm_len))
278 length = scm_to_double (scm_len);
281 s = me->get_property ("lengths");
283 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
287 'set-default-stemlen' sets direction too. */
288 Direction dir = get_direction (me);
291 dir = get_default_dir (me);
292 set_grob_direction (me, dir);
295 /* Stems in unnatural (forced) direction should be shortened,
296 according to [Roush & Gourlay] */
297 Interval hp = head_positions (me);
298 if (dir && dir * hp[dir] >= 0)
300 SCM sshorten = me->get_property ("stem-shorten");
301 SCM scm_shorten = scm_is_pair (sshorten) ?
302 robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
303 Real shorten = 2* robust_scm2double (scm_shorten, 0);
305 /* On boundary: shorten only half */
306 if (abs (head_positions (me)[dir]) <= 1)
313 Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
314 if (t_flag && !unsmob_grob (me->get_property ("beam")))
316 /* Crude hack: add extra space if tremolo flag is there.
318 We can't do this for the beam, since we get into a loop
319 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
322 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
327 Interval flag_ext = flag (me).extent (Y_AXIS);
328 if (!flag_ext.is_empty ())
329 minlen += 2 * flag_ext.length () / ss;
331 /* The clash is smaller for down stems (since the tremolo is
336 length = length >? (minlen + 1.0);
339 Real st = dir ? hp[dir] + dir * length : 0;
341 /* TODO: change name to extend-stems to staff/center/'() */
342 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
343 if (!no_extend_b && dir * st < 0)
346 /* Make a little room if we have a upflag and there is a dot.
347 previous approach was to lengthen the stem. This is not
348 good typesetting practice. */
349 if (!get_beam (me) && dir == UP
352 Grob *closest_to_flag = extremal_heads (me)[dir];
353 Grob *dots = closest_to_flag
354 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
358 Real dp = Staff_symbol_referencer::get_position (dots);
359 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
361 /* Very gory: add myself to the X-support of the parent,
362 which should be a dot-column. */
363 if (dir * (st + flagy - dp) < 0.5)
365 Grob *par = dots->get_parent (X_AXIS);
367 if (Dot_column::has_interface (par))
369 Side_position_interface::add_support (par, me);
371 /* TODO: apply some better logic here. The flag is
372 curved inwards, so this will typically be too
381 /* The log of the duration (Number of hooks on the flag minus two) */
383 Stem::duration_log (Grob *me)
385 SCM s = me->get_property ("duration-log");
386 return (scm_is_number (s)) ? scm_to_int (s) : 2;
390 Stem::position_noteheads (Grob *me)
392 if (!head_count (me))
395 Link_array<Grob> heads
396 = extract_grob_array (me, ly_symbol2scm ("note-heads"));
398 heads.sort (compare_position);
399 Direction dir = get_direction (me);
404 Real thick = thickness (me);
406 Grob *hed = support_head (me);
407 Real w = hed->extent (hed, X_AXIS)[dir];
408 for (int i = 0; i < heads.size (); i++)
409 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
413 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
414 for (int i = 1; i < heads.size (); i++)
416 Real p = Staff_symbol_referencer::get_position (heads[i]);
417 Real dy = fabs (lastpos- p);
420 dy should always be 0.5, 0.0, 1.0, but provide safety margin
427 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
429 Direction d = get_direction (me);
431 Reversed head should be shifted ell-thickness, but this
432 looks too crowded, so we only shift ell-0.5*thickness.
434 This leads to assymetry: Normal heads overlap the
435 stem 100% whereas reversed heads only overlaps the
439 Real reverse_overlap = 0.5;
440 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
443 if (is_invisible (me))
444 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
449 For some cases we should kern some more: when the
450 distance between the next or prev note is too large, we'd
451 get large white gaps, eg.
470 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
472 Stem::before_line_breaking (SCM smob)
474 Grob *me = unsmob_grob (smob);
477 Do the calculations for visible stems, but also for invisible stems
478 with note heads (i.e. half notes.)
482 stem_end_position (me); // ugh. Trigger direction calc.
483 position_noteheads (me);
486 return SCM_UNSPECIFIED;
491 When in a beam with tuplet brackets, brew_mol is called early,
492 caching a wrong value.
494 MAKE_SCHEME_CALLBACK (Stem, height, 2);
496 Stem::height (SCM smob, SCM ax)
498 Axis a = (Axis)scm_to_int (ax);
499 Grob *me = unsmob_grob (smob);
500 assert (a == Y_AXIS);
503 ugh. - this dependency should be automatic.
505 Grob *beam = get_beam (me);
508 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 /* 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";
564 staffline_offs = "2";
572 char dir = (get_direction (me) == UP) ? 'u' : 'd';
573 String font_char = flag_style
574 + to_string (dir) + staffline_offs + to_string (duration_log (me));
575 Font_metric *fm = Font_interface::get_default_font (me);
576 Stencil flag = fm->find_by_name ("flags." + font_char);
577 if (flag.is_empty ())
578 me->warning (_f ("flag `%s' not found", font_char));
580 SCM stroke_style_scm = me->get_property ("stroke-style");
581 if (scm_is_string (stroke_style_scm))
583 String stroke_style = ly_scm2string (stroke_style_scm);
584 if (!stroke_style.is_empty ())
586 String font_char = to_string (dir) + stroke_style;
587 Stencil stroke = fm->find_by_name ("flags." + font_char);
588 if (stroke.is_empty ())
589 me->warning (_f ("flag stroke `%s' not found", font_char));
591 flag.add_stencil (stroke);
598 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
600 Stem::width_callback (SCM e, SCM ax)
602 Axis a = (Axis) scm_to_int (ax);
603 assert (a == X_AXIS);
604 Grob *me = unsmob_grob (e);
608 if (is_invisible (me))
612 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
614 r = Interval (-1, 1);
615 r *= thickness (me) / 2;
619 r = flag (me).extent (X_AXIS)
620 + thickness (me) / 2;
622 return ly_interval2scm (r);
626 Stem::thickness (Grob *me)
628 return scm_to_double (me->get_property ("thickness"))
629 * Staff_symbol_referencer::line_thickness (me);
632 MAKE_SCHEME_CALLBACK (Stem, print, 1);
634 Stem::print (SCM smob)
636 Grob *me = unsmob_grob (smob);
638 Direction d = get_direction (me);
640 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
642 bool stemlet = stemlet_length > 0.0;
644 /* TODO: make the stem start a direction ?
645 This is required to avoid stems passing in tablature chords. */
647 = to_boolean (me->get_property ("avoid-note-head"))
650 Grob *beam = get_beam (me);
655 if (!lh && stemlet && !beam)
658 if (is_invisible (me))
661 Real y2 = stem_end_position (me);
663 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
666 y2 = Staff_symbol_referencer::get_position (lh);
669 Real beam_translation = Beam::get_beam_translation (beam);
670 Real beam_thickness = Beam::get_thickness (beam);
671 int beam_count = beam_multiplicity (me).length () + 1;
674 * (0.5 * beam_thickness
675 + beam_translation * (0 >? (beam_count - 1))
676 + stemlet_length) / half_space;
679 Interval stem_y (y1 <? y2, y2 >? y1);
681 if (Grob *hed = support_head (me))
684 must not take ledgers into account.
686 Interval head_height = hed->extent (hed, Y_AXIS);
687 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
689 y_attach = head_height.linear_combination (y_attach);
690 stem_y[Direction (-d)] += d * y_attach / half_space;
694 Real stem_width = thickness (me);
696 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
698 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
699 Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
701 Stencil ss = Lookup::round_filled_box (b, blot);
702 mol.add_stencil (ss);
704 if (!get_beam (me) && abs (duration_log (me)) > 2)
706 Stencil fl = flag (me);
707 fl.translate_axis (stem_y[d]*half_space - d * blot / 2, Y_AXIS);
708 fl.translate_axis (stem_width / 2, X_AXIS);
709 mol.add_stencil (fl);
712 return mol.smobbed_copy ();
716 move the stem to right of the notehead if it is up.
718 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
720 Stem::offset_callback (SCM element_smob, SCM)
722 Grob *me = unsmob_grob (element_smob);
725 if (Grob *f = first_head (me))
727 Interval head_wid = f->extent (f, X_AXIS);
730 if (is_invisible (me))
733 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
735 Direction d = get_direction (me);
736 Real real_attach = head_wid.linear_combination (d * attach);
739 /* If not centered: correct for stem thickness. */
742 Real rule_thick = thickness (me);
743 r += -d * rule_thick * 0.5;
748 SCM rests = me->get_property ("rests");
749 if (scm_is_pair (rests))
751 Grob *rest = unsmob_grob (scm_car (rests));
752 r = rest->extent (rest, X_AXIS).center ();
755 return scm_make_real (r);
759 Stem::get_beam (Grob *me)
761 SCM b = me->get_property ("beam");
762 return dynamic_cast<Spanner *> (unsmob_grob (b));
766 Stem::get_stem_info (Grob *me)
768 /* Return cached info if available */
769 SCM scm_info = me->get_property ("stem-info");
770 if (!scm_is_pair (scm_info))
773 scm_info = me->get_property ("stem-info");
777 si.dir_ = get_grob_direction (me);
778 si.ideal_y_ = scm_to_double (scm_car (scm_info));
779 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
783 /* TODO: add extra space for tremolos! */
785 Stem::calc_stem_info (Grob *me)
787 Direction my_dir = get_grob_direction (me);
791 programming_error ("No stem dir set?");
795 Real staff_space = Staff_symbol_referencer::staff_space (me);
796 Grob *beam = get_beam (me);
797 Real beam_translation = Beam::get_beam_translation (beam);
798 Real beam_thickness = Beam::get_thickness (beam);
799 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
801 /* Simple standard stem length */
802 SCM lengths = me->get_property ("beamed-lengths");
804 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
807 /* stem only extends to center of beam
809 - 0.5 * beam_thickness;
811 /* Condition: sane minimum free stem length (chord to beams) */
812 lengths = me->get_property ("beamed-minimum-free-lengths");
813 Real ideal_minimum_free
814 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
818 It seems that also for ideal minimum length, we must use
819 the maximum beam count (for this direction):
821 \score{ \notes\relative c''{ [a8 a32] }}
823 must be horizontal. */
824 Real height_of_my_beams = beam_thickness
825 + (beam_count - 1) * beam_translation;
827 Real ideal_minimum_length = ideal_minimum_free
829 /* stem only extends to center of beam */
830 - 0.5 * beam_thickness;
832 ideal_length = ideal_length >? ideal_minimum_length;
834 /* Convert to Y position, calculate for dir == UP */
836 = /* staff positions */
837 head_positions (me)[my_dir] * 0.5
838 * my_dir * staff_space;
839 Real ideal_y = note_start + ideal_length;
841 /* Conditions for Y position */
843 /* Lowest beam of (UP) beam must never be lower than second staffline
847 Although this (additional) rule is probably correct,
848 I expect that highest beam (UP) should also never be lower
849 than middle staffline, just as normal stems.
853 Obviously not for grace beams.
855 Also, not for knees. Seems to be a good thing. */
856 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
857 bool is_knee = to_boolean (beam->get_property ("knee"));
858 if (!no_extend_b && !is_knee)
860 /* Highest beam of (UP) beam must never be lower than middle
862 ideal_y = ideal_y >? 0;
863 /* Lowest beam of (UP) beam must never be lower than second staffline */
864 ideal_y = ideal_y >? (-staff_space
865 - beam_thickness + height_of_my_beams);
868 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
871 = scm_to_double (robust_list_ref
874 ("beamed-extreme-minimum-free-lengths")))
877 Real minimum_length = minimum_free
879 /* stem only extends to center of beam */
880 - 0.5 * beam_thickness;
883 Real minimum_y = note_start + minimum_length;
884 Real shortest_y = minimum_y * my_dir;
886 me->set_property ("stem-info",
887 scm_list_2 (scm_make_real (ideal_y),
888 scm_make_real (shortest_y)));
892 Stem::beam_multiplicity (Grob *stem)
894 SCM beaming = stem->get_property ("beaming");
895 Slice le = int_list_to_slice (scm_car (beaming));
896 Slice ri = int_list_to_slice (scm_cdr (beaming));
901 /* FIXME: Too many properties */
902 ADD_INTERFACE (Stem, "stem-interface",
903 "The stem represent the graphical stem. "
904 "In addition, it internally connects note heads, beams and"
906 "Rests and whole notes have invisible stems.",
907 "tremolo-flag french-beaming "
908 "avoid-note-head thickness "
909 "stemlet-length rests "
910 "stem-info beamed-lengths beamed-minimum-free-lengths "
911 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
912 "duration-log beaming neutral-direction stem-end-position "
913 "note-heads direction length flag-style "
914 "no-stem-extend stroke-style");
916 /****************************************************************/
918 Stem_info::Stem_info ()
920 ideal_y_ = shortest_y_ = 0;
925 Stem_info::scale (Real x)