2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2002 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"
38 Stem::set_beaming (Grob*me, int beam_count, Direction d)
40 SCM pair = me->get_grob_property ("beaming");
42 if (!gh_pair_p (pair))
44 pair = gh_cons (SCM_EOL, SCM_EOL);
45 me->set_grob_property ("beaming", pair);
48 SCM l = index_get_cell (pair, d);
49 for( int i = 0; i< beam_count; i++)
51 l = gh_cons (gh_int2scm (i), l);
53 index_set_cell (pair, d, l);
58 Stem::head_positions (Grob*me)
66 Drul_array<Grob*> e (extremal_heads (me));
68 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
69 Staff_symbol_referencer::get_position (e[UP]));
74 Stem::chord_start_y (Grob*me)
76 return head_positions (me)[get_direction (me)]
77 * Staff_symbol_referencer::staff_space (me)/2.0;
81 Stem::stem_end_position (Grob*me)
83 SCM p =me->get_grob_property ("stem-end-position");
87 pos = get_default_stem_end_position (me);
88 me->set_grob_property ("stem-end-position", gh_double2scm (pos));
91 pos = gh_scm2double (p);
97 Stem::get_direction (Grob*me)
99 Direction d = Directional_element_interface::get (me);
103 d = get_default_dir (me);
105 Directional_element_interface::set (me, d);
112 Stem::set_stemend (Grob*me, Real se)
115 Direction d= get_direction (me);
117 if (d && d * head_positions (me)[get_direction (me)] >= se*d)
118 me->warning (_ ("Weird stem size; check for narrow beams"));
120 me->set_grob_property ("stem-end-position", gh_double2scm (se));
125 Note head that determines hshift for upstems
127 WARNING: triggers direction
130 Stem::support_head (Grob*me)
132 SCM h = me->get_grob_property ("support-head");
133 Grob * nh = unsmob_grob (h);
136 else if (head_count (me) == 1)
142 return unsmob_grob (ly_car (me->get_grob_property ("note-heads")));
145 return first_head (me);
150 Stem::head_count (Grob*me)
152 return Pointer_group_interface::count (me, "note-heads");
156 The note head which forms one end of the stem.
158 WARNING: triggers direction
161 Stem::first_head (Grob*me)
163 Direction d = get_direction (me);
166 return extremal_heads (me)[-d];
170 The note head opposite to the first head.
173 Stem::last_head (Grob*me)
175 Direction d = get_direction (me);
178 return extremal_heads (me)[d];
182 START is part where stem reaches `last' head.
185 Stem::extremal_heads (Grob*me)
187 const int inf = 1000000;
188 Drul_array<int> extpos;
192 Drul_array<Grob *> exthead;
193 exthead[LEFT] = exthead[RIGHT] =0;
195 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
197 Grob * n = unsmob_grob (ly_car (s));
200 int p = int (Staff_symbol_referencer::get_position (n));
204 if (d* p > d* extpos[d])
209 } while (flip (&d) != DOWN);
216 icmp (int const &a, int const &b)
222 Stem::note_head_positions (Grob *me)
225 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
227 Grob * n = unsmob_grob (ly_car (s));
228 int p = int (Staff_symbol_referencer::get_position (n));
239 Stem::add_head (Grob*me, Grob *n)
241 n->set_grob_property ("stem", me->self_scm ());
242 n->add_dependency (me);
244 if (Note_head::has_interface (n))
246 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
251 Stem::invisible_b (Grob*me)
253 return ! (head_count (me)
254 && gh_scm2int (me->get_grob_property ("duration-log")) >= 1);
258 Stem::get_default_dir (Grob*me)
260 int staff_center = 0;
261 Interval hp = head_positions (me);
267 int udistance = (int) (UP * hp[UP] - staff_center);
268 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
270 if (sign (ddistance - udistance))
271 return Direction (sign (ddistance -udistance));
273 return to_dir (me->get_grob_property ("neutral-direction"));
277 Stem::get_default_stem_end_position (Grob*me)
279 /* Tab notation feature: make stem end extend out of staff. */
280 SCM up_to_staff = me->get_grob_property ("up-to-staff");
281 if (to_boolean (up_to_staff))
283 int line_count = Staff_symbol_referencer::line_count (me);
284 Direction dir = get_direction (me);
285 return dir * (line_count + 3.5);
288 bool grace_b = to_boolean (me->get_grob_property ("grace"));
293 SCM scm_len = me->get_grob_property ("length");
294 if (gh_number_p (scm_len))
296 length_f = gh_scm2double (scm_len);
300 s = me->get_grob_property ("lengths");
303 length_f = 2* gh_scm2double (robust_list_ref (duration_log(me) -2, s));
307 Real shorten_f = 0.0;
309 SCM sshorten = me->get_grob_property ("stem-shorten");
310 if (gh_pair_p (sshorten))
312 shorten_f = 2* gh_scm2double (robust_list_ref ((duration_log (me) - 2) >? 0, sshorten));
315 /* On boundary: shorten only half */
316 if (abs (chord_start_y (me)) == 0.5)
320 'set-default-stemlen' sets direction too
322 Direction dir = get_direction (me);
325 dir = get_default_dir (me);
326 Directional_element_interface::set (me, dir);
329 /* stems in unnatural (forced) direction should be shortened,
330 according to [Roush & Gourlay] */
331 if (chord_start_y (me)
332 && (get_direction (me) != get_default_dir (me)))
333 length_f -= shorten_f;
335 Interval hp = head_positions (me);
336 Real st = hp[dir] + dir * length_f;
340 TODO: change name to extend-stems to staff/center/'()
342 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
343 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
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.
352 if (!get_beam (me) && dir == UP
353 && duration_log (me) > 2)
355 Grob * closest_to_flag = extremal_heads (me)[dir];
356 Grob * dots = closest_to_flag
357 ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
361 Real dp = Staff_symbol_referencer::get_position (dots);
362 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
363 / Staff_symbol_referencer::staff_space (me);
366 Very gory: add myself to the X-support of the parent,
367 which should be a dot-column.
369 if (dir * (st + flagy - dp) < 0.5)
371 Grob *par = dots->get_parent (X_AXIS);
373 if (Dot_column::has_interface (par))
375 Side_position_interface::add_support (par, me);
378 TODO: apply some better logic here. The flag is
379 curved inwards, so this will typically be too
395 the log of the duration (Number of hooks on the flag minus two)
398 Stem::duration_log (Grob*me)
400 SCM s = me->get_grob_property ("duration-log");
401 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
405 Stem::position_noteheads (Grob*me)
407 if (!head_count (me))
410 Link_array<Grob> heads =
411 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
413 heads.sort (compare_position);
414 Direction dir =get_direction (me);
420 bool invisible = invisible_b (me);
423 thick = gh_scm2double (me->get_grob_property ("thickness"))
424 * me->get_paper ()->get_var ("linethickness");
427 Grob *hed = support_head (me);
428 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
429 for (int i=0; i < heads.size (); i++)
431 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
435 bool parity= true; // todo: make me settable.
436 int lastpos = int (Staff_symbol_referencer::get_position (heads[0]));
437 for (int i=1; i < heads.size (); i ++)
439 Real p = Staff_symbol_referencer::get_position (heads[i]);
440 int dy =abs (lastpos- (int)p);
446 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
448 Direction d = get_direction (me);
449 heads[i]->translate_axis (l * d, X_AXIS);
452 heads[i]->translate_axis (-thick *2* d , X_AXIS);
457 For some cases we should kern some more: when the
458 distance between the next or prev note is too large, we'd
459 get large white gaps, eg.
478 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
480 Stem::before_line_breaking (SCM smob)
482 Grob*me = unsmob_grob (smob);
486 Do the calculations for visible stems, but also for invisible stems
487 with note heads (i.e. half notes.)
491 stem_end_position (me); // ugh. Trigger direction calc.
492 position_noteheads (me);
496 me->set_grob_property ("molecule-callback", SCM_EOL);
499 return SCM_UNSPECIFIED;
504 When in a beam with tuplet brackets, brew_mol is called early,
505 caching a wrong value.
507 MAKE_SCHEME_CALLBACK (Stem, height, 2);
509 Stem::height (SCM smob, SCM ax)
511 Axis a = (Axis)gh_scm2int (ax);
512 Grob * me = unsmob_grob (smob);
513 assert (a == Y_AXIS);
515 SCM mol = me->get_uncached_molecule ();
518 iv = unsmob_molecule (mol)->extent (a);
519 return ly_interval2scm (iv);
526 /* TODO: rename flag-style into something more appropriate,
527 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
528 "single" and "double". Needs more discussion.
530 String style, fstyle, staffline_offs;
531 SCM fst = me->get_grob_property ("flag-style");
532 if (gh_string_p (fst))
534 fstyle = ly_scm2string (fst);
537 SCM st = me->get_grob_property ("style");
538 if (gh_symbol_p (st))
540 style = (ly_scm2string (scm_symbol_to_string (st)));
546 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
548 if (String::compare (style, "mensural") == 0)
549 /* Mensural notation: For notes on staff lines, use different
550 flags than for notes between staff lines. The idea is that
551 flags are always vertically aligned with the staff lines,
552 regardless if the note head is on a staff line or between two
553 staff lines. In other words, the inner end of a flag always
554 touches a staff line.
559 /* Urrgh! We have to detect wether this stem ends on a staff
560 line or between two staff lines. But we can not call
561 stem_end_position(me) or get_default_stem_end_position(me),
562 since this encounters the flag and hence results in an
563 infinite recursion. However, in pure mensural notation,
564 there are no multiple note heads attached to a single stem,
565 neither is there usually need for using the stem_shorten
566 property (except for 32th and 64th notes, but that is not a
567 problem since the stem length in this case is augmented by
568 an integral multiple of staff_space). Hence, it should be
569 sufficient to just take the first note head, assume it's
570 the only one, look if it's on a staff line, and select the
571 flag's shape accordingly. In the worst case, the shape
572 looks slightly misplaced, but that will usually be the
573 programmer's fault (e.g. when trying to attach multiple
574 note heads to a single stem in mensural notation). */
577 perhaps the detection whether this correction is needed should
578 happen in a different place to avoid the recursion.
582 Grob *first = first_head(me);
583 int sz = Staff_symbol_referencer::line_count (me)-1;
584 int p = (int)rint (Staff_symbol_referencer::get_position (first));
585 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
589 staffline_offs = "2";
596 char c = (get_direction (me) == UP) ? 'u' : 'd';
598 = String ("flags-") + style + to_string (c) + staffline_offs + to_string (duration_log (me));
600 = Font_interface::get_default_font (me)->find_by_name (index_string);
601 if (!fstyle.empty_b ())
602 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_string (c) + fstyle));
606 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
608 Stem::dim_callback (SCM e, SCM ax)
610 Axis a = (Axis) gh_scm2int (ax);
611 assert (a == X_AXIS);
612 Grob *se = unsmob_grob (e);
614 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
618 r = flag (se).extent (X_AXIS);
620 return ly_interval2scm (r);
625 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
628 Stem::brew_molecule (SCM smob)
630 Grob*me = unsmob_grob (smob);
632 Direction d = get_direction (me);
639 This is required to avoid stems passing in tablature chords...
644 TODO: make the stem start a direction ?
649 if (to_boolean (me->get_grob_property ("avoid-note-head")))
651 Grob * lh = last_head (me);
654 y1 = Staff_symbol_referencer::get_position (lh);
658 Grob * lh = first_head (me);
661 y1 = Staff_symbol_referencer::get_position (lh);
664 Real y2 = stem_end_position (me);
666 Interval stem_y (y1 <? y2,y2 >? y1);
670 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
672 if (Grob *hed = support_head (me))
675 must not take ledgers into account.
677 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
678 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
680 y_attach = head_height.linear_combination (y_attach);
681 stem_y[Direction (-d)] += d * y_attach/dy;
684 if (!invisible_b (me))
686 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
688 * me->get_paper ()->get_var ("linethickness");
690 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
691 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
692 mol.add_molecule (ss);
695 if (!get_beam (me) && abs (duration_log (me)) > 2)
697 Molecule fl = flag (me);
698 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
699 mol.add_molecule (fl);
702 return mol.smobbed_copy ();
706 move the stem to right of the notehead if it is up.
708 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
710 Stem::off_callback (SCM element_smob, SCM)
712 Grob *me = unsmob_grob (element_smob);
716 if (head_count (me) == 0)
718 return gh_double2scm (0.0);
721 if (Grob * f = first_head (me))
723 Interval head_wid = Note_head::head_extent(f, X_AXIS);
728 if (invisible_b (me))
733 attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
735 Direction d = get_direction (me);
737 Real real_attach = head_wid.linear_combination (d * attach);
742 If not centered: correct for stem thickness.
747 = gh_scm2double (me->get_grob_property ("thickness"))
748 * me->get_paper ()->get_var ("linethickness");
751 r += - d * rule_thick * 0.5;
754 return gh_double2scm (r);
758 Stem::get_beam (Grob*me)
760 SCM b= me->get_grob_property ("beam");
761 return unsmob_grob (b);
765 Stem::get_stem_info (Grob *me)
767 /* Return cached info if available */
768 SCM scm_info = me->get_grob_property ("stem-info");
769 if (!gh_pair_p (scm_info))
772 scm_info = me->get_grob_property ("stem-info");
776 si.dir_ = Directional_element_interface::get (me);
777 si.ideal_y_ = gh_scm2double (gh_car (scm_info));
778 si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
783 Stem::calc_stem_info (Grob *me)
785 /* Tab notation feature: make stem end extend out of staff. */
786 SCM up_to_staff = me->get_grob_property ("up-to-staff");
787 if (to_boolean (up_to_staff))
789 int line_count = Staff_symbol_referencer::line_count (me);
790 Direction dir = get_direction (me);
791 Real ideal_y = dir * (line_count + 1.5);
792 Real shortest_y = ideal_y;
794 me->set_grob_property ("stem-info",
795 scm_list_n (gh_double2scm (ideal_y),
796 gh_double2scm (shortest_y),
801 Direction my_dir = Directional_element_interface::get (me);
802 Real staff_space = Staff_symbol_referencer::staff_space (me);
803 Grob *beam = get_beam (me);
804 Real beam_translation = Beam::get_beam_translation (beam);
805 Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
806 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
809 /* Simple standard stem length */
811 gh_scm2double (robust_list_ref
813 me->get_grob_property ("beamed-lengths")))
815 /* stem only extends to center of beam */
816 - 0.5 * beam_thickness;
819 /* Condition: sane minimum free stem length (chord to beams) */
820 Real ideal_minimum_free =
821 gh_scm2double (robust_list_ref
823 me->get_grob_property ("beamed-minimum-free-lengths")))
826 int my_beam_count = Stem::beam_multiplicity (me).length () + 1;
827 Real height_of_my_beams = beam_thickness
828 + (my_beam_count - 1) * beam_translation;
830 Real ideal_minimum_length = ideal_minimum_free
832 /* stem only extends to center of beam */
833 - 0.5 * beam_thickness;
835 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
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 SCM grace = me->get_grob_property ("grace");
862 bool grace_b = to_boolean (grace);
863 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
864 bool knee_b = to_boolean (beam->get_grob_property ("knee"));
865 if (!grace_b && !no_extend_b && !knee_b)
867 /* Highest beam of (UP) beam must never be lower than middle
869 ideal_y = ideal_y >? 0;
870 /* Lowest beam of (UP) beam must never be lower than second staffline */
871 ideal_y = ideal_y >? (-staff_space
872 - beam_thickness + height_of_my_beams);
876 SCM shorten = beam->get_grob_property ("shorten");
877 if (gh_number_p (shorten))
878 ideal_y -= gh_scm2double (shorten);
882 gh_scm2double (robust_list_ref
884 me->get_grob_property
885 ("beamed-extreme-minimum-free-lengths")))
888 Real minimum_length = minimum_free
890 /* stem only extends to center of beam */
891 - 0.5 * beam_thickness;
893 Real minimum_y = note_start + minimum_length;
897 Real shortest_y = minimum_y * my_dir;
899 me->set_grob_property ("stem-info",
900 scm_list_n (gh_double2scm (ideal_y),
901 gh_double2scm (shortest_y),
906 Stem::beam_multiplicity (Grob *stem)
908 SCM beaming= stem->get_grob_property ("beaming");
909 Slice l = int_list_to_slice (gh_car (beaming));
910 Slice r = int_list_to_slice (gh_cdr (beaming));
917 ADD_INTERFACE (Stem,"stem-interface",
919 "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 style no-stem-extend flag-style");