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.
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);
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_make_real (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_make_real (se));
130 /* Note head that determines hshift for upstems
131 WARNING: triggers direction */
133 Stem::support_head (Grob *me)
135 if (head_count (me) == 1)
137 return unsmob_grob (scm_car (me->get_property ("note-heads")));
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).
175 Stem::extremal_heads (Grob *me)
177 const int inf = 1000000;
178 Drul_array<int> extpos;
182 Drul_array<Grob *> exthead (0, 0);
183 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
186 Grob *n = unsmob_grob (scm_car (s));
187 int p = Staff_symbol_referencer::get_rounded_position (n);
192 if (d * p > d * extpos[d])
197 } 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 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "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 ());
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);
532 Stem::flag (Grob *me)
534 /* TODO: maybe property stroke-style should take different values,
535 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
539 SCM flag_style_scm = me->get_property ("flag-style");
540 if (scm_is_symbol (flag_style_scm))
541 flag_style = ly_symbol2string (flag_style_scm);
543 if (flag_style == "no-flag")
548 String staffline_offs;
549 if (String::compare (flag_style, "mensural") == 0)
550 /* Mensural notation: For notes on staff lines, use different
551 flags than for notes between staff lines. The idea is that
552 flags are always vertically aligned with the staff lines,
553 regardless if the note head is on a staff line or between two
554 staff lines. In other words, the inner end of a flag always
555 touches a staff line.
560 int p = (int)(rint (stem_end_position (me)));
562 Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
566 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 (duration_log (me));
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)
604 Axis a = (Axis) scm_to_int (ax);
605 assert (a == X_AXIS);
606 Grob *me = unsmob_grob (e);
610 if (is_invisible (me))
614 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
617 r *= thickness (me)/2;
621 r = flag (me).extent (X_AXIS)
624 return ly_interval2scm (r);
628 Stem::thickness (Grob *me)
630 return scm_to_double (me->get_property ("thickness"))
631 * Staff_symbol_referencer::line_thickness (me);
634 MAKE_SCHEME_CALLBACK (Stem, print, 1);
636 Stem::print (SCM smob)
638 Grob *me = unsmob_grob (smob);
640 Direction d = get_direction (me);
642 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
644 bool stemlet = stemlet_length > 0.0;
646 /* TODO: make the stem start a direction ?
647 This is required to avoid stems passing in tablature chords. */
649 to_boolean (me->get_property ("avoid-note-head"))
651 : lh = first_head (me);
652 Grob *beam = get_beam (me);
657 if (stemlet && !beam)
660 if (is_invisible (me))
663 Real y2 = stem_end_position (me);
665 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
669 y2 = Staff_symbol_referencer::get_position (lh);
672 Real beam_translation = Beam::get_beam_translation (beam);
673 Real beam_thickness = Beam::get_thickness (beam);
674 int beam_count = beam_multiplicity (me).length () + 1;
677 * (0.5 * beam_thickness
678 + beam_translation * (0 >? (beam_count - 1))
679 + stemlet_length) / half_space;
682 Interval stem_y (y1 <? y2,y2 >? y1);
684 if (Grob *hed = support_head (me))
687 must not take ledgers into account.
689 Interval head_height = hed->extent (hed,Y_AXIS);
690 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
692 y_attach = head_height.linear_combination (y_attach);
693 stem_y[Direction (-d)] += d * y_attach/half_space;
698 Real stem_width = thickness (me);
700 me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
702 Box b = Box (Interval (-stem_width/2, stem_width/2),
703 Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
705 Stencil ss = Lookup::round_filled_box (b, blot);
706 mol.add_stencil (ss);
708 if (!get_beam (me) && abs (duration_log (me)) > 2)
710 Stencil fl = flag (me);
711 fl.translate_axis (stem_y[d]*half_space - d * blot/2, Y_AXIS);
712 fl.translate_axis (stem_width/2, X_AXIS);
713 mol.add_stencil (fl);
716 return mol.smobbed_copy ();
720 move the stem to right of the notehead if it is up.
722 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
724 Stem::offset_callback (SCM element_smob, SCM)
726 Grob *me = unsmob_grob (element_smob);
729 if (Grob *f = first_head (me))
731 Interval head_wid = f->extent (f, X_AXIS);
734 if (is_invisible (me))
737 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
739 Direction d = get_direction (me);
740 Real real_attach = head_wid.linear_combination (d * attach);
743 /* If not centered: correct for stem thickness. */
746 Real rule_thick = thickness (me);
747 r += - d * rule_thick * 0.5;
752 SCM rests = me->get_property ("rests");
753 if (scm_is_pair (rests))
755 Grob * rest = unsmob_grob (scm_car (rests));
756 r = rest->extent (rest, X_AXIS).center ();
759 return scm_make_real (r);
763 Stem::get_beam (Grob *me)
765 SCM b = me->get_property ("beam");
766 return dynamic_cast<Spanner*> (unsmob_grob (b));
770 Stem::get_stem_info (Grob *me)
772 /* Return cached info if available */
773 SCM scm_info = me->get_property ("stem-info");
774 if (!scm_is_pair (scm_info))
777 scm_info = me->get_property ("stem-info");
781 si.dir_ = get_grob_direction (me);
782 si.ideal_y_ = scm_to_double (scm_car (scm_info));
783 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
788 /* TODO: add extra space for tremolos! */
790 Stem::calc_stem_info (Grob *me)
792 Direction my_dir = get_grob_direction (me);
796 programming_error ("No stem dir set?");
800 Real staff_space = Staff_symbol_referencer::staff_space (me);
801 Grob *beam = get_beam (me);
802 Real beam_translation = Beam::get_beam_translation (beam);
803 Real beam_thickness = Beam::get_thickness (beam);
804 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
807 /* Simple standard stem length */
808 SCM lengths = me->get_property ("beamed-lengths");
810 scm_to_double (robust_list_ref (beam_count - 1,lengths))
813 /* stem only extends to center of beam
815 - 0.5 * beam_thickness
818 /* Condition: sane minimum free stem length (chord to beams) */
819 lengths = me->get_property ("beamed-minimum-free-lengths");
820 Real ideal_minimum_free =
821 scm_to_double (robust_list_ref (beam_count - 1, lengths))
826 It seems that also for ideal minimum length, we must use
827 the maximum beam count (for this direction):
829 \score{ \notes\relative c''{ [a8 a32] }}
831 must be horizontal. */
832 Real height_of_my_beams = beam_thickness
833 + (beam_count - 1) * beam_translation;
835 Real ideal_minimum_length = ideal_minimum_free
837 /* stem only extends to center of beam */
838 - 0.5 * beam_thickness;
840 ideal_length = ideal_length >? ideal_minimum_length;
842 /* Convert to Y position, calculate for dir == UP */
844 /* staff positions */
845 head_positions (me)[my_dir] * 0.5
846 * my_dir * staff_space;
847 Real ideal_y = note_start + ideal_length;
850 /* Conditions for Y position */
852 /* Lowest beam of (UP) beam must never be lower than second staffline
856 Although this (additional) rule is probably correct,
857 I expect that highest beam (UP) should also never be lower
858 than middle staffline, just as normal stems.
862 Obviously not for grace beams.
864 Also, not for knees. Seems to be a good thing. */
865 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
866 bool is_knee = to_boolean (beam->get_property ("knee"));
867 if (!no_extend_b && !is_knee)
869 /* Highest beam of (UP) beam must never be lower than middle
871 ideal_y = ideal_y >? 0;
872 /* Lowest beam of (UP) beam must never be lower than second staffline */
873 ideal_y = ideal_y >? (-staff_space
874 - beam_thickness + height_of_my_beams);
878 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
881 scm_to_double (robust_list_ref
884 ("beamed-extreme-minimum-free-lengths")))
887 Real minimum_length = minimum_free
889 /* stem only extends to center of beam */
890 - 0.5 * beam_thickness;
893 Real minimum_y = note_start + minimum_length;
894 Real shortest_y = minimum_y * my_dir;
896 me->set_property ("stem-info",
897 scm_list_2 (scm_make_real (ideal_y),
898 scm_make_real (shortest_y)));
902 Stem::beam_multiplicity (Grob *stem)
904 SCM beaming = stem->get_property ("beaming");
905 Slice le = int_list_to_slice (scm_car (beaming));
906 Slice ri = int_list_to_slice (scm_cdr (beaming));
912 /* FIXME: Too many properties */
913 ADD_INTERFACE (Stem, "stem-interface",
914 "The stem represent the graphical stem. "
915 "In addition, it internally connects note heads, beams and"
917 "Rests and whole notes have invisible stems.",
918 "tremolo-flag french-beaming "
919 "avoid-note-head thickness "
920 "stemlet-length rests "
921 "stem-info beamed-lengths beamed-minimum-free-lengths "
922 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
923 "duration-log beaming neutral-direction stem-end-position "
924 "note-heads direction length flag-style "
925 "no-stem-extend stroke-style");
927 /****************************************************************/
929 Stem_info::Stem_info ()
931 ideal_y_ = shortest_y_ = 0;
936 Stem_info::scale (Real x)