2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2001 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"
33 Stem::set_beaming (Grob*me ,int i, Direction d)
35 SCM pair = me->get_grob_property ("beaming");
37 if (!gh_pair_p (pair))
39 pair = gh_cons (gh_int2scm (0),gh_int2scm (0));
40 me-> set_grob_property ("beaming", pair);
42 index_set_cell (pair, d, gh_int2scm (i));
46 Stem::beam_count (Grob*me,Direction d)
48 SCM p=me->get_grob_property ("beaming");
50 return gh_scm2int (index_cell (p,d));
56 Stem::head_positions (Grob*me)
64 Drul_array<Grob*> e (extremal_heads (me));
66 return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
67 Staff_symbol_referencer::position_f (e[UP]));
72 Stem::chord_start_f (Grob*me)
74 return head_positions (me)[get_direction (me)]
75 * Staff_symbol_referencer::staff_space (me)/2.0;
79 Stem::stem_end_position (Grob*me)
81 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 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_element (me, ly_symbol2scm ("heads"), n);
235 n->set_grob_property ("rest", n->self_scm ());
240 Stem::invisible_b (Grob*me)
242 return ! (heads_i (me) && Rhythmic_head::balltype_i (support_head (me)) >= 1);
246 Stem::get_center_distance (Grob*me, Direction d)
248 int staff_center = 0;
249 int distance = (int) (d* (head_positions (me)[d] - staff_center));
250 return distance >? 0;
254 Stem::get_default_dir (Grob*me)
256 int du = get_center_distance (me,UP);
257 int dd = get_center_distance (me,DOWN);
260 return Direction (sign (dd -du));
262 return to_dir (me->get_grob_property ("neutral-direction"));
266 Stem::get_default_stem_end_position (Grob*me)
268 bool grace_b = to_boolean (me->get_grob_property ("grace"));
273 SCM scm_len = me->get_grob_property ("length");
274 if (gh_number_p (scm_len))
276 length_f = gh_scm2double (scm_len);
280 s = me->get_grob_property ("lengths");
281 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
282 a.push (gh_scm2double (ly_car (q)));
284 // stem uses half-spaces
285 length_f = a[ ((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
290 s = me->get_grob_property ("stem-shorten");
291 for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
292 a.push (gh_scm2double (ly_car (q)));
295 // stem uses half-spaces
297 // fixme: use scm_list_n_ref () iso. array[]
298 Real shorten_f = a[ ((flag_i (me) - 2) >? 0) <? (a.size () - 1)] * 2;
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);
311 stems in unnatural (forced) direction should be shortened,
312 according to [Roush & Gourlay]
314 if (( (int)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;
325 Make a little room if we have a flag and there is a dot.
329 maybe we should consider moving the dot to the right?
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; // should divide by staffspace
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)
348 Side_position_interface::add_support (dots->get_parent (X_AXIS), me);
351 previous approach was to lengthen the stem. This is not
352 good typesetting practice. */
357 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
358 if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
367 Number of hooks on the flag, ie. the log of the duration.
370 Stem::flag_i (Grob*me)
372 SCM s = me->get_grob_property ("duration-log");
373 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
377 Stem::position_noteheads (Grob*me)
382 Link_array<Grob> heads =
383 Pointer_group_interface__extract_elements (me, (Grob*)0, "heads");
385 heads.sort (compare_position);
386 Direction dir =get_direction (me);
392 Grob *hed = support_head (me);
393 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
394 for (int i=0; i < heads.size (); i++)
396 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
400 bool parity= true; // todo: make me settable.
401 int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
402 for (int i=1; i < heads.size (); i ++)
404 Real p = Staff_symbol_referencer::position_f (heads[i]);
405 int dy =abs (lastpos- (int)p);
411 Real l = heads[i]->extent (heads[i], X_AXIS).length ();
412 heads[i]->translate_axis (l * get_direction (me), X_AXIS);
423 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
425 Stem::before_line_breaking (SCM smob)
427 Grob*me = unsmob_grob (smob);
428 stem_end_position (me); // ugh. Trigger direction calc.
429 position_noteheads (me);
431 if (invisible_b (me))
433 me->remove_grob_property ("molecule-callback");
437 set_spacing_hints (me);
438 return SCM_UNSPECIFIED;
444 When in a beam with tuplet brackets, brew_mol is called early,
445 caching a wrong value.
447 MAKE_SCHEME_CALLBACK (Stem, height, 2);
449 Stem::height (SCM smob, SCM ax)
451 Axis a = (Axis)gh_scm2int (ax);
452 Grob * me = unsmob_grob (smob);
453 assert (a == Y_AXIS);
455 SCM mol = me->get_uncached_molecule ();
458 iv = unsmob_molecule (mol)->extent (a);
459 return ly_interval2scm (iv);
464 set stem directions for hinting the optical spacing correction.
466 Modifies DIR_LIST property of the Stem's Paper_column
468 TODO: more advanced: supply height of noteheads as well, for more advanced spacing possibilities
471 Stem::set_spacing_hints (Grob*me)
473 if (!invisible_b (me))
475 SCM scmdir = gh_int2scm (get_direction (me));
477 Item* item = dynamic_cast<Item*> (me);
478 Item * col = item->column_l ();
479 SCM dirlist =col->get_grob_property ("dir-list");
480 if (scm_c_memq (scmdir, dirlist) == SCM_BOOL_F)
482 dirlist = gh_cons (scmdir, dirlist);
483 col->set_grob_property ("dir-list", dirlist);
491 /* TODO: rename flag-style into something more appropriate,
492 e.g. "stroke-style", maybe with values "" (i.e. no stroke),
493 "single" and "double". Needs more discussion.
495 String style, fstyle, staffline_offs;
496 SCM fst = me->get_grob_property ("flag-style");
497 if (gh_string_p (fst))
499 fstyle = ly_scm2string (fst);
502 SCM st = me->get_grob_property ("style");
503 if (gh_symbol_p (st))
505 style = (ly_scm2string (scm_symbol_to_string (st)));
511 if (String::compare_i (style, "mensural") == 0)
512 /* Mensural notation: For notes on staff lines, use different
513 flags than for notes between staff lines. The idea is that
514 flags are always vertically aligned with the staff lines,
515 regardless if the note head is on a staff line or between two
516 staff lines. In other words, the inner end of a flag always
517 touches a staff line.
520 /* Urrgh! We have to detect wether this stem ends on a staff
521 line or between two staff lines. But we can not call
522 stem_end_position(me) or get_default_stem_end_position(me),
523 since this encounters the flag and hence results in an
524 infinite recursion. However, in pure mensural notation,
525 there are no multiple note heads attached to a single stem,
526 neither is there usually need for using the stem_shorten
527 property (except for 32th and 64th notes, but that is not a
528 problem since the stem length in this case is augmented by
529 an integral multiple of staff_space). Hence, it should be
530 sufficient to just take the first note head, assume it's
531 the only one, look if it's on a staff line, and select the
532 flag's shape accordingly. In the worst case, the shape
533 looks slightly misplaced, but that will usually be the
534 programmer's fault (e.g. when trying to attach multiple
535 note heads to a single stem in mensural notation).
538 Grob *first = first_head(me);
539 int sz = Staff_symbol_referencer::line_count (me)-1;
540 int p = (int)rint (Staff_symbol_referencer::position_f (first));
541 staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
547 char c = (get_direction (me) == UP) ? 'u' : 'd';
549 = String ("flags-") + style + to_str (c) + staffline_offs + to_str (flag_i (me));
551 = Font_interface::get_default_font (me)->find_by_name (index_str);
552 if (!fstyle.empty_b ())
553 m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
557 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
559 Stem::dim_callback (SCM e, SCM ax)
561 Axis a = (Axis) gh_scm2int (ax);
562 assert (a == X_AXIS);
563 Grob *se = unsmob_grob (e);
565 if (unsmob_grob (se->get_grob_property ("beam")) || abs (flag_i (se)) <= 2)
569 r = flag (se).extent (X_AXIS);
571 return ly_interval2scm (r);
576 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
579 Stem::brew_molecule (SCM smob)
581 Grob*me = unsmob_grob (smob);
583 Direction d = get_direction (me);
586 Real y1 = Staff_symbol_referencer::position_f (first_head (me));
587 Real y2 = stem_end_position (me);
589 Interval stem_y (y1 <? y2,y2 >? y1);
593 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
595 if (Grob *hed = support_head (me))
598 must not take ledgers into account.
600 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
601 Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
603 y_attach = head_height.linear_combination (y_attach);
604 stem_y[Direction (-d)] += d * 2*y_attach;
607 if (!invisible_b (me))
609 Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
611 * me->paper_l ()->get_var ("stafflinethickness");
613 Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
614 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
615 mol.add_molecule (ss);
618 if (!beam_l (me) && abs (flag_i (me)) > 2)
620 Molecule fl = flag (me);
621 fl.translate_axis (stem_y[d]*dy, Y_AXIS);
622 mol.add_molecule (fl);
625 return mol.smobbed_copy ();
629 move the stem to right of the notehead if it is up.
631 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
633 Stem::off_callback (SCM element_smob, SCM)
635 Grob *me = unsmob_grob (element_smob);
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 ("stafflinethickness");
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 Grob * beam = beam_l (me);
683 Direction beam_dir = Directional_element_interface::get (beam);
686 programming_error ("Beam dir not set.");
691 Real staff_space = Staff_symbol_referencer::staff_space (me);
692 Real half_space = staff_space / 2;
693 int multiplicity = Beam::get_multiplicity (beam);
696 SCM space_proc = beam->get_grob_property ("space-function");
697 SCM space = gh_call1 (space_proc, gh_int2scm (multiplicity));
698 Real interbeam_f = gh_scm2double (space) * staff_space;
700 Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
702 info.idealy_f_ = chord_start_f (me);
704 // for simplicity, we calculate as if dir == UP
705 info.idealy_f_ *= beam_dir;
706 SCM grace_prop = me->get_grob_property ("grace");
708 bool grace_b = to_boolean (grace_prop);
713 s = me->get_grob_property ("beamed-minimum-lengths");
715 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
716 a.push (gh_scm2double (ly_car (q)));
719 Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
720 s = me->get_grob_property ("beamed-lengths");
723 for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
724 a.push (gh_scm2double (ly_car (q)));
726 Real stem_length = a[multiplicity <? (a.size () - 1)] * staff_space;
728 if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
729 /* normal beamed stem */
733 info.idealy_f_ += thick + (multiplicity - 1) * interbeam_f;
735 info.miny_f_ = info.idealy_f_;
736 info.maxy_f_ = INT_MAX;
738 info.idealy_f_ += stem_length;
739 info.miny_f_ += minimum_length;
742 lowest beam of (UP) beam must never be lower than second staffline
744 Hmm, reference (Wanske?)
746 Although this (additional) rule is probably correct,
747 I expect that highest beam (UP) should also never be lower
748 than middle staffline, just as normal stems.
751 bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
752 if (!grace_b && !no_extend_b)
754 /* highest beam of (UP) beam must never be lower than middle
756 lowest beam of (UP) beam must never be lower than second staffline
760 >? (- 2 * half_space - thick
761 + (multiplicity > 0) * thick
762 + interbeam_f * (multiplicity - 1));
768 info.idealy_f_ -= thick;
769 info.maxy_f_ = info.idealy_f_;
770 info.miny_f_ = -INT_MAX;
772 info.idealy_f_ -= stem_length;
773 info.maxy_f_ -= minimum_length;
776 info.idealy_f_ = (info.maxy_f_ <? info.idealy_f_) >? info.miny_f_;
778 s = beam->get_grob_property ("shorten");
780 info.idealy_f_ -= gh_scm2double (s);
782 Grob *common = me->common_refpoint (beam, Y_AXIS);
783 Real interstaff_f = beam_dir *
784 (me->relative_coordinate (common, Y_AXIS)
785 - beam->relative_coordinate (common, Y_AXIS));
787 info.idealy_f_ += interstaff_f;
788 info.miny_f_ += interstaff_f;
789 info.maxy_f_ += interstaff_f ;
795 Stem::has_interface (Grob*m)
797 return m && m->has_interface (ly_symbol2scm ("stem-interface"));
801 Stem::set_interface (Grob*me)
803 me->set_interface (ly_symbol2scm ("stem-interface"));