]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
Update.
[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   return exthead;
199 }
200
201 static int
202 integer_compare (int const &a, int const &b)
203 {
204   return a - b;
205 }
206
207 /* The positions, in ascending order.  */
208 Array<int>
209 Stem::note_head_positions (Grob *me)
210 {
211   Array<int> ps;
212   for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
213        s = scm_cdr (s))
214     {
215       Grob *n = unsmob_grob (scm_car (s));
216       int p = Staff_symbol_referencer::get_rounded_position (n);
217
218       ps.push (p);
219     }
220
221   ps.sort (integer_compare);
222   return ps;
223 }
224
225 void
226 Stem::add_head (Grob *me, Grob *n)
227 {
228   n->set_property ("stem", me->self_scm ());
229   n->add_dependency (me);
230
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);
235 }
236
237 bool
238 Stem::is_invisible (Grob *me)
239 {
240   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
241                                            0.0);
242
243   return !((head_count (me)
244             || stemlet_length > 0.0)
245            && scm_to_int (me->get_property ("duration-log")) >= 1);
246 }
247
248 Direction
249 Stem::get_default_dir (Grob *me)
250 {
251   int staff_center = 0;
252   Interval hp = head_positions (me);
253   if (hp.is_empty ())
254     return CENTER;
255
256   int udistance = (int) (UP *hp[UP] - staff_center);
257   int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
258
259   if (sign (ddistance - udistance))
260     return Direction (sign (ddistance - udistance));
261
262   return to_dir (me->get_property ("neutral-direction"));
263 }
264
265 Real
266 Stem::get_default_stem_end_position (Grob *me)
267 {
268   Real ss = Staff_symbol_referencer::staff_space (me);
269   int durlog = duration_log (me);
270   SCM s;
271   Array<Real> a;
272
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
279     {
280       s = me->get_property ("lengths");
281       if (scm_is_pair (s))
282         length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
283     }
284
285   /* URGURGURG
286      'set-default-stemlen' sets direction too.   */
287   Direction dir = get_direction (me);
288   if (!dir)
289     {
290       dir = get_default_dir (me);
291       set_grob_direction (me, dir);
292     }
293
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)
298     {
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);
303
304       /* On boundary: shorten only half */
305       if (abs (head_positions (me)[dir]) <= 1)
306         shorten *= 0.5;
307
308       length -= shorten;
309     }
310
311   /* Tremolo stuff.  */
312   Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
313   if (t_flag && !unsmob_grob (me->get_property ("beam")))
314     {
315       /* Crude hack: add extra space if tremolo flag is there.
316
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  */
319
320       Real minlen = 1.0
321         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
322         / ss;
323
324       if (durlog >= 3)
325         {
326           Interval flag_ext = flag (me).extent (Y_AXIS);
327           if (!flag_ext.is_empty ())
328             minlen += 2 * flag_ext.length () / ss;
329
330           /* The clash is smaller for down stems (since the tremolo is
331              angled up.) */
332           if (dir == DOWN)
333             minlen -= 1.0;
334         }
335       length = length >? (minlen + 1.0);
336     }
337
338   Real st = dir ? hp[dir] + dir * length : 0;
339
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;
344
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)
350     {
351       Grob *closest_to_flag = extremal_heads (me)[dir];
352       Grob *dots = closest_to_flag
353         ? Rhythmic_head::get_dots (closest_to_flag) : 0;
354
355       if (dots)
356         {
357           Real dp = Staff_symbol_referencer::get_position (dots);
358           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
359
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)
363             {
364               Grob *par = dots->get_parent (X_AXIS);
365
366               if (Dot_column::has_interface (par))
367                 {
368                   Side_position_interface::add_support (par, me);
369
370                   /* TODO: apply some better logic here. The flag is
371                      curved inwards, so this will typically be too
372                      much. */
373                 }
374             }
375         }
376     }
377   return st;
378 }
379
380 /* The log of the duration (Number of hooks on the flag minus two)  */
381 int
382 Stem::duration_log (Grob *me)
383 {
384   SCM s = me->get_property ("duration-log");
385   return (scm_is_number (s)) ? scm_to_int (s) : 2;
386 }
387
388 void
389 Stem::position_noteheads (Grob *me)
390 {
391   if (!head_count (me))
392     return;
393
394   Link_array<Grob> heads
395     = extract_grob_array (me, ly_symbol2scm ("note-heads"));
396
397   heads.sort (compare_position);
398   Direction dir = get_direction (me);
399
400   if (dir < 0)
401     heads.reverse ();
402
403   Real thick = thickness (me);
404
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);
410
411   bool parity = true;
412   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
413   for (int i = 1; i < heads.size (); i++)
414     {
415       Real p = Staff_symbol_referencer::get_position (heads[i]);
416       Real dy = fabs (lastpos- p);
417
418       /*
419         dy should always be 0.5, 0.0, 1.0, but provide safety margin
420         for rounding errors.
421       */
422       if (dy < 1.1)
423         {
424           if (parity)
425             {
426               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
427
428               Direction d = get_direction (me);
429               /*
430                 Reversed head should be shifted ell-thickness, but this
431                 looks too crowded, so we only shift ell-0.5*thickness.
432
433                 This leads to assymetry: Normal heads overlap the
434                 stem 100% whereas reversed heads only overlaps the
435                 stem 50%
436               */
437
438               Real reverse_overlap = 0.5;
439               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
440                                         X_AXIS);
441
442               if (is_invisible (me))
443                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
444                                           X_AXIS);
445
446               /* TODO:
447
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.
451
452               |
453               X|
454               |X  <- kern this.
455               |
456               X
457
458               */
459             }
460           parity = !parity;
461         }
462       else
463         parity = true;
464
465       lastpos = int (p);
466     }
467 }
468
469 MAKE_SCHEME_CALLBACK (Stem, before_line_breaking, 1);
470 SCM
471 Stem::before_line_breaking (SCM smob)
472 {
473   Grob *me = unsmob_grob (smob);
474
475   /*
476     Do the calculations for visible stems, but also for invisible stems
477     with note heads (i.e. half notes.)
478   */
479   if (head_count (me))
480     {
481       stem_end_position (me);   // ugh. Trigger direction calc.
482       position_noteheads (me);
483     }
484
485   return SCM_UNSPECIFIED;
486 }
487
488 /*
489   ugh.
490   When in a beam with tuplet brackets, brew_mol is called early,
491   caching a wrong value.
492 */
493 MAKE_SCHEME_CALLBACK (Stem, height, 2);
494 SCM
495 Stem::height (SCM smob, SCM ax)
496 {
497   Axis a = (Axis)scm_to_int (ax);
498   Grob *me = unsmob_grob (smob);
499   assert (a == Y_AXIS);
500
501   /*
502     ugh. - this dependency should be automatic.
503   */
504   Grob *beam = get_beam (me);
505   if (beam)
506     {
507       Beam::after_line_breaking (beam->self_scm ());
508     }
509
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))
515     {
516       Direction d = get_direction (me);
517       if (d == CENTER)
518         {
519           programming_error ("No stem direction");
520           d = UP;
521         }
522       iv[d] += d * Beam::get_thickness (b) * 0.5;
523     }
524
525   return ly_interval2scm (iv);
526 }
527
528 Stencil
529 Stem::flag (Grob *me)
530 {
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;
535
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);
539
540   if (flag_style == "no-flag")
541     return Stencil ();
542
543   bool adjust = true;
544
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.
553     */
554     {
555       if (adjust)
556         {
557           int p = (int) (rint (stem_end_position (me)));
558           staffline_offs
559             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
560         }
561       else
562         {
563           staffline_offs = "2";
564         }
565     }
566   else
567     {
568       staffline_offs = "";
569     }
570
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));
578
579   SCM stroke_style_scm = me->get_property ("stroke-style");
580   if (scm_is_string (stroke_style_scm))
581     {
582       String stroke_style = ly_scm2string (stroke_style_scm);
583       if (!stroke_style.is_empty ())
584         {
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);
591         }
592     }
593
594   return flag;
595 }
596
597 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
598 SCM
599 Stem::width_callback (SCM e, SCM ax)
600 {
601   Axis a = (Axis) scm_to_int (ax);
602   assert (a == X_AXIS);
603   Grob *me = unsmob_grob (e);
604
605   Interval r;
606
607   if (is_invisible (me))
608     {
609       r.set_empty ();
610     }
611   else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
612     {
613       r = Interval (-1, 1);
614       r *= thickness (me) / 2;
615     }
616   else
617     {
618       r = flag (me).extent (X_AXIS)
619         + thickness (me) / 2;
620     }
621   return ly_interval2scm (r);
622 }
623
624 Real
625 Stem::thickness (Grob *me)
626 {
627   return scm_to_double (me->get_property ("thickness"))
628     * Staff_symbol_referencer::line_thickness (me);
629 }
630
631 MAKE_SCHEME_CALLBACK (Stem, print, 1);
632 SCM
633 Stem::print (SCM smob)
634 {
635   Grob *me = unsmob_grob (smob);
636   Stencil mol;
637   Direction d = get_direction (me);
638
639   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
640                                            0.0);
641   bool stemlet = stemlet_length > 0.0;
642
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);
650
651   if (!lh && !stemlet)
652     return SCM_EOL;
653
654   if (!lh && stemlet && !beam)
655     return SCM_EOL;
656
657   if (is_invisible (me))
658     return SCM_EOL;
659
660   Real y2 = stem_end_position (me);
661   Real y1 = y2;
662   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
663
664   if (lh)
665     y2 = Staff_symbol_referencer::get_position (lh);
666   else if (stemlet)
667     {
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;
671
672       y2 -= d
673         * (0.5 * beam_thickness
674            + beam_translation * (0 >? (beam_count - 1))
675            + stemlet_length) / half_space;
676     }
677
678   Interval stem_y (y1 <? y2, y2 >? y1);
679
680   if (Grob *hed = support_head (me))
681     {
682       /*
683         must not take ledgers into account.
684       */
685       Interval head_height = hed->extent (hed, Y_AXIS);
686       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
687
688       y_attach = head_height.linear_combination (y_attach);
689       stem_y[Direction (-d)] += d * y_attach / half_space;
690     }
691
692   // URG
693   Real stem_width = thickness (me);
694   Real blot
695     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
696
697   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
698                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
699
700   Stencil ss = Lookup::round_filled_box (b, blot);
701   mol.add_stencil (ss);
702
703   if (!get_beam (me) && abs (duration_log (me)) > 2)
704     {
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);
709     }
710
711   return mol.smobbed_copy ();
712 }
713
714 /*
715   move the stem to right of the notehead if it is up.
716 */
717 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
718 SCM
719 Stem::offset_callback (SCM element_smob, SCM)
720 {
721   Grob *me = unsmob_grob (element_smob);
722   Real r = 0.0;
723
724   if (Grob *f = first_head (me))
725     {
726       Interval head_wid = f->extent (f, X_AXIS);
727       Real attach = 0.0;
728
729       if (is_invisible (me))
730         attach = 0.0;
731       else
732         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
733
734       Direction d = get_direction (me);
735       Real real_attach = head_wid.linear_combination (d * attach);
736       r = real_attach;
737
738       /* If not centered: correct for stem thickness.  */
739       if (attach)
740         {
741           Real rule_thick = thickness (me);
742           r += -d * rule_thick * 0.5;
743         }
744     }
745   else
746     {
747       SCM rests = me->get_property ("rests");
748       if (scm_is_pair (rests))
749         {
750           Grob *rest = unsmob_grob (scm_car (rests));
751           r = rest->extent (rest, X_AXIS).center ();
752         }
753     }
754   return scm_make_real (r);
755 }
756
757 Spanner *
758 Stem::get_beam (Grob *me)
759 {
760   SCM b = me->get_property ("beam");
761   return dynamic_cast<Spanner *> (unsmob_grob (b));
762 }
763
764 Stem_info
765 Stem::get_stem_info (Grob *me)
766 {
767   /* Return cached info if available */
768   SCM scm_info = me->get_property ("stem-info");
769   if (!scm_is_pair (scm_info))
770     {
771       calc_stem_info (me);
772       scm_info = me->get_property ("stem-info");
773     }
774
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;
780 }
781
782 /* TODO: add extra space for tremolos!  */
783 void
784 Stem::calc_stem_info (Grob *me)
785 {
786   Direction my_dir = get_grob_direction (me);
787
788   if (!my_dir)
789     {
790       programming_error ("No stem dir set?");
791       my_dir = UP;
792     }
793
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);
799
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))
804
805     * staff_space
806     /* stem only extends to center of beam
807      */
808     - 0.5 * beam_thickness;
809
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;
815
816   /* UGH
817      It seems that also for ideal minimum length, we must use
818      the maximum beam count (for this direction):
819
820      \score{ \notes\relative c''{ [a8 a32] }}
821
822      must be horizontal. */
823   Real height_of_my_beams = beam_thickness
824     + (beam_count - 1) * beam_translation;
825
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;
830
831   ideal_length = ideal_length >? ideal_minimum_length;
832
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;
839
840   /* Conditions for Y position */
841
842   /* Lowest beam of (UP) beam must never be lower than second staffline
843
844   Reference?
845
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.
849
850   Reference?
851
852   Obviously not for grace beams.
853
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)
858     {
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);
865     }
866
867   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
868
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;
875
876   Real minimum_length = minimum_free
877     + height_of_my_beams
878     /* stem only extends to center of beam */
879     - 0.5 * beam_thickness;
880
881   ideal_y *= my_dir;
882   Real minimum_y = note_start + minimum_length;
883   Real shortest_y = minimum_y * my_dir;
884
885   me->set_property ("stem-info",
886                     scm_list_2 (scm_make_real (ideal_y),
887                                 scm_make_real (shortest_y)));
888 }
889
890 Slice
891 Stem::beam_multiplicity (Grob *stem)
892 {
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;
898 }
899
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");
914
915 /****************************************************************/
916
917 Stem_info::Stem_info ()
918 {
919   ideal_y_ = shortest_y_ = 0;
920   dir_ = CENTER;
921 }
922
923 void
924 Stem_info::scale (Real x)
925 {
926   ideal_y_ *= x;
927   shortest_y_ *= x;
928 }