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> // rint
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) ? Note_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 (head_count (me) == 1)
144 return unsmob_grob (ly_car (me->get_grob_property ("note-heads")));
147 return first_head (me);
152 Stem::head_count (Grob*me)
154 return Pointer_group_interface::count (me, "note-heads");
158 The note head which forms one end of the stem.
161 Stem::first_head (Grob*me)
163 Direction d = get_direction (me);
166 return extremal_heads (me)[-d];
170 START is part where stem reaches `last' head.
173 Stem::extremal_heads (Grob*me)
175 const int inf = 1000000;
176 Drul_array<int> extpos;
180 Drul_array<Grob *> exthead;
181 exthead[LEFT] = exthead[RIGHT] =0;
183 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
185 Grob * n = unsmob_grob (ly_car (s));
188 int p = int (Staff_symbol_referencer::position_f (n));
192 if (d* p > d* extpos[d])
197 } while (flip (&d) != DOWN);
204 icmp (int const &a, int const &b)
210 Stem::note_head_positions (Grob *me)
213 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
215 Grob * n = unsmob_grob (ly_car (s));
216 int p = int (Staff_symbol_referencer::position_f (n));
227 Stem::add_head (Grob*me, Grob *n)
229 n->set_grob_property ("stem", me->self_scm ());
230 n->add_dependency (me);
232 if (Note_head::has_interface (n))
234 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
239 Stem::invisible_b (Grob*me)
241 return ! (head_count (me) && Note_head::balltype_i (support_head (me)) >= 1);
245 Stem::get_default_dir (Grob*me)
247 int staff_center = 0;
248 Interval hp = head_positions (me);
254 int udistance = (int) (UP * hp[UP] - staff_center);
255 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
257 if (sign (ddistance - udistance))
258 return Direction (sign (ddistance -udistance));
260 return to_dir (me->get_grob_property ("neutral-direction"));
264 Stem::get_default_stem_end_position (Grob*me)
266 bool grace_b = to_boolean (me->get_grob_property ("grace"));
271 SCM scm_len = me->get_grob_property ("length");
272 if (gh_number_p (scm_len))
274 length_f = gh_scm2double (scm_len);
278 s = me->get_grob_property ("lengths");
279 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
280 a.push (gh_scm2double (ly_car (q)));
282 // stem uses half-spaces
283 length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
288 s = me->get_grob_property ("stem-shorten");
289 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
290 a.push (gh_scm2double (ly_car (q)));
293 // stem uses half-spaces
295 // fixme: use scm_list_n_ref () iso. array[]
296 Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
298 /* On boundary: shorten only half */
299 if (abs (chord_start_f (me)) == 0.5)
303 'set-default-stemlen' sets direction too
305 Direction dir = get_direction (me);
308 dir = get_default_dir (me);
309 Directional_element_interface::set (me, dir);
312 /* stems in unnatural (forced) direction should be shortened,
313 according to [Roush & Gourlay] */
314 if (chord_start_f (me)
315 && (get_direction (me) != get_default_dir (me)))
316 length_f -= shorten_f;
318 Interval hp = head_positions (me);
319 Real st = hp[dir] + dir * length_f;
321 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
322 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
326 Make a little room if we have a upflag and there is a dot.
327 previous approach was to lengthen the stem. This is not
328 good typesetting practice.
331 if (!beam_l (me) && dir == UP
332 && duration_log (me) > 2)
334 Grob * closest_to_flag = extremal_heads (me)[dir];
335 Grob * dots = closest_to_flag
336 ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
340 Real dp = Staff_symbol_referencer::position_f (dots);
341 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
342 / Staff_symbol_referencer::staff_space (me);
345 Very gory: add myself to the X-support of the parent,
346 which should be a dot-column.
348 if (dir * (st + flagy - dp) < 0.5)
350 Grob *par = dots->get_parent (X_AXIS);
352 if (Dot_column::has_interface (par))
354 Side_position_interface::add_support (par, me);
357 TODO: apply some better logic here. The flag is
358 curved inwards, so this will typically be too
374 the log of the duration (Number of hooks on the flag minus two)
377 Stem::duration_log (Grob*me)
379 SCM s = me->get_grob_property ("duration-log");
380 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
384 Stem::position_noteheads (Grob*me)
386 if (!head_count (me))
389 Link_array<Grob> heads =
390 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
392 heads.sort (compare_position);
393 Direction dir =get_direction (me);
399 bool invisible = invisible_b (me);
402 thick = gh_scm2double (me->get_grob_property ("thickness"))
403 * me->paper_l ()->get_var ("linethickness");
406 Grob *hed = support_head (me);
407 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
408 for (int i=0; i < heads.size (); i++)
410 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
414 bool parity= true; // todo: make me settable.
415 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
416 for (int i=1; i < heads.size (); i ++)
418 Real p = Staff_symbol_referencer::position_f (heads[i]);
419 int dy =abs (lastpos- (int)p);
425 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
427 Direction d = get_direction (me);
428 heads[i]->translate_axis (l * d, X_AXIS);
431 heads[i]->translate_axis (-thick *2* d , X_AXIS);
436 For some cases we should kern some more: when the
437 distance between the next or prev note is too large, we'd
438 get large white gaps, eg.
457 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
459 Stem::before_line_breaking (SCM smob)
461 Grob*me = unsmob_grob (smob);
465 Do the calculations for visible stems, but also for invisible stems
466 with note heads (i.e. half notes.)
470 stem_end_position (me); // ugh. Trigger direction calc.
471 position_noteheads (me);
475 me->remove_grob_property ("molecule-callback");
478 return SCM_UNSPECIFIED;
483 When in a beam with tuplet brackets, brew_mol is called early,
484 caching a wrong value.
486 MAKE_SCHEME_CALLBACK (Stem, height, 2);
488 Stem::height (SCM smob, SCM ax)
490 Axis a = (Axis)gh_scm2int (ax);
491 Grob * me = unsmob_grob (smob);
492 assert (a == Y_AXIS);
494 SCM mol = me->get_uncached_molecule ();
497 iv = unsmob_molecule (mol)->extent (a);
498 return ly_interval2scm (iv);
505 /* TODO: rename flag-style into something more appropriate,
506 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
507 "single" and "double". Needs more discussion.
509 String style, fstyle, staffline_offs;
510 SCM fst = me->get_grob_property ("flag-style");
511 if (gh_string_p (fst))
513 fstyle = ly_scm2string (fst);
516 SCM st = me->get_grob_property ("style");
517 if (gh_symbol_p (st))
519 style = (ly_scm2string (scm_symbol_to_string (st)));
525 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
527 if (String::compare_i (style, "mensural") == 0)
528 /* Mensural notation: For notes on staff lines, use different
529 flags than for notes between staff lines. The idea is that
530 flags are always vertically aligned with the staff lines,
531 regardless if the note head is on a staff line or between two
532 staff lines. In other words, the inner end of a flag always
533 touches a staff line.
538 /* Urrgh! We have to detect wether this stem ends on a staff
539 line or between two staff lines. But we can not call
540 stem_end_position(me) or get_default_stem_end_position(me),
541 since this encounters the flag and hence results in an
542 infinite recursion. However, in pure mensural notation,
543 there are no multiple note heads attached to a single stem,
544 neither is there usually need for using the stem_shorten
545 property (except for 32th and 64th notes, but that is not a
546 problem since the stem length in this case is augmented by
547 an integral multiple of staff_space). Hence, it should be
548 sufficient to just take the first note head, assume it's
549 the only one, look if it's on a staff line, and select the
550 flag's shape accordingly. In the worst case, the shape
551 looks slightly misplaced, but that will usually be the
552 programmer's fault (e.g. when trying to attach multiple
553 note heads to a single stem in mensural notation). */
556 perhaps the detection whether this correction is needed should
557 happen in a different place to avoid the recursion.
561 Grob *first = first_head(me);
562 int sz = Staff_symbol_referencer::line_count (me)-1;
563 int p = (int)rint (Staff_symbol_referencer::position_f (first));
564 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
568 staffline_offs = "2";
575 char c = (get_direction (me) == UP) ? 'u' : 'd';
577 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
579 = Font_interface::get_default_font (me)->find_by_name (index_str);
580 if (!fstyle.empty_b ())
581 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
585 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
587 Stem::dim_callback (SCM e, SCM ax)
589 Axis a = (Axis) gh_scm2int (ax);
590 assert (a == X_AXIS);
591 Grob *se = unsmob_grob (e);
593 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
597 r = flag (se).extent (X_AXIS);
599 return ly_interval2scm (r);
604 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
607 Stem::brew_molecule (SCM smob)
609 Grob*me = unsmob_grob (smob);
611 Direction d = get_direction (me);
614 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
615 Real y2 = stem_end_position (me);
617 Interval stem_y (y1 <? y2,y2 >? y1);
621 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
623 if (Grob *hed = support_head (me))
626 must not take ledgers into account.
628 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
629 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
631 y_attach = head_height.linear_combination (y_attach);
632 stem_y[Direction (-d)] += d * y_attach/dy;
635 if (!invisible_b (me))
637 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
639 * me->paper_l ()->get_var ("linethickness");
641 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
642 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
643 mol.add_molecule (ss);
646 if (!beam_l (me) && abs (duration_log (me)) > 2)
648 Molecule fl = flag (me);
649 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
650 mol.add_molecule (fl);
653 return mol.smobbed_copy ();
657 move the stem to right of the notehead if it is up.
659 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
661 Stem::off_callback (SCM element_smob, SCM)
663 Grob *me = unsmob_grob (element_smob);
667 if (head_count (me) == 0)
669 return gh_double2scm (0.0);
672 if (Grob * f = first_head (me))
674 Interval head_wid = Note_head::head_extent(f, X_AXIS);
679 if (invisible_b (me))
684 attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
686 Direction d = get_direction (me);
688 Real real_attach = head_wid.linear_combination (d * attach);
693 If not centered: correct for stem thickness.
698 = gh_scm2double (me->get_grob_property ("thickness"))
699 * me->paper_l ()->get_var ("linethickness");
702 r += - d * rule_thick * 0.5;
705 return gh_double2scm (r);
711 Stem::beam_l (Grob*me)
713 SCM b= me->get_grob_property ("beam");
714 return unsmob_grob (b);
718 // ugh still very long.
720 Stem::calc_stem_info (Grob*me)
722 SCM scm_info = me->get_grob_property ("stem-info");
724 if (gh_pair_p (scm_info ))
728 si.ideal_y = gh_scm2double (gh_car (scm_info));
729 si.max_y = gh_scm2double (gh_cadr (scm_info));
730 si.min_y = gh_scm2double (gh_caddr (scm_info));
735 Grob * beam = beam_l (me);
737 Direction beam_dir = Directional_element_interface::get (beam);
740 programming_error ("Beam dir not set.");
745 Real staff_space = Staff_symbol_referencer::staff_space (me);
746 Real half_space = staff_space / 2;
748 int multiplicity = Beam::get_multiplicity (beam);
749 Real interbeam_f = Beam::get_interbeam (beam);
751 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
753 info.ideal_y = chord_start_f (me);
755 // for simplicity, we calculate as if dir == UP
758 UGH. This confuses issues more. fixme. --hwn
760 info.ideal_y *= beam_dir;
761 SCM grace_prop = me->get_grob_property ("grace");
763 bool grace_b = to_boolean (grace_prop);
768 s = me->get_grob_property ("beamed-minimum-lengths");
770 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
771 a.push (gh_scm2double (ly_car (q)));
774 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
775 s = me->get_grob_property ("beamed-lengths");
778 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
779 a.push (gh_scm2double (ly_car (q)));
781 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
785 This sucks -- On a kneed beam, *all* stems are kneed, not half of them.
787 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
788 /* normal beamed stem */
792 info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
794 info.min_y = info.ideal_y;
795 info.max_y = 1000; // INT_MAX;
797 info.ideal_y += stem_length;
798 info.min_y += minimum_length;
801 lowest beam of (UP) beam must never be lower than second staffline
803 Hmm, reference (Wanske?)
805 Although this (additional) rule is probably correct,
806 I expect that highest beam (UP) should also never be lower
807 than middle staffline, just as normal stems.
810 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
811 if (!grace_b && !no_extend_b)
813 /* highest beam of (UP) beam must never be lower than middle
815 lowest beam of (UP) beam must never be lower than second staffline
819 >? (- 2 * half_space - thick
820 + (multiplicity > 0) * thick
821 + interbeam_f * (multiplicity - 1));
827 info.ideal_y -= thick + stem_length;
828 info.max_y = info.ideal_y - minimum_length;
831 We shouldn't invert the stems, so we set minimum at 0.
836 info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
838 s = beam->get_grob_property ("shorten");
840 info.ideal_y -= gh_scm2double (s);
842 Grob *common = me->common_refpoint (beam, Y_AXIS);
843 Real interstaff_f = beam_dir *
844 (me->relative_coordinate (common, Y_AXIS)
845 - beam->relative_coordinate (common, Y_AXIS));
847 info.ideal_y += interstaff_f;
848 info.min_y += interstaff_f;
849 info.max_y += interstaff_f ;
851 me->set_grob_property ("stem-info",
852 scm_list_n (gh_double2scm (info.ideal_y),
853 gh_double2scm (info.max_y),
854 gh_double2scm (info.min_y),
860 ADD_INTERFACE (Stem,"stem-interface",
862 "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");