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_from_int (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_from_double (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_from_double (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);
508 Beam::after_line_breaking (beam->self_scm ());
510 SCM mol = me->get_uncached_stencil ();
513 iv = unsmob_stencil (mol)->extent (a);
514 if (Grob *b = get_beam (me))
516 Direction d = get_direction (me);
519 programming_error ("no stem direction");
522 iv[d] += d * Beam::get_thickness (b) * 0.5;
525 return ly_interval2scm (iv);
529 Stem::flag (Grob *me)
531 /* TODO: maybe property stroke-style should take different values,
532 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
536 SCM flag_style_scm = me->get_property ("flag-style");
537 if (scm_is_symbol (flag_style_scm))
538 flag_style = ly_symbol2string (flag_style_scm);
540 if (flag_style == "no-flag")
545 String staffline_offs;
546 if (String::compare (flag_style, "mensural") == 0)
547 /* Mensural notation: For notes on staff lines, use different
548 flags than for notes between staff lines. The idea is that
549 flags are always vertically aligned with the staff lines,
550 regardless if the note head is on a staff line or between two
551 staff lines. In other words, the inner end of a flag always
552 touches a staff line.
557 int p = (int) (rint (stem_end_position (me)));
559 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
562 staffline_offs = "2";
567 char dir = (get_direction (me) == UP) ? 'u' : 'd';
568 String font_char = flag_style
569 + to_string (dir) + staffline_offs + to_string (duration_log (me));
570 Font_metric *fm = Font_interface::get_default_font (me);
571 Stencil flag = fm->find_by_name ("flags." + font_char);
572 if (flag.is_empty ())
573 me->warning (_f ("flag `%s' not found", font_char));
575 SCM stroke_style_scm = me->get_property ("stroke-style");
576 if (scm_is_string (stroke_style_scm))
578 String stroke_style = ly_scm2string (stroke_style_scm);
579 if (!stroke_style.is_empty ())
581 String font_char = to_string (dir) + stroke_style;
582 Stencil stroke = fm->find_by_name ("flags." + font_char);
583 if (stroke.is_empty ())
584 me->warning (_f ("flag stroke `%s' not found", font_char));
586 flag.add_stencil (stroke);
593 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
595 Stem::width_callback (SCM e, SCM ax)
598 assert (scm_to_int (ax) == X_AXIS);
599 Grob *me = unsmob_grob (e);
603 if (is_invisible (me))
605 else if (unsmob_grob (me->get_object ("beam")) || abs (duration_log (me)) <= 2)
607 r = Interval (-1, 1);
608 r *= thickness (me) / 2;
612 r = Interval (-1, 1) * thickness (me) * 0.5;
613 r.unite (flag (me).extent (X_AXIS));
615 return ly_interval2scm (r);
619 Stem::thickness (Grob *me)
621 return scm_to_double (me->get_property ("thickness"))
622 * Staff_symbol_referencer::line_thickness (me);
625 MAKE_SCHEME_CALLBACK (Stem, print, 1);
627 Stem::print (SCM smob)
629 Grob *me = unsmob_grob (smob);
631 Direction d = get_direction (me);
633 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
635 bool stemlet = stemlet_length > 0.0;
637 /* TODO: make the stem start a direction ?
638 This is required to avoid stems passing in tablature chords. */
640 = to_boolean (me->get_property ("avoid-note-head"))
643 Grob *beam = get_beam (me);
648 if (!lh && stemlet && !beam)
651 if (is_invisible (me))
654 Real y2 = stem_end_position (me);
656 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
659 y2 = Staff_symbol_referencer::get_position (lh);
662 Real beam_translation = Beam::get_beam_translation (beam);
663 Real beam_thickness = Beam::get_thickness (beam);
664 int beam_count = beam_multiplicity (me).length () + 1;
667 * (0.5 * beam_thickness
668 + beam_translation * max (0, (beam_count - 1))
669 + stemlet_length) / half_space;
672 Interval stem_y (min (y1, y2), max (y2, y1));
674 if (Grob *hed = support_head (me))
677 must not take ledgers into account.
679 Interval head_height = hed->extent (hed, Y_AXIS);
680 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
682 y_attach = head_height.linear_combination (y_attach);
683 stem_y[Direction (-d)] += d * y_attach / half_space;
687 Real stem_width = thickness (me);
689 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
691 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
692 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
694 Stencil ss = Lookup::round_filled_box (b, blot);
695 mol.add_stencil (ss);
697 if (!get_beam (me) && abs (duration_log (me)) > 2)
699 Stencil fl = flag (me);
700 fl.translate_axis (stem_y[d] * half_space - d * blot / 2, Y_AXIS);
701 fl.translate_axis (stem_width / 2, X_AXIS);
702 mol.add_stencil (fl);
705 return mol.smobbed_copy ();
709 move the stem to right of the notehead if it is up.
711 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
713 Stem::offset_callback (SCM element_smob, SCM)
715 Grob *me = unsmob_grob (element_smob);
718 if (Grob *f = first_head (me))
720 Interval head_wid = f->extent (f, X_AXIS);
723 if (is_invisible (me))
726 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
728 Direction d = get_direction (me);
729 Real real_attach = head_wid.linear_combination (d * attach);
732 /* If not centered: correct for stem thickness. */
735 Real rule_thick = thickness (me);
736 r += -d * rule_thick * 0.5;
741 extract_grob_set (me, "rests", rests);
744 Grob *rest = rests.top ();
745 r = rest->extent (rest, X_AXIS).center ();
748 return scm_from_double (r);
752 Stem::get_beam (Grob *me)
754 SCM b = me->get_object ("beam");
755 return dynamic_cast<Spanner *> (unsmob_grob (b));
759 Stem::get_stem_info (Grob *me)
761 /* Return cached info if available */
762 SCM scm_info = me->get_property ("stem-info");
763 if (!scm_is_pair (scm_info))
766 scm_info = me->get_property ("stem-info");
770 si.dir_ = get_grob_direction (me);
771 si.ideal_y_ = scm_to_double (scm_car (scm_info));
772 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
776 /* TODO: add extra space for tremolos! */
778 Stem::calc_stem_info (Grob *me)
780 Direction my_dir = get_grob_direction (me);
784 programming_error ("no stem dir set");
788 Real staff_space = Staff_symbol_referencer::staff_space (me);
789 Grob *beam = get_beam (me);
790 Real beam_translation = Beam::get_beam_translation (beam);
791 Real beam_thickness = Beam::get_thickness (beam);
792 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
794 /* Simple standard stem length */
795 SCM lengths = me->get_property ("beamed-lengths");
797 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
800 /* stem only extends to center of beam
802 - 0.5 * beam_thickness;
804 /* Condition: sane minimum free stem length (chord to beams) */
805 lengths = me->get_property ("beamed-minimum-free-lengths");
806 Real ideal_minimum_free
807 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
811 It seems that also for ideal minimum length, we must use
812 the maximum beam count (for this direction):
814 \score{ \notes\relative c''{ [a8 a32] }}
816 must be horizontal. */
817 Real height_of_my_beams = beam_thickness
818 + (beam_count - 1) * beam_translation;
820 Real ideal_minimum_length = ideal_minimum_free
822 /* stem only extends to center of beam */
823 - 0.5 * beam_thickness;
825 ideal_length = max (ideal_length, ideal_minimum_length);
827 /* Convert to Y position, calculate for dir == UP */
829 = /* staff positions */
830 head_positions (me)[my_dir] * 0.5
831 * my_dir * staff_space;
832 Real ideal_y = note_start + ideal_length;
834 /* Conditions for Y position */
836 /* Lowest beam of (UP) beam must never be lower than second staffline
840 Although this (additional) rule is probably correct,
841 I expect that highest beam (UP) should also never be lower
842 than middle staffline, just as normal stems.
846 Obviously not for grace beams.
848 Also, not for knees. Seems to be a good thing. */
849 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
850 bool is_knee = to_boolean (beam->get_property ("knee"));
851 if (!no_extend_b && !is_knee)
853 /* Highest beam of (UP) beam must never be lower than middle
855 ideal_y = max (ideal_y, 0.0);
856 /* Lowest beam of (UP) beam must never be lower than second staffline */
857 ideal_y = max (ideal_y, (-staff_space
858 - beam_thickness + height_of_my_beams));
861 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
864 = scm_to_double (robust_list_ref
867 ("beamed-extreme-minimum-free-lengths")))
870 Real minimum_length = minimum_free
872 /* stem only extends to center of beam */
873 - 0.5 * beam_thickness;
875 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
877 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
878 y_ext.widen (0.5); // FIXME. Should be tunable?
879 minimum_length = max (minimum_length, y_ext.length ());
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_from_double (ideal_y),
888 scm_from_double (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)