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 (!ly_c_pair_p (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 (!ly_c_pair_p (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 (!ly_c_number_p (p))
96 pos = get_default_stem_end_position (me);
97 me->set_property ("stem-end-position", scm_make_real (pos));
100 pos = ly_scm2double (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 (ly_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];
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;
183 exthead[LEFT] = exthead[RIGHT] =0;
185 for (SCM s = me->get_property ("note-heads"); ly_c_pair_p (s);
188 Grob *n = unsmob_grob (ly_car (s));
189 int p = Staff_symbol_referencer::get_rounded_position (n);
194 if (d * p > d * extpos[d])
199 } while (flip (&d) != DOWN);
205 icmp (int const &a, int const &b)
210 /* The positions, in ascending order. */
212 Stem::note_head_positions (Grob *me)
215 for (SCM s = me->get_property ("note-heads"); ly_c_pair_p (s);
218 Grob *n = unsmob_grob (ly_car (s));
219 int p = Staff_symbol_referencer::get_rounded_position (n);
229 Stem::add_head (Grob *me, Grob *n)
231 n->set_property ("stem", me->self_scm ());
232 n->add_dependency (me);
234 /* TODO: why not store Rest pointers? */
235 if (Note_head::has_interface (n))
236 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
240 Stem::is_invisible (Grob *me)
242 return !(head_count (me)
243 && ly_scm2int (me->get_property ("duration-log")) >= 1);
247 Stem::get_default_dir (Grob *me)
249 int staff_center = 0;
250 Interval hp = head_positions (me);
254 int udistance = (int) (UP * hp[UP] - staff_center);
255 int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
257 if (sign (ddistance - udistance))
258 return Direction (sign (ddistance - udistance));
260 return to_dir (me->get_property ("neutral-direction"));
264 Stem::get_default_stem_end_position (Grob *me)
266 Real ss = Staff_symbol_referencer::staff_space (me);
267 int durlog = duration_log (me);
271 /* WARNING: IN HALF SPACES */
273 SCM scm_len = me->get_property ("length");
274 if (ly_c_number_p (scm_len))
275 length = ly_scm2double (scm_len);
278 s = me->get_property ("lengths");
280 length = 2 * ly_scm2double (robust_list_ref (durlog - 2, s));
284 'set-default-stemlen' sets direction too. */
285 Direction dir = get_direction (me);
288 dir = get_default_dir (me);
289 set_grob_direction (me, dir);
292 /* Stems in unnatural (forced) direction should be shortened,
293 according to [Roush & Gourlay] */
294 Interval hp = head_positions (me);
295 if (dir && dir * hp[dir] >= 0)
297 SCM sshorten = me->get_property ("stem-shorten");
298 SCM scm_shorten = ly_c_pair_p (sshorten) ?
299 robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
300 Real shorten = 2* robust_scm2double (scm_shorten,0);
302 /* On boundary: shorten only half */
303 if (abs (head_positions (me)[dir]) <= 1)
310 Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
311 if (t_flag && !unsmob_grob (me->get_property ("beam")))
313 /* Crude hack: add extra space if tremolo flag is there.
315 We can't do this for the beam, since we get into a loop
316 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
319 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
324 Interval flag_ext = flag (me).extent (Y_AXIS);
325 if (!flag_ext.is_empty ())
326 minlen += 2 * flag_ext.length () / ss;
328 /* The clash is smaller for down stems (since the tremolo is
333 length = length >? (minlen + 1.0);
336 Real st = dir ? hp[dir] + dir * length : 0;
338 /* TODO: change name to extend-stems to staff/center/'() */
339 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
340 if (!no_extend_b && dir * st < 0)
343 /* Make a little room if we have a upflag and there is a dot.
344 previous approach was to lengthen the stem. This is not
345 good typesetting practice. */
346 if (!get_beam (me) && dir == UP
349 Grob * closest_to_flag = extremal_heads (me)[dir];
350 Grob * dots = closest_to_flag
351 ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
355 Real dp = Staff_symbol_referencer::get_position (dots);
356 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
358 /* Very gory: add myself to the X-support of the parent,
359 which should be a dot-column. */
360 if (dir * (st + flagy - dp) < 0.5)
362 Grob *par = dots->get_parent (X_AXIS);
364 if (Dot_column::has_interface (par))
366 Side_position_interface::add_support (par, me);
368 /* TODO: apply some better logic here. The flag is
369 curved inwards, so this will typically be too
378 /* The log of the duration (Number of hooks on the flag minus two) */
380 Stem::duration_log (Grob *me)
382 SCM s = me->get_property ("duration-log");
383 return (ly_c_number_p (s)) ? ly_scm2int (s) : 2;
387 Stem::position_noteheads (Grob *me)
389 if (!head_count (me))
392 Link_array<Grob> heads =
393 Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-heads");
395 heads.sort (compare_position);
396 Direction dir = get_direction (me);
401 Real thick = thickness (me);
403 Grob *hed = support_head (me);
404 Real w = Note_head::head_extent (hed, X_AXIS)[dir];
405 for (int i = 0; i < heads.size (); i++)
406 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],
411 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
412 for (int i = 1; i < heads.size (); i ++)
414 Real p = Staff_symbol_referencer::get_position (heads[i]);
415 Real dy =fabs (lastpos- p);
418 dy should always be 0.5, 0.0, 1.0, but provide safety margin
425 Real ell = Note_head::head_extent (heads[i], X_AXIS).length ();
427 Direction d = get_direction (me);
429 Reversed head should be shifted ell-thickness, but this
430 looks too crowded, so we only shift ell-0.5*thickness.
432 This leads to assymetry: Normal heads overlap the
433 stem 100% whereas reversed heads only overlaps the
437 Real reverse_overlap = 0.5;
438 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
441 if (is_invisible (me))
442 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
447 For some cases we should kern some more: when the
448 distance between the next or prev note is too large, we'd
449 get large white gaps, eg.
468 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
470 Stem::before_line_breaking (SCM smob)
472 Grob *me = unsmob_grob (smob);
475 Do the calculations for visible stems, but also for invisible stems
476 with note heads (i.e. half notes.)
480 stem_end_position (me); // ugh. Trigger direction calc.
481 position_noteheads (me);
484 me->set_property ("print-function", SCM_EOL);
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)ly_scm2int (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 (ly_c_symbol_p (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 /* Urrgh! We have to detect wether this stem ends on a staff
556 line or between two staff lines. But we can not call
557 stem_end_position (me) or get_default_stem_end_position (me),
558 since this encounters the flag and hence results in an
559 infinite recursion. However, in pure mensural notation,
560 there are no multiple note heads attached to a single stem,
561 neither is there usually need for using the stem_shorten
562 property (except for 32th and 64th notes, but that is not a
563 problem since the stem length in this case is augmented by
564 an integral multiple of staff_space). Hence, it should be
565 sufficient to just take the first note head, assume it's
566 the only one, look if it's on a staff line, and select the
567 flag's shape accordingly. In the worst case, the shape
568 looks slightly misplaced, but that will usually be the
569 programmer's fault (e.g. when trying to attach multiple
570 note heads to a single stem in mensural notation).
574 perhaps the detection whether this correction is needed should
575 happen in a different place to avoid the recursion.
579 int p = Staff_symbol_referencer::get_rounded_position (me);
580 staffline_offs = Staff_symbol_referencer::on_staffline (me, p)
585 staffline_offs = "2";
593 char dir = (get_direction (me) == UP) ? 'u' : 'd';
594 String font_char = flag_style
595 + to_string (dir) + staffline_offs + to_string (duration_log (me));
596 Font_metric *fm = Font_interface::get_default_font (me);
597 Stencil flag = fm->find_by_name ("flags-" + font_char);
598 if (flag.is_empty ())
599 me->warning (_f ("flag `%s' not found", font_char));
601 SCM stroke_style_scm = me->get_property ("stroke-style");
602 if (ly_c_string_p (stroke_style_scm))
604 String stroke_style = ly_scm2string (stroke_style_scm);
605 if (!stroke_style.is_empty ())
607 String font_char = to_string (dir) + stroke_style;
608 Stencil stroke = fm->find_by_name ("flags-" + font_char);
609 if (stroke.is_empty ())
610 me->warning (_f ("flag stroke `%s' not found", font_char));
612 flag.add_stencil (stroke);
619 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
621 Stem::dim_callback (SCM e, SCM ax)
623 Axis a = (Axis) ly_scm2int (ax);
624 assert (a == X_AXIS);
625 Grob *me = unsmob_grob (e);
629 if (is_invisible (me))
633 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
636 r *= thickness (me)/2;
640 r = flag (me).extent (X_AXIS)
643 return ly_interval2scm (r);
647 Stem::thickness (Grob *me)
649 return ly_scm2double (me->get_property ("thickness"))
650 * Staff_symbol_referencer::line_thickness (me);
653 MAKE_SCHEME_CALLBACK (Stem, print, 1);
655 Stem::print (SCM smob)
657 Grob *me = unsmob_grob (smob);
659 Direction d = get_direction (me);
661 /* TODO: make the stem start a direction ?
662 This is required to avoid stems passing in tablature chords. */
663 Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
664 ? last_head (me) : lh = first_head (me);
669 if (is_invisible (me))
672 Real y1 = Staff_symbol_referencer::get_position (lh);
673 Real y2 = stem_end_position (me);
675 Interval stem_y (y1 <? y2,y2 >? y1);
678 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
680 if (Grob *hed = support_head (me))
683 must not take ledgers into account.
685 Interval head_height = Note_head::head_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/dy;
694 Real stem_width = thickness (me);
696 me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
698 Box b = Box (Interval (-stem_width/2, stem_width/2),
699 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
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]*dy - 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);
726 if (Grob *f = first_head (me))
728 Interval head_wid = Note_head::head_extent (f, X_AXIS);
731 if (is_invisible (me))
734 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
736 Direction d = get_direction (me);
737 Real real_attach = head_wid.linear_combination (d * attach);
740 /* If not centered: correct for stem thickness. */
743 Real rule_thick = thickness (me);
744 r += - d * rule_thick * 0.5;
747 return scm_make_real (r);
751 Stem::get_beam (Grob *me)
753 SCM b = me->get_property ("beam");
754 return dynamic_cast<Spanner*> (unsmob_grob (b));
758 Stem::get_stem_info (Grob *me)
760 /* Return cached info if available */
761 SCM scm_info = me->get_property ("stem-info");
762 if (!ly_c_pair_p (scm_info))
765 scm_info = me->get_property ("stem-info");
769 si.dir_ = get_grob_direction (me);
770 si.ideal_y_ = ly_scm2double (ly_car (scm_info));
771 si.shortest_y_ = ly_scm2double (ly_cadr (scm_info));
776 /* TODO: add extra space for tremolos! */
778 Stem::calc_stem_info (Grob *me)
780 Direction my_dir = get_grob_direction (me);
784 programming_error ("No stem dir set?");
788 Real staff_space = Staff_symbol_referencer::staff_space (me);
789 Grob *beam = get_beam (me);
790 Real beam_translation = Beam::get_beam_translation (beam);
791 Real beam_thickness = Beam::get_thickness (beam);
792 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
795 /* Simple standard stem length */
796 SCM lengths = me->get_property ("beamed-lengths");
798 ly_scm2double (robust_list_ref (beam_count - 1,lengths))
801 /* stem only extends to center of beam
803 - 0.5 * beam_thickness
806 /* Condition: sane minimum free stem length (chord to beams) */
807 lengths = me->get_property ("beamed-minimum-free-lengths");
808 Real ideal_minimum_free =
809 ly_scm2double (robust_list_ref (beam_count - 1, lengths))
814 It seems that also for ideal minimum length, we must use
815 the maximum beam count (for this direction):
817 \score{ \notes\relative c''{ [a8 a32] }}
819 must be horizontal. */
820 Real height_of_my_beams = beam_thickness
821 + (beam_count - 1) * beam_translation;
823 Real ideal_minimum_length = ideal_minimum_free
825 /* stem only extends to center of beam */
826 - 0.5 * beam_thickness;
828 ideal_length = ideal_length >? ideal_minimum_length;
830 /* Convert to Y position, calculate for dir == UP */
832 /* staff positions */
833 head_positions (me)[my_dir] * 0.5
834 * my_dir * staff_space;
835 Real ideal_y = note_start + ideal_length;
838 /* Conditions for Y position */
840 /* Lowest beam of (UP) beam must never be lower than second staffline
844 Although this (additional) rule is probably correct,
845 I expect that highest beam (UP) should also never be lower
846 than middle staffline, just as normal stems.
850 Obviously not for grace beams.
852 Also, not for knees. Seems to be a good thing. */
853 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
854 bool is_knee = to_boolean (beam->get_property ("knee"));
855 if (!no_extend_b && !is_knee)
857 /* Highest beam of (UP) beam must never be lower than middle
859 ideal_y = ideal_y >? 0;
860 /* Lowest beam of (UP) beam must never be lower than second staffline */
861 ideal_y = ideal_y >? (-staff_space
862 - beam_thickness + height_of_my_beams);
866 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
869 ly_scm2double (robust_list_ref
872 ("beamed-extreme-minimum-free-lengths")))
875 Real minimum_length = minimum_free
877 /* stem only extends to center of beam */
878 - 0.5 * beam_thickness;
881 Real minimum_y = note_start + minimum_length;
882 Real shortest_y = minimum_y * my_dir;
884 me->set_property ("stem-info",
885 scm_list_2 (scm_make_real (ideal_y),
886 scm_make_real (shortest_y)));
890 Stem::beam_multiplicity (Grob *stem)
892 SCM beaming= stem->get_property ("beaming");
893 Slice le = int_list_to_slice (ly_car (beaming));
894 Slice ri = int_list_to_slice (ly_cdr (beaming));
900 /* FIXME: Too many properties */
901 ADD_INTERFACE (Stem, "stem-interface",
902 "The stem represent the graphical stem. "
903 "In addition, it internally connects note heads, beams and"
905 "Rests and whole notes have invisible stems.",
906 "tremolo-flag french-beaming "
907 "avoid-note-head thickness "
908 "stem-info beamed-lengths beamed-minimum-free-lengths "
909 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
910 "duration-log beaming neutral-direction stem-end-position "
911 "note-heads direction length flag-style "
912 "no-stem-extend stroke-style");
914 /****************************************************************/
916 Stem_info::Stem_info ()
918 ideal_y_ = shortest_y_ = 0;
923 Stem_info::scale (Real x)