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