2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2003 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 "paper-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "molecule.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_grob_property ("beaming");
43 if (!gh_pair_p (pair
))
45 pair
= gh_cons (SCM_EOL
, SCM_EOL
);
46 me
->set_grob_property ("beaming", pair
);
49 SCM l
= index_get_cell (pair
, d
);
50 for( int i
= 0; i
< beam_count
; i
++)
52 l
= gh_cons (gh_int2scm (i
), l
);
54 index_set_cell (pair
, d
, l
);
59 Stem::head_positions (Grob
*me
)
67 Drul_array
<Grob
*> e (extremal_heads (me
));
69 return Interval (Staff_symbol_referencer::get_position (e
[DOWN
]),
70 Staff_symbol_referencer::get_position (e
[UP
]));
75 Stem::chord_start_y (Grob
*me
)
77 return head_positions (me
)[get_direction (me
)]
78 * Staff_symbol_referencer::staff_space (me
)/2.0;
82 Stem::stem_end_position (Grob
*me
)
84 SCM p
=me
->get_grob_property ("stem-end-position");
88 pos
= get_default_stem_end_position (me
);
89 me
->set_grob_property ("stem-end-position", gh_double2scm (pos
));
92 pos
= gh_scm2double (p
);
98 Stem::get_direction (Grob
*me
)
100 Direction d
= Directional_element_interface::get (me
);
104 d
= get_default_dir (me
);
106 Directional_element_interface::set (me
, d
);
113 Stem::set_stemend (Grob
*me
, Real se
)
116 Direction d
= get_direction (me
);
118 if (d
&& d
* head_positions (me
)[get_direction (me
)] >= se
*d
)
119 me
->warning (_ ("Weird stem size; check for narrow beams"));
121 me
->set_grob_property ("stem-end-position", gh_double2scm (se
));
126 Note head that determines hshift for upstems
128 WARNING: triggers direction
131 Stem::support_head (Grob
*me
)
133 SCM h
= me
->get_grob_property ("support-head");
134 Grob
* nh
= unsmob_grob (h
);
137 else if (head_count (me
) == 1)
143 return unsmob_grob (ly_car (me
->get_grob_property ("note-heads")));
146 return first_head (me
);
151 Stem::head_count (Grob
*me
)
153 return Pointer_group_interface::count (me
, "note-heads");
157 The note head which forms one end of the stem.
159 WARNING: triggers direction
162 Stem::first_head (Grob
*me
)
164 Direction d
= get_direction (me
);
167 return extremal_heads (me
)[-d
];
171 The note head opposite to the first head.
174 Stem::last_head (Grob
*me
)
176 Direction d
= get_direction (me
);
179 return extremal_heads (me
)[d
];
183 START is part where stem reaches `last' head.
186 Stem::extremal_heads (Grob
*me
)
188 const int inf
= 1000000;
189 Drul_array
<int> extpos
;
193 Drul_array
<Grob
*> exthead
;
194 exthead
[LEFT
] = exthead
[RIGHT
] =0;
196 for (SCM s
= me
->get_grob_property ("note-heads"); gh_pair_p (s
); s
= ly_cdr (s
))
198 Grob
* n
= unsmob_grob (ly_car (s
));
201 int p
= int (Staff_symbol_referencer::get_position (n
));
205 if (d
* p
> d
* extpos
[d
])
210 } while (flip (&d
) != DOWN
);
217 icmp (int const &a
, int const &b
)
223 Stem::note_head_positions (Grob
*me
)
226 for (SCM s
= me
->get_grob_property ("note-heads"); gh_pair_p (s
); s
= ly_cdr (s
))
228 Grob
* n
= unsmob_grob (ly_car (s
));
229 int p
= int (Staff_symbol_referencer::get_position (n
));
240 Stem::add_head (Grob
*me
, Grob
*n
)
242 n
->set_grob_property ("stem", me
->self_scm ());
243 n
->add_dependency (me
);
245 if (Note_head::has_interface (n
))
247 Pointer_group_interface::add_grob (me
, ly_symbol2scm ("note-heads"), n
);
252 Stem::invisible_b (Grob
*me
)
254 return ! (head_count (me
)
255 && gh_scm2int (me
->get_grob_property ("duration-log")) >= 1);
259 Stem::get_default_dir (Grob
*me
)
261 int staff_center
= 0;
262 Interval hp
= head_positions (me
);
268 int udistance
= (int) (UP
* hp
[UP
] - staff_center
);
269 int ddistance
= (int) (DOWN
* hp
[DOWN
] - staff_center
);
271 if (sign (ddistance
- udistance
))
272 return Direction (sign (ddistance
-udistance
));
274 return to_dir (me
->get_grob_property ("neutral-direction"));
278 Stem::get_default_stem_end_position (Grob
*me
)
280 /* Tab notation feature: make stem end extend out of staff. */
281 SCM up_to_staff
= me
->get_grob_property ("up-to-staff");
282 if (to_boolean (up_to_staff
))
284 int line_count
= Staff_symbol_referencer::line_count (me
);
285 Direction dir
= get_direction (me
);
286 return dir
* (line_count
+ 3.5);
288 Real ss
= Staff_symbol_referencer::staff_space (me
);
290 int durlog
= duration_log (me
);
292 bool grace_b
= to_boolean (me
->get_grob_property ("grace"));
297 Real length
= 7; // WARNING: IN HALF SPACES
298 SCM scm_len
= me
->get_grob_property ("length");
299 if (gh_number_p (scm_len
))
301 length
= gh_scm2double (scm_len
);
305 s
= me
->get_grob_property ("lengths");
308 length
= 2* gh_scm2double (robust_list_ref (durlog
-2, s
));
315 'set-default-stemlen' sets direction too
317 Direction dir
= get_direction (me
);
320 dir
= get_default_dir (me
);
321 Directional_element_interface::set (me
, dir
);
325 /* stems in unnatural (forced) direction should be shortened,
326 according to [Roush & Gourlay] */
327 if (!chord_start_y (me
)
328 || (get_direction (me
) != get_default_dir (me
)))
333 SCM sshorten
= me
->get_grob_property ("stem-shorten");
334 SCM scm_shorten
= gh_pair_p (sshorten
) ?
335 robust_list_ref ((duration_log (me
) - 2) >? 0, sshorten
): SCM_EOL
;
336 if (gh_number_p (scm_shorten
))
338 shorten
= 2* gh_scm2double (scm_shorten
);
341 /* On boundary: shorten only half */
342 if (abs (head_positions (me
)[get_direction (me
)]) <= 1)
351 Grob
* trem
= unsmob_grob (me
->get_grob_property ("tremolo-flag"));
352 if (trem
&& !unsmob_grob (me
->get_grob_property ("beam")))
355 Crude hack: add extra space if tremolo flag is there.
357 We can't do this for the beam, since we get into a loop
358 (Stem_tremolo::raw_molecule() looks at the beam.)
364 1.0 + 2 * Stem_tremolo::raw_molecule (trem
).extent (Y_AXIS
).length () / ss
;
368 Interval flag_ext
= flag (me
).extent (Y_AXIS
) ;
369 if (!flag_ext
.empty_b())
370 minlen
+= 2 * flag_ext
.length () / ss
;
373 The clash is smaller for down stems (since the tremolo is
380 length
= length
>? (minlen
+ 1.0);
383 Interval hp
= head_positions (me
);
384 Real st
= hp
[dir
] + dir
* length
;
387 TODO: change name to extend-stems to staff/center/'()
389 bool no_extend_b
= to_boolean (me
->get_grob_property ("no-stem-extend"));
390 if (!grace_b
&& !no_extend_b
&& dir
* st
< 0) // junkme?
394 Make a little room if we have a upflag and there is a dot.
395 previous approach was to lengthen the stem. This is not
396 good typesetting practice.
399 if (!get_beam (me
) && dir
== UP
402 Grob
* closest_to_flag
= extremal_heads (me
)[dir
];
403 Grob
* dots
= closest_to_flag
404 ? Rhythmic_head::get_dots (closest_to_flag
) : 0;
408 Real dp
= Staff_symbol_referencer::get_position (dots
);
409 Real flagy
= flag (me
).extent (Y_AXIS
)[-dir
] * 2
413 Very gory: add myself to the X-support of the parent,
414 which should be a dot-column.
416 if (dir
* (st
+ flagy
- dp
) < 0.5)
418 Grob
*par
= dots
->get_parent (X_AXIS
);
420 if (Dot_column::has_interface (par
))
422 Side_position_interface::add_support (par
, me
);
425 TODO: apply some better logic here. The flag is
426 curved inwards, so this will typically be too
442 the log of the duration (Number of hooks on the flag minus two)
445 Stem::duration_log (Grob
*me
)
447 SCM s
= me
->get_grob_property ("duration-log");
448 return (gh_number_p (s
)) ? gh_scm2int (s
) : 2;
452 Stem::position_noteheads (Grob
*me
)
454 if (!head_count (me
))
457 Link_array
<Grob
> heads
=
458 Pointer_group_interface__extract_grobs (me
, (Grob
*)0, "note-heads");
460 heads
.sort (compare_position
);
461 Direction dir
=get_direction (me
);
467 Real thick
= gh_scm2double (me
->get_grob_property ("thickness"))
468 * me
->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
470 Grob
*hed
= support_head (me
);
471 Real w
= Note_head::head_extent (hed
,X_AXIS
)[dir
];
472 for (int i
=0; i
< heads
.size (); i
++)
474 heads
[i
]->translate_axis (w
- Note_head::head_extent (heads
[i
],X_AXIS
)[dir
],
479 int lastpos
= int (Staff_symbol_referencer::get_position (heads
[0]));
480 for (int i
=1; i
< heads
.size (); i
++)
482 Real p
= Staff_symbol_referencer::get_position (heads
[i
]);
483 int dy
=abs (lastpos
- (int)p
);
489 Real l
= Note_head::head_extent (heads
[i
], X_AXIS
).length ();
491 Direction d
= get_direction (me
);
492 /* reversed head should be shifted l-thickness, but this looks
493 too crowded, so we only shift l-0.5*thickness.
494 Notice that this leads to assymetry: Normal heads overlap
495 the stem 100% whereas reversed heads only overlaps the stem
498 heads
[i
]->translate_axis ((l
-thick
*magic
) * d
, X_AXIS
);
501 heads
[i
]->translate_axis (-thick
*(2-magic
) * d
, X_AXIS
);
506 For some cases we should kern some more: when the
507 distance between the next or prev note is too large, we'd
508 get large white gaps, eg.
527 MAKE_SCHEME_CALLBACK (Stem
,before_line_breaking
,1);
529 Stem::before_line_breaking (SCM smob
)
531 Grob
*me
= unsmob_grob (smob
);
535 Do the calculations for visible stems, but also for invisible stems
536 with note heads (i.e. half notes.)
540 stem_end_position (me
); // ugh. Trigger direction calc.
541 position_noteheads (me
);
545 me
->set_grob_property ("molecule-callback", SCM_EOL
);
548 return SCM_UNSPECIFIED
;
553 When in a beam with tuplet brackets, brew_mol is called early,
554 caching a wrong value.
556 MAKE_SCHEME_CALLBACK (Stem
, height
, 2);
558 Stem::height (SCM smob
, SCM ax
)
560 Axis a
= (Axis
)gh_scm2int (ax
);
561 Grob
* me
= unsmob_grob (smob
);
562 assert (a
== Y_AXIS
);
564 SCM mol
= me
->get_uncached_molecule ();
567 iv
= unsmob_molecule (mol
)->extent (a
);
568 if (Grob
*b
=get_beam (me
))
570 Direction d
= get_direction (me
);
571 iv
[d
] += d
* Beam::get_thickness (b
) /2.0 ;
574 return ly_interval2scm (iv
);
581 /* TODO: maybe property stroke-style should take different values,
582 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
586 SCM flag_style_scm
= me
->get_grob_property ("flag-style");
587 if (gh_symbol_p (flag_style_scm
))
589 flag_style
= ly_symbol2string (flag_style_scm
);
592 if (flag_style
== "no-flag")
597 bool adjust
= to_boolean (me
->get_grob_property ("adjust-if-on-staffline"));
599 String staffline_offs
;
600 if (String::compare (flag_style
, "mensural") == 0)
601 /* Mensural notation: For notes on staff lines, use different
602 flags than for notes between staff lines. The idea is that
603 flags are always vertically aligned with the staff lines,
604 regardless if the note head is on a staff line or between two
605 staff lines. In other words, the inner end of a flag always
606 touches a staff line.
611 /* Urrgh! We have to detect wether this stem ends on a staff
612 line or between two staff lines. But we can not call
613 stem_end_position(me) or get_default_stem_end_position(me),
614 since this encounters the flag and hence results in an
615 infinite recursion. However, in pure mensural notation,
616 there are no multiple note heads attached to a single stem,
617 neither is there usually need for using the stem_shorten
618 property (except for 32th and 64th notes, but that is not a
619 problem since the stem length in this case is augmented by
620 an integral multiple of staff_space). Hence, it should be
621 sufficient to just take the first note head, assume it's
622 the only one, look if it's on a staff line, and select the
623 flag's shape accordingly. In the worst case, the shape
624 looks slightly misplaced, but that will usually be the
625 programmer's fault (e.g. when trying to attach multiple
626 note heads to a single stem in mensural notation).
630 perhaps the detection whether this correction is needed should
631 happen in a different place to avoid the recursion.
635 int p
= (int)rint (Staff_symbol_referencer::get_position (first_head (me
)));
636 staffline_offs
= Staff_symbol_referencer::on_staffline (me
, p
) ?
641 staffline_offs
= "2";
649 char dir
= (get_direction (me
) == UP
) ? 'u' : 'd';
651 flag_style
+ to_string (dir
) + staffline_offs
+ to_string (duration_log (me
));
652 Font_metric
*fm
= Font_interface::get_default_font (me
);
653 Molecule flag
= fm
->find_by_name ("flags-" + font_char
);
656 me
->warning (_f ("flag `%s' not found", font_char
));
659 SCM stroke_style_scm
= me
->get_grob_property ("stroke-style");
660 if (gh_string_p (stroke_style_scm
))
662 String stroke_style
= ly_scm2string (stroke_style_scm
);
663 if (!stroke_style
.empty_b ())
665 String font_char
= to_string (dir
) + stroke_style
;
666 Molecule stroke
= fm
->find_by_name ("flags-" + font_char
);
667 if (stroke
.empty_b ())
669 me
->warning (_f ("flag stroke `%s' not found", font_char
));
673 flag
.add_molecule (stroke
);
681 MAKE_SCHEME_CALLBACK (Stem
,dim_callback
,2);
683 Stem::dim_callback (SCM e
, SCM ax
)
685 Axis a
= (Axis
) gh_scm2int (ax
);
686 assert (a
== X_AXIS
);
687 Grob
*se
= unsmob_grob (e
);
689 if (unsmob_grob (se
->get_grob_property ("beam")) || abs (duration_log (se
)) <= 2)
693 r
= flag (se
).extent (X_AXIS
);
695 return ly_interval2scm (r
);
700 MAKE_SCHEME_CALLBACK (Stem
,brew_molecule
,1);
703 Stem::brew_molecule (SCM smob
)
705 Grob
*me
= unsmob_grob (smob
);
707 Direction d
= get_direction (me
);
714 This is required to avoid stems passing in tablature chords...
719 TODO: make the stem start a direction ?
721 if (to_boolean (me
->get_grob_property ("avoid-note-head")))
723 Grob
* lh
= last_head (me
);
726 y1
= Staff_symbol_referencer::get_position (lh
);
730 Grob
* lh
= first_head (me
);
733 y1
= Staff_symbol_referencer::get_position (lh
);
736 Real y2
= stem_end_position (me
);
738 Interval
stem_y (y1
<? y2
,y2
>? y1
);
742 Real dy
= Staff_symbol_referencer::staff_space (me
) * 0.5;
744 if (Grob
*hed
= support_head (me
))
747 must not take ledgers into account.
749 Interval head_height
= Note_head::head_extent (hed
,Y_AXIS
);
750 Real y_attach
= Note_head::stem_attachment_coordinate ( hed
, Y_AXIS
);
752 y_attach
= head_height
.linear_combination (y_attach
);
753 stem_y
[Direction (-d
)] += d
* y_attach
/dy
;
756 if (!invisible_b (me
))
758 Real stem_width
= gh_scm2double (me
->get_grob_property ("thickness"))
760 * me
->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
762 Molecule ss
=Lookup::filledbox (Box (Interval (-stem_width
/2, stem_width
/2),
763 Interval (stem_y
[DOWN
]*dy
, stem_y
[UP
]*dy
)));
764 mol
.add_molecule (ss
);
767 if (!get_beam (me
) && abs (duration_log (me
)) > 2)
769 Molecule fl
= flag (me
);
770 fl
.translate_axis (stem_y
[d
]*dy
, Y_AXIS
);
771 mol
.add_molecule (fl
);
774 return mol
.smobbed_copy ();
778 move the stem to right of the notehead if it is up.
780 MAKE_SCHEME_CALLBACK (Stem
,off_callback
,2);
782 Stem::off_callback (SCM element_smob
, SCM
)
784 Grob
*me
= unsmob_grob (element_smob
);
788 if (head_count (me
) == 0)
790 return gh_double2scm (0.0);
793 if (Grob
* f
= first_head (me
))
795 Interval head_wid
= Note_head::head_extent(f
, X_AXIS
);
800 if (invisible_b (me
))
805 attach
= Note_head::stem_attachment_coordinate(f
, X_AXIS
);
807 Direction d
= get_direction (me
);
809 Real real_attach
= head_wid
.linear_combination (d
* attach
);
814 If not centered: correct for stem thickness.
819 = gh_scm2double (me
->get_grob_property ("thickness"))
820 * me
->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
823 r
+= - d
* rule_thick
* 0.5;
826 return gh_double2scm (r
);
830 Stem::get_beam (Grob
*me
)
832 SCM b
= me
->get_grob_property ("beam");
833 return unsmob_grob (b
);
837 Stem::get_stem_info (Grob
*me
)
839 /* Return cached info if available */
840 SCM scm_info
= me
->get_grob_property ("stem-info");
841 if (!gh_pair_p (scm_info
))
844 scm_info
= me
->get_grob_property ("stem-info");
848 si
.dir_
= Directional_element_interface::get (me
);
849 si
.ideal_y_
= gh_scm2double (gh_car (scm_info
));
850 si
.shortest_y_
= gh_scm2double (gh_cadr (scm_info
));
856 TODO: add extra space for tremolos!
859 Stem::calc_stem_info (Grob
*me
)
861 /* Tab notation feature: make stem end extend out of staff. */
862 SCM up_to_staff
= me
->get_grob_property ("up-to-staff");
863 if (to_boolean (up_to_staff
))
865 int line_count
= Staff_symbol_referencer::line_count (me
);
866 Direction dir
= get_direction (me
);
867 Real ideal_y
= dir
* (line_count
+ 1.5);
868 Real shortest_y
= ideal_y
;
870 me
->set_grob_property ("stem-info",
871 scm_list_n (gh_double2scm (ideal_y
),
872 gh_double2scm (shortest_y
),
877 Direction my_dir
= Directional_element_interface::get (me
);
878 Real staff_space
= Staff_symbol_referencer::staff_space (me
);
879 Grob
*beam
= get_beam (me
);
880 Real beam_translation
= Beam::get_beam_translation (beam
);
881 Real beam_thickness
= gh_scm2double (beam
->get_grob_property ("thickness"));
882 int beam_count
= Beam::get_direction_beam_count (beam
, my_dir
);
885 /* Simple standard stem length */
886 SCM lengths
= me
->get_grob_property ("beamed-lengths");
888 gh_scm2double (robust_list_ref (beam_count
- 1,lengths
))
891 /* stem only extends to center of beam */
892 - 0.5 * beam_thickness
;
894 /* Condition: sane minimum free stem length (chord to beams) */
895 lengths
= me
->get_grob_property ("beamed-minimum-free-lengths");
896 Real ideal_minimum_free
=
897 gh_scm2double (robust_list_ref (beam_count
- 1, lengths
))
902 It seems that also for ideal minimum length, we must use
903 the maximum beam count (for this direction):
905 \score{ \notes\relative c''{ [a8 a32] }}
907 must be horizontal. */
908 Real height_of_my_beams
= beam_thickness
909 + (beam_count
- 1) * beam_translation
;
911 Real ideal_minimum_length
= ideal_minimum_free
913 /* stem only extends to center of beam */
914 - 0.5 * beam_thickness
;
916 ideal_length
= ideal_length
>? ideal_minimum_length
;
919 /* Convert to Y position, calculate for dir == UP */
921 /* staff positions */
922 head_positions (me
)[my_dir
] * 0.5
924 Real ideal_y
= note_start
+ ideal_length
;
927 /* Conditions for Y position */
929 /* Lowest beam of (UP) beam must never be lower than second staffline
933 Although this (additional) rule is probably correct,
934 I expect that highest beam (UP) should also never be lower
935 than middle staffline, just as normal stems.
939 Obviously not for grace beams.
941 Also, not for knees. Seems to be a good thing. */
942 SCM grace
= me
->get_grob_property ("grace");
943 bool grace_b
= to_boolean (grace
);
944 bool no_extend_b
= to_boolean (me
->get_grob_property ("no-stem-extend"));
945 bool knee_b
= to_boolean (beam
->get_grob_property ("knee"));
946 if (!grace_b
&& !no_extend_b
&& !knee_b
)
948 /* Highest beam of (UP) beam must never be lower than middle
950 ideal_y
= ideal_y
>? 0;
951 /* Lowest beam of (UP) beam must never be lower than second staffline */
952 ideal_y
= ideal_y
>? (-staff_space
953 - beam_thickness
+ height_of_my_beams
);
957 SCM shorten
= beam
->get_grob_property ("shorten");
958 if (gh_number_p (shorten
))
959 ideal_y
-= gh_scm2double (shorten
);
962 gh_scm2double (robust_list_ref
964 me
->get_grob_property
965 ("beamed-extreme-minimum-free-lengths")))
968 Real minimum_length
= minimum_free
970 /* stem only extends to center of beam */
971 - 0.5 * beam_thickness
;
973 Real minimum_y
= note_start
+ minimum_length
;
977 Real shortest_y
= minimum_y
* my_dir
;
979 me
->set_grob_property ("stem-info",
980 scm_list_n (gh_double2scm (ideal_y
),
981 gh_double2scm (shortest_y
),
986 Stem::beam_multiplicity (Grob
*stem
)
988 SCM beaming
= stem
->get_grob_property ("beaming");
989 Slice l
= int_list_to_slice (gh_car (beaming
));
990 Slice r
= int_list_to_slice (gh_cdr (beaming
));
998 these are too many props.
1000 ADD_INTERFACE (Stem
,"stem-interface",
1002 "tremolo-flag french-beaming up-to-staff avoid-note-head adjust-if-on-staffline thickness stem-info beamed-lengths beamed-minimum-free-lengths beamed-extreme-minimum-free-lengths lengths beam stem-shorten duration-log beaming neutral-direction stem-end-position support-head note-heads direction length flag-style no-stem-extend stroke-style");