2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2004 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.
16 #include <math.h> // rint
19 #include "directional-element-interface.hh"
20 #include "note-head.hh"
23 #include "output-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
27 #include "paper-column.hh"
31 #include "group-interface.hh"
32 #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_int2num (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);
68 Stem::head_positions (Grob *me)
72 Drul_array<Grob*> e (extremal_heads (me));
73 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
74 Staff_symbol_referencer::get_position (e[UP]));
80 Stem::chord_start_y (Grob *me)
82 Interval hp = head_positions (me);
84 return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
90 Stem::stem_end_position (Grob *me)
92 SCM p = me->get_property ("stem-end-position");
94 if (!scm_is_number (p))
96 pos = get_default_stem_end_position (me);
97 me->set_property ("stem-end-position", scm_make_real (pos));
100 pos = scm_to_double (p);
106 Stem::get_direction (Grob *me)
108 Direction d = get_grob_direction (me);
112 d = get_default_dir (me);
114 set_grob_direction (me, d);
120 Stem::set_stemend (Grob *me, Real se)
123 Direction d = get_direction (me);
125 if (d && d * head_positions (me)[get_direction (me)] >= se*d)
126 me->warning (_ ("Weird stem size; check for narrow beams"));
128 me->set_property ("stem-end-position", scm_make_real (se));
131 /* Note head that determines hshift for upstems
132 WARNING: triggers direction */
134 Stem::support_head (Grob *me)
136 if (head_count (me) == 1)
138 return unsmob_grob (scm_car (me->get_property ("note-heads")));
139 return first_head (me);
143 Stem::head_count (Grob *me)
145 return Pointer_group_interface::count (me, "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];
169 /* START is part where stem reaches `last' head.
171 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;
183 exthead[LEFT] = exthead[RIGHT] =0;
185 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
188 Grob *n = unsmob_grob (scm_car (s));
189 int p = Staff_symbol_referencer::get_rounded_position (n);
194 if (d * p > d * extpos[d])
199 } while (flip (&d) != DOWN);
205 icmp (int const &a, int const &b)
210 /* The positions, in ascending order. */
212 Stem::note_head_positions (Grob *me)
215 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
218 Grob *n = unsmob_grob (scm_car (s));
219 int p = Staff_symbol_referencer::get_rounded_position (n);
229 Stem::add_head (Grob *me, Grob *n)
231 n->set_property ("stem", me->self_scm ());
232 n->add_dependency (me);
234 /* TODO: why not store Rest pointers? */
235 if (Note_head::has_interface (n))
236 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
240 Stem::is_invisible (Grob *me)
242 return !(head_count (me)
243 && scm_to_int (me->get_property ("duration-log")) >= 1);
247 Stem::get_default_dir (Grob *me)
249 int staff_center = 0;
250 Interval hp = head_positions (me);
254 int udistance = (int) (UP * hp[UP] - staff_center);
255 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
257 if (sign (ddistance - udistance))
258 return Direction (sign (ddistance - udistance));
260 return to_dir (me->get_property ("neutral-direction"));
264 Stem::get_default_stem_end_position (Grob *me)
266 Real ss = Staff_symbol_referencer::staff_space (me);
267 int durlog = duration_log (me);
271 /* WARNING: IN HALF SPACES */
273 SCM scm_len = me->get_property ("length");
274 if (scm_is_number (scm_len))
275 length = scm_to_double (scm_len);
278 s = me->get_property ("lengths");
280 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
284 'set-default-stemlen' sets direction too. */
285 Direction dir = get_direction (me);
288 dir = get_default_dir (me);
289 set_grob_direction (me, dir);
292 /* Stems in unnatural (forced) direction should be shortened,
293 according to [Roush & Gourlay] */
294 Interval hp = head_positions (me);
295 if (dir && dir * hp[dir] >= 0)
297 SCM sshorten = me->get_property ("stem-shorten");
298 SCM scm_shorten = scm_is_pair (sshorten) ?
299 robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
300 Real shorten = 2* robust_scm2double (scm_shorten,0);
302 /* On boundary: shorten only half */
303 if (abs (head_positions (me)[dir]) <= 1)
310 Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
311 if (t_flag && !unsmob_grob (me->get_property ("beam")))
313 /* Crude hack: add extra space if tremolo flag is there.
315 We can't do this for the beam, since we get into a loop
316 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
319 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
324 Interval flag_ext = flag (me).extent (Y_AXIS);
325 if (!flag_ext.is_empty ())
326 minlen += 2 * flag_ext.length () / ss;
328 /* The clash is smaller for down stems (since the tremolo is
333 length = length >? (minlen + 1.0);
336 Real st = dir ? hp[dir] + dir * length : 0;
338 /* TODO: change name to extend-stems to staff/center/'() */
339 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
340 if (!no_extend_b && dir * st < 0)
343 /* Make a little room if we have a upflag and there is a dot.
344 previous approach was to lengthen the stem. This is not
345 good typesetting practice. */
346 if (!get_beam (me) && dir == UP
349 Grob * closest_to_flag = extremal_heads (me)[dir];
350 Grob * dots = closest_to_flag
351 ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
355 Real dp = Staff_symbol_referencer::get_position (dots);
356 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
358 /* Very gory: add myself to the X-support of the parent,
359 which should be a dot-column. */
360 if (dir * (st + flagy - dp) < 0.5)
362 Grob *par = dots->get_parent (X_AXIS);
364 if (Dot_column::has_interface (par))
366 Side_position_interface::add_support (par, me);
368 /* TODO: apply some better logic here. The flag is
369 curved inwards, so this will typically be too
378 /* The log of the duration (Number of hooks on the flag minus two) */
380 Stem::duration_log (Grob *me)
382 SCM s = me->get_property ("duration-log");
383 return (scm_is_number (s)) ? scm_to_int (s) : 2;
387 Stem::position_noteheads (Grob *me)
389 if (!head_count (me))
392 Link_array<Grob> heads =
393 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-heads");
395 heads.sort (compare_position);
396 Direction dir = get_direction (me);
401 Real thick = thickness (me);
403 Grob *hed = support_head (me);
404 Real w = hed->extent (hed, X_AXIS)[dir];
405 for (int i = 0; i < heads.size (); i++)
406 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
410 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
411 for (int i = 1; i < heads.size (); i ++)
413 Real p = Staff_symbol_referencer::get_position (heads[i]);
414 Real dy =fabs (lastpos- p);
417 dy should always be 0.5, 0.0, 1.0, but provide safety margin
424 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
426 Direction d = get_direction (me);
428 Reversed head should be shifted ell-thickness, but this
429 looks too crowded, so we only shift ell-0.5*thickness.
431 This leads to assymetry: Normal heads overlap the
432 stem 100% whereas reversed heads only overlaps the
436 Real reverse_overlap = 0.5;
437 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
440 if (is_invisible (me))
441 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
446 For some cases we should kern some more: when the
447 distance between the next or prev note is too large, we'd
448 get large white gaps, eg.
467 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
469 Stem::before_line_breaking (SCM smob)
471 Grob *me = unsmob_grob (smob);
474 Do the calculations for visible stems, but also for invisible stems
475 with note heads (i.e. half notes.)
479 stem_end_position (me); // ugh. Trigger direction calc.
480 position_noteheads (me);
483 me->set_property ("print-function", SCM_EOL);
485 return SCM_UNSPECIFIED;
490 When in a beam with tuplet brackets, brew_mol is called early,
491 caching a wrong value.
493 MAKE_SCHEME_CALLBACK (Stem, height, 2);
495 Stem::height (SCM smob, SCM ax)
497 Axis a = (Axis)scm_to_int (ax);
498 Grob *me = unsmob_grob (smob);
499 assert (a == Y_AXIS);
502 ugh. - this dependency should be automatic.
504 Grob *beam= get_beam (me);
507 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);
518 iv[d] += d * Beam::get_thickness (b) * 0.5 ;
521 return ly_interval2scm (iv);
526 Stem::flag (Grob *me)
528 /* TODO: maybe property stroke-style should take different values,
529 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
533 SCM flag_style_scm = me->get_property ("flag-style");
534 if (scm_is_symbol (flag_style_scm))
535 flag_style = ly_symbol2string (flag_style_scm);
537 if (flag_style == "no-flag")
542 String staffline_offs;
543 if (String::compare (flag_style, "mensural") == 0)
544 /* Mensural notation: For notes on staff lines, use different
545 flags than for notes between staff lines. The idea is that
546 flags are always vertically aligned with the staff lines,
547 regardless if the note head is on a staff line or between two
548 staff lines. In other words, the inner end of a flag always
549 touches a staff line.
554 int p = (int)(rint (stem_end_position (me)));
556 Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
560 staffline_offs = "2";
568 char dir = (get_direction (me) == UP) ? 'u' : 'd';
569 String font_char = flag_style
570 + to_string (dir) + staffline_offs + to_string (duration_log (me));
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,dim_callback,2);
596 Stem::dim_callback (SCM e, SCM ax)
598 Axis a = (Axis) scm_to_int (ax);
599 assert (a == X_AXIS);
600 Grob *me = unsmob_grob (e);
604 if (is_invisible (me))
608 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
611 r *= thickness (me)/2;
615 r = flag (me).extent (X_AXIS)
618 return ly_interval2scm (r);
622 Stem::thickness (Grob *me)
624 return scm_to_double (me->get_property ("thickness"))
625 * Staff_symbol_referencer::line_thickness (me);
628 MAKE_SCHEME_CALLBACK (Stem, print, 1);
630 Stem::print (SCM smob)
632 Grob *me = unsmob_grob (smob);
634 Direction d = get_direction (me);
636 /* TODO: make the stem start a direction ?
637 This is required to avoid stems passing in tablature chords. */
638 Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
639 ? last_head (me) : lh = first_head (me);
644 if (is_invisible (me))
647 Real y1 = Staff_symbol_referencer::get_position (lh);
648 Real y2 = stem_end_position (me);
650 Interval stem_y (y1 <? y2,y2 >? y1);
653 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
655 if (Grob *hed = support_head (me))
658 must not take ledgers into account.
660 Interval head_height = hed->extent (hed,Y_AXIS);
661 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
663 y_attach = head_height.linear_combination (y_attach);
664 stem_y[Direction (-d)] += d * y_attach/dy;
669 Real stem_width = thickness (me);
671 me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
673 Box b = Box (Interval (-stem_width/2, stem_width/2),
674 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
676 Stencil ss = Lookup::round_filled_box (b, blot);
677 mol.add_stencil (ss);
679 if (!get_beam (me) && abs (duration_log (me)) > 2)
681 Stencil fl = flag (me);
682 fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
683 fl.translate_axis (stem_width/2, X_AXIS);
684 mol.add_stencil (fl);
687 return mol.smobbed_copy ();
691 move the stem to right of the notehead if it is up.
693 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
695 Stem::off_callback (SCM element_smob, SCM)
697 Grob *me = unsmob_grob (element_smob);
701 if (Grob *f = first_head (me))
703 Interval head_wid = f->extent (f, X_AXIS);
706 if (is_invisible (me))
709 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
711 Direction d = get_direction (me);
712 Real real_attach = head_wid.linear_combination (d * attach);
715 /* If not centered: correct for stem thickness. */
718 Real rule_thick = thickness (me);
719 r += - d * rule_thick * 0.5;
722 return scm_make_real (r);
726 Stem::get_beam (Grob *me)
728 SCM b = me->get_property ("beam");
729 return dynamic_cast<Spanner*> (unsmob_grob (b));
733 Stem::get_stem_info (Grob *me)
735 /* Return cached info if available */
736 SCM scm_info = me->get_property ("stem-info");
737 if (!scm_is_pair (scm_info))
740 scm_info = me->get_property ("stem-info");
744 si.dir_ = get_grob_direction (me);
745 si.ideal_y_ = scm_to_double (scm_car (scm_info));
746 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
751 /* TODO: add extra space for tremolos! */
753 Stem::calc_stem_info (Grob *me)
755 Direction my_dir = get_grob_direction (me);
759 programming_error ("No stem dir set?");
763 Real staff_space = Staff_symbol_referencer::staff_space (me);
764 Grob *beam = get_beam (me);
765 Real beam_translation = Beam::get_beam_translation (beam);
766 Real beam_thickness = Beam::get_thickness (beam);
767 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
770 /* Simple standard stem length */
771 SCM lengths = me->get_property ("beamed-lengths");
773 scm_to_double (robust_list_ref (beam_count - 1,lengths))
776 /* stem only extends to center of beam
778 - 0.5 * beam_thickness
781 /* Condition: sane minimum free stem length (chord to beams) */
782 lengths = me->get_property ("beamed-minimum-free-lengths");
783 Real ideal_minimum_free =
784 scm_to_double (robust_list_ref (beam_count - 1, lengths))
789 It seems that also for ideal minimum length, we must use
790 the maximum beam count (for this direction):
792 \score{ \notes\relative c''{ [a8 a32] }}
794 must be horizontal. */
795 Real height_of_my_beams = beam_thickness
796 + (beam_count - 1) * beam_translation;
798 Real ideal_minimum_length = ideal_minimum_free
800 /* stem only extends to center of beam */
801 - 0.5 * beam_thickness;
803 ideal_length = ideal_length >? ideal_minimum_length;
805 /* Convert to Y position, calculate for dir == UP */
807 /* staff positions */
808 head_positions (me)[my_dir] * 0.5
809 * my_dir * staff_space;
810 Real ideal_y = note_start + ideal_length;
813 /* Conditions for Y position */
815 /* Lowest beam of (UP) beam must never be lower than second staffline
819 Although this (additional) rule is probably correct,
820 I expect that highest beam (UP) should also never be lower
821 than middle staffline, just as normal stems.
825 Obviously not for grace beams.
827 Also, not for knees. Seems to be a good thing. */
828 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
829 bool is_knee = to_boolean (beam->get_property ("knee"));
830 if (!no_extend_b && !is_knee)
832 /* Highest beam of (UP) beam must never be lower than middle
834 ideal_y = ideal_y >? 0;
835 /* Lowest beam of (UP) beam must never be lower than second staffline */
836 ideal_y = ideal_y >? (-staff_space
837 - beam_thickness + height_of_my_beams);
841 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
844 scm_to_double (robust_list_ref
847 ("beamed-extreme-minimum-free-lengths")))
850 Real minimum_length = minimum_free
852 /* stem only extends to center of beam */
853 - 0.5 * beam_thickness;
856 Real minimum_y = note_start + minimum_length;
857 Real shortest_y = minimum_y * my_dir;
859 me->set_property ("stem-info",
860 scm_list_2 (scm_make_real (ideal_y),
861 scm_make_real (shortest_y)));
865 Stem::beam_multiplicity (Grob *stem)
867 SCM beaming= stem->get_property ("beaming");
868 Slice le = int_list_to_slice (scm_car (beaming));
869 Slice ri = int_list_to_slice (scm_cdr (beaming));
875 /* FIXME: Too many properties */
876 ADD_INTERFACE (Stem, "stem-interface",
877 "The stem represent the graphical stem. "
878 "In addition, it internally connects note heads, beams and"
880 "Rests and whole notes have invisible stems.",
881 "tremolo-flag french-beaming "
882 "avoid-note-head thickness "
883 "stem-info beamed-lengths beamed-minimum-free-lengths "
884 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
885 "duration-log beaming neutral-direction stem-end-position "
886 "note-heads direction length flag-style "
887 "no-stem-extend stroke-style");
889 /****************************************************************/
891 Stem_info::Stem_info ()
893 ideal_y_ = shortest_y_ = 0;
898 Stem_info::scale (Real x)