]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/tie-column.cc (set_manual_tie_configuration): new function.
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include "stem.hh"
17
18 #include <cmath>                // rint
19 using namespace std;
20
21 #include "lookup.hh"
22 #include "directional-element-interface.hh"
23 #include "note-head.hh"
24 #include "warn.hh"
25 #include "output-def.hh"
26 #include "rhythmic-head.hh"
27 #include "font-interface.hh"
28 #include "paper-column.hh"
29 #include "misc.hh"
30 #include "beam.hh"
31 #include "rest.hh"
32 #include "pointer-group-interface.hh"
33 #include "staff-symbol-referencer.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
36 #include "stem-tremolo.hh"
37
38 void
39 Stem::set_beaming (Grob *me, int beam_count, Direction d)
40 {
41   SCM pair = me->get_property ("beaming");
42
43   if (!scm_is_pair (pair))
44     {
45       pair = scm_cons (SCM_EOL, SCM_EOL);
46       me->set_property ("beaming", pair);
47     }
48
49   SCM lst = index_get_cell (pair, d);
50   for (int i = 0; i < beam_count; i++)
51     lst = scm_cons (scm_from_int (i), lst);
52   index_set_cell (pair, d, lst);
53 }
54
55 int
56 Stem::get_beaming (Grob *me, Direction d)
57 {
58   SCM pair = me->get_property ("beaming");
59   if (!scm_is_pair (pair))
60     return 0;
61
62   SCM lst = index_get_cell (pair, d);
63   return scm_ilength (lst);
64 }
65
66 Interval
67 Stem::head_positions (Grob *me)
68 {
69   if (head_count (me))
70     {
71       Drul_array<Grob *> e (extremal_heads (me));
72       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
73                        Staff_symbol_referencer::get_position (e[UP]));
74     }
75   return Interval ();
76 }
77
78 Real
79 Stem::chord_start_y (Grob *me)
80 {
81   Interval hp = head_positions (me);
82   if (!hp.is_empty ())
83     return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
84       * 0.5;
85   return 0;
86 }
87
88 Real
89 Stem::stem_end_position (Grob *me)
90 {
91   SCM p = me->get_property ("stem-end-position");
92   Real pos;
93   if (!scm_is_number (p))
94     {
95       pos = get_default_stem_end_position (me);
96       me->set_property ("stem-end-position", scm_from_double (pos));
97     }
98   else
99     pos = scm_to_double (p);
100
101   return pos;
102 }
103
104 Direction
105 Stem::get_direction (Grob *me)
106 {
107   Direction d = get_grob_direction (me);
108
109   if (!d)
110     {
111       d = get_default_dir (me);
112       // urg, AAARGH!
113       set_grob_direction (me, d);
114     }
115   return d;
116 }
117
118 void
119 Stem::set_stemend (Grob *me, Real se)
120 {
121   // todo: margins
122   Direction d = get_direction (me);
123
124   if (d && d * head_positions (me)[get_direction (me)] >= se * d)
125     me->warning (_ ("weird stem size, check for narrow beams"));
126
127   me->set_property ("stem-end-position", scm_from_double (se));
128 }
129
130 /* Note head that determines hshift for upstems
131    WARNING: triggers direction  */
132 Grob *
133 Stem::support_head (Grob *me)
134 {
135   extract_grob_set (me, "note-heads", heads);
136   if (heads.size () == 1)
137     return heads[0];
138
139   return first_head (me);
140 }
141
142 int
143 Stem::head_count (Grob *me)
144 {
145   return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
146 }
147
148 /* The note head which forms one end of the stem.
149    WARNING: triggers direction  */
150 Grob *
151 Stem::first_head (Grob *me)
152 {
153   Direction d = get_direction (me);
154   if (d)
155     return extremal_heads (me)[-d];
156   return 0;
157 }
158
159 /* The note head opposite to the first head.  */
160 Grob *
161 Stem::last_head (Grob *me)
162 {
163   Direction d = get_direction (me);
164   if (d)
165     return extremal_heads (me)[d];
166   return 0;
167 }
168
169 /*
170   START is part where stem reaches `last' head.
171
172   This function returns a drul with (bottom-head, top-head).
173 */
174 Drul_array<Grob *>
175 Stem::extremal_heads (Grob *me)
176 {
177   const int inf = 1000000;
178   Drul_array<int> extpos;
179   extpos[DOWN] = inf;
180   extpos[UP] = -inf;
181
182   Drul_array<Grob *> exthead (0, 0);
183   extract_grob_set (me, "note-heads", heads);
184
185   for (int i = heads.size (); i--;)
186     {
187       Grob *n = heads[i];
188       int p = Staff_symbol_referencer::get_rounded_position (n);
189
190       Direction d = LEFT;
191       do
192         {
193           if (d * p > d * extpos[d])
194             {
195               exthead[d] = n;
196               extpos[d] = p;
197             }
198         }
199       while (flip (&d) != DOWN);
200     }
201   return exthead;
202 }
203
204 static int
205 integer_compare (int const &a, int const &b)
206 {
207   return a - b;
208 }
209
210 /* The positions, in ascending order.  */
211 Array<int>
212 Stem::note_head_positions (Grob *me)
213 {
214   Array<int> ps;
215   extract_grob_set (me, "note-heads", heads);
216
217   for (int i = heads.size (); i--;)
218     {
219       Grob *n = heads[i];
220       int p = Staff_symbol_referencer::get_rounded_position (n);
221
222       ps.push (p);
223     }
224
225   ps.sort (integer_compare);
226   return ps;
227 }
228
229 void
230 Stem::add_head (Grob *me, Grob *n)
231 {
232   n->set_object ("stem", me->self_scm ());
233   n->add_dependency (me);
234
235   if (Note_head::has_interface (n))
236     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
237   else if (Rest::has_interface (n))
238     Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
239 }
240
241 bool
242 Stem::is_invisible (Grob *me)
243 {
244   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
245                                            0.0);
246
247   return !((head_count (me)
248             || stemlet_length > 0.0)
249            && scm_to_int (me->get_property ("duration-log")) >= 1);
250 }
251
252 Direction
253 Stem::get_default_dir (Grob *me)
254 {
255   int staff_center = 0;
256   Interval hp = head_positions (me);
257   if (hp.is_empty ())
258     return CENTER;
259
260   int udistance = (int) (UP *hp[UP] - staff_center);
261   int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
262
263   if (sign (ddistance - udistance))
264     return Direction (sign (ddistance - udistance));
265
266   return to_dir (me->get_property ("neutral-direction"));
267 }
268
269 Real
270 Stem::get_default_stem_end_position (Grob *me)
271 {
272   Real ss = Staff_symbol_referencer::staff_space (me);
273   int durlog = duration_log (me);
274   SCM s;
275   Array<Real> a;
276
277   /* WARNING: IN HALF SPACES */
278   Real length = 7;
279   SCM scm_len = me->get_property ("length");
280   if (scm_is_number (scm_len))
281     length = scm_to_double (scm_len);
282   else
283     {
284       s = me->get_property ("lengths");
285       if (scm_is_pair (s))
286         length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
287     }
288
289   /* URGURGURG
290      'set-default-stemlen' sets direction too.   */
291   Direction dir = get_direction (me);
292   if (!dir)
293     {
294       dir = get_default_dir (me);
295       set_grob_direction (me, dir);
296     }
297
298   /* Stems in unnatural (forced) direction should be shortened,
299      according to [Roush & Gourlay] */
300   Interval hp = head_positions (me);
301   if (dir && dir * hp[dir] >= 0)
302     {
303       SCM sshorten = me->get_property ("stem-shorten");
304       SCM scm_shorten = scm_is_pair (sshorten)
305         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
306       Real shorten = 2* robust_scm2double (scm_shorten, 0);
307
308       /* On boundary: shorten only half */
309       if (abs (head_positions (me)[dir]) <= 1)
310         shorten *= 0.5;
311
312       length -= shorten;
313     }
314
315   /* Tremolo stuff.  */
316   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
317   if (t_flag && !unsmob_grob (me->get_object ("beam")))
318     {
319       /* Crude hack: add extra space if tremolo flag is there.
320
321       We can't do this for the beam, since we get into a loop
322       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
323
324       Real minlen = 1.0
325         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
326         / ss;
327
328       if (durlog >= 3)
329         {
330           Interval flag_ext = flag (me).extent (Y_AXIS);
331           if (!flag_ext.is_empty ())
332             minlen += 2 * flag_ext.length () / ss;
333
334           /* The clash is smaller for down stems (since the tremolo is
335              angled up.) */
336           if (dir == DOWN)
337             minlen -= 1.0;
338         }
339       length = max (length, minlen + 1.0);
340     }
341
342   Real st = dir ? hp[dir] + dir * length : 0;
343
344   /* TODO: change name  to extend-stems to staff/center/'()  */
345   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
346   if (!no_extend_b && dir * st < 0)
347     st = 0.0;
348
349   /* Make a little room if we have a upflag and there is a dot.
350      previous approach was to lengthen the stem. This is not
351      good typesetting practice.  */
352   if (!get_beam (me) && dir == UP
353       && durlog > 2)
354     {
355       Grob *closest_to_flag = extremal_heads (me)[dir];
356       Grob *dots = closest_to_flag
357         ? Rhythmic_head::get_dots (closest_to_flag) : 0;
358
359       if (dots)
360         {
361           Real dp = Staff_symbol_referencer::get_position (dots);
362           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
363
364           /* Very gory: add myself to the X-support of the parent,
365              which should be a dot-column. */
366           if (dir * (st + flagy - dp) < 0.5)
367             {
368               Grob *par = dots->get_parent (X_AXIS);
369
370               if (Dot_column::has_interface (par))
371                 {
372                   Side_position_interface::add_support (par, me);
373
374                   /* TODO: apply some better logic here. The flag is
375                      curved inwards, so this will typically be too
376                      much. */
377                 }
378             }
379         }
380     }
381   return st;
382 }
383
384 /* The log of the duration (Number of hooks on the flag minus two)  */
385 int
386 Stem::duration_log (Grob *me)
387 {
388   SCM s = me->get_property ("duration-log");
389   return (scm_is_number (s)) ? scm_to_int (s) : 2;
390 }
391
392 void
393 Stem::position_noteheads (Grob *me)
394 {
395   if (!head_count (me))
396     return;
397
398   extract_grob_set (me, "note-heads", ro_heads);
399   Link_array<Grob> heads (ro_heads);
400   heads.sort (compare_position);
401   Direction dir = get_direction (me);
402
403   if (dir < 0)
404     heads.reverse ();
405
406   Real thick = thickness (me);
407
408   Grob *hed = support_head (me);
409   Real w = hed->extent (hed, X_AXIS)[dir];
410   for (int i = 0; i < heads.size (); i++)
411     heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
412                               X_AXIS);
413
414   bool parity = true;
415   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
416   for (int i = 1; i < heads.size (); i++)
417     {
418       Real p = Staff_symbol_referencer::get_position (heads[i]);
419       Real dy = fabs (lastpos- p);
420
421       /*
422         dy should always be 0.5, 0.0, 1.0, but provide safety margin
423         for rounding errors.
424       */
425       if (dy < 1.1)
426         {
427           if (parity)
428             {
429               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
430
431               Direction d = get_direction (me);
432               /*
433                 Reversed head should be shifted ell-thickness, but this
434                 looks too crowded, so we only shift ell-0.5*thickness.
435
436                 This leads to assymetry: Normal heads overlap the
437                 stem 100% whereas reversed heads only overlaps the
438                 stem 50%
439               */
440
441               Real reverse_overlap = 0.5;
442               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
443                                         X_AXIS);
444
445               if (is_invisible (me))
446                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
447                                           X_AXIS);
448
449               /* TODO:
450
451               For some cases we should kern some more: when the
452               distance between the next or prev note is too large, we'd
453               get large white gaps, eg.
454
455               |
456               X|
457               |X  <- kern this.
458               |
459               X
460
461               */
462             }
463           parity = !parity;
464         }
465       else
466         parity = true;
467
468       lastpos = int (p);
469     }
470 }
471
472 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
473 SCM
474 Stem::before_line_breaking (SCM smob)
475 {
476   Grob *me = unsmob_grob (smob);
477
478   /*
479     Do the calculations for visible stems, but also for invisible stems
480     with note heads (i.e. half notes.)
481   */
482   if (head_count (me))
483     {
484       stem_end_position (me);   // ugh. Trigger direction calc.
485       position_noteheads (me);
486     }
487
488   return SCM_UNSPECIFIED;
489 }
490
491 /*
492   ugh.
493   When in a beam with tuplet brackets, brew_mol is called early,
494   caching a wrong value.
495 */
496 MAKE_SCHEME_CALLBACK (Stem, height, 2);
497 SCM
498 Stem::height (SCM smob, SCM ax)
499 {
500   Axis a = (Axis)scm_to_int (ax);
501   Grob *me = unsmob_grob (smob);
502   assert (a == Y_AXIS);
503
504   /*
505     ugh. - this dependency should be automatic.
506   */
507   Grob *beam = get_beam (me);
508   if (beam)
509     Beam::after_line_breaking (beam->self_scm ());
510
511   SCM mol = me->get_uncached_stencil ();
512   Interval iv;
513   if (mol != SCM_EOL)
514     iv = unsmob_stencil (mol)->extent (a);
515   if (Grob *b = get_beam (me))
516     {
517       Direction d = get_direction (me);
518       if (d == CENTER)
519         {
520           programming_error ("no stem direction");
521           d = UP;
522         }
523       iv[d] += d * Beam::get_thickness (b) * 0.5;
524     }
525
526   return ly_interval2scm (iv);
527 }
528
529 Stencil
530 Stem::flag (Grob *me)
531 {
532   int log = duration_log (me);
533   if (log < 3
534       || unsmob_grob (me->get_object ("beam")))
535     return Stencil ();
536
537   /*
538     TODO: maybe property stroke-style should take different values,
539     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
540     '() or "grace").  */
541   String flag_style;
542
543   SCM flag_style_scm = me->get_property ("flag-style");
544   if (scm_is_symbol (flag_style_scm))
545     flag_style = ly_symbol2string (flag_style_scm);
546
547   if (flag_style == "no-flag")
548     return Stencil ();
549
550   bool adjust = true;
551
552   String staffline_offs;
553   if (String::compare (flag_style, "mensural") == 0)
554     /* Mensural notation: For notes on staff lines, use different
555        flags than for notes between staff lines.  The idea is that
556        flags are always vertically aligned with the staff lines,
557        regardless if the note head is on a staff line or between two
558        staff lines.  In other words, the inner end of a flag always
559        touches a staff line.
560     */
561     {
562       if (adjust)
563         {
564           int p = (int) (rint (stem_end_position (me)));
565           staffline_offs
566             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
567         }
568       else
569         staffline_offs = "2";
570     }
571   else
572     staffline_offs = "";
573
574   char dir = (get_direction (me) == UP) ? 'u' : 'd';
575   String font_char = flag_style
576     + to_string (dir) + staffline_offs + to_string (log);
577   Font_metric *fm = Font_interface::get_default_font (me);
578   Stencil flag = fm->find_by_name ("flags." + font_char);
579   if (flag.is_empty ())
580     me->warning (_f ("flag `%s' not found", font_char));
581
582   SCM stroke_style_scm = me->get_property ("stroke-style");
583   if (scm_is_string (stroke_style_scm))
584     {
585       String stroke_style = ly_scm2string (stroke_style_scm);
586       if (!stroke_style.is_empty ())
587         {
588           String font_char = to_string (dir) + stroke_style;
589           Stencil stroke = fm->find_by_name ("flags." + font_char);
590           if (stroke.is_empty ())
591             me->warning (_f ("flag stroke `%s' not found", font_char));
592           else
593             flag.add_stencil (stroke);
594         }
595     }
596
597   return flag;
598 }
599
600 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
601 SCM
602 Stem::width_callback (SCM e, SCM ax)
603 {
604   (void) ax;
605   assert (scm_to_int (ax) == X_AXIS);
606   Grob *me = unsmob_grob (e);
607
608   Interval r;
609
610   if (is_invisible (me))
611     r.set_empty ();
612   else if (unsmob_grob (me->get_object ("beam"))
613            || abs (duration_log (me)) <= 2)
614     {
615       r = Interval (-1, 1);
616       r *= thickness (me) / 2;
617     }
618   else
619     {
620       r = Interval (-1, 1) * thickness (me) * 0.5;
621       r.unite (flag (me).extent (X_AXIS));
622     }
623   return ly_interval2scm (r);
624 }
625
626 Real
627 Stem::thickness (Grob *me)
628 {
629   return scm_to_double (me->get_property ("thickness"))
630     * Staff_symbol_referencer::line_thickness (me);
631 }
632
633 MAKE_SCHEME_CALLBACK (Stem, print, 1);
634 SCM
635 Stem::print (SCM smob)
636 {
637   Grob *me = unsmob_grob (smob);
638   Stencil mol;
639   Direction d = get_direction (me);
640
641   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
642                                            0.0);
643   bool stemlet = stemlet_length > 0.0;
644
645   /* TODO: make the stem start a direction ?
646      This is required to avoid stems passing in tablature chords.  */
647   Grob *lh
648     = to_boolean (me->get_property ("avoid-note-head"))
649     ? last_head (me)
650     : first_head (me);
651   Grob *beam = get_beam (me);
652
653   if (!lh && !stemlet)
654     return SCM_EOL;
655
656   if (!lh && stemlet && !beam)
657     return SCM_EOL;
658
659   if (is_invisible (me))
660     return SCM_EOL;
661
662   Real y2 = stem_end_position (me);
663   Real y1 = y2;
664   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
665
666   if (lh)
667     y2 = Staff_symbol_referencer::get_position (lh);
668   else if (stemlet)
669     {
670       Real beam_translation = Beam::get_beam_translation (beam);
671       Real beam_thickness = Beam::get_thickness (beam);
672       int beam_count = beam_multiplicity (me).length () + 1;
673
674       y2 -= d
675         * (0.5 * beam_thickness
676            + beam_translation * max (0, (beam_count - 1))
677            + stemlet_length) / half_space;
678     }
679
680   Interval stem_y (min (y1, y2), max (y2, y1));
681
682   if (Grob *hed = support_head (me))
683     {
684       /*
685         must not take ledgers into account.
686       */
687       Interval head_height = hed->extent (hed, Y_AXIS);
688       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
689
690       y_attach = head_height.linear_combination (y_attach);
691       stem_y[Direction (-d)] += d * y_attach / half_space;
692     }
693
694   // URG
695   Real stem_width = thickness (me);
696   Real blot
697     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
698
699   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
700                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
701
702   Stencil ss = Lookup::round_filled_box (b, blot);
703   mol.add_stencil (ss);
704
705   mol.add_stencil (get_translated_flag (me));
706
707   return mol.smobbed_copy ();
708 }
709
710 Stencil
711 Stem::get_translated_flag (Grob *me)
712 {
713   Stencil fl = flag (me);
714   if (!fl.is_empty ())
715     {
716       Direction d = get_direction (me);
717       Real blot
718         = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
719       Real stem_width = thickness (me);
720       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
721       Real y2 = stem_end_position (me);
722       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
723       fl.translate_axis (stem_width / 2, X_AXIS);
724     }
725   return fl;
726 }
727
728
729 /*
730   move the stem to right of the notehead if it is up.
731 */
732 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
733 SCM
734 Stem::offset_callback (SCM element_smob, SCM)
735 {
736   Grob *me = unsmob_grob (element_smob);
737   Real r = 0.0;
738
739   if (Grob *f = first_head (me))
740     {
741       Interval head_wid = f->extent (f, X_AXIS);
742       Real attach = 0.0;
743
744       if (is_invisible (me))
745         attach = 0.0;
746       else
747         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
748
749       Direction d = get_direction (me);
750       Real real_attach = head_wid.linear_combination (d * attach);
751       r = real_attach;
752
753       /* If not centered: correct for stem thickness.  */
754       if (attach)
755         {
756           Real rule_thick = thickness (me);
757           r += -d * rule_thick * 0.5;
758         }
759     }
760   else
761     {
762       extract_grob_set (me, "rests", rests);
763       if (rests.size ())
764         {
765           Grob *rest = rests.top ();
766           r = rest->extent (rest, X_AXIS).center ();
767         }
768     }
769   return scm_from_double (r);
770 }
771
772 Spanner *
773 Stem::get_beam (Grob *me)
774 {
775   SCM b = me->get_object ("beam");
776   return dynamic_cast<Spanner *> (unsmob_grob (b));
777 }
778
779 Stem_info
780 Stem::get_stem_info (Grob *me)
781 {
782   /* Return cached info if available */
783   SCM scm_info = me->get_property ("stem-info");
784   if (!scm_is_pair (scm_info))
785     {
786       calc_stem_info (me);
787       scm_info = me->get_property ("stem-info");
788     }
789
790   Stem_info si;
791   si.dir_ = get_grob_direction (me);
792   si.ideal_y_ = scm_to_double (scm_car (scm_info));
793   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
794   return si;
795 }
796
797 /* TODO: add extra space for tremolos!  */
798 void
799 Stem::calc_stem_info (Grob *me)
800 {
801   Direction my_dir = get_grob_direction (me);
802
803   if (!my_dir)
804     {
805       programming_error ("no stem dir set");
806       my_dir = UP;
807     }
808
809   Real staff_space = Staff_symbol_referencer::staff_space (me);
810   Grob *beam = get_beam (me);
811   Real beam_translation = Beam::get_beam_translation (beam);
812   Real beam_thickness = Beam::get_thickness (beam);
813   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
814
815   /* Simple standard stem length */
816   SCM lengths = me->get_property ("beamed-lengths");
817   Real ideal_length
818     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
819
820     * staff_space
821     /* stem only extends to center of beam
822      */
823     - 0.5 * beam_thickness;
824
825   /* Condition: sane minimum free stem length (chord to beams) */
826   lengths = me->get_property ("beamed-minimum-free-lengths");
827   Real ideal_minimum_free
828     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
829     * staff_space;
830
831   /* UGH
832      It seems that also for ideal minimum length, we must use
833      the maximum beam count (for this direction):
834
835      \score{ \notes\relative c''{ [a8 a32] }}
836
837      must be horizontal. */
838   Real height_of_my_beams = beam_thickness
839     + (beam_count - 1) * beam_translation;
840
841   Real ideal_minimum_length = ideal_minimum_free
842     + height_of_my_beams
843     /* stem only extends to center of beam */
844     - 0.5 * beam_thickness;
845
846   ideal_length = max (ideal_length, ideal_minimum_length);
847
848   /* Convert to Y position, calculate for dir == UP */
849   Real note_start
850     =     /* staff positions */
851     head_positions (me)[my_dir] * 0.5
852     * my_dir * staff_space;
853   Real ideal_y = note_start + ideal_length;
854
855   /* Conditions for Y position */
856
857   /* Lowest beam of (UP) beam must never be lower than second staffline
858
859   Reference?
860
861   Although this (additional) rule is probably correct,
862   I expect that highest beam (UP) should also never be lower
863   than middle staffline, just as normal stems.
864
865   Reference?
866
867   Obviously not for grace beams.
868
869   Also, not for knees.  Seems to be a good thing. */
870   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
871   bool is_knee = to_boolean (beam->get_property ("knee"));
872   if (!no_extend_b && !is_knee)
873     {
874       /* Highest beam of (UP) beam must never be lower than middle
875          staffline */
876       ideal_y = max (ideal_y, 0.0);
877       /* Lowest beam of (UP) beam must never be lower than second staffline */
878       ideal_y = max (ideal_y, (-staff_space
879                                - beam_thickness + height_of_my_beams));
880     }
881
882   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
883
884   Real minimum_free
885     = scm_to_double (robust_list_ref
886                      (beam_count - 1,
887                       me->get_property
888                       ("beamed-extreme-minimum-free-lengths")))
889     * staff_space;
890
891   Real minimum_length = minimum_free
892     + height_of_my_beams
893     /* stem only extends to center of beam */
894     - 0.5 * beam_thickness;
895
896   if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
897     {
898       Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
899       y_ext.widen (0.5);        // FIXME. Should be tunable? 
900       minimum_length = max (minimum_length, y_ext.length ());
901     }
902
903   ideal_y *= my_dir;
904   Real minimum_y = note_start + minimum_length;
905   Real shortest_y = minimum_y * my_dir;
906
907   me->set_property ("stem-info",
908                     scm_list_2 (scm_from_double (ideal_y),
909                                 scm_from_double (shortest_y)));
910 }
911
912 Slice
913 Stem::beam_multiplicity (Grob *stem)
914 {
915   SCM beaming = stem->get_property ("beaming");
916   Slice le = int_list_to_slice (scm_car (beaming));
917   Slice ri = int_list_to_slice (scm_cdr (beaming));
918   le.unite (ri);
919   return le;
920 }
921
922 /* FIXME:  Too many properties  */
923 ADD_INTERFACE (Stem, "stem-interface",
924                "The stem represent the graphical stem.  "
925                "In addition, it internally connects note heads, beams and"
926                "tremolos. "
927                "Rests and whole notes have invisible stems.",
928                "tremolo-flag french-beaming "
929                "avoid-note-head thickness "
930                "stemlet-length rests "
931                "stem-info beamed-lengths beamed-minimum-free-lengths "
932                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
933                "duration-log beaming neutral-direction stem-end-position "
934                "note-heads direction length flag-style "
935                "no-stem-extend stroke-style");
936
937 /****************************************************************/
938
939 Stem_info::Stem_info ()
940 {
941   ideal_y_ = shortest_y_ = 0;
942   dir_ = CENTER;
943 }
944
945 void
946 Stem_info::scale (Real x)
947 {
948   ideal_y_ *= x;
949   shortest_y_ *= x;
950 }