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
);
66 Stem::head_positions (Grob
*me
)
70 Drul_array
<Grob
*> e (extremal_heads (me
));
71 return Interval (Staff_symbol_referencer::get_position (e
[DOWN
]),
72 Staff_symbol_referencer::get_position (e
[UP
]));
78 Stem::chord_start_y (Grob
*me
)
80 Interval hp
= head_positions (me
);
82 return hp
[get_direction (me
)] * Staff_symbol_referencer::staff_space (me
)
88 Stem::stem_end_position (Grob
*me
)
90 SCM p
= me
->get_property ("stem-end-position");
92 if (!scm_is_number (p
))
94 pos
= get_default_stem_end_position (me
);
95 me
->set_property ("stem-end-position", scm_make_real (pos
));
98 pos
= scm_to_double (p
);
104 Stem::get_direction (Grob
*me
)
106 Direction d
= get_grob_direction (me
);
110 d
= get_default_dir (me
);
112 set_grob_direction (me
, d
);
118 Stem::set_stemend (Grob
*me
, Real se
)
121 Direction d
= get_direction (me
);
123 if (d
&& d
* head_positions (me
)[get_direction (me
)] >= se
* d
)
124 me
->warning (_ ("weird stem size, check for narrow beams"));
126 me
->set_property ("stem-end-position", scm_make_real (se
));
129 /* Note head that determines hshift for upstems
130 WARNING: triggers direction */
132 Stem::support_head (Grob
*me
)
134 if (head_count (me
) == 1)
136 return unsmob_grob (scm_car (me
->get_property ("note-heads")));
137 return first_head (me
);
141 Stem::head_count (Grob
*me
)
143 return Pointer_group_interface::count (me
, ly_symbol2scm ("note-heads"));
146 /* The note head which forms one end of the stem.
147 WARNING: triggers direction */
149 Stem::first_head (Grob
*me
)
151 Direction d
= get_direction (me
);
153 return extremal_heads (me
)[-d
];
157 /* The note head opposite to the first head. */
159 Stem::last_head (Grob
*me
)
161 Direction d
= get_direction (me
);
163 return extremal_heads (me
)[d
];
168 START is part where stem reaches `last' head.
170 This function returns a drul with (bottom-head, top-head).
173 Stem::extremal_heads (Grob
*me
)
175 const int inf
= 1000000;
176 Drul_array
<int> extpos
;
180 Drul_array
<Grob
*> exthead (0, 0);
181 for (SCM s
= me
->get_property ("note-heads"); scm_is_pair (s
);
184 Grob
*n
= unsmob_grob (scm_car (s
));
185 int p
= Staff_symbol_referencer::get_rounded_position (n
);
190 if (d
* p
> d
* extpos
[d
])
196 while (flip (&d
) != DOWN
);
202 integer_compare (int const &a
, int const &b
)
207 /* The positions, in ascending order. */
209 Stem::note_head_positions (Grob
*me
)
212 for (SCM s
= me
->get_property ("note-heads"); scm_is_pair (s
);
215 Grob
*n
= unsmob_grob (scm_car (s
));
216 int p
= Staff_symbol_referencer::get_rounded_position (n
);
221 ps
.sort (integer_compare
);
226 Stem::add_head (Grob
*me
, Grob
*n
)
228 n
->set_property ("stem", me
->self_scm ());
229 n
->add_dependency (me
);
231 if (Note_head::has_interface (n
))
232 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("note-heads"), n
);
233 else if (Rest::has_interface (n
))
234 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("rests"), n
);
238 Stem::is_invisible (Grob
*me
)
240 Real stemlet_length
= robust_scm2double (me
->get_property ("stemlet-length"),
243 return !((head_count (me
)
244 || stemlet_length
> 0.0)
245 && scm_to_int (me
->get_property ("duration-log")) >= 1);
249 Stem::get_default_dir (Grob
*me
)
251 int staff_center
= 0;
252 Interval hp
= head_positions (me
);
256 int udistance
= (int) (UP
*hp
[UP
] - staff_center
);
257 int ddistance
= (int) (DOWN
*hp
[DOWN
] - staff_center
);
259 if (sign (ddistance
- udistance
))
260 return Direction (sign (ddistance
- udistance
));
262 return to_dir (me
->get_property ("neutral-direction"));
266 Stem::get_default_stem_end_position (Grob
*me
)
268 Real ss
= Staff_symbol_referencer::staff_space (me
);
269 int durlog
= duration_log (me
);
273 /* WARNING: IN HALF SPACES */
275 SCM scm_len
= me
->get_property ("length");
276 if (scm_is_number (scm_len
))
277 length
= scm_to_double (scm_len
);
280 s
= me
->get_property ("lengths");
282 length
= 2 * scm_to_double (robust_list_ref (durlog
- 2, s
));
286 'set-default-stemlen' sets direction too. */
287 Direction dir
= get_direction (me
);
290 dir
= get_default_dir (me
);
291 set_grob_direction (me
, dir
);
294 /* Stems in unnatural (forced) direction should be shortened,
295 according to [Roush & Gourlay] */
296 Interval hp
= head_positions (me
);
297 if (dir
&& dir
* hp
[dir
] >= 0)
299 SCM sshorten
= me
->get_property ("stem-shorten");
300 SCM scm_shorten
= scm_is_pair (sshorten
)
301 ? robust_list_ref ((duration_log (me
) - 2) >? 0, sshorten
) : SCM_EOL
;
302 Real shorten
= 2* robust_scm2double (scm_shorten
, 0);
304 /* On boundary: shorten only half */
305 if (abs (head_positions (me
)[dir
]) <= 1)
312 Grob
*t_flag
= unsmob_grob (me
->get_property ("tremolo-flag"));
313 if (t_flag
&& !unsmob_grob (me
->get_property ("beam")))
315 /* Crude hack: add extra space if tremolo flag is there.
317 We can't do this for the beam, since we get into a loop
318 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
321 + 2 * Stem_tremolo::raw_stencil (t_flag
).extent (Y_AXIS
).length ()
326 Interval flag_ext
= flag (me
).extent (Y_AXIS
);
327 if (!flag_ext
.is_empty ())
328 minlen
+= 2 * flag_ext
.length () / ss
;
330 /* The clash is smaller for down stems (since the tremolo is
335 length
= length
>? (minlen
+ 1.0);
338 Real st
= dir
? hp
[dir
] + dir
* length
: 0;
340 /* TODO: change name to extend-stems to staff/center/'() */
341 bool no_extend_b
= to_boolean (me
->get_property ("no-stem-extend"));
342 if (!no_extend_b
&& dir
* st
< 0)
345 /* Make a little room if we have a upflag and there is a dot.
346 previous approach was to lengthen the stem. This is not
347 good typesetting practice. */
348 if (!get_beam (me
) && dir
== UP
351 Grob
*closest_to_flag
= extremal_heads (me
)[dir
];
352 Grob
*dots
= closest_to_flag
353 ? Rhythmic_head::get_dots (closest_to_flag
) : 0;
357 Real dp
= Staff_symbol_referencer::get_position (dots
);
358 Real flagy
= flag (me
).extent (Y_AXIS
)[-dir
] * 2 / ss
;
360 /* Very gory: add myself to the X-support of the parent,
361 which should be a dot-column. */
362 if (dir
* (st
+ flagy
- dp
) < 0.5)
364 Grob
*par
= dots
->get_parent (X_AXIS
);
366 if (Dot_column::has_interface (par
))
368 Side_position_interface::add_support (par
, me
);
370 /* TODO: apply some better logic here. The flag is
371 curved inwards, so this will typically be too
380 /* The log of the duration (Number of hooks on the flag minus two) */
382 Stem::duration_log (Grob
*me
)
384 SCM s
= me
->get_property ("duration-log");
385 return (scm_is_number (s
)) ? scm_to_int (s
) : 2;
389 Stem::position_noteheads (Grob
*me
)
391 if (!head_count (me
))
394 Link_array
<Grob
> heads
395 = extract_grob_array (me
, ly_symbol2scm ("note-heads"));
397 heads
.sort (compare_position
);
398 Direction dir
= get_direction (me
);
403 Real thick
= thickness (me
);
405 Grob
*hed
= support_head (me
);
406 Real w
= hed
->extent (hed
, X_AXIS
)[dir
];
407 for (int i
= 0; i
< heads
.size (); i
++)
408 heads
[i
]->translate_axis (w
- heads
[i
]->extent (heads
[i
], X_AXIS
)[dir
],
412 Real lastpos
= Real (Staff_symbol_referencer::get_position (heads
[0]));
413 for (int i
= 1; i
< heads
.size (); i
++)
415 Real p
= Staff_symbol_referencer::get_position (heads
[i
]);
416 Real dy
= fabs (lastpos
- p
);
419 dy should always be 0.5, 0.0, 1.0, but provide safety margin
426 Real ell
= heads
[i
]->extent (heads
[i
], X_AXIS
).length ();
428 Direction d
= get_direction (me
);
430 Reversed head should be shifted ell-thickness, but this
431 looks too crowded, so we only shift ell-0.5*thickness.
433 This leads to assymetry: Normal heads overlap the
434 stem 100% whereas reversed heads only overlaps the
438 Real reverse_overlap
= 0.5;
439 heads
[i
]->translate_axis ((ell
- thick
* reverse_overlap
) * d
,
442 if (is_invisible (me
))
443 heads
[i
]->translate_axis (-thick
* (2 - reverse_overlap
) * d
,
448 For some cases we should kern some more: when the
449 distance between the next or prev note is too large, we'd
450 get large white gaps, eg.
469 MAKE_SCHEME_CALLBACK (Stem
, before_line_breaking
, 1);
471 Stem::before_line_breaking (SCM smob
)
473 Grob
*me
= unsmob_grob (smob
);
476 Do the calculations for visible stems, but also for invisible stems
477 with note heads (i.e. half notes.)
481 stem_end_position (me
); // ugh. Trigger direction calc.
482 position_noteheads (me
);
485 return SCM_UNSPECIFIED
;
490 When in a beam with tuplet brackets, brew_mol is called early,
491 caching a wrong value.
493 MAKE_SCHEME_CALLBACK (Stem
, height
, 2);
495 Stem::height (SCM smob
, SCM ax
)
497 Axis a
= (Axis
)scm_to_int (ax
);
498 Grob
*me
= unsmob_grob (smob
);
499 assert (a
== Y_AXIS
);
502 ugh. - this dependency should be automatic.
504 Grob
*beam
= get_beam (me
);
507 Beam::after_line_breaking (beam
->self_scm ());
510 SCM mol
= me
->get_uncached_stencil ();
513 iv
= unsmob_stencil (mol
)->extent (a
);
514 if (Grob
*b
= get_beam (me
))
516 Direction d
= get_direction (me
);
519 programming_error ("no stem direction");
522 iv
[d
] += d
* Beam::get_thickness (b
) * 0.5;
525 return ly_interval2scm (iv
);
529 Stem::flag (Grob
*me
)
531 /* TODO: maybe property stroke-style should take different values,
532 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
536 SCM flag_style_scm
= me
->get_property ("flag-style");
537 if (scm_is_symbol (flag_style_scm
))
538 flag_style
= ly_symbol2string (flag_style_scm
);
540 if (flag_style
== "no-flag")
545 String staffline_offs
;
546 if (String::compare (flag_style
, "mensural") == 0)
547 /* Mensural notation: For notes on staff lines, use different
548 flags than for notes between staff lines. The idea is that
549 flags are always vertically aligned with the staff lines,
550 regardless if the note head is on a staff line or between two
551 staff lines. In other words, the inner end of a flag always
552 touches a staff line.
557 int p
= (int) (rint (stem_end_position (me
)));
559 = Staff_symbol_referencer::on_staffline (me
, p
) ? "0" : "1";
563 staffline_offs
= "2";
571 char dir
= (get_direction (me
) == UP
) ? 'u' : 'd';
572 String font_char
= flag_style
573 + to_string (dir
) + staffline_offs
+ to_string (duration_log (me
));
574 Font_metric
*fm
= Font_interface::get_default_font (me
);
575 Stencil flag
= fm
->find_by_name ("flags." + font_char
);
576 if (flag
.is_empty ())
577 me
->warning (_f ("flag `%s' not found", font_char
));
579 SCM stroke_style_scm
= me
->get_property ("stroke-style");
580 if (scm_is_string (stroke_style_scm
))
582 String stroke_style
= ly_scm2string (stroke_style_scm
);
583 if (!stroke_style
.is_empty ())
585 String font_char
= to_string (dir
) + stroke_style
;
586 Stencil stroke
= fm
->find_by_name ("flags." + font_char
);
587 if (stroke
.is_empty ())
588 me
->warning (_f ("flag stroke `%s' not found", font_char
));
590 flag
.add_stencil (stroke
);
597 MAKE_SCHEME_CALLBACK (Stem
, width_callback
, 2);
599 Stem::width_callback (SCM e
, SCM ax
)
601 Axis a
= (Axis
) scm_to_int (ax
);
602 assert (a
== X_AXIS
);
603 Grob
*me
= unsmob_grob (e
);
607 if (is_invisible (me
))
611 else if (unsmob_grob (me
->get_property ("beam")) || abs (duration_log (me
)) <= 2)
613 r
= Interval (-1, 1);
614 r
*= thickness (me
) / 2;
618 r
= flag (me
).extent (X_AXIS
)
619 + thickness (me
) / 2;
621 return ly_interval2scm (r
);
625 Stem::thickness (Grob
*me
)
627 return scm_to_double (me
->get_property ("thickness"))
628 * Staff_symbol_referencer::line_thickness (me
);
631 MAKE_SCHEME_CALLBACK (Stem
, print
, 1);
633 Stem::print (SCM smob
)
635 Grob
*me
= unsmob_grob (smob
);
637 Direction d
= get_direction (me
);
639 Real stemlet_length
= robust_scm2double (me
->get_property ("stemlet-length"),
641 bool stemlet
= stemlet_length
> 0.0;
643 /* TODO: make the stem start a direction ?
644 This is required to avoid stems passing in tablature chords. */
646 = to_boolean (me
->get_property ("avoid-note-head"))
649 Grob
*beam
= get_beam (me
);
654 if (!lh
&& stemlet
&& !beam
)
657 if (is_invisible (me
))
660 Real y2
= stem_end_position (me
);
662 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
;
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
));
782 /* TODO: add extra space for tremolos! */
784 Stem::calc_stem_info (Grob
*me
)
786 Direction my_dir
= get_grob_direction (me
);
790 programming_error ("no stem dir set");
794 Real staff_space
= Staff_symbol_referencer::staff_space (me
);
795 Grob
*beam
= get_beam (me
);
796 Real beam_translation
= Beam::get_beam_translation (beam
);
797 Real beam_thickness
= Beam::get_thickness (beam
);
798 int beam_count
= Beam::get_direction_beam_count (beam
, my_dir
);
800 /* Simple standard stem length */
801 SCM lengths
= me
->get_property ("beamed-lengths");
803 = scm_to_double (robust_list_ref (beam_count
- 1, lengths
))
806 /* stem only extends to center of beam
808 - 0.5 * beam_thickness
;
810 /* Condition: sane minimum free stem length (chord to beams) */
811 lengths
= me
->get_property ("beamed-minimum-free-lengths");
812 Real ideal_minimum_free
813 = scm_to_double (robust_list_ref (beam_count
- 1, lengths
))
817 It seems that also for ideal minimum length, we must use
818 the maximum beam count (for this direction):
820 \score{ \notes\relative c''{ [a8 a32] }}
822 must be horizontal. */
823 Real height_of_my_beams
= beam_thickness
824 + (beam_count
- 1) * beam_translation
;
826 Real ideal_minimum_length
= ideal_minimum_free
828 /* stem only extends to center of beam */
829 - 0.5 * beam_thickness
;
831 ideal_length
= ideal_length
>? ideal_minimum_length
;
833 /* Convert to Y position, calculate for dir == UP */
835 = /* staff positions */
836 head_positions (me
)[my_dir
] * 0.5
837 * my_dir
* staff_space
;
838 Real ideal_y
= note_start
+ ideal_length
;
840 /* Conditions for Y position */
842 /* Lowest beam of (UP) beam must never be lower than second staffline
846 Although this (additional) rule is probably correct,
847 I expect that highest beam (UP) should also never be lower
848 than middle staffline, just as normal stems.
852 Obviously not for grace beams.
854 Also, not for knees. Seems to be a good thing. */
855 bool no_extend_b
= to_boolean (me
->get_property ("no-stem-extend"));
856 bool is_knee
= to_boolean (beam
->get_property ("knee"));
857 if (!no_extend_b
&& !is_knee
)
859 /* Highest beam of (UP) beam must never be lower than middle
861 ideal_y
= ideal_y
>? 0;
862 /* Lowest beam of (UP) beam must never be lower than second staffline */
863 ideal_y
= ideal_y
>? (-staff_space
864 - beam_thickness
+ height_of_my_beams
);
867 ideal_y
-= robust_scm2double (beam
->get_property ("shorten"), 0);
870 = scm_to_double (robust_list_ref
873 ("beamed-extreme-minimum-free-lengths")))
876 Real minimum_length
= minimum_free
878 /* stem only extends to center of beam */
879 - 0.5 * beam_thickness
;
882 Real minimum_y
= note_start
+ minimum_length
;
883 Real shortest_y
= minimum_y
* my_dir
;
885 me
->set_property ("stem-info",
886 scm_list_2 (scm_make_real (ideal_y
),
887 scm_make_real (shortest_y
)));
891 Stem::beam_multiplicity (Grob
*stem
)
893 SCM beaming
= stem
->get_property ("beaming");
894 Slice le
= int_list_to_slice (scm_car (beaming
));
895 Slice ri
= int_list_to_slice (scm_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 "stemlet-length rests "
909 "stem-info beamed-lengths beamed-minimum-free-lengths "
910 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
911 "duration-log beaming neutral-direction stem-end-position "
912 "note-heads direction length flag-style "
913 "no-stem-extend stroke-style");
915 /****************************************************************/
917 Stem_info::Stem_info ()
919 ideal_y_
= shortest_y_
= 0;
924 Stem_info::scale (Real x
)