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