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