*** empty log message ***
[lilypond/patrick.git] / lily / stem.cc
blob9378869c7b956d0a7964db0d9ea7b50dfb8d9dab
1 /*
2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2005 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 "stem.hh"
18 #include <math.h> // rint
20 #include "lookup.hh"
21 #include "directional-element-interface.hh"
22 #include "note-head.hh"
23 #include "warn.hh"
24 #include "output-def.hh"
25 #include "rhythmic-head.hh"
26 #include "font-interface.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 "side-position-interface.hh"
34 #include "dot-column.hh"
35 #include "stem-tremolo.hh"
37 void
38 Stem::set_beaming (Grob *me, int beam_count, Direction d)
40 SCM pair = me->get_property ("beaming");
42 if (!scm_is_pair (pair))
44 pair = scm_cons (SCM_EOL, SCM_EOL);
45 me->set_property ("beaming", pair);
48 SCM lst = index_get_cell (pair, d);
49 for (int i = 0; i < beam_count; i++)
50 lst = scm_cons (scm_int2num (i), lst);
51 index_set_cell (pair, d, lst);
54 int
55 Stem::get_beaming (Grob *me, Direction d)
57 SCM pair = me->get_property ("beaming");
58 if (!scm_is_pair (pair))
59 return 0;
61 SCM lst = index_get_cell (pair, d);
62 return scm_ilength (lst);
65 Interval
66 Stem::head_positions (Grob *me)
68 if (head_count (me))
70 Drul_array<Grob *> e (extremal_heads (me));
71 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
72 Staff_symbol_referencer::get_position (e[UP]));
74 return Interval ();
77 Real
78 Stem::chord_start_y (Grob *me)
80 Interval hp = head_positions (me);
81 if (!hp.is_empty ())
82 return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
83 * 0.5;
84 return 0;
87 Real
88 Stem::stem_end_position (Grob *me)
90 SCM p = me->get_property ("stem-end-position");
91 Real pos;
92 if (!scm_is_number (p))
94 pos = get_default_stem_end_position (me);
95 me->set_property ("stem-end-position", scm_make_real (pos));
97 else
98 pos = scm_to_double (p);
100 return pos;
103 Direction
104 Stem::get_direction (Grob *me)
106 Direction d = get_grob_direction (me);
108 if (!d)
110 d = get_default_dir (me);
111 // urg, AAARGH!
112 set_grob_direction (me, d);
114 return d;
117 void
118 Stem::set_stemend (Grob *me, Real se)
120 // todo: margins
121 Direction d = get_direction (me);
123 if (d && d * head_positions (me)[get_direction (me)] >= se * d)
124 me->warning (_ ("weird stem size, check for narrow beams"));
126 me->set_property ("stem-end-position", scm_make_real (se));
129 /* Note head that determines hshift for upstems
130 WARNING: triggers direction */
131 Grob *
132 Stem::support_head (Grob *me)
134 if (head_count (me) == 1)
135 /* UGH. */
136 return unsmob_grob (scm_car (me->get_property ("note-heads")));
137 return first_head (me);
141 Stem::head_count (Grob *me)
143 return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
146 /* The note head which forms one end of the stem.
147 WARNING: triggers direction */
148 Grob *
149 Stem::first_head (Grob *me)
151 Direction d = get_direction (me);
152 if (d)
153 return extremal_heads (me)[-d];
154 return 0;
157 /* The note head opposite to the first head. */
158 Grob *
159 Stem::last_head (Grob *me)
161 Direction d = get_direction (me);
162 if (d)
163 return extremal_heads (me)[d];
164 return 0;
168 START is part where stem reaches `last' head.
170 This function returns a drul with (bottom-head, top-head).
172 Drul_array<Grob *>
173 Stem::extremal_heads (Grob *me)
175 const int inf = 1000000;
176 Drul_array<int> extpos;
177 extpos[DOWN] = inf;
178 extpos[UP] = -inf;
180 Drul_array<Grob *> exthead (0, 0);
181 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
182 s = scm_cdr (s))
184 Grob *n = unsmob_grob (scm_car (s));
185 int p = Staff_symbol_referencer::get_rounded_position (n);
187 Direction d = LEFT;
190 if (d * p > d * extpos[d])
192 exthead[d] = n;
193 extpos[d] = p;
196 while (flip (&d) != DOWN);
198 return exthead;
201 static int
202 integer_compare (int const &a, int const &b)
204 return a - b;
207 /* The positions, in ascending order. */
208 Array<int>
209 Stem::note_head_positions (Grob *me)
211 Array<int> ps;
212 for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
213 s = scm_cdr (s))
215 Grob *n = unsmob_grob (scm_car (s));
216 int p = Staff_symbol_referencer::get_rounded_position (n);
218 ps.push (p);
221 ps.sort (integer_compare);
222 return ps;
225 void
226 Stem::add_head (Grob *me, Grob *n)
228 n->set_property ("stem", me->self_scm ());
229 n->add_dependency (me);
231 if (Note_head::has_interface (n))
232 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
233 else if (Rest::has_interface (n))
234 Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
237 bool
238 Stem::is_invisible (Grob *me)
240 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
241 0.0);
243 return !((head_count (me)
244 || stemlet_length > 0.0)
245 && scm_to_int (me->get_property ("duration-log")) >= 1);
248 Direction
249 Stem::get_default_dir (Grob *me)
251 int staff_center = 0;
252 Interval hp = head_positions (me);
253 if (hp.is_empty ())
254 return CENTER;
256 int udistance = (int) (UP *hp[UP] - staff_center);
257 int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
259 if (sign (ddistance - udistance))
260 return Direction (sign (ddistance - udistance));
262 return to_dir (me->get_property ("neutral-direction"));
265 Real
266 Stem::get_default_stem_end_position (Grob *me)
268 Real ss = Staff_symbol_referencer::staff_space (me);
269 int durlog = duration_log (me);
270 SCM s;
271 Array<Real> a;
273 /* WARNING: IN HALF SPACES */
274 Real length = 7;
275 SCM scm_len = me->get_property ("length");
276 if (scm_is_number (scm_len))
277 length = scm_to_double (scm_len);
278 else
280 s = me->get_property ("lengths");
281 if (scm_is_pair (s))
282 length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
285 /* URGURGURG
286 'set-default-stemlen' sets direction too. */
287 Direction dir = get_direction (me);
288 if (!dir)
290 dir = get_default_dir (me);
291 set_grob_direction (me, dir);
294 /* Stems in unnatural (forced) direction should be shortened,
295 according to [Roush & Gourlay] */
296 Interval hp = head_positions (me);
297 if (dir && dir * hp[dir] >= 0)
299 SCM sshorten = me->get_property ("stem-shorten");
300 SCM scm_shorten = scm_is_pair (sshorten)
301 ? robust_list_ref ((duration_log (me) - 2) >? 0, sshorten) : SCM_EOL;
302 Real shorten = 2* robust_scm2double (scm_shorten, 0);
304 /* On boundary: shorten only half */
305 if (abs (head_positions (me)[dir]) <= 1)
306 shorten *= 0.5;
308 length -= shorten;
311 /* Tremolo stuff. */
312 Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
313 if (t_flag && !unsmob_grob (me->get_property ("beam")))
315 /* Crude hack: add extra space if tremolo flag is there.
317 We can't do this for the beam, since we get into a loop
318 (Stem_tremolo::raw_stencil () looks at the beam.) --hwn */
320 Real minlen = 1.0
321 + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
322 / ss;
324 if (durlog >= 3)
326 Interval flag_ext = flag (me).extent (Y_AXIS);
327 if (!flag_ext.is_empty ())
328 minlen += 2 * flag_ext.length () / ss;
330 /* The clash is smaller for down stems (since the tremolo is
331 angled up.) */
332 if (dir == DOWN)
333 minlen -= 1.0;
335 length = length >? (minlen + 1.0);
338 Real st = dir ? hp[dir] + dir * length : 0;
340 /* TODO: change name to extend-stems to staff/center/'() */
341 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
342 if (!no_extend_b && dir * st < 0)
343 st = 0.0;
345 /* Make a little room if we have a upflag and there is a dot.
346 previous approach was to lengthen the stem. This is not
347 good typesetting practice. */
348 if (!get_beam (me) && dir == UP
349 && durlog > 2)
351 Grob *closest_to_flag = extremal_heads (me)[dir];
352 Grob *dots = closest_to_flag
353 ? Rhythmic_head::get_dots (closest_to_flag) : 0;
355 if (dots)
357 Real dp = Staff_symbol_referencer::get_position (dots);
358 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
360 /* Very gory: add myself to the X-support of the parent,
361 which should be a dot-column. */
362 if (dir * (st + flagy - dp) < 0.5)
364 Grob *par = dots->get_parent (X_AXIS);
366 if (Dot_column::has_interface (par))
368 Side_position_interface::add_support (par, me);
370 /* TODO: apply some better logic here. The flag is
371 curved inwards, so this will typically be too
372 much. */
377 return st;
380 /* The log of the duration (Number of hooks on the flag minus two) */
382 Stem::duration_log (Grob *me)
384 SCM s = me->get_property ("duration-log");
385 return (scm_is_number (s)) ? scm_to_int (s) : 2;
388 void
389 Stem::position_noteheads (Grob *me)
391 if (!head_count (me))
392 return;
394 Link_array<Grob> heads
395 = extract_grob_array (me, ly_symbol2scm ("note-heads"));
397 heads.sort (compare_position);
398 Direction dir = get_direction (me);
400 if (dir < 0)
401 heads.reverse ();
403 Real thick = thickness (me);
405 Grob *hed = support_head (me);
406 Real w = hed->extent (hed, X_AXIS)[dir];
407 for (int i = 0; i < heads.size (); i++)
408 heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
409 X_AXIS);
411 bool parity = true;
412 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
413 for (int i = 1; i < heads.size (); i++)
415 Real p = Staff_symbol_referencer::get_position (heads[i]);
416 Real dy = fabs (lastpos- p);
419 dy should always be 0.5, 0.0, 1.0, but provide safety margin
420 for rounding errors.
422 if (dy < 1.1)
424 if (parity)
426 Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
428 Direction d = get_direction (me);
430 Reversed head should be shifted ell-thickness, but this
431 looks too crowded, so we only shift ell-0.5*thickness.
433 This leads to assymetry: Normal heads overlap the
434 stem 100% whereas reversed heads only overlaps the
435 stem 50%
438 Real reverse_overlap = 0.5;
439 heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
440 X_AXIS);
442 if (is_invisible (me))
443 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
444 X_AXIS);
446 /* TODO:
448 For some cases we should kern some more: when the
449 distance between the next or prev note is too large, we'd
450 get large white gaps, eg.
454 |X <- kern this.
460 parity = !parity;
462 else
463 parity = true;
465 lastpos = int (p);
469 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
471 Stem::before_line_breaking (SCM smob)
473 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);
485 return SCM_UNSPECIFIED;
489 ugh.
490 When in a beam with tuplet brackets, brew_mol is called early,
491 caching a wrong value.
493 MAKE_SCHEME_CALLBACK (Stem, height, 2);
495 Stem::height (SCM smob, SCM ax)
497 Axis a = (Axis)scm_to_int (ax);
498 Grob *me = unsmob_grob (smob);
499 assert (a == Y_AXIS);
502 ugh. - this dependency should be automatic.
504 Grob *beam = get_beam (me);
505 if (beam)
507 Beam::after_line_breaking (beam->self_scm ());
510 SCM mol = me->get_uncached_stencil ();
511 Interval iv;
512 if (mol != SCM_EOL)
513 iv = unsmob_stencil (mol)->extent (a);
514 if (Grob *b = get_beam (me))
516 Direction d = get_direction (me);
517 if (d == CENTER)
519 programming_error ("no stem direction");
520 d = UP;
522 iv[d] += d * Beam::get_thickness (b) * 0.5;
525 return ly_interval2scm (iv);
528 Stencil
529 Stem::flag (Grob *me)
531 /* TODO: maybe property stroke-style should take different values,
532 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
533 '() or "grace"). */
534 String flag_style;
536 SCM flag_style_scm = me->get_property ("flag-style");
537 if (scm_is_symbol (flag_style_scm))
538 flag_style = ly_symbol2string (flag_style_scm);
540 if (flag_style == "no-flag")
541 return Stencil ();
543 bool adjust = true;
545 String staffline_offs;
546 if (String::compare (flag_style, "mensural") == 0)
547 /* Mensural notation: For notes on staff lines, use different
548 flags than for notes between staff lines. The idea is that
549 flags are always vertically aligned with the staff lines,
550 regardless if the note head is on a staff line or between two
551 staff lines. In other words, the inner end of a flag always
552 touches a staff line.
555 if (adjust)
557 int p = (int) (rint (stem_end_position (me)));
558 staffline_offs
559 = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
561 else
563 staffline_offs = "2";
566 else
568 staffline_offs = "";
571 char dir = (get_direction (me) == UP) ? 'u' : 'd';
572 String font_char = flag_style
573 + to_string (dir) + staffline_offs + to_string (duration_log (me));
574 Font_metric *fm = Font_interface::get_default_font (me);
575 Stencil flag = fm->find_by_name ("flags." + font_char);
576 if (flag.is_empty ())
577 me->warning (_f ("flag `%s' not found", font_char));
579 SCM stroke_style_scm = me->get_property ("stroke-style");
580 if (scm_is_string (stroke_style_scm))
582 String stroke_style = ly_scm2string (stroke_style_scm);
583 if (!stroke_style.is_empty ())
585 String font_char = to_string (dir) + stroke_style;
586 Stencil stroke = fm->find_by_name ("flags." + font_char);
587 if (stroke.is_empty ())
588 me->warning (_f ("flag stroke `%s' not found", font_char));
589 else
590 flag.add_stencil (stroke);
594 return flag;
597 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
599 Stem::width_callback (SCM e, SCM ax)
601 Axis a = (Axis) scm_to_int (ax);
602 assert (a == X_AXIS);
603 Grob *me = unsmob_grob (e);
605 Interval r;
607 if (is_invisible (me))
609 r.set_empty ();
611 else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
613 r = Interval (-1, 1);
614 r *= thickness (me) / 2;
616 else
618 r = flag (me).extent (X_AXIS)
619 + thickness (me) / 2;
621 return ly_interval2scm (r);
624 Real
625 Stem::thickness (Grob *me)
627 return scm_to_double (me->get_property ("thickness"))
628 * Staff_symbol_referencer::line_thickness (me);
631 MAKE_SCHEME_CALLBACK (Stem, print, 1);
633 Stem::print (SCM smob)
635 Grob *me = unsmob_grob (smob);
636 Stencil mol;
637 Direction d = get_direction (me);
639 Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
640 0.0);
641 bool stemlet = stemlet_length > 0.0;
643 /* TODO: make the stem start a direction ?
644 This is required to avoid stems passing in tablature chords. */
645 Grob *lh
646 = to_boolean (me->get_property ("avoid-note-head"))
647 ? last_head (me)
648 : first_head (me);
649 Grob *beam = get_beam (me);
651 if (!lh && !stemlet)
652 return SCM_EOL;
654 if (!lh && stemlet && !beam)
655 return SCM_EOL;
657 if (is_invisible (me))
658 return SCM_EOL;
660 Real y2 = stem_end_position (me);
661 Real y1 = y2;
662 Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
664 if (lh)
665 y2 = Staff_symbol_referencer::get_position (lh);
666 else if (stemlet)
668 Real beam_translation = Beam::get_beam_translation (beam);
669 Real beam_thickness = Beam::get_thickness (beam);
670 int beam_count = beam_multiplicity (me).length () + 1;
672 y2 -= d
673 * (0.5 * beam_thickness
674 + beam_translation * (0 >? (beam_count - 1))
675 + stemlet_length) / half_space;
678 Interval stem_y (y1 <? y2, y2 >? y1);
680 if (Grob *hed = support_head (me))
683 must not take ledgers into account.
685 Interval head_height = hed->extent (hed, Y_AXIS);
686 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
688 y_attach = head_height.linear_combination (y_attach);
689 stem_y[Direction (-d)] += d * y_attach / half_space;
692 // URG
693 Real stem_width = thickness (me);
694 Real blot
695 = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
697 Box b = Box (Interval (-stem_width / 2, stem_width / 2),
698 Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
700 Stencil ss = Lookup::round_filled_box (b, blot);
701 mol.add_stencil (ss);
703 if (!get_beam (me) && abs (duration_log (me)) > 2)
705 Stencil fl = flag (me);
706 fl.translate_axis (stem_y[d] * half_space - d * blot / 2, Y_AXIS);
707 fl.translate_axis (stem_width / 2, X_AXIS);
708 mol.add_stencil (fl);
711 return mol.smobbed_copy ();
715 move the stem to right of the notehead if it is up.
717 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
719 Stem::offset_callback (SCM element_smob, SCM)
721 Grob *me = unsmob_grob (element_smob);
722 Real r = 0.0;
724 if (Grob *f = first_head (me))
726 Interval head_wid = f->extent (f, X_AXIS);
727 Real attach = 0.0;
729 if (is_invisible (me))
730 attach = 0.0;
731 else
732 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
734 Direction d = get_direction (me);
735 Real real_attach = head_wid.linear_combination (d * attach);
736 r = real_attach;
738 /* If not centered: correct for stem thickness. */
739 if (attach)
741 Real rule_thick = thickness (me);
742 r += -d * rule_thick * 0.5;
745 else
747 SCM rests = me->get_property ("rests");
748 if (scm_is_pair (rests))
750 Grob *rest = unsmob_grob (scm_car (rests));
751 r = rest->extent (rest, X_AXIS).center ();
754 return scm_make_real (r);
757 Spanner *
758 Stem::get_beam (Grob *me)
760 SCM b = me->get_property ("beam");
761 return dynamic_cast<Spanner *> (unsmob_grob (b));
764 Stem_info
765 Stem::get_stem_info (Grob *me)
767 /* Return cached info if available */
768 SCM scm_info = me->get_property ("stem-info");
769 if (!scm_is_pair (scm_info))
771 calc_stem_info (me);
772 scm_info = me->get_property ("stem-info");
775 Stem_info si;
776 si.dir_ = get_grob_direction (me);
777 si.ideal_y_ = scm_to_double (scm_car (scm_info));
778 si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
779 return si;
782 /* TODO: add extra space for tremolos! */
783 void
784 Stem::calc_stem_info (Grob *me)
786 Direction my_dir = get_grob_direction (me);
788 if (!my_dir)
790 programming_error ("no stem dir set");
791 my_dir = UP;
794 Real staff_space = Staff_symbol_referencer::staff_space (me);
795 Grob *beam = get_beam (me);
796 Real beam_translation = Beam::get_beam_translation (beam);
797 Real beam_thickness = Beam::get_thickness (beam);
798 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
800 /* Simple standard stem length */
801 SCM lengths = me->get_property ("beamed-lengths");
802 Real ideal_length
803 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
805 * staff_space
806 /* stem only extends to center of beam
808 - 0.5 * beam_thickness;
810 /* Condition: sane minimum free stem length (chord to beams) */
811 lengths = me->get_property ("beamed-minimum-free-lengths");
812 Real ideal_minimum_free
813 = scm_to_double (robust_list_ref (beam_count - 1, lengths))
814 * staff_space;
816 /* UGH
817 It seems that also for ideal minimum length, we must use
818 the maximum beam count (for this direction):
820 \score{ \notes\relative c''{ [a8 a32] }}
822 must be horizontal. */
823 Real height_of_my_beams = beam_thickness
824 + (beam_count - 1) * beam_translation;
826 Real ideal_minimum_length = ideal_minimum_free
827 + height_of_my_beams
828 /* stem only extends to center of beam */
829 - 0.5 * beam_thickness;
831 ideal_length = ideal_length >? ideal_minimum_length;
833 /* Convert to Y position, calculate for dir == UP */
834 Real note_start
835 = /* staff positions */
836 head_positions (me)[my_dir] * 0.5
837 * my_dir * staff_space;
838 Real ideal_y = note_start + ideal_length;
840 /* Conditions for Y position */
842 /* Lowest beam of (UP) beam must never be lower than second staffline
844 Reference?
846 Although this (additional) rule is probably correct,
847 I expect that highest beam (UP) should also never be lower
848 than middle staffline, just as normal stems.
850 Reference?
852 Obviously not for grace beams.
854 Also, not for knees. Seems to be a good thing. */
855 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
856 bool is_knee = to_boolean (beam->get_property ("knee"));
857 if (!no_extend_b && !is_knee)
859 /* Highest beam of (UP) beam must never be lower than middle
860 staffline */
861 ideal_y = ideal_y >? 0;
862 /* Lowest beam of (UP) beam must never be lower than second staffline */
863 ideal_y = ideal_y >? (-staff_space
864 - beam_thickness + height_of_my_beams);
867 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
869 Real minimum_free
870 = scm_to_double (robust_list_ref
871 (beam_count - 1,
872 me->get_property
873 ("beamed-extreme-minimum-free-lengths")))
874 * staff_space;
876 Real minimum_length = minimum_free
877 + height_of_my_beams
878 /* stem only extends to center of beam */
879 - 0.5 * beam_thickness;
881 ideal_y *= my_dir;
882 Real minimum_y = note_start + minimum_length;
883 Real shortest_y = minimum_y * my_dir;
885 me->set_property ("stem-info",
886 scm_list_2 (scm_make_real (ideal_y),
887 scm_make_real (shortest_y)));
890 Slice
891 Stem::beam_multiplicity (Grob *stem)
893 SCM beaming = stem->get_property ("beaming");
894 Slice le = int_list_to_slice (scm_car (beaming));
895 Slice ri = int_list_to_slice (scm_cdr (beaming));
896 le.unite (ri);
897 return le;
900 /* FIXME: Too many properties */
901 ADD_INTERFACE (Stem, "stem-interface",
902 "The stem represent the graphical stem. "
903 "In addition, it internally connects note heads, beams and"
904 "tremolos. "
905 "Rests and whole notes have invisible stems.",
906 "tremolo-flag french-beaming "
907 "avoid-note-head thickness "
908 "stemlet-length rests "
909 "stem-info beamed-lengths beamed-minimum-free-lengths "
910 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
911 "duration-log beaming neutral-direction stem-end-position "
912 "note-heads direction length flag-style "
913 "no-stem-extend stroke-style");
915 /****************************************************************/
917 Stem_info::Stem_info ()
919 ideal_y_ = shortest_y_ = 0;
920 dir_ = CENTER;
923 void
924 Stem_info::scale (Real x)
926 ideal_y_ *= x;
927 shortest_y_ *= x;