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_center_distance (Grob*me, Direction d)
244 int staff_center = 0;
245 int distance = (int) (d* (head_positions (me)[d] - staff_center));
246 return distance >? 0;
250 Stem::get_default_dir (Grob*me)
252 int du = get_center_distance (me,UP);
253 int dd = get_center_distance (me,DOWN);
256 return Direction (sign (dd -du));
258 return to_dir (me->get_grob_property ("neutral-direction"));
262 Stem::get_default_stem_end_position (Grob*me)
264 bool grace_b = to_boolean (me->get_grob_property ("grace"));
269 SCM scm_len = me->get_grob_property ("length");
270 if (gh_number_p (scm_len))
272 length_f = gh_scm2double (scm_len);
276 s = me->get_grob_property ("lengths");
277 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
278 a.push (gh_scm2double (ly_car (q)));
280 // stem uses half-spaces
281 length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
286 s = me->get_grob_property ("stem-shorten");
287 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
288 a.push (gh_scm2double (ly_car (q)));
291 // stem uses half-spaces
293 // fixme: use scm_list_n_ref () iso. array[]
294 Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
296 /* On boundary: shorten only half */
297 if (abs (chord_start_f (me)) == 0.5)
301 'set-default-stemlen' sets direction too
303 Direction dir = get_direction (me);
306 dir = get_default_dir (me);
307 Directional_element_interface::set (me, dir);
310 /* stems in unnatural (forced) direction should be shortened,
311 according to [Roush & Gourlay] */
312 if (chord_start_f (me)
313 && (get_direction (me) != get_default_dir (me)))
314 length_f -= shorten_f;
316 Interval hp = head_positions (me);
317 Real st = hp[dir] + dir * length_f;
319 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
320 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
324 Make a little room if we have a upflag and there is a dot.
325 previous approach was to lengthen the stem. This is not
326 good typesetting practice.
329 if (!beam_l (me) && dir == UP
330 && duration_log (me) > 2)
332 Grob * closest_to_flag = extremal_heads (me)[dir];
333 Grob * dots = closest_to_flag
334 ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
338 Real dp = Staff_symbol_referencer::position_f (dots);
339 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
340 / Staff_symbol_referencer::staff_space (me);
343 Very gory: add myself to the X-support of the parent,
344 which should be a dot-column.
346 if (dir * (st + flagy - dp) < 0.5)
348 Grob *par = dots->get_parent (X_AXIS);
350 if (Dot_column::has_interface (par))
352 Side_position_interface::add_support (par, me);
355 TODO: apply some better logic here. The flag is
356 curved inwards, so this will typically be too
372 the log of the duration (Number of hooks on the flag minus two)
375 Stem::duration_log (Grob*me)
377 SCM s = me->get_grob_property ("duration-log");
378 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
382 Stem::position_noteheads (Grob*me)
387 Link_array<Grob> heads =
388 Pointer_group_interface__extract_grobs (me, (Grob*)0, "heads");
390 heads.sort (compare_position);
391 Direction dir =get_direction (me);
397 Grob *hed = support_head (me);
398 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
399 for (int i=0; i < heads.size (); i++)
401 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
405 bool parity= true; // todo: make me settable.
406 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
407 for (int i=1; i < heads.size (); i ++)
409 Real p = Staff_symbol_referencer::position_f (heads[i]);
410 int dy =abs (lastpos- (int)p);
416 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
418 heads[i]->translate_axis (l * get_direction (me), X_AXIS);
429 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
431 Stem::before_line_breaking (SCM smob)
433 Grob*me = unsmob_grob (smob);
434 stem_end_position (me); // ugh. Trigger direction calc.
435 position_noteheads (me);
437 if (invisible_b (me))
439 me->remove_grob_property ("molecule-callback");
443 return SCM_UNSPECIFIED;
448 When in a beam with tuplet brackets, brew_mol is called early,
449 caching a wrong value.
451 MAKE_SCHEME_CALLBACK (Stem, height, 2);
453 Stem::height (SCM smob, SCM ax)
455 Axis a = (Axis)gh_scm2int (ax);
456 Grob * me = unsmob_grob (smob);
457 assert (a == Y_AXIS);
459 SCM mol = me->get_uncached_molecule ();
462 iv = unsmob_molecule (mol)->extent (a);
463 return ly_interval2scm (iv);
470 /* TODO: rename flag-style into something more appropriate,
471 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
472 "single" and "double". Needs more discussion.
474 String style, fstyle, staffline_offs;
475 SCM fst = me->get_grob_property ("flag-style");
476 if (gh_string_p (fst))
478 fstyle = ly_scm2string (fst);
481 SCM st = me->get_grob_property ("style");
482 if (gh_symbol_p (st))
484 style = (ly_scm2string (scm_symbol_to_string (st)));
490 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
492 if (String::compare_i (style, "mensural") == 0)
493 /* Mensural notation: For notes on staff lines, use different
494 flags than for notes between staff lines. The idea is that
495 flags are always vertically aligned with the staff lines,
496 regardless if the note head is on a staff line or between two
497 staff lines. In other words, the inner end of a flag always
498 touches a staff line.
503 /* Urrgh! We have to detect wether this stem ends on a staff
504 line or between two staff lines. But we can not call
505 stem_end_position(me) or get_default_stem_end_position(me),
506 since this encounters the flag and hence results in an
507 infinite recursion. However, in pure mensural notation,
508 there are no multiple note heads attached to a single stem,
509 neither is there usually need for using the stem_shorten
510 property (except for 32th and 64th notes, but that is not a
511 problem since the stem length in this case is augmented by
512 an integral multiple of staff_space). Hence, it should be
513 sufficient to just take the first note head, assume it's
514 the only one, look if it's on a staff line, and select the
515 flag's shape accordingly. In the worst case, the shape
516 looks slightly misplaced, but that will usually be the
517 programmer's fault (e.g. when trying to attach multiple
518 note heads to a single stem in mensural notation). */
521 perhaps the detection whether this correction is needed should
522 happen in a different place to avoid the recursion.
526 Grob *first = first_head(me);
527 int sz = Staff_symbol_referencer::line_count (me)-1;
528 int p = (int)rint (Staff_symbol_referencer::position_f (first));
529 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
533 staffline_offs = "2";
540 char c = (get_direction (me) == UP) ? 'u' : 'd';
542 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
544 = Font_interface::get_default_font (me)->find_by_name (index_str);
545 if (!fstyle.empty_b ())
546 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
550 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
552 Stem::dim_callback (SCM e, SCM ax)
554 Axis a = (Axis) gh_scm2int (ax);
555 assert (a == X_AXIS);
556 Grob *se = unsmob_grob (e);
558 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
562 r = flag (se).extent (X_AXIS);
564 return ly_interval2scm (r);
569 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
572 Stem::brew_molecule (SCM smob)
574 Grob*me = unsmob_grob (smob);
576 Direction d = get_direction (me);
579 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
580 Real y2 = stem_end_position (me);
582 Interval stem_y (y1 <? y2,y2 >? y1);
586 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
588 if (Grob *hed = support_head (me))
591 must not take ledgers into account.
593 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
594 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
596 y_attach = head_height.linear_combination (y_attach);
597 stem_y[Direction (-d)] += d * y_attach/dy;
600 if (!invisible_b (me))
602 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
604 * me->paper_l ()->get_var ("linethickness");
606 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
607 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
608 mol.add_molecule (ss);
611 if (!beam_l (me) && abs (duration_log (me)) > 2)
613 Molecule fl = flag (me);
614 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
615 mol.add_molecule (fl);
618 return mol.smobbed_copy ();
622 move the stem to right of the notehead if it is up.
624 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
626 Stem::off_callback (SCM element_smob, SCM)
628 Grob *me = unsmob_grob (element_smob);
631 if (Grob * f = first_head (me))
633 Interval head_wid = Note_head::head_extent(f, X_AXIS);
636 Note_head::stem_attachment_coordinate(f, X_AXIS);
638 Direction d = get_direction (me);
640 Real real_attach = head_wid.linear_combination (d * attach);
645 If not centered: correct for stem thickness.
650 = gh_scm2double (me->get_grob_property ("thickness"))
651 * me->paper_l ()->get_var ("linethickness");
654 r += - d * rule_thick * 0.5;
657 return gh_double2scm (r);
663 Stem::beam_l (Grob*me)
665 SCM b= me->get_grob_property ("beam");
666 return unsmob_grob (b);
670 // ugh still very long.
672 Stem::calc_stem_info (Grob*me)
674 SCM scm_info = me->get_grob_property ("stem-info");
676 if (gh_pair_p (scm_info ))
680 si.ideal_y = gh_scm2double (gh_car (scm_info));
681 si.max_y = gh_scm2double (gh_cadr (scm_info));
682 si.min_y = gh_scm2double (gh_caddr (scm_info));
687 Grob * beam = beam_l (me);
689 Direction beam_dir = Directional_element_interface::get (beam);
692 programming_error ("Beam dir not set.");
697 Real staff_space = Staff_symbol_referencer::staff_space (me);
698 Real half_space = staff_space / 2;
700 int multiplicity = Beam::get_multiplicity (beam);
701 Real interbeam_f = Beam::get_interbeam (beam);
703 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
705 info.ideal_y = chord_start_f (me);
707 // for simplicity, we calculate as if dir == UP
708 info.ideal_y *= beam_dir;
709 SCM grace_prop = me->get_grob_property ("grace");
711 bool grace_b = to_boolean (grace_prop);
716 s = me->get_grob_property ("beamed-minimum-lengths");
718 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
719 a.push (gh_scm2double (ly_car (q)));
722 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
723 s = me->get_grob_property ("beamed-lengths");
726 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
727 a.push (gh_scm2double (ly_car (q)));
729 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
731 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
732 /* normal beamed stem */
736 info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
738 info.min_y = info.ideal_y;
739 info.max_y = 1000; // INT_MAX;
741 info.ideal_y += stem_length;
742 info.min_y += minimum_length;
745 lowest beam of (UP) beam must never be lower than second staffline
747 Hmm, reference (Wanske?)
749 Although this (additional) rule is probably correct,
750 I expect that highest beam (UP) should also never be lower
751 than middle staffline, just as normal stems.
754 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
755 if (!grace_b && !no_extend_b)
757 /* highest beam of (UP) beam must never be lower than middle
759 lowest beam of (UP) beam must never be lower than second staffline
763 >? (- 2 * half_space - thick
764 + (multiplicity > 0) * thick
765 + interbeam_f * (multiplicity - 1));
771 info.ideal_y -= thick;
772 info.max_y = info.ideal_y;
773 info.min_y = - 1000 ; // INT_MAX;
775 info.ideal_y -= stem_length;
776 info.max_y -= minimum_length;
779 info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
781 s = beam->get_grob_property ("shorten");
783 info.ideal_y -= gh_scm2double (s);
785 Grob *common = me->common_refpoint (beam, Y_AXIS);
786 Real interstaff_f = beam_dir *
787 (me->relative_coordinate (common, Y_AXIS)
788 - beam->relative_coordinate (common, Y_AXIS));
790 info.ideal_y += interstaff_f;
791 info.min_y += interstaff_f;
792 info.max_y += interstaff_f ;
794 me->set_grob_property ("stem-info",
795 scm_list_n (gh_double2scm (info.ideal_y),
796 gh_double2scm (info.max_y),
797 gh_double2scm (info.min_y),
804 Stem::has_interface (Grob*m)
806 return m && m->has_interface (ly_symbol2scm ("stem-interface"));
809 ADD_INTERFACE (Stem,"stem-interface",
811 "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");