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