]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* ly/engraver-init.ly (AncientRemoveEmptyStaffContext): use length-fraction.
[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   Real w = hed->extent (hed, X_AXIS)[dir];
379   for (int i = 0; i < heads.size (); i++)
380     heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
381                               X_AXIS);
382
383   bool parity = true;
384   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
385   for (int i = 1; i < heads.size (); i++)
386     {
387       Real p = Staff_symbol_referencer::get_position (heads[i]);
388       Real dy = fabs (lastpos- p);
389
390       /*
391         dy should always be 0.5, 0.0, 1.0, but provide safety margin
392         for rounding errors.
393       */
394       if (dy < 1.1)
395         {
396           if (parity)
397             {
398               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
399
400               Direction d = get_grob_direction (me);
401               /*
402                 Reversed head should be shifted ell-thickness, but this
403                 looks too crowded, so we only shift ell-0.5*thickness.
404
405                 This leads to assymetry: Normal heads overlap the
406                 stem 100% whereas reversed heads only overlaps the
407                 stem 50%
408               */
409
410               Real reverse_overlap = 0.5;
411               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
412                                         X_AXIS);
413
414               if (is_invisible (me))
415                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
416                                           X_AXIS);
417
418               /* TODO:
419
420               For some cases we should kern some more: when the
421               distance between the next or prev note is too large, we'd
422               get large white gaps, eg.
423
424               |
425               X|
426               |X  <- kern this.
427               |
428               X
429
430               */
431             }
432           parity = !parity;
433         }
434       else
435         parity = true;
436
437       lastpos = int (p);
438     }
439
440   return SCM_BOOL_T;
441 }
442
443
444
445 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
446 SCM
447 Stem::calc_direction (SCM smob)
448 {
449   Grob *me = unsmob_grob (smob);
450   Direction dir = CENTER;
451   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
452     {
453       SCM ignore_me = beam->get_property ("direction");
454       (void) ignore_me;
455       dir = get_grob_direction (me);
456     }
457   else
458     dir = get_default_dir (me);
459   
460   return scm_from_int (dir);
461 }
462
463 Direction
464 Stem::get_default_dir (Grob *me)
465 {
466   Direction dir = CENTER;
467   int staff_center = 0;
468   Interval hp = head_positions (me);
469   if (!hp.is_empty ())
470     {
471       int udistance = (int) (UP *hp[UP] - staff_center);
472       int ddistance = (int) (DOWN *hp[DOWN] - staff_center);
473       
474       if (sign (ddistance - udistance))
475         dir = Direction (sign (ddistance - udistance));
476       else
477         dir = to_dir (me->get_property ("neutral-direction"));
478     }
479   return dir;
480 }
481
482
483
484 MAKE_SCHEME_CALLBACK (Stem, height, 2);
485 SCM
486 Stem::height (SCM smob, SCM ax)
487 {
488   Axis a = (Axis)scm_to_int (ax);
489   Grob *me = unsmob_grob (smob);
490   assert (a == Y_AXIS);
491
492   Direction dir = get_grob_direction (me);
493   
494   /* Trigger callback.
495
496   UGH. Should be automatic
497   */
498   Grob *beam = get_beam (me);
499   if (beam)
500     {
501       beam->get_property ("positions");
502     }
503
504   /* FIXME uncached? */
505   Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
506   if (beam)
507     {
508       if (dir == CENTER)
509         {
510           programming_error ("no stem direction");
511           dir = UP;
512         }
513       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
514     }
515
516   return ly_interval2scm (iv);
517 }
518
519 Real
520 Stem::stem_end_position (Grob *me)
521 {
522   return robust_scm2double (me->get_property ("stem-end-position"), 0);
523 }
524
525 Stencil
526 Stem::flag (Grob *me)
527 {
528   int log = duration_log (me);
529   if (log < 3
530       || unsmob_grob (me->get_object ("beam")))
531     return Stencil ();
532
533   /*
534     TODO: maybe property stroke-style should take different values,
535     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
536     '() or "grace").  */
537   String flag_style;
538
539   SCM flag_style_scm = me->get_property ("flag-style");
540   if (scm_is_symbol (flag_style_scm))
541     flag_style = ly_symbol2string (flag_style_scm);
542
543   if (flag_style == "no-flag")
544     return Stencil ();
545
546   bool adjust = true;
547
548   String staffline_offs;
549   if (String::compare (flag_style, "mensural") == 0)
550     /* Mensural notation: For notes on staff lines, use different
551        flags than for notes between staff lines.  The idea is that
552        flags are always vertically aligned with the staff lines,
553        regardless if the note head is on a staff line or between two
554        staff lines.  In other words, the inner end of a flag always
555        touches a staff line.
556     */
557     {
558       if (adjust)
559         {
560           int p = (int) (rint (stem_end_position (me)));
561           staffline_offs
562             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
563         }
564       else
565         staffline_offs = "2";
566     }
567   else
568     staffline_offs = "";
569
570   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
571   String font_char = flag_style
572     + to_string (dir) + staffline_offs + to_string (log);
573   Font_metric *fm = Font_interface::get_default_font (me);
574   Stencil flag = fm->find_by_name ("flags." + font_char);
575   if (flag.is_empty ())
576     me->warning (_f ("flag `%s' not found", font_char));
577
578   SCM stroke_style_scm = me->get_property ("stroke-style");
579   if (scm_is_string (stroke_style_scm))
580     {
581       String stroke_style = ly_scm2string (stroke_style_scm);
582       if (!stroke_style.is_empty ())
583         {
584           String font_char = to_string (dir) + stroke_style;
585           Stencil stroke = fm->find_by_name ("flags." + font_char);
586           if (stroke.is_empty ())
587             me->warning (_f ("flag stroke `%s' not found", font_char));
588           else
589             flag.add_stencil (stroke);
590         }
591     }
592
593   return flag;
594 }
595
596 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
597 SCM
598 Stem::width_callback (SCM e, SCM ax)
599 {
600   (void) ax;
601   assert (scm_to_int (ax) == X_AXIS);
602   Grob *me = unsmob_grob (e);
603
604   Interval r;
605
606   if (is_invisible (me))
607     r.set_empty ();
608   else if (unsmob_grob (me->get_object ("beam"))
609            || abs (duration_log (me)) <= 2)
610     {
611       r = Interval (-1, 1);
612       r *= thickness (me) / 2;
613     }
614   else
615     {
616       r = Interval (-1, 1) * thickness (me) * 0.5;
617       r.unite (flag (me).extent (X_AXIS));
618     }
619   return ly_interval2scm (r);
620 }
621
622 Real
623 Stem::thickness (Grob *me)
624 {
625   return scm_to_double (me->get_property ("thickness"))
626     * Staff_symbol_referencer::line_thickness (me);
627 }
628
629 MAKE_SCHEME_CALLBACK (Stem, print, 1);
630 SCM
631 Stem::print (SCM smob)
632 {
633   Grob *me = unsmob_grob (smob);
634   Stencil mol;
635   Direction d = get_grob_direction (me);
636
637   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
638                                            0.0);
639   bool stemlet = stemlet_length > 0.0;
640
641   /* TODO: make the stem start a direction ?
642      This is required to avoid stems passing in tablature chords.  */
643   Grob *lh
644     = to_boolean (me->get_property ("avoid-note-head"))
645     ? last_head (me)
646     : first_head (me);
647   Grob *beam = get_beam (me);
648
649   if (!lh && !stemlet)
650     return SCM_EOL;
651
652   if (!lh && stemlet && !beam)
653     return SCM_EOL;
654
655   if (is_invisible (me))
656     return SCM_EOL;
657
658   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
659   Real y1 = y2;
660   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
661
662   if (lh)
663     y2 = Staff_symbol_referencer::get_position (lh);
664   else if (stemlet)
665     {
666       Real beam_translation = Beam::get_beam_translation (beam);
667       Real beam_thickness = Beam::get_thickness (beam);
668       int beam_count = beam_multiplicity (me).length () + 1;
669
670       y2 -= d
671         * (0.5 * beam_thickness
672            + beam_translation * max (0, (beam_count - 1))
673            + stemlet_length) / half_space;
674     }
675
676   Interval stem_y (min (y1, y2), max (y2, y1));
677
678   if (Grob *hed = support_head (me))
679     {
680       /*
681         must not take ledgers into account.
682       */
683       Interval head_height = hed->extent (hed, Y_AXIS);
684       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
685
686       y_attach = head_height.linear_combination (y_attach);
687       stem_y[Direction (-d)] += d * y_attach / half_space;
688     }
689
690   // URG
691   Real stem_width = thickness (me);
692   Real blot
693     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
694
695   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
696                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
697
698   Stencil ss = Lookup::round_filled_box (b, blot);
699   mol.add_stencil (ss);
700
701   mol.add_stencil (get_translated_flag (me));
702
703   return mol.smobbed_copy ();
704 }
705
706 Stencil
707 Stem::get_translated_flag (Grob *me)
708 {
709   Stencil fl = flag (me);
710   if (!fl.is_empty ())
711     {
712       Direction d = get_grob_direction (me);
713       Real blot
714         = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
715       Real stem_width = thickness (me);
716       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
717       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
718       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
719       fl.translate_axis (stem_width / 2, X_AXIS);
720     }
721   return fl;
722 }
723
724
725 /*
726   move the stem to right of the notehead if it is up.
727 */
728 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
729 SCM
730 Stem::offset_callback (SCM element_smob, SCM)
731 {
732   Grob *me = unsmob_grob (element_smob);
733   Real r = 0.0;
734
735   if (Grob *f = first_head (me))
736     {
737       Interval head_wid = f->extent (f, X_AXIS);
738       Real attach = 0.0;
739
740       if (is_invisible (me))
741         attach = 0.0;
742       else
743         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
744
745       Direction d = get_grob_direction (me);
746       Real real_attach = head_wid.linear_combination (d * attach);
747       r = real_attach;
748
749       /* If not centered: correct for stem thickness.  */
750       if (attach)
751         {
752           Real rule_thick = thickness (me);
753           r += -d * rule_thick * 0.5;
754         }
755     }
756   else
757     {
758       extract_grob_set (me, "rests", rests);
759       if (rests.size ())
760         {
761           Grob *rest = rests.top ();
762           r = rest->extent (rest, X_AXIS).center ();
763         }
764     }
765   return scm_from_double (r);
766 }
767
768 Spanner *
769 Stem::get_beam (Grob *me)
770 {
771   SCM b = me->get_object ("beam");
772   return dynamic_cast<Spanner *> (unsmob_grob (b));
773 }
774
775 Stem_info
776 Stem::get_stem_info (Grob *me)
777 {
778   Stem_info si;
779   si.dir_ = get_grob_direction (me);
780   
781   SCM scm_info = me->get_property ("stem-info");
782   si.ideal_y_ = scm_to_double (scm_car (scm_info));
783   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
784   return si;
785 }
786
787 /* TODO: add extra space for tremolos!  */
788 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
789 SCM
790 Stem::calc_stem_info (SCM smob)
791 {
792   Grob *me = unsmob_grob (smob);
793   Direction my_dir = get_grob_direction (me);
794
795   if (!my_dir)
796     {
797       programming_error ("no stem dir set");
798       my_dir = UP;
799     }
800
801   Real staff_space = Staff_symbol_referencer::staff_space (me);
802   Grob *beam = get_beam (me);
803   Real beam_translation = Beam::get_beam_translation (beam);
804   Real beam_thickness = Beam::get_thickness (beam);
805   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
806
807   /* Simple standard stem length */
808   SCM details = me->get_property ("details");
809   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
810
811   
812   Real ideal_length
813     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
814
815     * staff_space
816     /* stem only extends to center of beam
817      */
818     - 0.5 * beam_thickness;
819
820   /* Condition: sane minimum free stem length (chord to beams) */
821   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
822   Real length_fraction
823     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
824
825   Real ideal_minimum_free
826     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
827     * staff_space * length_fraction;
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   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
883                                  details));
884   
885   Real minimum_free
886     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
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   return scm_list_2 (scm_from_double (ideal_y),
906                      scm_from_double (shortest_y));
907 }
908
909 Slice
910 Stem::beam_multiplicity (Grob *stem)
911 {
912   SCM beaming = stem->get_property ("beaming");
913   Slice le = int_list_to_slice (scm_car (beaming));
914   Slice ri = int_list_to_slice (scm_cdr (beaming));
915   le.unite (ri);
916   return le;
917 }
918
919 /* FIXME:  Too many properties  */
920 ADD_INTERFACE (Stem, "stem-interface",
921                "The stem represent the graphical stem.  "
922                "In addition, it internally connects note heads, beams and"
923                "tremolos. "
924                "Rests and whole notes have invisible stems."
925
926                "\n\nThe following properties may be set in the details list." 
927                "@table @code\n"
928                "@item  beamed-lengths \n"
929                "list of stem lengths given beam multiplicity. \n"
930                "@item beamed-minimum-free-lengths \n"
931                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
932                "@item beamed-extreme-minimum-free-lengths\n"
933                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
934                "@item lengths\n"
935                "Default stem lengths. The list gives a length for each flag-count.\n"
936                "@item stem-shorten\n"
937                "How much a stem in a forced direction "
938                "should be shortened. The list gives an amount depending on the number "
939                "of flags/beams."
940                "@end table\n"
941
942                ,
943
944                /* properties */
945                
946                "avoid-note-head "
947                "beam "
948                "beaming "
949                "details "
950                "direction "
951                "duration-log "
952                "flag-style "
953                "french-beaming "
954                "length "
955                "length-fraction "
956                "neutral-direction "
957                "no-stem-extend "
958                "note-heads "
959                "positioning-done "
960                "rests "
961                "stem-end-position "
962                "stem-info "
963                "stemlet-length "
964                "stroke-style "
965                "thickness "
966                "tremolo-flag "
967                );
968
969 /****************************************************************/
970
971 Stem_info::Stem_info ()
972 {
973   ideal_y_ = shortest_y_ = 0;
974   dir_ = CENTER;
975 }
976
977 void
978 Stem_info::scale (Real x)
979 {
980   ideal_y_ *= x;
981   shortest_y_ *= x;
982 }