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
12 #include <math.h> // m_pi
15 #include "directional-element-interface.hh"
16 #include "note-head.hh"
19 #include "paper-def.hh"
20 #include "rhythmic-head.hh"
21 #include "font-interface.hh"
22 #include "molecule.hh"
23 #include "paper-column.hh"
27 #include "group-interface.hh"
28 #include "staff-symbol-referencer.hh"
30 #include "side-position-interface.hh"
31 #include "dot-column.hh"
34 Stem::set_beaming (Grob*me ,int i, Direction d)
36 SCM pair = me->get_grob_property ("beaming");
38 if (!gh_pair_p (pair))
40 pair = gh_cons (gh_int2scm (-1),gh_int2scm (-1));
41 me-> set_grob_property ("beaming", pair);
43 index_set_cell (pair, d, gh_int2scm (i));
47 Stem::beam_count (Grob*me,Direction d)
49 SCM p=me->get_grob_property ("beaming");
51 return gh_scm2int (index_cell (p,d));
57 Stem::head_positions (Grob*me)
65 Drul_array<Grob*> e (extremal_heads (me));
67 return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
68 Staff_symbol_referencer::position_f (e[UP]));
73 Stem::chord_start_f (Grob*me)
75 return head_positions (me)[get_direction (me)]
76 * Staff_symbol_referencer::staff_space (me)/2.0;
80 Stem::stem_end_position (Grob*me)
82 SCM p =me->get_grob_property ("stem-end-position");
86 pos = get_default_stem_end_position (me);
87 me->set_grob_property ("stem-end-position", gh_double2scm (pos));
90 pos = gh_scm2double (p);
96 Stem::get_direction (Grob*me)
98 Direction d = Directional_element_interface::get (me);
102 d = get_default_dir (me);
104 Directional_element_interface::set (me, d);
111 Stem::set_stemend (Grob*me, Real se)
114 Direction d= get_direction (me);
116 if (d && d * head_positions (me)[get_direction (me)] >= se*d)
117 me->warning (_ ("Weird stem size; check for narrow beams"));
119 me->set_grob_property ("stem-end-position", gh_double2scm (se));
123 Stem::type_i (Grob*me)
125 return first_head (me) ? Rhythmic_head::balltype_i (first_head (me)) : 2;
129 Note head that determines hshift for upstems
132 Stem::support_head (Grob*me)
134 SCM h = me->get_grob_property ("support-head");
135 Grob * nh = unsmob_grob (h);
138 else if (heads_i (me) == 1)
144 return unsmob_grob (ly_car (me->get_grob_property ("heads")));
147 return first_head (me);
152 Stem::heads_i (Grob*me)
154 return Pointer_group_interface::count (me, "heads");
158 The note head which forms one end of the stem.
161 Stem::first_head (Grob*me)
163 return extremal_heads (me)[-get_direction (me)];
167 START is part where stem reaches `last' head.
170 Stem::extremal_heads (Grob*me)
172 const int inf = 1000000;
173 Drul_array<int> extpos;
177 Drul_array<Grob *> exthead;
178 exthead[LEFT] = exthead[RIGHT] =0;
180 for (SCM s = me->get_grob_property ("heads"); gh_pair_p (s); s = ly_cdr (s))
182 Grob * n = unsmob_grob (ly_car (s));
185 int p = int (Staff_symbol_referencer::position_f (n));
189 if (d* p > d* extpos[d])
194 } while (flip (&d) != DOWN);
201 icmp (int const &a, int const &b)
207 Stem::note_head_positions (Grob *me)
210 for (SCM s = me->get_grob_property ("heads"); gh_pair_p (s); s = ly_cdr (s))
212 Grob * n = unsmob_grob (ly_car (s));
213 int p = int (Staff_symbol_referencer::position_f (n));
224 Stem::add_head (Grob*me, Grob *n)
226 n->set_grob_property ("stem", me->self_scm ());
227 n->add_dependency (me);
229 if (Note_head::has_interface (n))
231 Pointer_group_interface::add_grob (me, ly_symbol2scm ("heads"), n);
236 Stem::invisible_b (Grob*me)
238 return ! (heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
242 Stem::get_default_dir (Grob*me)
244 int staff_center = 0;
245 Interval hp = head_positions (me);
251 int udistance = (int) (UP * hp[UP] - staff_center);
252 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
254 if (sign (ddistance - udistance))
255 return Direction (sign (ddistance -udistance));
257 return to_dir (me->get_grob_property ("neutral-direction"));
261 Stem::get_default_stem_end_position (Grob*me)
263 bool grace_b = to_boolean (me->get_grob_property ("grace"));
268 SCM scm_len = me->get_grob_property ("length");
269 if (gh_number_p (scm_len))
271 length_f = gh_scm2double (scm_len);
275 s = me->get_grob_property ("lengths");
276 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
277 a.push (gh_scm2double (ly_car (q)));
279 // stem uses half-spaces
280 length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
285 s = me->get_grob_property ("stem-shorten");
286 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
287 a.push (gh_scm2double (ly_car (q)));
290 // stem uses half-spaces
292 // fixme: use scm_list_n_ref () iso. array[]
293 Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
295 /* On boundary: shorten only half */
296 if (abs (chord_start_f (me)) == 0.5)
300 'set-default-stemlen' sets direction too
302 Direction dir = get_direction (me);
305 dir = get_default_dir (me);
306 Directional_element_interface::set (me, dir);
309 /* stems in unnatural (forced) direction should be shortened,
310 according to [Roush & Gourlay] */
311 if (chord_start_f (me)
312 && (get_direction (me) != get_default_dir (me)))
313 length_f -= shorten_f;
315 Interval hp = head_positions (me);
316 Real st = hp[dir] + dir * length_f;
318 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
319 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
323 Make a little room if we have a upflag and there is a dot.
324 previous approach was to lengthen the stem. This is not
325 good typesetting practice.
328 if (!beam_l (me) && dir == UP
329 && duration_log (me) > 2)
331 Grob * closest_to_flag = extremal_heads (me)[dir];
332 Grob * dots = closest_to_flag
333 ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
337 Real dp = Staff_symbol_referencer::position_f (dots);
338 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
339 / Staff_symbol_referencer::staff_space (me);
342 Very gory: add myself to the X-support of the parent,
343 which should be a dot-column.
345 if (dir * (st + flagy - dp) < 0.5)
347 Grob *par = dots->get_parent (X_AXIS);
349 if (Dot_column::has_interface (par))
351 Side_position_interface::add_support (par, me);
354 TODO: apply some better logic here. The flag is
355 curved inwards, so this will typically be too
371 the log of the duration (Number of hooks on the flag minus two)
374 Stem::duration_log (Grob*me)
376 SCM s = me->get_grob_property ("duration-log");
377 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
381 Stem::position_noteheads (Grob*me)
386 Link_array<Grob> heads =
387 Pointer_group_interface__extract_grobs (me, (Grob*)0, "heads");
389 heads.sort (compare_position);
390 Direction dir =get_direction (me);
396 Grob *hed = support_head (me);
397 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
398 for (int i=0; i < heads.size (); i++)
400 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
404 bool parity= true; // todo: make me settable.
405 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
406 for (int i=1; i < heads.size (); i ++)
408 Real p = Staff_symbol_referencer::position_f (heads[i]);
409 int dy =abs (lastpos- (int)p);
415 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
417 heads[i]->translate_axis (l * get_direction (me), X_AXIS);
428 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
430 Stem::before_line_breaking (SCM smob)
432 Grob*me = unsmob_grob (smob);
434 if (!invisible_b (me))
436 stem_end_position (me); // ugh. Trigger direction calc.
437 position_noteheads (me);
441 me->remove_grob_property ("molecule-callback");
444 return SCM_UNSPECIFIED;
449 When in a beam with tuplet brackets, brew_mol is called early,
450 caching a wrong value.
452 MAKE_SCHEME_CALLBACK (Stem, height, 2);
454 Stem::height (SCM smob, SCM ax)
456 Axis a = (Axis)gh_scm2int (ax);
457 Grob * me = unsmob_grob (smob);
458 assert (a == Y_AXIS);
460 SCM mol = me->get_uncached_molecule ();
463 iv = unsmob_molecule (mol)->extent (a);
464 return ly_interval2scm (iv);
471 /* TODO: rename flag-style into something more appropriate,
472 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
473 "single" and "double". Needs more discussion.
475 String style, fstyle, staffline_offs;
476 SCM fst = me->get_grob_property ("flag-style");
477 if (gh_string_p (fst))
479 fstyle = ly_scm2string (fst);
482 SCM st = me->get_grob_property ("style");
483 if (gh_symbol_p (st))
485 style = (ly_scm2string (scm_symbol_to_string (st)));
491 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
493 if (String::compare_i (style, "mensural") == 0)
494 /* Mensural notation: For notes on staff lines, use different
495 flags than for notes between staff lines. The idea is that
496 flags are always vertically aligned with the staff lines,
497 regardless if the note head is on a staff line or between two
498 staff lines. In other words, the inner end of a flag always
499 touches a staff line.
504 /* Urrgh! We have to detect wether this stem ends on a staff
505 line or between two staff lines. But we can not call
506 stem_end_position(me) or get_default_stem_end_position(me),
507 since this encounters the flag and hence results in an
508 infinite recursion. However, in pure mensural notation,
509 there are no multiple note heads attached to a single stem,
510 neither is there usually need for using the stem_shorten
511 property (except for 32th and 64th notes, but that is not a
512 problem since the stem length in this case is augmented by
513 an integral multiple of staff_space). Hence, it should be
514 sufficient to just take the first note head, assume it's
515 the only one, look if it's on a staff line, and select the
516 flag's shape accordingly. In the worst case, the shape
517 looks slightly misplaced, but that will usually be the
518 programmer's fault (e.g. when trying to attach multiple
519 note heads to a single stem in mensural notation). */
522 perhaps the detection whether this correction is needed should
523 happen in a different place to avoid the recursion.
527 Grob *first = first_head(me);
528 int sz = Staff_symbol_referencer::line_count (me)-1;
529 int p = (int)rint (Staff_symbol_referencer::position_f (first));
530 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
534 staffline_offs = "2";
541 char c = (get_direction (me) == UP) ? 'u' : 'd';
543 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
545 = Font_interface::get_default_font (me)->find_by_name (index_str);
546 if (!fstyle.empty_b ())
547 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
551 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
553 Stem::dim_callback (SCM e, SCM ax)
555 Axis a = (Axis) gh_scm2int (ax);
556 assert (a == X_AXIS);
557 Grob *se = unsmob_grob (e);
559 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
563 r = flag (se).extent (X_AXIS);
565 return ly_interval2scm (r);
570 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
573 Stem::brew_molecule (SCM smob)
575 Grob*me = unsmob_grob (smob);
577 Direction d = get_direction (me);
580 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
581 Real y2 = stem_end_position (me);
583 Interval stem_y (y1 <? y2,y2 >? y1);
587 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
589 if (Grob *hed = support_head (me))
592 must not take ledgers into account.
594 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
595 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
597 y_attach = head_height.linear_combination (y_attach);
598 stem_y[Direction (-d)] += d * y_attach/dy;
601 if (!invisible_b (me))
603 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
605 * me->paper_l ()->get_var ("linethickness");
607 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
608 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
609 mol.add_molecule (ss);
612 if (!beam_l (me) && abs (duration_log (me)) > 2)
614 Molecule fl = flag (me);
615 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
616 mol.add_molecule (fl);
619 return mol.smobbed_copy ();
623 move the stem to right of the notehead if it is up.
625 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
627 Stem::off_callback (SCM element_smob, SCM)
629 Grob *me = unsmob_grob (element_smob);
633 if (invisible_b (me))
635 return gh_double2scm (0.0);
638 if (Grob * f = first_head (me))
640 Interval head_wid = Note_head::head_extent(f, X_AXIS);
643 Note_head::stem_attachment_coordinate(f, X_AXIS);
645 Direction d = get_direction (me);
647 Real real_attach = head_wid.linear_combination (d * attach);
652 If not centered: correct for stem thickness.
657 = gh_scm2double (me->get_grob_property ("thickness"))
658 * me->paper_l ()->get_var ("linethickness");
661 r += - d * rule_thick * 0.5;
664 return gh_double2scm (r);
670 Stem::beam_l (Grob*me)
672 SCM b= me->get_grob_property ("beam");
673 return unsmob_grob (b);
677 // ugh still very long.
679 Stem::calc_stem_info (Grob*me)
681 SCM scm_info = me->get_grob_property ("stem-info");
683 if (gh_pair_p (scm_info ))
687 si.ideal_y = gh_scm2double (gh_car (scm_info));
688 si.max_y = gh_scm2double (gh_cadr (scm_info));
689 si.min_y = gh_scm2double (gh_caddr (scm_info));
694 Grob * beam = beam_l (me);
696 Direction beam_dir = Directional_element_interface::get (beam);
699 programming_error ("Beam dir not set.");
704 Real staff_space = Staff_symbol_referencer::staff_space (me);
705 Real half_space = staff_space / 2;
707 int multiplicity = Beam::get_multiplicity (beam);
708 Real interbeam_f = Beam::get_interbeam (beam);
710 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
712 info.ideal_y = chord_start_f (me);
714 // for simplicity, we calculate as if dir == UP
715 info.ideal_y *= beam_dir;
716 SCM grace_prop = me->get_grob_property ("grace");
718 bool grace_b = to_boolean (grace_prop);
723 s = me->get_grob_property ("beamed-minimum-lengths");
725 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
726 a.push (gh_scm2double (ly_car (q)));
729 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
730 s = me->get_grob_property ("beamed-lengths");
733 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
734 a.push (gh_scm2double (ly_car (q)));
736 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
738 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
739 /* normal beamed stem */
743 info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
745 info.min_y = info.ideal_y;
746 info.max_y = 1000; // INT_MAX;
748 info.ideal_y += stem_length;
749 info.min_y += minimum_length;
752 lowest beam of (UP) beam must never be lower than second staffline
754 Hmm, reference (Wanske?)
756 Although this (additional) rule is probably correct,
757 I expect that highest beam (UP) should also never be lower
758 than middle staffline, just as normal stems.
761 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
762 if (!grace_b && !no_extend_b)
764 /* highest beam of (UP) beam must never be lower than middle
766 lowest beam of (UP) beam must never be lower than second staffline
770 >? (- 2 * half_space - thick
771 + (multiplicity > 0) * thick
772 + interbeam_f * (multiplicity - 1));
778 info.ideal_y -= thick;
779 info.max_y = info.ideal_y;
780 info.min_y = - 1000 ; // INT_MAX;
782 info.ideal_y -= stem_length;
783 info.max_y -= minimum_length;
786 info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
788 s = beam->get_grob_property ("shorten");
790 info.ideal_y -= gh_scm2double (s);
792 Grob *common = me->common_refpoint (beam, Y_AXIS);
793 Real interstaff_f = beam_dir *
794 (me->relative_coordinate (common, Y_AXIS)
795 - beam->relative_coordinate (common, Y_AXIS));
797 info.ideal_y += interstaff_f;
798 info.min_y += interstaff_f;
799 info.max_y += interstaff_f ;
801 me->set_grob_property ("stem-info",
802 scm_list_n (gh_double2scm (info.ideal_y),
803 gh_double2scm (info.max_y),
804 gh_double2scm (info.min_y),
810 ADD_INTERFACE (Stem,"stem-interface",
812 "adjust-if-on-staffline thickness stem-info beamed-lengths beamed-minimum-lengths lengths beam stem-shorten duration-log beaming neutral-direction stem-end-position support-head heads direction length style no-stem-extend flag-style dir-forced");