''
[lilypond.git] / lily / stem.cc
bloba3255a0da178ac4e115c7fa034116c94c800c9ef
1 /*
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
11 TODO: fix naming.
13 Stem-end, chord-start, etc. is all confusing naming.
16 #include <math.h> // rint
18 #include "lookup.hh"
19 #include "directional-element-interface.hh"
20 #include "note-head.hh"
21 #include "stem.hh"
22 #include "debug.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"
28 #include "misc.hh"
29 #include "beam.hh"
30 #include "rest.hh"
31 #include "group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "spanner.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
37 void
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));
50 int
51 Stem::beam_count (Grob*me,Direction d)
53 SCM p=me->get_grob_property ("beaming");
54 if (gh_pair_p (p))
55 return gh_scm2int (index_cell (p,d));
56 else
57 return -1;
60 Interval
61 Stem::head_positions (Grob*me)
63 if (!head_count (me))
65 Interval iv;
66 return iv;
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]));
76 Real
77 Stem::chord_start_y (Grob*me)
79 return head_positions (me)[get_direction (me)]
80 * Staff_symbol_referencer::staff_space (me)/2.0;
83 Real
84 Stem::stem_end_position (Grob*me)
86 SCM p =me->get_grob_property ("stem-end-position");
87 Real pos;
88 if (!gh_number_p (p))
90 pos = get_default_stem_end_position (me);
91 me->set_grob_property ("stem-end-position", gh_double2scm (pos));
93 else
94 pos = gh_scm2double (p);
96 return pos;
99 Direction
100 Stem::get_direction (Grob*me)
102 Direction d = Directional_element_interface::get (me);
104 if (!d)
106 d = get_default_dir (me);
107 // urg, AAARGH!
108 Directional_element_interface::set (me, d);
110 return d ;
114 void
115 Stem::set_stemend (Grob*me, Real se)
117 // todo: margins
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
130 Grob*
131 Stem::support_head (Grob*me)
133 SCM h = me->get_grob_property ("support-head");
134 Grob * nh = unsmob_grob (h);
135 if (nh)
136 return nh;
137 else if (head_count (me) == 1)
140 UGH.
143 return unsmob_grob (ly_car (me->get_grob_property ("note-heads")));
145 else
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.
159 Grob*
160 Stem::first_head (Grob*me)
162 Direction d = get_direction (me);
163 if (!d)
164 return 0;
165 return extremal_heads (me)[-d];
169 The note head opposite to the first head.
171 Grob*
172 Stem::last_head (Grob*me)
174 Direction d = get_direction (me);
175 if (!d)
176 return 0;
177 return extremal_heads (me)[d];
181 START is part where stem reaches `last' head.
183 Drul_array<Grob*>
184 Stem::extremal_heads (Grob*me)
186 const int inf = 1000000;
187 Drul_array<int> extpos;
188 extpos[DOWN] = inf;
189 extpos[UP] = -inf;
191 Drul_array<Grob *> exthead;
192 exthead[LEFT] = exthead[RIGHT] =0;
194 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
196 Grob * n = unsmob_grob (ly_car (s));
199 int p = int (Staff_symbol_referencer::position_f (n));
201 Direction d = LEFT;
202 do {
203 if (d* p > d* extpos[d])
205 exthead[d] = n;
206 extpos[d] = p;
208 } while (flip (&d) != DOWN);
211 return exthead;
214 static int
215 icmp (int const &a, int const &b)
217 return a-b;
220 Array<int>
221 Stem::note_head_positions (Grob *me)
223 Array<int> ps ;
224 for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
226 Grob * n = unsmob_grob (ly_car (s));
227 int p = int (Staff_symbol_referencer::position_f (n));
229 ps.push (p);
232 ps.sort (icmp);
233 return ps;
237 void
238 Stem::add_head (Grob*me, Grob *n)
240 n->set_grob_property ("stem", me->self_scm ());
241 n->add_dependency (me);
243 if (Note_head::has_interface (n))
245 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
249 bool
250 Stem::invisible_b (Grob*me)
252 return ! (head_count (me) && Note_head::balltype_i (support_head (me)) >= 1);
255 Direction
256 Stem::get_default_dir (Grob*me)
258 int staff_center = 0;
259 Interval hp = head_positions (me);
260 if (hp.empty_b())
262 return CENTER;
265 int udistance = (int) (UP * hp[UP] - staff_center);
266 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
268 if (sign (ddistance - udistance))
269 return Direction (sign (ddistance -udistance));
271 return to_dir (me->get_grob_property ("neutral-direction"));
274 Real
275 Stem::get_default_stem_end_position (Grob*me)
277 bool grace_b = to_boolean (me->get_grob_property ("grace"));
278 SCM s;
279 Array<Real> a;
281 Real length_f = 0.;
282 SCM scm_len = me->get_grob_property ("length");
283 if (gh_number_p (scm_len))
285 length_f = gh_scm2double (scm_len);
287 else
289 s = me->get_grob_property ("lengths");
290 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
291 a.push (gh_scm2double (ly_car (q)));
293 // stem uses half-spaces
294 length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
298 a.clear ();
299 s = me->get_grob_property ("stem-shorten");
300 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
301 a.push (gh_scm2double (ly_car (q)));
304 // stem uses half-spaces
306 // fixme: use scm_list_n_ref () iso. array[]
307 Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
309 /* On boundary: shorten only half */
310 if (abs (chord_start_y (me)) == 0.5)
311 shorten_f *= 0.5;
313 /* URGURGURG
314 'set-default-stemlen' sets direction too
316 Direction dir = get_direction (me);
317 if (!dir)
319 dir = get_default_dir (me);
320 Directional_element_interface::set (me, dir);
323 /* stems in unnatural (forced) direction should be shortened,
324 according to [Roush & Gourlay] */
325 if (chord_start_y (me)
326 && (get_direction (me) != get_default_dir (me)))
327 length_f -= shorten_f;
329 Interval hp = head_positions (me);
330 Real st = hp[dir] + dir * length_f;
332 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
333 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
334 st = 0.0;
337 Make a little room if we have a upflag and there is a dot.
338 previous approach was to lengthen the stem. This is not
339 good typesetting practice.
342 if (!beam_l (me) && dir == UP
343 && duration_log (me) > 2)
345 Grob * closest_to_flag = extremal_heads (me)[dir];
346 Grob * dots = closest_to_flag
347 ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
349 if (dots)
351 Real dp = Staff_symbol_referencer::position_f (dots);
352 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
353 / Staff_symbol_referencer::staff_space (me);
356 Very gory: add myself to the X-support of the parent,
357 which should be a dot-column.
359 if (dir * (st + flagy - dp) < 0.5)
361 Grob *par = dots->get_parent (X_AXIS);
363 if (Dot_column::has_interface (par))
365 Side_position_interface::add_support (par, me);
368 TODO: apply some better logic here. The flag is
369 curved inwards, so this will typically be too
370 much.
378 return st;
385 the log of the duration (Number of hooks on the flag minus two)
388 Stem::duration_log (Grob*me)
390 SCM s = me->get_grob_property ("duration-log");
391 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
394 void
395 Stem::position_noteheads (Grob*me)
397 if (!head_count (me))
398 return;
400 Link_array<Grob> heads =
401 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
403 heads.sort (compare_position);
404 Direction dir =get_direction (me);
406 if (dir < 0)
407 heads.reverse ();
410 bool invisible = invisible_b (me);
411 Real thick = 0.0;
412 if (invisible)
413 thick = gh_scm2double (me->get_grob_property ("thickness"))
414 * me->paper_l ()->get_var ("linethickness");
417 Grob *hed = support_head (me);
418 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
419 for (int i=0; i < heads.size (); i++)
421 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
422 X_AXIS);
425 bool parity= true; // todo: make me settable.
426 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
427 for (int i=1; i < heads.size (); i ++)
429 Real p = Staff_symbol_referencer::position_f (heads[i]);
430 int dy =abs (lastpos- (int)p);
432 if (dy <= 1)
434 if (parity)
436 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
438 Direction d = get_direction (me);
439 heads[i]->translate_axis (l * d, X_AXIS);
441 if (invisible_b(me))
442 heads[i]->translate_axis (-thick *2* d , X_AXIS);
445 /* TODO:
447 For some cases we should kern some more: when the
448 distance between the next or prev note is too large, we'd
449 get large white gaps, eg.
453 |X <- kern this.
459 parity = !parity;
461 else
462 parity = true;
464 lastpos = int (p);
468 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
470 Stem::before_line_breaking (SCM smob)
472 Grob*me = unsmob_grob (smob);
476 Do the calculations for visible stems, but also for invisible stems
477 with note heads (i.e. half notes.)
479 if (head_count (me))
481 stem_end_position (me); // ugh. Trigger direction calc.
482 position_noteheads (me);
484 else
486 me->remove_grob_property ("molecule-callback");
489 return SCM_UNSPECIFIED;
493 ugh.
494 When in a beam with tuplet brackets, brew_mol is called early,
495 caching a wrong value.
497 MAKE_SCHEME_CALLBACK (Stem, height, 2);
499 Stem::height (SCM smob, SCM ax)
501 Axis a = (Axis)gh_scm2int (ax);
502 Grob * me = unsmob_grob (smob);
503 assert (a == Y_AXIS);
505 SCM mol = me->get_uncached_molecule ();
506 Interval iv;
507 if (mol != SCM_EOL)
508 iv = unsmob_molecule (mol)->extent (a);
509 return ly_interval2scm (iv);
513 Molecule
514 Stem::flag (Grob*me)
516 /* TODO: rename flag-style into something more appropriate,
517 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
518 "single" and "double". Needs more discussion.
520 String style, fstyle, staffline_offs;
521 SCM fst = me->get_grob_property ("flag-style");
522 if (gh_string_p (fst))
524 fstyle = ly_scm2string (fst);
527 SCM st = me->get_grob_property ("style");
528 if (gh_symbol_p (st))
530 style = (ly_scm2string (scm_symbol_to_string (st)));
532 else
534 style = "";
536 bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
538 if (String::compare_i (style, "mensural") == 0)
539 /* Mensural notation: For notes on staff lines, use different
540 flags than for notes between staff lines. The idea is that
541 flags are always vertically aligned with the staff lines,
542 regardless if the note head is on a staff line or between two
543 staff lines. In other words, the inner end of a flag always
544 touches a staff line.
547 if (adjust)
549 /* Urrgh! We have to detect wether this stem ends on a staff
550 line or between two staff lines. But we can not call
551 stem_end_position(me) or get_default_stem_end_position(me),
552 since this encounters the flag and hence results in an
553 infinite recursion. However, in pure mensural notation,
554 there are no multiple note heads attached to a single stem,
555 neither is there usually need for using the stem_shorten
556 property (except for 32th and 64th notes, but that is not a
557 problem since the stem length in this case is augmented by
558 an integral multiple of staff_space). Hence, it should be
559 sufficient to just take the first note head, assume it's
560 the only one, look if it's on a staff line, and select the
561 flag's shape accordingly. In the worst case, the shape
562 looks slightly misplaced, but that will usually be the
563 programmer's fault (e.g. when trying to attach multiple
564 note heads to a single stem in mensural notation). */
567 perhaps the detection whether this correction is needed should
568 happen in a different place to avoid the recursion.
570 --hwn.
572 Grob *first = first_head(me);
573 int sz = Staff_symbol_referencer::line_count (me)-1;
574 int p = (int)rint (Staff_symbol_referencer::position_f (first));
575 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
577 else
579 staffline_offs = "2";
582 else
584 staffline_offs = "";
586 char c = (get_direction (me) == UP) ? 'u' : 'd';
587 String index_str
588 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
589 Molecule m
590 = Font_interface::get_default_font (me)->find_by_name (index_str);
591 if (!fstyle.empty_b ())
592 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
593 return m;
596 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
598 Stem::dim_callback (SCM e, SCM ax)
600 Axis a = (Axis) gh_scm2int (ax);
601 assert (a == X_AXIS);
602 Grob *se = unsmob_grob (e);
603 Interval r (0, 0);
604 if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
605 ; // TODO!
606 else
608 r = flag (se).extent (X_AXIS);
610 return ly_interval2scm (r);
615 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
618 Stem::brew_molecule (SCM smob)
620 Grob*me = unsmob_grob (smob);
621 Molecule mol;
622 Direction d = get_direction (me);
626 Real y1;
629 This is required to avoid stems passing in tablature chords...
634 TODO: make the stem start a direction ?
637 if (to_boolean (me->get_grob_property ("avoid-note-head")))
639 y1 = Staff_symbol_referencer::position_f (last_head (me));
641 else
643 y1 = Staff_symbol_referencer::position_f (first_head (me));
646 Real y2 = stem_end_position (me);
648 Interval stem_y (y1 <? y2,y2 >? y1);
651 // dy?
652 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
654 if (Grob *hed = support_head (me))
657 must not take ledgers into account.
659 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
660 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
662 y_attach = head_height.linear_combination (y_attach);
663 stem_y[Direction (-d)] += d * y_attach/dy;
666 if (!invisible_b (me))
668 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
669 // URG
670 * me->paper_l ()->get_var ("linethickness");
672 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
673 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
674 mol.add_molecule (ss);
677 if (!beam_l (me) && abs (duration_log (me)) > 2)
679 Molecule fl = flag (me);
680 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
681 mol.add_molecule (fl);
684 return mol.smobbed_copy ();
688 move the stem to right of the notehead if it is up.
690 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
692 Stem::off_callback (SCM element_smob, SCM)
694 Grob *me = unsmob_grob (element_smob);
696 Real r=0;
698 if (head_count (me) == 0)
700 return gh_double2scm (0.0);
703 if (Grob * f = first_head (me))
705 Interval head_wid = Note_head::head_extent(f, X_AXIS);
708 Real attach =0.0;
710 if (invisible_b (me))
712 attach = 0.0;
714 else
715 attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
717 Direction d = get_direction (me);
719 Real real_attach = head_wid.linear_combination (d * attach);
721 r = real_attach;
724 If not centered: correct for stem thickness.
726 if (attach)
728 Real rule_thick
729 = gh_scm2double (me->get_grob_property ("thickness"))
730 * me->paper_l ()->get_var ("linethickness");
733 r += - d * rule_thick * 0.5;
736 return gh_double2scm (r);
741 Grob*
742 Stem::beam_l (Grob*me)
744 SCM b= me->get_grob_property ("beam");
745 return unsmob_grob (b);
749 // ugh still very long.
750 Stem_info
751 Stem::calc_stem_info (Grob*me)
753 SCM scm_info = me->get_grob_property ("stem-info");
755 if (gh_pair_p (scm_info ))
757 Stem_info si ;
759 si.ideal_y = gh_scm2double (gh_car (scm_info));
760 si.max_y = gh_scm2double (gh_cadr (scm_info));
761 si.min_y = gh_scm2double (gh_caddr (scm_info));
763 return si;
766 Grob * beam = beam_l (me);
768 Direction beam_dir = Directional_element_interface::get (beam);
769 if (!beam_dir)
771 programming_error ("Beam dir not set.");
772 beam_dir = UP;
776 Real staff_space = Staff_symbol_referencer::staff_space (me);
777 Real half_space = staff_space / 2;
779 int multiplicity = Beam::get_multiplicity (beam);
780 Real interbeam_f = Beam::get_interbeam (beam);
782 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
783 Stem_info info;
784 info.ideal_y = chord_start_y (me);
786 // for simplicity, we calculate as if dir == UP
789 UGH. This confuses issues more. fixme. --hwn
791 info.ideal_y *= beam_dir;
792 SCM grace_prop = me->get_grob_property ("grace");
794 bool grace_b = to_boolean (grace_prop);
796 Array<Real> a;
797 SCM s;
799 s = me->get_grob_property ("beamed-minimum-lengths");
800 a.clear ();
801 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
802 a.push (gh_scm2double (ly_car (q)));
805 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
806 s = me->get_grob_property ("beamed-lengths");
808 a.clear ();
809 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
810 a.push (gh_scm2double (ly_car (q)));
812 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
816 This sucks -- On a kneed beam, *all* stems are kneed, not half of them.
818 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
819 /* normal beamed stem */
821 if (multiplicity)
823 info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
825 info.min_y = info.ideal_y;
826 info.max_y = 1000; // INT_MAX;
828 info.ideal_y += stem_length;
829 info.min_y += minimum_length;
832 lowest beam of (UP) beam must never be lower than second staffline
834 Hmm, reference (Wanske?)
836 Although this (additional) rule is probably correct,
837 I expect that highest beam (UP) should also never be lower
838 than middle staffline, just as normal stems.
841 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
842 if (!grace_b && !no_extend_b)
844 /* highest beam of (UP) beam must never be lower than middle
845 staffline
846 lowest beam of (UP) beam must never be lower than second staffline
848 info.min_y =
849 info.min_y >? 0
850 >? (- 2 * half_space - thick
851 + (multiplicity > 0) * thick
852 + interbeam_f * (multiplicity - 1));
855 else
856 /* knee */
858 info.ideal_y -= thick + stem_length;
859 info.max_y = info.ideal_y - minimum_length;
862 We shouldn't invert the stems, so we set minimum at 0.
864 info.min_y = 0.5;
867 info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
869 s = beam->get_grob_property ("shorten");
870 if (gh_number_p (s))
871 info.ideal_y -= gh_scm2double (s);
873 Grob *common = me->common_refpoint (beam, Y_AXIS);
874 Real interstaff_f = beam_dir *
875 (me->relative_coordinate (common, Y_AXIS)
876 - beam->relative_coordinate (common, Y_AXIS));
878 info.ideal_y += interstaff_f;
879 info.min_y += interstaff_f;
880 info.max_y += interstaff_f ;
882 me->set_grob_property ("stem-info",
883 scm_list_n (gh_double2scm (info.ideal_y),
884 gh_double2scm (info.max_y),
885 gh_double2scm (info.min_y),
886 SCM_UNDEFINED));
888 return info;
891 ADD_INTERFACE (Stem,"stem-interface",
892 "A stem",
893 "avoid-note-head 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");