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 i, Direction d)
40 SCM pair = me->get_grob_property ("beaming");
42 if (!gh_pair_p (pair))
44 pair = gh_cons (gh_int2scm (-1),gh_int2scm (-1));
45 me-> set_grob_property ("beaming", pair);
47 index_set_cell (pair, d, gh_int2scm (i));
51 Stem::beam_count (Grob*me,Direction d)
53 SCM p=me->get_grob_property ("beaming");
55 return gh_scm2int (index_cell (p,d));
61 Stem::head_positions (Grob*me)
69 Drul_array<Grob*> e (extremal_heads (me));
71 return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
72 Staff_symbol_referencer::position_f (e[UP]));
77 Stem::chord_start_y (Grob*me)
79 return head_positions (me)[get_direction (me)]
80 * Staff_symbol_referencer::staff_space (me)/2.0;
84 Stem::stem_end_position (Grob*me)
86 SCM p =me->get_grob_property ("stem-end-position");
90 pos = get_default_stem_end_position (me);
91 me->set_grob_property ("stem-end-position", gh_double2scm (pos));
94 pos = gh_scm2double (p);
100 Stem::get_direction (Grob*me)
102 Direction d = Directional_element_interface::get (me);
106 d = get_default_dir (me);
108 Directional_element_interface::set (me, d);
115 Stem::set_stemend (Grob*me, Real se)
118 Direction d= get_direction (me);
120 if (d && d * head_positions (me)[get_direction (me)] >= se*d)
121 me->warning (_ ("Weird stem size; check for narrow beams"));
123 me->set_grob_property ("stem-end-position", gh_double2scm (se));
128 Note head that determines hshift for upstems
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.
160 Stem::first_head (Grob*me)
162 Direction d = get_direction (me);
165 return extremal_heads (me)[-d];
169 START is part where stem reaches `last' head.
172 Stem::extremal_heads (Grob*me)
174 const int inf = 1000000;
175 Drul_array<int> extpos;
179 Drul_array<Grob *> exthead;
180 exthead[LEFT] = exthead[RIGHT] =0;
182 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
184 Grob * n = unsmob_grob (ly_car (s));
187 int p = int (Staff_symbol_referencer::position_f (n));
191 if (d* p > d* extpos[d])
196 } while (flip (&d) != DOWN);
203 icmp (int const &a, int const &b)
209 Stem::note_head_positions (Grob *me)
212 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
214 Grob * n = unsmob_grob (ly_car (s));
215 int p = int (Staff_symbol_referencer::position_f (n));
226 Stem::add_head (Grob*me, Grob *n)
228 n->set_grob_property ("stem", me->self_scm ());
229 n->add_dependency (me);
231 if (Note_head::has_interface (n))
233 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
238 Stem::invisible_b (Grob*me)
240 return ! (head_count (me) && Note_head::balltype_i (support_head (me)) >= 1);
244 Stem::get_default_dir (Grob*me)
246 int staff_center = 0;
247 Interval hp = head_positions (me);
253 int udistance = (int) (UP * hp[UP] - staff_center);
254 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
256 if (sign (ddistance - udistance))
257 return Direction (sign (ddistance -udistance));
259 return to_dir (me->get_grob_property ("neutral-direction"));
263 Stem::get_default_stem_end_position (Grob*me)
265 bool grace_b = to_boolean (me->get_grob_property ("grace"));
270 SCM scm_len = me->get_grob_property ("length");
271 if (gh_number_p (scm_len))
273 length_f = gh_scm2double (scm_len);
277 s = me->get_grob_property ("lengths");
278 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
279 a.push (gh_scm2double (ly_car (q)));
281 // stem uses half-spaces
282 length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
287 s = me->get_grob_property ("stem-shorten");
288 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
289 a.push (gh_scm2double (ly_car (q)));
292 // stem uses half-spaces
294 // fixme: use scm_list_n_ref () iso. array[]
295 Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
297 /* On boundary: shorten only half */
298 if (abs (chord_start_y (me)) == 0.5)
302 'set-default-stemlen' sets direction too
304 Direction dir = get_direction (me);
307 dir = get_default_dir (me);
308 Directional_element_interface::set (me, dir);
311 /* stems in unnatural (forced) direction should be shortened,
312 according to [Roush & Gourlay] */
313 if (chord_start_y (me)
314 && (get_direction (me) != get_default_dir (me)))
315 length_f -= shorten_f;
317 Interval hp = head_positions (me);
318 Real st = hp[dir] + dir * length_f;
320 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
321 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
325 Make a little room if we have a upflag and there is a dot.
326 previous approach was to lengthen the stem. This is not
327 good typesetting practice.
330 if (!beam_l (me) && dir == UP
331 && duration_log (me) > 2)
333 Grob * closest_to_flag = extremal_heads (me)[dir];
334 Grob * dots = closest_to_flag
335 ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
339 Real dp = Staff_symbol_referencer::position_f (dots);
340 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
341 / Staff_symbol_referencer::staff_space (me);
344 Very gory: add myself to the X-support of the parent,
345 which should be a dot-column.
347 if (dir * (st + flagy - dp) < 0.5)
349 Grob *par = dots->get_parent (X_AXIS);
351 if (Dot_column::has_interface (par))
353 Side_position_interface::add_support (par, me);
356 TODO: apply some better logic here. The flag is
357 curved inwards, so this will typically be too
373 the log of the duration (Number of hooks on the flag minus two)
376 Stem::duration_log (Grob*me)
378 SCM s = me->get_grob_property ("duration-log");
379 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
383 Stem::position_noteheads (Grob*me)
385 if (!head_count (me))
388 Link_array<Grob> heads =
389 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
391 heads.sort (compare_position);
392 Direction dir =get_direction (me);
398 bool invisible = invisible_b (me);
401 thick = gh_scm2double (me->get_grob_property ("thickness"))
402 * me->paper_l ()->get_var ("linethickness");
405 Grob *hed = support_head (me);
406 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
407 for (int i=0; i < heads.size (); i++)
409 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
413 bool parity= true; // todo: make me settable.
414 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
415 for (int i=1; i < heads.size (); i ++)
417 Real p = Staff_symbol_referencer::position_f (heads[i]);
418 int dy =abs (lastpos- (int)p);
424 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
426 Direction d = get_direction (me);
427 heads[i]->translate_axis (l * d, X_AXIS);
430 heads[i]->translate_axis (-thick *2* d , X_AXIS);
435 For some cases we should kern some more: when the
436 distance between the next or prev note is too large, we'd
437 get large white gaps, eg.
456 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
458 Stem::before_line_breaking (SCM smob)
460 Grob*me = unsmob_grob (smob);
464 Do the calculations for visible stems, but also for invisible stems
465 with note heads (i.e. half notes.)
469 stem_end_position (me); // ugh. Trigger direction calc.
470 position_noteheads (me);
474 me->remove_grob_property ("molecule-callback");
477 return SCM_UNSPECIFIED;
482 When in a beam with tuplet brackets, brew_mol is called early,
483 caching a wrong value.
485 MAKE_SCHEME_CALLBACK (Stem, height, 2);
487 Stem::height (SCM smob, SCM ax)
489 Axis a = (Axis)gh_scm2int (ax);
490 Grob * me = unsmob_grob (smob);
491 assert (a == Y_AXIS);
493 SCM mol = me->get_uncached_molecule ();
496 iv = unsmob_molecule (mol)->extent (a);
497 return ly_interval2scm (iv);
504 /* TODO: rename flag-style into something more appropriate,
505 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
506 "single" and "double". Needs more discussion.
508 String style, fstyle, staffline_offs;
509 SCM fst = me->get_grob_property ("flag-style");
510 if (gh_string_p (fst))
512 fstyle = ly_scm2string (fst);
515 SCM st = me->get_grob_property ("style");
516 if (gh_symbol_p (st))
518 style = (ly_scm2string (scm_symbol_to_string (st)));
524 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
526 if (String::compare_i (style, "mensural") == 0)
527 /* Mensural notation: For notes on staff lines, use different
528 flags than for notes between staff lines. The idea is that
529 flags are always vertically aligned with the staff lines,
530 regardless if the note head is on a staff line or between two
531 staff lines. In other words, the inner end of a flag always
532 touches a staff line.
537 /* Urrgh! We have to detect wether this stem ends on a staff
538 line or between two staff lines. But we can not call
539 stem_end_position(me) or get_default_stem_end_position(me),
540 since this encounters the flag and hence results in an
541 infinite recursion. However, in pure mensural notation,
542 there are no multiple note heads attached to a single stem,
543 neither is there usually need for using the stem_shorten
544 property (except for 32th and 64th notes, but that is not a
545 problem since the stem length in this case is augmented by
546 an integral multiple of staff_space). Hence, it should be
547 sufficient to just take the first note head, assume it's
548 the only one, look if it's on a staff line, and select the
549 flag's shape accordingly. In the worst case, the shape
550 looks slightly misplaced, but that will usually be the
551 programmer's fault (e.g. when trying to attach multiple
552 note heads to a single stem in mensural notation). */
555 perhaps the detection whether this correction is needed should
556 happen in a different place to avoid the recursion.
560 Grob *first = first_head(me);
561 int sz = Staff_symbol_referencer::line_count (me)-1;
562 int p = (int)rint (Staff_symbol_referencer::position_f (first));
563 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
567 staffline_offs = "2";
574 char c = (get_direction (me) == UP) ? 'u' : 'd';
576 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
578 = Font_interface::get_default_font (me)->find_by_name (index_str);
579 if (!fstyle.empty_b ())
580 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
584 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
586 Stem::dim_callback (SCM e, SCM ax)
588 Axis a = (Axis) gh_scm2int (ax);
589 assert (a == X_AXIS);
590 Grob *se = unsmob_grob (e);
592 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
596 r = flag (se).extent (X_AXIS);
598 return ly_interval2scm (r);
603 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
606 Stem::brew_molecule (SCM smob)
608 Grob*me = unsmob_grob (smob);
610 Direction d = get_direction (me);
613 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
614 Real y2 = stem_end_position (me);
616 Interval stem_y (y1 <? y2,y2 >? y1);
620 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
622 if (Grob *hed = support_head (me))
625 must not take ledgers into account.
627 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
628 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
630 y_attach = head_height.linear_combination (y_attach);
631 stem_y[Direction (-d)] += d * y_attach/dy;
634 if (!invisible_b (me))
636 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
638 * me->paper_l ()->get_var ("linethickness");
640 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
641 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
642 mol.add_molecule (ss);
645 if (!beam_l (me) && abs (duration_log (me)) > 2)
647 Molecule fl = flag (me);
648 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
649 mol.add_molecule (fl);
652 return mol.smobbed_copy ();
656 move the stem to right of the notehead if it is up.
658 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
660 Stem::off_callback (SCM element_smob, SCM)
662 Grob *me = unsmob_grob (element_smob);
666 if (head_count (me) == 0)
668 return gh_double2scm (0.0);
671 if (Grob * f = first_head (me))
673 Interval head_wid = Note_head::head_extent(f, X_AXIS);
678 if (invisible_b (me))
683 attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
685 Direction d = get_direction (me);
687 Real real_attach = head_wid.linear_combination (d * attach);
692 If not centered: correct for stem thickness.
697 = gh_scm2double (me->get_grob_property ("thickness"))
698 * me->paper_l ()->get_var ("linethickness");
701 r += - d * rule_thick * 0.5;
704 return gh_double2scm (r);
710 Stem::beam_l (Grob*me)
712 SCM b= me->get_grob_property ("beam");
713 return unsmob_grob (b);
717 // ugh still very long.
719 Stem::calc_stem_info (Grob*me)
721 SCM scm_info = me->get_grob_property ("stem-info");
723 if (gh_pair_p (scm_info ))
727 si.ideal_y = gh_scm2double (gh_car (scm_info));
728 si.max_y = gh_scm2double (gh_cadr (scm_info));
729 si.min_y = gh_scm2double (gh_caddr (scm_info));
734 Grob * beam = beam_l (me);
736 Direction beam_dir = Directional_element_interface::get (beam);
739 programming_error ("Beam dir not set.");
744 Real staff_space = Staff_symbol_referencer::staff_space (me);
745 Real half_space = staff_space / 2;
747 int multiplicity = Beam::get_multiplicity (beam);
748 Real interbeam_f = Beam::get_interbeam (beam);
750 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
752 info.ideal_y = chord_start_y (me);
754 // for simplicity, we calculate as if dir == UP
757 UGH. This confuses issues more. fixme. --hwn
759 info.ideal_y *= beam_dir;
760 SCM grace_prop = me->get_grob_property ("grace");
762 bool grace_b = to_boolean (grace_prop);
767 s = me->get_grob_property ("beamed-minimum-lengths");
769 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
770 a.push (gh_scm2double (ly_car (q)));
773 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
774 s = me->get_grob_property ("beamed-lengths");
777 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
778 a.push (gh_scm2double (ly_car (q)));
780 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
784 This sucks -- On a kneed beam, *all* stems are kneed, not half of them.
786 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
787 /* normal beamed stem */
791 info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
793 info.min_y = info.ideal_y;
794 info.max_y = 1000; // INT_MAX;
796 info.ideal_y += stem_length;
797 info.min_y += minimum_length;
800 lowest beam of (UP) beam must never be lower than second staffline
802 Hmm, reference (Wanske?)
804 Although this (additional) rule is probably correct,
805 I expect that highest beam (UP) should also never be lower
806 than middle staffline, just as normal stems.
809 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
810 if (!grace_b && !no_extend_b)
812 /* highest beam of (UP) beam must never be lower than middle
814 lowest beam of (UP) beam must never be lower than second staffline
818 >? (- 2 * half_space - thick
819 + (multiplicity > 0) * thick
820 + interbeam_f * (multiplicity - 1));
826 info.ideal_y -= thick + stem_length;
827 info.max_y = info.ideal_y - minimum_length;
830 We shouldn't invert the stems, so we set minimum at 0.
835 info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
837 s = beam->get_grob_property ("shorten");
839 info.ideal_y -= gh_scm2double (s);
841 Grob *common = me->common_refpoint (beam, Y_AXIS);
842 Real interstaff_f = beam_dir *
843 (me->relative_coordinate (common, Y_AXIS)
844 - beam->relative_coordinate (common, Y_AXIS));
846 info.ideal_y += interstaff_f;
847 info.min_y += interstaff_f;
848 info.max_y += interstaff_f ;
850 me->set_grob_property ("stem-info",
851 scm_list_n (gh_double2scm (info.ideal_y),
852 gh_double2scm (info.max_y),
853 gh_double2scm (info.min_y),
859 ADD_INTERFACE (Stem,"stem-interface",
861 "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 note-heads direction length style no-stem-extend flag-style dir-forced");