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