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];
170 START is part where stem reaches `last' head.
172 This function returns a drul with (bottom-head, top-head).
176 Stem::extremal_heads (Grob *me)
178 const int inf = 1000000;
179 Drul_array<int> extpos;
183 Drul_array<Grob *> exthead (0, 0);
184 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
187 Grob *n = unsmob_grob (scm_car (s));
188 int p = Staff_symbol_referencer::get_rounded_position (n);
193 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 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
217 Grob *n = unsmob_grob (scm_car (s));
218 int p = Staff_symbol_referencer::get_rounded_position (n);
223 ps.sort (integer_compare);
228 Stem::add_head (Grob *me, Grob *n)
230 n->set_property ("stem", me->self_scm ());
231 n->add_dependency (me);
233 if (Note_head::has_interface (n))
234 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
235 else if (Rest::has_interface (n))
236 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
240 Stem::is_invisible (Grob *me)
242 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
245 return !((head_count (me)
246 || stemlet_length > 0.0)
247 && scm_to_int (me->get_property ("duration-log")) >= 1);
251 Stem::get_default_dir (Grob *me)
253 int staff_center = 0;
254 Interval hp = head_positions (me);
258 int udistance = (int) (UP * hp[UP] - staff_center);
259 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
261 if (sign (ddistance - udistance))
262 return Direction (sign (ddistance - udistance));
264 return to_dir (me->get_property ("neutral-direction"));
268 Stem::get_default_stem_end_position (Grob *me)
270 Real ss = Staff_symbol_referencer::staff_space (me);
271 int durlog = duration_log (me);
275 /* WARNING: IN HALF SPACES */
277 SCM scm_len = me->get_property ("length");
278 if (scm_is_number (scm_len))
279 length = scm_to_double (scm_len);
282 s = me->get_property ("lengths");
284 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
288 'set-default-stemlen' sets direction too. */
289 Direction dir = get_direction (me);
292 dir = get_default_dir (me);
293 set_grob_direction (me, dir);
296 /* Stems in unnatural (forced) direction should be shortened,
297 according to [Roush & Gourlay] */
298 Interval hp = head_positions (me);
299 if (dir && dir * hp[dir] >= 0)
301 SCM sshorten = me->get_property ("stem-shorten");
302 SCM scm_shorten = scm_is_pair (sshorten) ?
303 robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
304 Real shorten = 2* robust_scm2double (scm_shorten,0);
306 /* On boundary: shorten only half */
307 if (abs (head_positions (me)[dir]) <= 1)
314 Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
315 if (t_flag && !unsmob_grob (me->get_property ("beam")))
317 /* Crude hack: add extra space if tremolo flag is there.
319 We can't do this for the beam, since we get into a loop
320 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
323 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
328 Interval flag_ext = flag (me).extent (Y_AXIS);
329 if (!flag_ext.is_empty ())
330 minlen += 2 * flag_ext.length () / ss;
332 /* The clash is smaller for down stems (since the tremolo is
337 length = length >? (minlen + 1.0);
340 Real st = dir ? hp[dir] + dir * length : 0;
342 /* TODO: change name to extend-stems to staff/center/'() */
343 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
344 if (!no_extend_b && dir * st < 0)
347 /* Make a little room if we have a upflag and there is a dot.
348 previous approach was to lengthen the stem. This is not
349 good typesetting practice. */
350 if (!get_beam (me) && dir == UP
353 Grob * closest_to_flag = extremal_heads (me)[dir];
354 Grob * dots = closest_to_flag
355 ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
359 Real dp = Staff_symbol_referencer::get_position (dots);
360 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
362 /* Very gory: add myself to the X-support of the parent,
363 which should be a dot-column. */
364 if (dir * (st + flagy - dp) < 0.5)
366 Grob *par = dots->get_parent (X_AXIS);
368 if (Dot_column::has_interface (par))
370 Side_position_interface::add_support (par, me);
372 /* TODO: apply some better logic here. The flag is
373 curved inwards, so this will typically be too
382 /* The log of the duration (Number of hooks on the flag minus two) */
384 Stem::duration_log (Grob *me)
386 SCM s = me->get_property ("duration-log");
387 return (scm_is_number (s)) ? scm_to_int (s) : 2;
391 Stem::position_noteheads (Grob *me)
393 if (!head_count (me))
396 Link_array<Grob> heads =
397 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-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);
509 Beam::after_line_breaking (beam->self_scm ());
513 SCM mol = me->get_uncached_stencil ();
516 iv = unsmob_stencil (mol)->extent (a);
517 if (Grob *b =get_beam (me))
519 Direction d = get_direction (me);
520 iv[d] += d * Beam::get_thickness (b) * 0.5 ;
523 return ly_interval2scm (iv);
528 Stem::flag (Grob *me)
530 /* TODO: maybe property stroke-style should take different values,
531 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
535 SCM flag_style_scm = me->get_property ("flag-style");
536 if (scm_is_symbol (flag_style_scm))
537 flag_style = ly_symbol2string (flag_style_scm);
539 if (flag_style == "no-flag")
544 String staffline_offs;
545 if (String::compare (flag_style, "mensural") == 0)
546 /* Mensural notation: For notes on staff lines, use different
547 flags than for notes between staff lines. The idea is that
548 flags are always vertically aligned with the staff lines,
549 regardless if the note head is on a staff line or between two
550 staff lines. In other words, the inner end of a flag always
551 touches a staff line.
556 int p = (int)(rint (stem_end_position (me)));
558 Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
562 staffline_offs = "2";
570 char dir = (get_direction (me) == UP) ? 'u' : 'd';
571 String font_char = flag_style
572 + to_string (dir) + staffline_offs + to_string (duration_log (me));
573 Font_metric *fm = Font_interface::get_default_font (me);
574 Stencil flag = fm->find_by_name ("flags-" + font_char);
575 if (flag.is_empty ())
576 me->warning (_f ("flag `%s' not found", font_char));
578 SCM stroke_style_scm = me->get_property ("stroke-style");
579 if (scm_is_string (stroke_style_scm))
581 String stroke_style = ly_scm2string (stroke_style_scm);
582 if (!stroke_style.is_empty ())
584 String font_char = to_string (dir) + stroke_style;
585 Stencil stroke = fm->find_by_name ("flags-" + font_char);
586 if (stroke.is_empty ())
587 me->warning (_f ("flag stroke `%s' not found", font_char));
589 flag.add_stencil (stroke);
596 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
598 Stem::dim_callback (SCM e, SCM ax)
600 Axis a = (Axis) scm_to_int (ax);
601 assert (a == X_AXIS);
602 Grob *me = unsmob_grob (e);
606 if (is_invisible (me))
610 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
613 r *= thickness (me)/2;
617 r = flag (me).extent (X_AXIS)
620 return ly_interval2scm (r);
624 Stem::thickness (Grob *me)
626 return scm_to_double (me->get_property ("thickness"))
627 * Staff_symbol_referencer::line_thickness (me);
630 MAKE_SCHEME_CALLBACK (Stem, print, 1);
632 Stem::print (SCM smob)
634 Grob *me = unsmob_grob (smob);
636 Direction d = get_direction (me);
638 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
640 bool stemlet = stemlet_length > 0.0;
642 /* TODO: make the stem start a direction ?
643 This is required to avoid stems passing in tablature chords. */
645 to_boolean (me->get_property ("avoid-note-head"))
647 : lh = first_head (me);
648 Grob *beam = get_beam (me);
653 if (stemlet && !beam)
656 if (is_invisible (me))
659 Real y2 = stem_end_position (me);
661 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
665 y2 = Staff_symbol_referencer::get_position (lh);
668 Real beam_translation = Beam::get_beam_translation (beam);
669 Real beam_thickness = Beam::get_thickness (beam);
670 int beam_count = beam_multiplicity (me).length () + 1;
673 * (0.5 * beam_thickness
674 + beam_translation * (0 >? (beam_count - 1))
675 + stemlet_length) / half_space;
678 Interval stem_y (y1 <? y2,y2 >? y1);
680 if (Grob *hed = support_head (me))
683 must not take ledgers into account.
685 Interval head_height = hed->extent (hed,Y_AXIS);
686 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
688 y_attach = head_height.linear_combination (y_attach);
689 stem_y[Direction (-d)] += d * y_attach/half_space;
694 Real stem_width = thickness (me);
696 me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
698 Box b = Box (Interval (-stem_width/2, stem_width/2),
699 Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
701 Stencil ss = Lookup::round_filled_box (b, blot);
702 mol.add_stencil (ss);
704 if (!get_beam (me) && abs (duration_log (me)) > 2)
706 Stencil fl = flag (me);
707 fl.translate_axis (stem_y[d]*half_space - d * blot/2, Y_AXIS);
708 fl.translate_axis (stem_width/2, X_AXIS);
709 mol.add_stencil (fl);
712 return mol.smobbed_copy ();
716 move the stem to right of the notehead if it is up.
718 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
720 Stem::off_callback (SCM element_smob, SCM)
722 Grob *me = unsmob_grob (element_smob);
725 if (Grob *f = first_head (me))
727 Interval head_wid = f->extent (f, X_AXIS);
730 if (is_invisible (me))
733 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
735 Direction d = get_direction (me);
736 Real real_attach = head_wid.linear_combination (d * attach);
739 /* If not centered: correct for stem thickness. */
742 Real rule_thick = thickness (me);
743 r += - d * rule_thick * 0.5;
748 SCM rests = me->get_property ("rests");
749 if (scm_is_pair (rests))
751 Grob * rest = unsmob_grob (scm_car (rests));
752 r = rest->extent (rest, X_AXIS).center ();
755 return scm_make_real (r);
759 Stem::get_beam (Grob *me)
761 SCM b = me->get_property ("beam");
762 return dynamic_cast<Spanner*> (unsmob_grob (b));
766 Stem::get_stem_info (Grob *me)
768 /* Return cached info if available */
769 SCM scm_info = me->get_property ("stem-info");
770 if (!scm_is_pair (scm_info))
773 scm_info = me->get_property ("stem-info");
777 si.dir_ = get_grob_direction (me);
778 si.ideal_y_ = scm_to_double (scm_car (scm_info));
779 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
784 /* TODO: add extra space for tremolos! */
786 Stem::calc_stem_info (Grob *me)
788 Direction my_dir = get_grob_direction (me);
792 programming_error ("No stem dir set?");
796 Real staff_space = Staff_symbol_referencer::staff_space (me);
797 Grob *beam = get_beam (me);
798 Real beam_translation = Beam::get_beam_translation (beam);
799 Real beam_thickness = Beam::get_thickness (beam);
800 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
803 /* Simple standard stem length */
804 SCM lengths = me->get_property ("beamed-lengths");
806 scm_to_double (robust_list_ref (beam_count - 1,lengths))
809 /* stem only extends to center of beam
811 - 0.5 * beam_thickness
814 /* Condition: sane minimum free stem length (chord to beams) */
815 lengths = me->get_property ("beamed-minimum-free-lengths");
816 Real ideal_minimum_free =
817 scm_to_double (robust_list_ref (beam_count - 1, lengths))
822 It seems that also for ideal minimum length, we must use
823 the maximum beam count (for this direction):
825 \score{ \notes\relative c''{ [a8 a32] }}
827 must be horizontal. */
828 Real height_of_my_beams = beam_thickness
829 + (beam_count - 1) * beam_translation;
831 Real ideal_minimum_length = ideal_minimum_free
833 /* stem only extends to center of beam */
834 - 0.5 * beam_thickness;
836 ideal_length = ideal_length >? ideal_minimum_length;
838 /* Convert to Y position, calculate for dir == UP */
840 /* staff positions */
841 head_positions (me)[my_dir] * 0.5
842 * my_dir * staff_space;
843 Real ideal_y = note_start + ideal_length;
846 /* Conditions for Y position */
848 /* Lowest beam of (UP) beam must never be lower than second staffline
852 Although this (additional) rule is probably correct,
853 I expect that highest beam (UP) should also never be lower
854 than middle staffline, just as normal stems.
858 Obviously not for grace beams.
860 Also, not for knees. Seems to be a good thing. */
861 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
862 bool is_knee = to_boolean (beam->get_property ("knee"));
863 if (!no_extend_b && !is_knee)
865 /* Highest beam of (UP) beam must never be lower than middle
867 ideal_y = ideal_y >? 0;
868 /* Lowest beam of (UP) beam must never be lower than second staffline */
869 ideal_y = ideal_y >? (-staff_space
870 - beam_thickness + height_of_my_beams);
874 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
877 scm_to_double (robust_list_ref
880 ("beamed-extreme-minimum-free-lengths")))
883 Real minimum_length = minimum_free
885 /* stem only extends to center of beam */
886 - 0.5 * beam_thickness;
889 Real minimum_y = note_start + minimum_length;
890 Real shortest_y = minimum_y * my_dir;
892 me->set_property ("stem-info",
893 scm_list_2 (scm_make_real (ideal_y),
894 scm_make_real (shortest_y)));
898 Stem::beam_multiplicity (Grob *stem)
900 SCM beaming= stem->get_property ("beaming");
901 Slice le = int_list_to_slice (scm_car (beaming));
902 Slice ri = int_list_to_slice (scm_cdr (beaming));
908 /* FIXME: Too many properties */
909 ADD_INTERFACE (Stem, "stem-interface",
910 "The stem represent the graphical stem. "
911 "In addition, it internally connects note heads, beams and"
913 "Rests and whole notes have invisible stems.",
914 "tremolo-flag french-beaming "
915 "avoid-note-head thickness "
916 "stemlet-length rests "
917 "stem-info beamed-lengths beamed-minimum-free-lengths "
918 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
919 "duration-log beaming neutral-direction stem-end-position "
920 "note-heads direction length flag-style "
921 "no-stem-extend stroke-style");
923 /****************************************************************/
925 Stem_info::Stem_info ()
927 ideal_y_ = shortest_y_ = 0;
932 Stem_info::scale (Real x)