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);
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 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);
531 Stem::flag (Grob *me)
533 /* TODO: maybe property stroke-style should take different values,
534 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
538 SCM flag_style_scm = me->get_property ("flag-style");
539 if (scm_is_symbol (flag_style_scm))
540 flag_style = ly_symbol2string (flag_style_scm);
542 if (flag_style == "no-flag")
547 String staffline_offs;
548 if (String::compare (flag_style, "mensural") == 0)
549 /* Mensural notation: For notes on staff lines, use different
550 flags than for notes between staff lines. The idea is that
551 flags are always vertically aligned with the staff lines,
552 regardless if the note head is on a staff line or between two
553 staff lines. In other words, the inner end of a flag always
554 touches a staff line.
559 int p = (int)(rint (stem_end_position (me)));
561 Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
565 staffline_offs = "2";
573 char dir = (get_direction (me) == UP) ? 'u' : 'd';
574 String font_char = flag_style
575 + to_string (dir) + staffline_offs + to_string (duration_log (me));
576 Font_metric *fm = Font_interface::get_default_font (me);
577 Stencil flag = fm->find_by_name ("flags." + font_char);
578 if (flag.is_empty ())
579 me->warning (_f ("flag `%s' not found", font_char));
581 SCM stroke_style_scm = me->get_property ("stroke-style");
582 if (scm_is_string (stroke_style_scm))
584 String stroke_style = ly_scm2string (stroke_style_scm);
585 if (!stroke_style.is_empty ())
587 String font_char = to_string (dir) + stroke_style;
588 Stencil stroke = fm->find_by_name ("flags." + font_char);
589 if (stroke.is_empty ())
590 me->warning (_f ("flag stroke `%s' not found", font_char));
592 flag.add_stencil (stroke);
599 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
601 Stem::width_callback (SCM e, SCM ax)
603 Axis a = (Axis) scm_to_int (ax);
604 assert (a == X_AXIS);
605 Grob *me = unsmob_grob (e);
609 if (is_invisible (me))
613 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
615 r = Interval (-1, 1);
616 r *= thickness (me)/2;
620 r = flag (me).extent (X_AXIS)
623 return ly_interval2scm (r);
627 Stem::thickness (Grob *me)
629 return scm_to_double (me->get_property ("thickness"))
630 * Staff_symbol_referencer::line_thickness (me);
633 MAKE_SCHEME_CALLBACK (Stem, print, 1);
635 Stem::print (SCM smob)
637 Grob *me = unsmob_grob (smob);
639 Direction d = get_direction (me);
641 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
643 bool stemlet = stemlet_length > 0.0;
645 /* TODO: make the stem start a direction ?
646 This is required to avoid stems passing in tablature chords. */
648 to_boolean (me->get_property ("avoid-note-head"))
650 : lh = first_head (me);
651 Grob *beam = get_beam (me);
656 if (stemlet && !beam)
659 if (is_invisible (me))
662 Real y2 = stem_end_position (me);
664 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
668 y2 = Staff_symbol_referencer::get_position (lh);
671 Real beam_translation = Beam::get_beam_translation (beam);
672 Real beam_thickness = Beam::get_thickness (beam);
673 int beam_count = beam_multiplicity (me).length () + 1;
676 * (0.5 * beam_thickness
677 + beam_translation * (0 >? (beam_count - 1))
678 + stemlet_length) / half_space;
681 Interval stem_y (y1 <? y2, y2 >? y1);
683 if (Grob *hed = support_head (me))
686 must not take ledgers into account.
688 Interval head_height = hed->extent (hed, Y_AXIS);
689 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
691 y_attach = head_height.linear_combination (y_attach);
692 stem_y[Direction (-d)] += d * y_attach/half_space;
697 Real stem_width = thickness (me);
699 me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
701 Box b = Box (Interval (-stem_width/2, stem_width/2),
702 Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
704 Stencil ss = Lookup::round_filled_box (b, blot);
705 mol.add_stencil (ss);
707 if (!get_beam (me) && abs (duration_log (me)) > 2)
709 Stencil fl = flag (me);
710 fl.translate_axis (stem_y[d]*half_space - d * blot/2, Y_AXIS);
711 fl.translate_axis (stem_width/2, X_AXIS);
712 mol.add_stencil (fl);
715 return mol.smobbed_copy ();
719 move the stem to right of the notehead if it is up.
721 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
723 Stem::offset_callback (SCM element_smob, SCM)
725 Grob *me = unsmob_grob (element_smob);
728 if (Grob *f = first_head (me))
730 Interval head_wid = f->extent (f, X_AXIS);
733 if (is_invisible (me))
736 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
738 Direction d = get_direction (me);
739 Real real_attach = head_wid.linear_combination (d * attach);
742 /* If not centered: correct for stem thickness. */
745 Real rule_thick = thickness (me);
746 r += - d * rule_thick * 0.5;
751 SCM rests = me->get_property ("rests");
752 if (scm_is_pair (rests))
754 Grob * rest = unsmob_grob (scm_car (rests));
755 r = rest->extent (rest, X_AXIS).center ();
758 return scm_make_real (r);
762 Stem::get_beam (Grob *me)
764 SCM b = me->get_property ("beam");
765 return dynamic_cast<Spanner*> (unsmob_grob (b));
769 Stem::get_stem_info (Grob *me)
771 /* Return cached info if available */
772 SCM scm_info = me->get_property ("stem-info");
773 if (!scm_is_pair (scm_info))
776 scm_info = me->get_property ("stem-info");
780 si.dir_ = get_grob_direction (me);
781 si.ideal_y_ = scm_to_double (scm_car (scm_info));
782 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
787 /* TODO: add extra space for tremolos! */
789 Stem::calc_stem_info (Grob *me)
791 Direction my_dir = get_grob_direction (me);
795 programming_error ("No stem dir set?");
799 Real staff_space = Staff_symbol_referencer::staff_space (me);
800 Grob *beam = get_beam (me);
801 Real beam_translation = Beam::get_beam_translation (beam);
802 Real beam_thickness = Beam::get_thickness (beam);
803 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
806 /* Simple standard stem length */
807 SCM lengths = me->get_property ("beamed-lengths");
809 scm_to_double (robust_list_ref (beam_count - 1, lengths))
812 /* stem only extends to center of beam
814 - 0.5 * beam_thickness
817 /* Condition: sane minimum free stem length (chord to beams) */
818 lengths = me->get_property ("beamed-minimum-free-lengths");
819 Real ideal_minimum_free =
820 scm_to_double (robust_list_ref (beam_count - 1, lengths))
825 It seems that also for ideal minimum length, we must use
826 the maximum beam count (for this direction):
828 \score{ \notes\relative c''{ [a8 a32] }}
830 must be horizontal. */
831 Real height_of_my_beams = beam_thickness
832 + (beam_count - 1) * beam_translation;
834 Real ideal_minimum_length = ideal_minimum_free
836 /* stem only extends to center of beam */
837 - 0.5 * beam_thickness;
839 ideal_length = ideal_length >? ideal_minimum_length;
841 /* Convert to Y position, calculate for dir == UP */
843 /* staff positions */
844 head_positions (me)[my_dir] * 0.5
845 * my_dir * staff_space;
846 Real ideal_y = note_start + ideal_length;
849 /* Conditions for Y position */
851 /* Lowest beam of (UP) beam must never be lower than second staffline
855 Although this (additional) rule is probably correct,
856 I expect that highest beam (UP) should also never be lower
857 than middle staffline, just as normal stems.
861 Obviously not for grace beams.
863 Also, not for knees. Seems to be a good thing. */
864 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
865 bool is_knee = to_boolean (beam->get_property ("knee"));
866 if (!no_extend_b && !is_knee)
868 /* Highest beam of (UP) beam must never be lower than middle
870 ideal_y = ideal_y >? 0;
871 /* Lowest beam of (UP) beam must never be lower than second staffline */
872 ideal_y = ideal_y >? (-staff_space
873 - beam_thickness + height_of_my_beams);
877 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
880 scm_to_double (robust_list_ref
883 ("beamed-extreme-minimum-free-lengths")))
886 Real minimum_length = minimum_free
888 /* stem only extends to center of beam */
889 - 0.5 * beam_thickness;
892 Real minimum_y = note_start + minimum_length;
893 Real shortest_y = minimum_y * my_dir;
895 me->set_property ("stem-info",
896 scm_list_2 (scm_make_real (ideal_y),
897 scm_make_real (shortest_y)));
901 Stem::beam_multiplicity (Grob *stem)
903 SCM beaming = stem->get_property ("beaming");
904 Slice le = int_list_to_slice (scm_car (beaming));
905 Slice ri = int_list_to_slice (scm_cdr (beaming));
911 /* FIXME: Too many properties */
912 ADD_INTERFACE (Stem, "stem-interface",
913 "The stem represent the graphical stem. "
914 "In addition, it internally connects note heads, beams and"
916 "Rests and whole notes have invisible stems.",
917 "tremolo-flag french-beaming "
918 "avoid-note-head thickness "
919 "stemlet-length rests "
920 "stem-info beamed-lengths beamed-minimum-free-lengths "
921 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
922 "duration-log beaming neutral-direction stem-end-position "
923 "note-heads direction length flag-style "
924 "no-stem-extend stroke-style");
926 /****************************************************************/
928 Stem_info::Stem_info ()
930 ideal_y_ = shortest_y_ = 0;
935 Stem_info::scale (Real x)