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, "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);
519 iv[d] += d * Beam::get_thickness (b) * 0.5 ;
522 return ly_interval2scm (iv);
527 Stem::flag (Grob *me)
529 /* TODO: maybe property stroke-style should take different values,
530 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
534 SCM flag_style_scm = me->get_property ("flag-style");
535 if (scm_is_symbol (flag_style_scm))
536 flag_style = ly_symbol2string (flag_style_scm);
538 if (flag_style == "no-flag")
543 String staffline_offs;
544 if (String::compare (flag_style, "mensural") == 0)
545 /* Mensural notation: For notes on staff lines, use different
546 flags than for notes between staff lines. The idea is that
547 flags are always vertically aligned with the staff lines,
548 regardless if the note head is on a staff line or between two
549 staff lines. In other words, the inner end of a flag always
550 touches a staff line.
555 int p = (int)(rint (stem_end_position (me)));
557 Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
561 staffline_offs = "2";
569 char dir = (get_direction (me) == UP) ? 'u' : 'd';
570 String font_char = flag_style
571 + to_string (dir) + staffline_offs + to_string (duration_log (me));
572 Font_metric *fm = Font_interface::get_default_font (me);
573 Stencil flag = fm->find_by_name ("flags." + font_char);
574 if (flag.is_empty ())
575 me->warning (_f ("flag `%s' not found", font_char));
577 SCM stroke_style_scm = me->get_property ("stroke-style");
578 if (scm_is_string (stroke_style_scm))
580 String stroke_style = ly_scm2string (stroke_style_scm);
581 if (!stroke_style.is_empty ())
583 String font_char = to_string (dir) + stroke_style;
584 Stencil stroke = fm->find_by_name ("flags." + font_char);
585 if (stroke.is_empty ())
586 me->warning (_f ("flag stroke `%s' not found", font_char));
588 flag.add_stencil (stroke);
595 MAKE_SCHEME_CALLBACK (Stem,width_callback,2);
597 Stem::width_callback (SCM e, SCM ax)
599 Axis a = (Axis) scm_to_int (ax);
600 assert (a == X_AXIS);
601 Grob *me = unsmob_grob (e);
605 if (is_invisible (me))
609 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
612 r *= thickness (me)/2;
616 r = flag (me).extent (X_AXIS)
619 return ly_interval2scm (r);
623 Stem::thickness (Grob *me)
625 return scm_to_double (me->get_property ("thickness"))
626 * Staff_symbol_referencer::line_thickness (me);
629 MAKE_SCHEME_CALLBACK (Stem, print, 1);
631 Stem::print (SCM smob)
633 Grob *me = unsmob_grob (smob);
635 Direction d = get_direction (me);
637 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
639 bool stemlet = stemlet_length > 0.0;
641 /* TODO: make the stem start a direction ?
642 This is required to avoid stems passing in tablature chords. */
644 to_boolean (me->get_property ("avoid-note-head"))
646 : lh = first_head (me);
647 Grob *beam = get_beam (me);
652 if (stemlet && !beam)
655 if (is_invisible (me))
658 Real y2 = stem_end_position (me);
660 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
664 y2 = Staff_symbol_referencer::get_position (lh);
667 Real beam_translation = Beam::get_beam_translation (beam);
668 Real beam_thickness = Beam::get_thickness (beam);
669 int beam_count = beam_multiplicity (me).length () + 1;
672 * (0.5 * beam_thickness
673 + beam_translation * (0 >? (beam_count - 1))
674 + stemlet_length) / half_space;
677 Interval stem_y (y1 <? y2,y2 >? y1);
679 if (Grob *hed = support_head (me))
682 must not take ledgers into account.
684 Interval head_height = hed->extent (hed,Y_AXIS);
685 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
687 y_attach = head_height.linear_combination (y_attach);
688 stem_y[Direction (-d)] += d * y_attach/half_space;
693 Real stem_width = thickness (me);
695 me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
697 Box b = Box (Interval (-stem_width/2, stem_width/2),
698 Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
700 Stencil ss = Lookup::round_filled_box (b, blot);
701 mol.add_stencil (ss);
703 if (!get_beam (me) && abs (duration_log (me)) > 2)
705 Stencil fl = flag (me);
706 fl.translate_axis (stem_y[d]*half_space - d * blot/2, Y_AXIS);
707 fl.translate_axis (stem_width/2, X_AXIS);
708 mol.add_stencil (fl);
711 return mol.smobbed_copy ();
715 move the stem to right of the notehead if it is up.
717 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
719 Stem::offset_callback (SCM element_smob, SCM)
721 Grob *me = unsmob_grob (element_smob);
724 if (Grob *f = first_head (me))
726 Interval head_wid = f->extent (f, X_AXIS);
729 if (is_invisible (me))
732 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
734 Direction d = get_direction (me);
735 Real real_attach = head_wid.linear_combination (d * attach);
738 /* If not centered: correct for stem thickness. */
741 Real rule_thick = thickness (me);
742 r += - d * rule_thick * 0.5;
747 SCM rests = me->get_property ("rests");
748 if (scm_is_pair (rests))
750 Grob * rest = unsmob_grob (scm_car (rests));
751 r = rest->extent (rest, X_AXIS).center ();
754 return scm_make_real (r);
758 Stem::get_beam (Grob *me)
760 SCM b = me->get_property ("beam");
761 return dynamic_cast<Spanner*> (unsmob_grob (b));
765 Stem::get_stem_info (Grob *me)
767 /* Return cached info if available */
768 SCM scm_info = me->get_property ("stem-info");
769 if (!scm_is_pair (scm_info))
772 scm_info = me->get_property ("stem-info");
776 si.dir_ = get_grob_direction (me);
777 si.ideal_y_ = scm_to_double (scm_car (scm_info));
778 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);
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
813 /* Condition: sane minimum free stem length (chord to beams) */
814 lengths = me->get_property ("beamed-minimum-free-lengths");
815 Real ideal_minimum_free =
816 scm_to_double (robust_list_ref (beam_count - 1, lengths))
821 It seems that also for ideal minimum length, we must use
822 the maximum beam count (for this direction):
824 \score{ \notes\relative c''{ [a8 a32] }}
826 must be horizontal. */
827 Real height_of_my_beams = beam_thickness
828 + (beam_count - 1) * beam_translation;
830 Real ideal_minimum_length = ideal_minimum_free
832 /* stem only extends to center of beam */
833 - 0.5 * beam_thickness;
835 ideal_length = ideal_length >? ideal_minimum_length;
837 /* Convert to Y position, calculate for dir == UP */
839 /* staff positions */
840 head_positions (me)[my_dir] * 0.5
841 * my_dir * staff_space;
842 Real ideal_y = note_start + ideal_length;
845 /* Conditions for Y position */
847 /* Lowest beam of (UP) beam must never be lower than second staffline
851 Although this (additional) rule is probably correct,
852 I expect that highest beam (UP) should also never be lower
853 than middle staffline, just as normal stems.
857 Obviously not for grace beams.
859 Also, not for knees. Seems to be a good thing. */
860 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
861 bool is_knee = to_boolean (beam->get_property ("knee"));
862 if (!no_extend_b && !is_knee)
864 /* Highest beam of (UP) beam must never be lower than middle
866 ideal_y = ideal_y >? 0;
867 /* Lowest beam of (UP) beam must never be lower than second staffline */
868 ideal_y = ideal_y >? (-staff_space
869 - beam_thickness + height_of_my_beams);
873 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
876 scm_to_double (robust_list_ref
879 ("beamed-extreme-minimum-free-lengths")))
882 Real minimum_length = minimum_free
884 /* stem only extends to center of beam */
885 - 0.5 * beam_thickness;
888 Real minimum_y = note_start + minimum_length;
889 Real shortest_y = minimum_y * my_dir;
891 me->set_property ("stem-info",
892 scm_list_2 (scm_make_real (ideal_y),
893 scm_make_real (shortest_y)));
897 Stem::beam_multiplicity (Grob *stem)
899 SCM beaming = stem->get_property ("beaming");
900 Slice le = int_list_to_slice (scm_car (beaming));
901 Slice ri = int_list_to_slice (scm_cdr (beaming));
907 /* FIXME: Too many properties */
908 ADD_INTERFACE (Stem, "stem-interface",
909 "The stem represent the graphical stem. "
910 "In addition, it internally connects note heads, beams and"
912 "Rests and whole notes have invisible stems.",
913 "tremolo-flag french-beaming "
914 "avoid-note-head thickness "
915 "stemlet-length rests "
916 "stem-info beamed-lengths beamed-minimum-free-lengths "
917 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
918 "duration-log beaming neutral-direction stem-end-position "
919 "note-heads direction length flag-style "
920 "no-stem-extend stroke-style");
922 /****************************************************************/
924 Stem_info::Stem_info ()
926 ideal_y_ = shortest_y_ = 0;
931 Stem_info::scale (Real x)