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 <cmath> // rint
22 #include "directional-element-interface.hh"
23 #include "note-head.hh"
25 #include "output-def.hh"
26 #include "rhythmic-head.hh"
27 #include "font-interface.hh"
28 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #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_from_int (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);
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_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
91 Stem::set_stemend (Grob *me, Real se)
94 Direction d = get_grob_direction (me);
96 if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
97 me->warning (_ ("weird stem size, check for narrow beams"));
99 me->set_property ("stem-end-position", scm_from_double (se));
102 /* Note head that determines hshift for upstems
103 WARNING: triggers direction */
105 Stem::support_head (Grob *me)
107 extract_grob_set (me, "note-heads", heads);
108 if (heads.size () == 1)
111 return first_head (me);
115 Stem::head_count (Grob *me)
117 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
120 /* The note head which forms one end of the stem.
121 WARNING: triggers direction */
123 Stem::first_head (Grob *me)
125 Direction d = get_grob_direction (me);
127 return extremal_heads (me)[-d];
131 /* The note head opposite to the first head. */
133 Stem::last_head (Grob *me)
135 Direction d = get_grob_direction (me);
137 return extremal_heads (me)[d];
142 START is part where stem reaches `last' head.
144 This function returns a drul with (bottom-head, top-head).
147 Stem::extremal_heads (Grob *me)
149 const int inf = 1000000;
150 Drul_array<int> extpos;
154 Drul_array<Grob *> exthead (0, 0);
155 extract_grob_set (me, "note-heads", heads);
157 for (int i = heads.size (); i--;)
160 int p = Staff_symbol_referencer::get_rounded_position (n);
165 if (d * p > d * extpos[d])
171 while (flip (&d) != DOWN);
177 integer_compare (int const &a, int const &b)
182 /* The positions, in ascending order. */
184 Stem::note_head_positions (Grob *me)
187 extract_grob_set (me, "note-heads", heads);
189 for (int i = heads.size (); i--;)
192 int p = Staff_symbol_referencer::get_rounded_position (n);
197 ps.sort (integer_compare);
202 Stem::add_head (Grob *me, Grob *n)
204 n->set_object ("stem", me->self_scm ());
206 if (Note_head::has_interface (n))
207 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
208 else if (Rest::has_interface (n))
209 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
213 Stem::is_invisible (Grob *me)
215 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
218 return !((head_count (me)
219 || stemlet_length > 0.0)
220 && scm_to_int (me->get_property ("duration-log")) >= 1);
223 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
225 Stem::calc_stem_end_position (SCM smob)
227 Grob *me = unsmob_grob (smob);
229 if (!head_count (me))
230 return scm_from_double (0.0);
233 Real ss = Staff_symbol_referencer::staff_space (me);
234 int durlog = duration_log (me);
237 /* WARNING: IN HALF SPACES */
238 Real length = robust_scm2double (me->get_property ("length"), 7);
240 Direction dir = get_grob_direction (me);
241 Interval hp = head_positions (me);
242 Real st = dir ? hp[dir] + dir * length : 0;
244 /* TODO: change name to extend-stems to staff/center/'() */
245 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
246 if (!no_extend_b && dir * st < 0)
249 /* Make a little room if we have a upflag and there is a dot.
250 previous approach was to lengthen the stem. This is not
251 good typesetting practice. */
252 if (!get_beam (me) && dir == UP
255 Grob *closest_to_flag = extremal_heads (me)[dir];
256 Grob *dots = closest_to_flag
257 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
261 Real dp = Staff_symbol_referencer::get_position (dots);
262 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
264 /* Very gory: add myself to the X-support of the parent,
265 which should be a dot-column. */
266 if (dir * (st + flagy - dp) < 0.5)
268 Grob *par = dots->get_parent (X_AXIS);
270 if (Dot_column::has_interface (par))
272 Side_position_interface::add_support (par, me);
274 /* TODO: apply some better logic here. The flag is
275 curved inwards, so this will typically be too
282 return scm_from_double (st);
286 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
288 Stem::calc_length (SCM smob)
290 Grob *me = unsmob_grob (smob);
292 SCM details = me->get_property ("details");
293 int durlog = duration_log (me);
295 Real ss = Staff_symbol_referencer::staff_space (me);
297 SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
299 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
301 Direction dir = get_grob_direction (me);
303 /* Stems in unnatural (forced) direction should be shortened,
304 according to [Roush & Gourlay] */
305 Interval hp = head_positions (me);
306 if (dir && dir * hp[dir] >= 0)
308 SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
309 SCM scm_shorten = scm_is_pair (sshorten)
310 ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
311 Real shorten = 2* robust_scm2double (scm_shorten, 0);
313 /* On boundary: shorten only half */
314 if (abs (head_positions (me)[dir]) <= 1)
320 length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
323 Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
324 if (t_flag && !unsmob_grob (me->get_object ("beam")))
326 /* Crude hack: add extra space if tremolo flag is there.
328 We can't do this for the beam, since we get into a loop
329 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
332 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
337 Interval flag_ext = flag (me).extent (Y_AXIS);
338 if (!flag_ext.is_empty ())
339 minlen += 2 * flag_ext.length () / ss;
341 /* The clash is smaller for down stems (since the tremolo is
346 length = max (length, minlen + 1.0);
349 return scm_from_double (length);
351 /* The log of the duration (Number of hooks on the flag minus two) */
353 Stem::duration_log (Grob *me)
355 SCM s = me->get_property ("duration-log");
356 return (scm_is_number (s)) ? scm_to_int (s) : 2;
359 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
361 Stem::calc_positioning_done (SCM smob)
363 Grob *me = unsmob_grob (smob);
364 if (!head_count (me))
367 extract_grob_set (me, "note-heads", ro_heads);
368 Link_array<Grob> heads (ro_heads);
369 heads.sort (compare_position);
370 Direction dir = get_grob_direction (me);
375 Real thick = thickness (me);
377 Grob *hed = support_head (me);
380 programming_error ("Stem dir must be up or down.");
382 set_grob_direction (me, dir);
385 Real w = hed->extent (hed, X_AXIS)[dir];
386 for (int i = 0; i < heads.size (); i++)
387 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
391 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
392 for (int i = 1; i < heads.size (); i++)
394 Real p = Staff_symbol_referencer::get_position (heads[i]);
395 Real dy = fabs (lastpos- p);
398 dy should always be 0.5, 0.0, 1.0, but provide safety margin
405 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
407 Direction d = get_grob_direction (me);
409 Reversed head should be shifted ell-thickness, but this
410 looks too crowded, so we only shift ell-0.5*thickness.
412 This leads to assymetry: Normal heads overlap the
413 stem 100% whereas reversed heads only overlaps the
417 Real reverse_overlap = 0.5;
418 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
421 if (is_invisible (me))
422 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
427 For some cases we should kern some more: when the
428 distance between the next or prev note is too large, we'd
429 get large white gaps, eg.
452 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
454 Stem::calc_direction (SCM smob)
456 Grob *me = unsmob_grob (smob);
457 Direction dir = CENTER;
458 if (Grob *beam = unsmob_grob (me->get_object ("beam")))
460 SCM ignore_me = beam->get_property ("direction");
462 dir = get_grob_direction (me);
465 dir = get_default_dir (me);
467 return scm_from_int (dir);
470 /* A separate function, since this is used elsewhere too. */
472 Stem::get_default_dir (Grob *me)
474 Direction dir = CENTER;
475 int staff_center = 0;
476 Interval hp = head_positions (me);
479 int udistance = (int) (UP *hp[UP] - staff_center);
480 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
482 if (sign (ddistance - udistance))
483 dir = Direction (sign (ddistance - udistance));
485 dir = to_dir (me->get_property ("neutral-direction"));
492 MAKE_SCHEME_CALLBACK (Stem, height, 1);
494 Stem::height (SCM smob)
496 Grob *me = unsmob_grob (smob);
498 Direction dir = get_grob_direction (me);
502 UGH. Should be automatic
504 Grob *beam = get_beam (me);
507 beam->get_property ("positions");
510 /* FIXME uncached? */
511 Interval iv = me->get_stencil () ? me->get_stencil ()->extent (Y_AXIS) : Interval();
516 programming_error ("no stem direction");
519 iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
522 return ly_interval2scm (iv);
526 Stem::stem_end_position (Grob *me)
528 return robust_scm2double (me->get_property ("stem-end-position"), 0);
532 Stem::flag (Grob *me)
534 int log = duration_log (me);
536 || unsmob_grob (me->get_object ("beam")))
540 TODO: maybe property stroke-style should take different values,
541 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
545 SCM flag_style_scm = me->get_property ("flag-style");
546 if (scm_is_symbol (flag_style_scm))
547 flag_style = ly_symbol2string (flag_style_scm);
549 if (flag_style == "no-flag")
554 String staffline_offs;
555 if (String::compare (flag_style, "mensural") == 0)
556 /* Mensural notation: For notes on staff lines, use different
557 flags than for notes between staff lines. The idea is that
558 flags are always vertically aligned with the staff lines,
559 regardless if the note head is on a staff line or between two
560 staff lines. In other words, the inner end of a flag always
561 touches a staff line.
566 int p = (int) (rint (stem_end_position (me)));
568 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
571 staffline_offs = "2";
576 char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
577 String font_char = flag_style
578 + to_string (dir) + staffline_offs + to_string (log);
579 Font_metric *fm = Font_interface::get_default_font (me);
580 Stencil flag = fm->find_by_name ("flags." + font_char);
581 if (flag.is_empty ())
582 me->warning (_f ("flag `%s' not found", font_char));
584 SCM stroke_style_scm = me->get_property ("stroke-style");
585 if (scm_is_string (stroke_style_scm))
587 String stroke_style = ly_scm2string (stroke_style_scm);
588 if (!stroke_style.is_empty ())
590 String font_char = to_string (dir) + stroke_style;
591 Stencil stroke = fm->find_by_name ("flags." + font_char);
592 if (stroke.is_empty ())
593 me->warning (_f ("flag stroke `%s' not found", font_char));
595 flag.add_stencil (stroke);
602 MAKE_SCHEME_CALLBACK (Stem, width, 1);
606 Grob *me = unsmob_grob (e);
610 if (is_invisible (me))
612 else if (unsmob_grob (me->get_object ("beam"))
613 || abs (duration_log (me)) <= 2)
615 r = Interval (-1, 1);
616 r *= thickness (me) / 2;
620 r = Interval (-1, 1) * thickness (me) * 0.5;
621 r.unite (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_grob_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"))
651 Grob *beam = get_beam (me);
656 if (!lh && stemlet && !beam)
659 if (is_invisible (me))
662 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
664 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
667 y2 = Staff_symbol_referencer::get_position (lh);
670 Real beam_translation = Beam::get_beam_translation (beam);
671 Real beam_thickness = Beam::get_thickness (beam);
672 int beam_count = beam_multiplicity (me).length () + 1;
675 * (0.5 * beam_thickness
676 + beam_translation * max (0, (beam_count - 1))
677 + stemlet_length) / half_space;
680 Interval stem_y (min (y1, y2), max (y2, y1));
682 if (Grob *hed = support_head (me))
685 must not take ledgers into account.
687 Interval head_height = hed->extent (hed, Y_AXIS);
688 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
690 y_attach = head_height.linear_combination (y_attach);
691 stem_y[Direction (-d)] += d * y_attach / half_space;
695 Real stem_width = thickness (me);
697 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
699 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
700 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
702 Stencil ss = Lookup::round_filled_box (b, blot);
703 mol.add_stencil (ss);
705 mol.add_stencil (get_translated_flag (me));
707 return mol.smobbed_copy ();
711 Stem::get_translated_flag (Grob *me)
713 Stencil fl = flag (me);
716 Direction d = get_grob_direction (me);
718 = me->layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
719 Real stem_width = thickness (me);
720 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
721 Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
722 fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
723 fl.translate_axis (stem_width / 2, X_AXIS);
730 move the stem to right of the notehead if it is up.
732 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
734 Stem::offset_callback (SCM smob)
736 Grob *me = unsmob_grob (smob);
739 if (Grob *f = first_head (me))
741 Interval head_wid = f->extent (f, X_AXIS);
744 if (is_invisible (me))
747 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
749 Direction d = get_grob_direction (me);
750 Real real_attach = head_wid.linear_combination (d * attach);
753 /* If not centered: correct for stem thickness. */
756 Real rule_thick = thickness (me);
757 r += -d * rule_thick * 0.5;
762 extract_grob_set (me, "rests", rests);
765 Grob *rest = rests.top ();
766 r = rest->extent (rest, X_AXIS).center ();
769 return scm_from_double (r);
773 Stem::get_beam (Grob *me)
775 SCM b = me->get_object ("beam");
776 return dynamic_cast<Spanner *> (unsmob_grob (b));
780 Stem::get_stem_info (Grob *me)
783 si.dir_ = get_grob_direction (me);
785 SCM scm_info = me->get_property ("stem-info");
786 si.ideal_y_ = scm_to_double (scm_car (scm_info));
787 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
791 /* TODO: add extra space for tremolos! */
792 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
794 Stem::calc_stem_info (SCM smob)
796 Grob *me = unsmob_grob (smob);
797 Direction my_dir = get_grob_direction (me);
801 programming_error ("no stem dir set");
805 Real staff_space = Staff_symbol_referencer::staff_space (me);
806 Grob *beam = get_beam (me);
807 Real beam_translation = Beam::get_beam_translation (beam);
808 Real beam_thickness = Beam::get_thickness (beam);
809 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
811 /* Simple standard stem length */
812 SCM details = me->get_property ("details");
813 SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
817 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
820 /* stem only extends to center of beam
822 - 0.5 * beam_thickness;
824 /* Condition: sane minimum free stem length (chord to beams) */
825 lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
827 = robust_scm2double (me->get_property ("length-fraction"), 1.0);
829 Real ideal_minimum_free
830 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
831 * staff_space * length_fraction;
834 It seems that also for ideal minimum length, we must use
835 the maximum beam count (for this direction):
837 \score{ \notes\relative c''{ [a8 a32] }}
839 must be horizontal. */
840 Real height_of_my_beams = beam_thickness
841 + (beam_count - 1) * beam_translation;
843 Real ideal_minimum_length = ideal_minimum_free
845 /* stem only extends to center of beam */
846 - 0.5 * beam_thickness;
848 ideal_length = max (ideal_length, ideal_minimum_length);
850 /* Convert to Y position, calculate for dir == UP */
852 = /* staff positions */
853 head_positions (me)[my_dir] * 0.5
854 * my_dir * staff_space;
855 Real ideal_y = note_start + ideal_length;
857 /* Conditions for Y position */
859 /* Lowest beam of (UP) beam must never be lower than second staffline
863 Although this (additional) rule is probably correct,
864 I expect that highest beam (UP) should also never be lower
865 than middle staffline, just as normal stems.
869 Obviously not for grace beams.
871 Also, not for knees. Seems to be a good thing. */
872 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
873 bool is_knee = to_boolean (beam->get_property ("knee"));
874 if (!no_extend_b && !is_knee)
876 /* Highest beam of (UP) beam must never be lower than middle
878 ideal_y = max (ideal_y, 0.0);
879 /* Lowest beam of (UP) beam must never be lower than second staffline */
880 ideal_y = max (ideal_y, (-staff_space
881 - beam_thickness + height_of_my_beams));
884 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
886 SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
890 = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
893 Real minimum_length = minimum_free
895 /* stem only extends to center of beam */
896 - 0.5 * beam_thickness;
898 if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
900 Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
901 y_ext.widen (0.5); // FIXME. Should be tunable?
902 minimum_length = max (minimum_length, y_ext.length ());
906 Real minimum_y = note_start + minimum_length;
907 Real shortest_y = minimum_y * my_dir;
909 return scm_list_2 (scm_from_double (ideal_y),
910 scm_from_double (shortest_y));
914 Stem::beam_multiplicity (Grob *stem)
916 SCM beaming = stem->get_property ("beaming");
917 Slice le = int_list_to_slice (scm_car (beaming));
918 Slice ri = int_list_to_slice (scm_cdr (beaming));
923 /* FIXME: Too many properties */
924 ADD_INTERFACE (Stem, "stem-interface",
925 "The stem represent the graphical stem. "
926 "In addition, it internally connects note heads, beams and"
928 "Rests and whole notes have invisible stems."
930 "\n\nThe following properties may be set in the details list."
932 "@item beamed-lengths \n"
933 "list of stem lengths given beam multiplicity. \n"
934 "@item beamed-minimum-free-lengths \n"
935 "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
936 "@item beamed-extreme-minimum-free-lengths\n"
937 "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
939 "Default stem lengths. The list gives a length for each flag-count.\n"
940 "@item stem-shorten\n"
941 "How much a stem in a forced direction "
942 "should be shortened. The list gives an amount depending on the number "
973 /****************************************************************/
975 Stem_info::Stem_info ()
977 ideal_y_ = shortest_y_ = 0;
982 Stem_info::scale (Real x)