]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/grob.cc (get_stencil): simplify: use callback mechanism to
[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
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
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
293   /* Stems in unnatural (forced) direction should be shortened,
294      according to [Roush & Gourlay] */
295   Interval hp = head_positions (me);
296   if (dir && dir * hp[dir] >= 0)
297     {
298       SCM sshorten = me->get_property ("stem-shorten");
299       SCM scm_shorten = scm_is_pair (sshorten)
300         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
301       Real shorten = 2* robust_scm2double (scm_shorten, 0);
302
303       /* On boundary: shorten only half */
304       if (abs (head_positions (me)[dir]) <= 1)
305         shorten *= 0.5;
306
307       length -= shorten;
308     }
309
310   /* Tremolo stuff.  */
311   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
312   if (t_flag && !unsmob_grob (me->get_object ("beam")))
313     {
314       /* Crude hack: add extra space if tremolo flag is there.
315
316       We can't do this for the beam, since we get into a loop
317       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
318
319       Real minlen = 1.0
320         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
321         / ss;
322
323       if (durlog >= 3)
324         {
325           Interval flag_ext = flag (me).extent (Y_AXIS);
326           if (!flag_ext.is_empty ())
327             minlen += 2 * flag_ext.length () / ss;
328
329           /* The clash is smaller for down stems (since the tremolo is
330              angled up.) */
331           if (dir == DOWN)
332             minlen -= 1.0;
333         }
334       length = max (length, minlen + 1.0);
335     }
336
337   Real st = dir ? hp[dir] + dir * length : 0;
338
339   /* TODO: change name  to extend-stems to staff/center/'()  */
340   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
341   if (!no_extend_b && dir * st < 0)
342     st = 0.0;
343
344   /* Make a little room if we have a upflag and there is a dot.
345      previous approach was to lengthen the stem. This is not
346      good typesetting practice.  */
347   if (!get_beam (me) && dir == UP
348       && durlog > 2)
349     {
350       Grob *closest_to_flag = extremal_heads (me)[dir];
351       Grob *dots = closest_to_flag
352         ? Rhythmic_head::get_dots (closest_to_flag) : 0;
353
354       if (dots)
355         {
356           Real dp = Staff_symbol_referencer::get_position (dots);
357           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
358
359           /* Very gory: add myself to the X-support of the parent,
360              which should be a dot-column. */
361           if (dir * (st + flagy - dp) < 0.5)
362             {
363               Grob *par = dots->get_parent (X_AXIS);
364
365               if (Dot_column::has_interface (par))
366                 {
367                   Side_position_interface::add_support (par, me);
368
369                   /* TODO: apply some better logic here. The flag is
370                      curved inwards, so this will typically be too
371                      much. */
372                 }
373             }
374         }
375     }
376   return st;
377 }
378
379 /* The log of the duration (Number of hooks on the flag minus two)  */
380 int
381 Stem::duration_log (Grob *me)
382 {
383   SCM s = me->get_property ("duration-log");
384   return (scm_is_number (s)) ? scm_to_int (s) : 2;
385 }
386
387 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
388 SCM
389 Stem::calc_positioning_done (SCM smob)
390 {
391   Grob *me = unsmob_grob (smob);  
392   if (!head_count (me))
393     return SCM_BOOL_T;
394
395   extract_grob_set (me, "note-heads", ro_heads);
396   Link_array<Grob> heads (ro_heads);
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   return SCM_BOOL_T;
469 }
470
471
472
473 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
474 SCM
475 Stem::calc_direction (SCM smob)
476 {
477   Grob *me = unsmob_grob (smob);
478   Direction dir = CENTER;
479   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
480     {
481       SCM ignore_me = beam->get_property ("direction");
482       (void) ignore_me;
483       dir = get_grob_direction (me);
484     }
485   else
486     dir = get_default_dir (me);
487
488   return scm_from_int (dir);
489 }
490
491 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1);
492 SCM
493 Stem::calc_stem_end_position (SCM smob)
494 {
495   Grob *me = unsmob_grob (smob);
496
497   /*
498     Do the calculations for visible stems, but also for invisible stems
499     with note heads (i.e. half notes.)
500   */
501   Real pos = 0.0;
502   if (head_count (me))
503     {
504       pos = stem_end_position (me);     // ugh. Trigger direction calc.
505     }
506
507   return scm_from_double (pos);
508 }
509
510 MAKE_SCHEME_CALLBACK (Stem, height, 2);
511 SCM
512 Stem::height (SCM smob, SCM ax)
513 {
514   Axis a = (Axis)scm_to_int (ax);
515   Grob *me = unsmob_grob (smob);
516   assert (a == Y_AXIS);
517
518   Direction dir = get_grob_direction (me);
519   
520   /* Trigger callback.
521
522   UGH. Should be automatic
523   */
524   Grob *beam = get_beam (me);
525   if (beam)
526     {
527       beam->get_property ("positions");
528     }
529
530   /* FIXME uncached? */
531   Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
532   if (beam)
533     {
534       if (dir == CENTER)
535         {
536           programming_error ("no stem direction");
537           dir = UP;
538         }
539       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
540     }
541
542   return ly_interval2scm (iv);
543 }
544
545 Stencil
546 Stem::flag (Grob *me)
547 {
548   int log = duration_log (me);
549   if (log < 3
550       || unsmob_grob (me->get_object ("beam")))
551     return Stencil ();
552
553   /*
554     TODO: maybe property stroke-style should take different values,
555     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
556     '() or "grace").  */
557   String flag_style;
558
559   SCM flag_style_scm = me->get_property ("flag-style");
560   if (scm_is_symbol (flag_style_scm))
561     flag_style = ly_symbol2string (flag_style_scm);
562
563   if (flag_style == "no-flag")
564     return Stencil ();
565
566   bool adjust = true;
567
568   String staffline_offs;
569   if (String::compare (flag_style, "mensural") == 0)
570     /* Mensural notation: For notes on staff lines, use different
571        flags than for notes between staff lines.  The idea is that
572        flags are always vertically aligned with the staff lines,
573        regardless if the note head is on a staff line or between two
574        staff lines.  In other words, the inner end of a flag always
575        touches a staff line.
576     */
577     {
578       if (adjust)
579         {
580           int p = (int) (rint (stem_end_position (me)));
581           staffline_offs
582             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
583         }
584       else
585         staffline_offs = "2";
586     }
587   else
588     staffline_offs = "";
589
590   char dir = (get_direction (me) == UP) ? 'u' : 'd';
591   String font_char = flag_style
592     + to_string (dir) + staffline_offs + to_string (log);
593   Font_metric *fm = Font_interface::get_default_font (me);
594   Stencil flag = fm->find_by_name ("flags." + font_char);
595   if (flag.is_empty ())
596     me->warning (_f ("flag `%s' not found", font_char));
597
598   SCM stroke_style_scm = me->get_property ("stroke-style");
599   if (scm_is_string (stroke_style_scm))
600     {
601       String stroke_style = ly_scm2string (stroke_style_scm);
602       if (!stroke_style.is_empty ())
603         {
604           String font_char = to_string (dir) + stroke_style;
605           Stencil stroke = fm->find_by_name ("flags." + font_char);
606           if (stroke.is_empty ())
607             me->warning (_f ("flag stroke `%s' not found", font_char));
608           else
609             flag.add_stencil (stroke);
610         }
611     }
612
613   return flag;
614 }
615
616 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
617 SCM
618 Stem::width_callback (SCM e, SCM ax)
619 {
620   (void) ax;
621   assert (scm_to_int (ax) == X_AXIS);
622   Grob *me = unsmob_grob (e);
623
624   Interval r;
625
626   if (is_invisible (me))
627     r.set_empty ();
628   else if (unsmob_grob (me->get_object ("beam"))
629            || abs (duration_log (me)) <= 2)
630     {
631       r = Interval (-1, 1);
632       r *= thickness (me) / 2;
633     }
634   else
635     {
636       r = Interval (-1, 1) * thickness (me) * 0.5;
637       r.unite (flag (me).extent (X_AXIS));
638     }
639   return ly_interval2scm (r);
640 }
641
642 Real
643 Stem::thickness (Grob *me)
644 {
645   return scm_to_double (me->get_property ("thickness"))
646     * Staff_symbol_referencer::line_thickness (me);
647 }
648
649 MAKE_SCHEME_CALLBACK (Stem, print, 1);
650 SCM
651 Stem::print (SCM smob)
652 {
653   Grob *me = unsmob_grob (smob);
654   Stencil mol;
655   Direction d = get_direction (me);
656
657   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
658                                            0.0);
659   bool stemlet = stemlet_length > 0.0;
660
661   /* TODO: make the stem start a direction ?
662      This is required to avoid stems passing in tablature chords.  */
663   Grob *lh
664     = to_boolean (me->get_property ("avoid-note-head"))
665     ? last_head (me)
666     : first_head (me);
667   Grob *beam = get_beam (me);
668
669   if (!lh && !stemlet)
670     return SCM_EOL;
671
672   if (!lh && stemlet && !beam)
673     return SCM_EOL;
674
675   if (is_invisible (me))
676     return SCM_EOL;
677
678   Real y2 = stem_end_position (me);
679   Real y1 = y2;
680   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
681
682   if (lh)
683     y2 = Staff_symbol_referencer::get_position (lh);
684   else if (stemlet)
685     {
686       Real beam_translation = Beam::get_beam_translation (beam);
687       Real beam_thickness = Beam::get_thickness (beam);
688       int beam_count = beam_multiplicity (me).length () + 1;
689
690       y2 -= d
691         * (0.5 * beam_thickness
692            + beam_translation * max (0, (beam_count - 1))
693            + stemlet_length) / half_space;
694     }
695
696   Interval stem_y (min (y1, y2), max (y2, y1));
697
698   if (Grob *hed = support_head (me))
699     {
700       /*
701         must not take ledgers into account.
702       */
703       Interval head_height = hed->extent (hed, Y_AXIS);
704       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
705
706       y_attach = head_height.linear_combination (y_attach);
707       stem_y[Direction (-d)] += d * y_attach / half_space;
708     }
709
710   // URG
711   Real stem_width = thickness (me);
712   Real blot
713     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
714
715   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
716                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
717
718   Stencil ss = Lookup::round_filled_box (b, blot);
719   mol.add_stencil (ss);
720
721   mol.add_stencil (get_translated_flag (me));
722
723   return mol.smobbed_copy ();
724 }
725
726 Stencil
727 Stem::get_translated_flag (Grob *me)
728 {
729   Stencil fl = flag (me);
730   if (!fl.is_empty ())
731     {
732       Direction d = get_direction (me);
733       Real blot
734         = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
735       Real stem_width = thickness (me);
736       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
737       Real y2 = stem_end_position (me);
738       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
739       fl.translate_axis (stem_width / 2, X_AXIS);
740     }
741   return fl;
742 }
743
744
745 /*
746   move the stem to right of the notehead if it is up.
747 */
748 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
749 SCM
750 Stem::offset_callback (SCM element_smob, SCM)
751 {
752   Grob *me = unsmob_grob (element_smob);
753   Real r = 0.0;
754
755   if (Grob *f = first_head (me))
756     {
757       Interval head_wid = f->extent (f, X_AXIS);
758       Real attach = 0.0;
759
760       if (is_invisible (me))
761         attach = 0.0;
762       else
763         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
764
765       Direction d = get_direction (me);
766       Real real_attach = head_wid.linear_combination (d * attach);
767       r = real_attach;
768
769       /* If not centered: correct for stem thickness.  */
770       if (attach)
771         {
772           Real rule_thick = thickness (me);
773           r += -d * rule_thick * 0.5;
774         }
775     }
776   else
777     {
778       extract_grob_set (me, "rests", rests);
779       if (rests.size ())
780         {
781           Grob *rest = rests.top ();
782           r = rest->extent (rest, X_AXIS).center ();
783         }
784     }
785   return scm_from_double (r);
786 }
787
788 Spanner *
789 Stem::get_beam (Grob *me)
790 {
791   SCM b = me->get_object ("beam");
792   return dynamic_cast<Spanner *> (unsmob_grob (b));
793 }
794
795 Stem_info
796 Stem::get_stem_info (Grob *me)
797 {
798   Stem_info si;
799   si.dir_ = get_grob_direction (me);
800   
801   SCM scm_info = me->get_property ("stem-info");
802   si.ideal_y_ = scm_to_double (scm_car (scm_info));
803   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
804   return si;
805 }
806
807 /* TODO: add extra space for tremolos!  */
808 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
809 SCM
810 Stem::calc_stem_info (SCM smob)
811 {
812   Grob *me = unsmob_grob (smob);
813   Direction my_dir = get_grob_direction (me);
814
815   if (!my_dir)
816     {
817       programming_error ("no stem dir set");
818       my_dir = UP;
819     }
820
821   Real staff_space = Staff_symbol_referencer::staff_space (me);
822   Grob *beam = get_beam (me);
823   Real beam_translation = Beam::get_beam_translation (beam);
824   Real beam_thickness = Beam::get_thickness (beam);
825   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
826
827   /* Simple standard stem length */
828   SCM lengths = me->get_property ("beamed-lengths");
829   Real ideal_length
830     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
831
832     * staff_space
833     /* stem only extends to center of beam
834      */
835     - 0.5 * beam_thickness;
836
837   /* Condition: sane minimum free stem length (chord to beams) */
838   lengths = me->get_property ("beamed-minimum-free-lengths");
839   Real ideal_minimum_free
840     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
841     * staff_space;
842
843   /* UGH
844      It seems that also for ideal minimum length, we must use
845      the maximum beam count (for this direction):
846
847      \score{ \notes\relative c''{ [a8 a32] }}
848
849      must be horizontal. */
850   Real height_of_my_beams = beam_thickness
851     + (beam_count - 1) * beam_translation;
852
853   Real ideal_minimum_length = ideal_minimum_free
854     + height_of_my_beams
855     /* stem only extends to center of beam */
856     - 0.5 * beam_thickness;
857
858   ideal_length = max (ideal_length, ideal_minimum_length);
859
860   /* Convert to Y position, calculate for dir == UP */
861   Real note_start
862     =     /* staff positions */
863     head_positions (me)[my_dir] * 0.5
864     * my_dir * staff_space;
865   Real ideal_y = note_start + ideal_length;
866
867   /* Conditions for Y position */
868
869   /* Lowest beam of (UP) beam must never be lower than second staffline
870
871   Reference?
872
873   Although this (additional) rule is probably correct,
874   I expect that highest beam (UP) should also never be lower
875   than middle staffline, just as normal stems.
876
877   Reference?
878
879   Obviously not for grace beams.
880
881   Also, not for knees.  Seems to be a good thing. */
882   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
883   bool is_knee = to_boolean (beam->get_property ("knee"));
884   if (!no_extend_b && !is_knee)
885     {
886       /* Highest beam of (UP) beam must never be lower than middle
887          staffline */
888       ideal_y = max (ideal_y, 0.0);
889       /* Lowest beam of (UP) beam must never be lower than second staffline */
890       ideal_y = max (ideal_y, (-staff_space
891                                - beam_thickness + height_of_my_beams));
892     }
893
894   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
895
896   Real minimum_free
897     = scm_to_double (robust_list_ref
898                      (beam_count - 1,
899                       me->get_property
900                       ("beamed-extreme-minimum-free-lengths")))
901     * staff_space;
902
903   Real minimum_length = minimum_free
904     + height_of_my_beams
905     /* stem only extends to center of beam */
906     - 0.5 * beam_thickness;
907
908   if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
909     {
910       Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
911       y_ext.widen (0.5);        // FIXME. Should be tunable? 
912       minimum_length = max (minimum_length, y_ext.length ());
913     }
914
915   ideal_y *= my_dir;
916   Real minimum_y = note_start + minimum_length;
917   Real shortest_y = minimum_y * my_dir;
918
919   return scm_list_2 (scm_from_double (ideal_y),
920                      scm_from_double (shortest_y));
921 }
922
923 Slice
924 Stem::beam_multiplicity (Grob *stem)
925 {
926   SCM beaming = stem->get_property ("beaming");
927   Slice le = int_list_to_slice (scm_car (beaming));
928   Slice ri = int_list_to_slice (scm_cdr (beaming));
929   le.unite (ri);
930   return le;
931 }
932
933 /* FIXME:  Too many properties  */
934 ADD_INTERFACE (Stem, "stem-interface",
935                "The stem represent the graphical stem.  "
936                "In addition, it internally connects note heads, beams and"
937                "tremolos. "
938                "Rests and whole notes have invisible stems.",
939
940                /* properties */
941                
942                "avoid-note-head "
943                "beam "
944                "beamed-extreme-minimum-free-lengths "
945                "beamed-lengths "
946                "beamed-minimum-free-lengths "
947                "beaming "
948                "direction "
949                "duration-log "
950                "flag-style "
951                "french-beaming "
952                "length "
953                "lengths "
954                "neutral-direction "
955                "no-stem-extend "
956                "note-heads "
957                "positioning-done "
958                "rests "
959                "stem-end-position "
960                "stem-info "
961                "stem-shorten "
962                "stemlet-length "
963                "stroke-style "
964                "thickness "
965                "tremolo-flag "
966                );
967
968 /****************************************************************/
969
970 Stem_info::Stem_info ()
971 {
972   ideal_y_ = shortest_y_ = 0;
973   dir_ = CENTER;
974 }
975
976 void
977 Stem_info::scale (Real x)
978 {
979   ideal_y_ *= x;
980   shortest_y_ *= x;
981 }