]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* stepmake/stepmake/*:
[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, 2);
493 SCM
494 Stem::height (SCM smob, SCM ax)
495 {
496   Axis a = (Axis)scm_to_int (ax);
497   Grob *me = unsmob_grob (smob);
498   assert (a == Y_AXIS);
499
500   Direction dir = get_grob_direction (me);
501   
502   /* Trigger callback.
503
504   UGH. Should be automatic
505   */
506   Grob *beam = get_beam (me);
507   if (beam)
508     {
509       beam->get_property ("positions");
510     }
511
512   /* FIXME uncached? */
513   Interval iv = me->get_stencil () ? me->get_stencil ()->extent (a) : Interval();
514   if (beam)
515     {
516       if (dir == CENTER)
517         {
518           programming_error ("no stem direction");
519           dir = UP;
520         }
521       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
522     }
523
524   return ly_interval2scm (iv);
525 }
526
527 Real
528 Stem::stem_end_position (Grob *me)
529 {
530   return robust_scm2double (me->get_property ("stem-end-position"), 0);
531 }
532
533 Stencil
534 Stem::flag (Grob *me)
535 {
536   int log = duration_log (me);
537   if (log < 3
538       || unsmob_grob (me->get_object ("beam")))
539     return Stencil ();
540
541   /*
542     TODO: maybe property stroke-style should take different values,
543     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
544     '() or "grace").  */
545   String flag_style;
546
547   SCM flag_style_scm = me->get_property ("flag-style");
548   if (scm_is_symbol (flag_style_scm))
549     flag_style = ly_symbol2string (flag_style_scm);
550
551   if (flag_style == "no-flag")
552     return Stencil ();
553
554   bool adjust = true;
555
556   String staffline_offs;
557   if (String::compare (flag_style, "mensural") == 0)
558     /* Mensural notation: For notes on staff lines, use different
559        flags than for notes between staff lines.  The idea is that
560        flags are always vertically aligned with the staff lines,
561        regardless if the note head is on a staff line or between two
562        staff lines.  In other words, the inner end of a flag always
563        touches a staff line.
564     */
565     {
566       if (adjust)
567         {
568           int p = (int) (rint (stem_end_position (me)));
569           staffline_offs
570             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
571         }
572       else
573         staffline_offs = "2";
574     }
575   else
576     staffline_offs = "";
577
578   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
579   String font_char = flag_style
580     + to_string (dir) + staffline_offs + to_string (log);
581   Font_metric *fm = Font_interface::get_default_font (me);
582   Stencil flag = fm->find_by_name ("flags." + font_char);
583   if (flag.is_empty ())
584     me->warning (_f ("flag `%s' not found", font_char));
585
586   SCM stroke_style_scm = me->get_property ("stroke-style");
587   if (scm_is_string (stroke_style_scm))
588     {
589       String stroke_style = ly_scm2string (stroke_style_scm);
590       if (!stroke_style.is_empty ())
591         {
592           String font_char = to_string (dir) + stroke_style;
593           Stencil stroke = fm->find_by_name ("flags." + font_char);
594           if (stroke.is_empty ())
595             me->warning (_f ("flag stroke `%s' not found", font_char));
596           else
597             flag.add_stencil (stroke);
598         }
599     }
600
601   return flag;
602 }
603
604 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
605 SCM
606 Stem::width_callback (SCM e, SCM ax)
607 {
608   (void) ax;
609   assert (scm_to_int (ax) == X_AXIS);
610   Grob *me = unsmob_grob (e);
611
612   Interval r;
613
614   if (is_invisible (me))
615     r.set_empty ();
616   else if (unsmob_grob (me->get_object ("beam"))
617            || abs (duration_log (me)) <= 2)
618     {
619       r = Interval (-1, 1);
620       r *= thickness (me) / 2;
621     }
622   else
623     {
624       r = Interval (-1, 1) * thickness (me) * 0.5;
625       r.unite (flag (me).extent (X_AXIS));
626     }
627   return ly_interval2scm (r);
628 }
629
630 Real
631 Stem::thickness (Grob *me)
632 {
633   return scm_to_double (me->get_property ("thickness"))
634     * Staff_symbol_referencer::line_thickness (me);
635 }
636
637 MAKE_SCHEME_CALLBACK (Stem, print, 1);
638 SCM
639 Stem::print (SCM smob)
640 {
641   Grob *me = unsmob_grob (smob);
642   Stencil mol;
643   Direction d = get_grob_direction (me);
644
645   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
646                                            0.0);
647   bool stemlet = stemlet_length > 0.0;
648
649   /* TODO: make the stem start a direction ?
650      This is required to avoid stems passing in tablature chords.  */
651   Grob *lh
652     = to_boolean (me->get_property ("avoid-note-head"))
653     ? last_head (me)
654     : first_head (me);
655   Grob *beam = get_beam (me);
656
657   if (!lh && !stemlet)
658     return SCM_EOL;
659
660   if (!lh && stemlet && !beam)
661     return SCM_EOL;
662
663   if (is_invisible (me))
664     return SCM_EOL;
665
666   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
667   Real y1 = y2;
668   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
669
670   if (lh)
671     y2 = Staff_symbol_referencer::get_position (lh);
672   else if (stemlet)
673     {
674       Real beam_translation = Beam::get_beam_translation (beam);
675       Real beam_thickness = Beam::get_thickness (beam);
676       int beam_count = beam_multiplicity (me).length () + 1;
677
678       y2 -= d
679         * (0.5 * beam_thickness
680            + beam_translation * max (0, (beam_count - 1))
681            + stemlet_length) / half_space;
682     }
683
684   Interval stem_y (min (y1, y2), max (y2, y1));
685
686   if (Grob *hed = support_head (me))
687     {
688       /*
689         must not take ledgers into account.
690       */
691       Interval head_height = hed->extent (hed, Y_AXIS);
692       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
693
694       y_attach = head_height.linear_combination (y_attach);
695       stem_y[Direction (-d)] += d * y_attach / half_space;
696     }
697
698   // URG
699   Real stem_width = thickness (me);
700   Real blot
701     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
702
703   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
704                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
705
706   Stencil ss = Lookup::round_filled_box (b, blot);
707   mol.add_stencil (ss);
708
709   mol.add_stencil (get_translated_flag (me));
710
711   return mol.smobbed_copy ();
712 }
713
714 Stencil
715 Stem::get_translated_flag (Grob *me)
716 {
717   Stencil fl = flag (me);
718   if (!fl.is_empty ())
719     {
720       Direction d = get_grob_direction (me);
721       Real blot
722         = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
723       Real stem_width = thickness (me);
724       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
725       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
726       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
727       fl.translate_axis (stem_width / 2, X_AXIS);
728     }
729   return fl;
730 }
731
732
733 /*
734   move the stem to right of the notehead if it is up.
735 */
736 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
737 SCM
738 Stem::offset_callback (SCM element_smob, SCM)
739 {
740   Grob *me = unsmob_grob (element_smob);
741   Real r = 0.0;
742
743   if (Grob *f = first_head (me))
744     {
745       Interval head_wid = f->extent (f, X_AXIS);
746       Real attach = 0.0;
747
748       if (is_invisible (me))
749         attach = 0.0;
750       else
751         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
752
753       Direction d = get_grob_direction (me);
754       Real real_attach = head_wid.linear_combination (d * attach);
755       r = real_attach;
756
757       /* If not centered: correct for stem thickness.  */
758       if (attach)
759         {
760           Real rule_thick = thickness (me);
761           r += -d * rule_thick * 0.5;
762         }
763     }
764   else
765     {
766       extract_grob_set (me, "rests", rests);
767       if (rests.size ())
768         {
769           Grob *rest = rests.top ();
770           r = rest->extent (rest, X_AXIS).center ();
771         }
772     }
773   return scm_from_double (r);
774 }
775
776 Spanner *
777 Stem::get_beam (Grob *me)
778 {
779   SCM b = me->get_object ("beam");
780   return dynamic_cast<Spanner *> (unsmob_grob (b));
781 }
782
783 Stem_info
784 Stem::get_stem_info (Grob *me)
785 {
786   Stem_info si;
787   si.dir_ = get_grob_direction (me);
788   
789   SCM scm_info = me->get_property ("stem-info");
790   si.ideal_y_ = scm_to_double (scm_car (scm_info));
791   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
792   return si;
793 }
794
795 /* TODO: add extra space for tremolos!  */
796 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
797 SCM
798 Stem::calc_stem_info (SCM smob)
799 {
800   Grob *me = unsmob_grob (smob);
801   Direction my_dir = get_grob_direction (me);
802
803   if (!my_dir)
804     {
805       programming_error ("no stem dir set");
806       my_dir = UP;
807     }
808
809   Real staff_space = Staff_symbol_referencer::staff_space (me);
810   Grob *beam = get_beam (me);
811   Real beam_translation = Beam::get_beam_translation (beam);
812   Real beam_thickness = Beam::get_thickness (beam);
813   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
814
815   /* Simple standard stem length */
816   SCM details = me->get_property ("details");
817   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
818
819   
820   Real ideal_length
821     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
822
823     * staff_space
824     /* stem only extends to center of beam
825      */
826     - 0.5 * beam_thickness;
827
828   /* Condition: sane minimum free stem length (chord to beams) */
829   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
830   Real length_fraction
831     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
832
833   Real ideal_minimum_free
834     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
835     * staff_space * length_fraction;
836
837   /* UGH
838      It seems that also for ideal minimum length, we must use
839      the maximum beam count (for this direction):
840
841      \score{ \notes\relative c''{ [a8 a32] }}
842
843      must be horizontal. */
844   Real height_of_my_beams = beam_thickness
845     + (beam_count - 1) * beam_translation;
846
847   Real ideal_minimum_length = ideal_minimum_free
848     + height_of_my_beams
849     /* stem only extends to center of beam */
850     - 0.5 * beam_thickness;
851
852   ideal_length = max (ideal_length, ideal_minimum_length);
853
854   /* Convert to Y position, calculate for dir == UP */
855   Real note_start
856     =     /* staff positions */
857     head_positions (me)[my_dir] * 0.5
858     * my_dir * staff_space;
859   Real ideal_y = note_start + ideal_length;
860
861   /* Conditions for Y position */
862
863   /* Lowest beam of (UP) beam must never be lower than second staffline
864
865   Reference?
866
867   Although this (additional) rule is probably correct,
868   I expect that highest beam (UP) should also never be lower
869   than middle staffline, just as normal stems.
870
871   Reference?
872
873   Obviously not for grace beams.
874
875   Also, not for knees.  Seems to be a good thing. */
876   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
877   bool is_knee = to_boolean (beam->get_property ("knee"));
878   if (!no_extend_b && !is_knee)
879     {
880       /* Highest beam of (UP) beam must never be lower than middle
881          staffline */
882       ideal_y = max (ideal_y, 0.0);
883       /* Lowest beam of (UP) beam must never be lower than second staffline */
884       ideal_y = max (ideal_y, (-staff_space
885                                - beam_thickness + height_of_my_beams));
886     }
887
888   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
889
890   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
891                                  details));
892   
893   Real minimum_free
894     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
895     * staff_space;
896
897   Real minimum_length = minimum_free
898     + height_of_my_beams
899     /* stem only extends to center of beam */
900     - 0.5 * beam_thickness;
901
902   if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
903     {
904       Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
905       y_ext.widen (0.5);        // FIXME. Should be tunable? 
906       minimum_length = max (minimum_length, y_ext.length ());
907     }
908
909   ideal_y *= my_dir;
910   Real minimum_y = note_start + minimum_length;
911   Real shortest_y = minimum_y * my_dir;
912
913   return scm_list_2 (scm_from_double (ideal_y),
914                      scm_from_double (shortest_y));
915 }
916
917 Slice
918 Stem::beam_multiplicity (Grob *stem)
919 {
920   SCM beaming = stem->get_property ("beaming");
921   Slice le = int_list_to_slice (scm_car (beaming));
922   Slice ri = int_list_to_slice (scm_cdr (beaming));
923   le.unite (ri);
924   return le;
925 }
926
927 /* FIXME:  Too many properties  */
928 ADD_INTERFACE (Stem, "stem-interface",
929                "The stem represent the graphical stem.  "
930                "In addition, it internally connects note heads, beams and"
931                "tremolos. "
932                "Rests and whole notes have invisible stems."
933
934                "\n\nThe following properties may be set in the details list." 
935                "@table @code\n"
936                "@item  beamed-lengths \n"
937                "list of stem lengths given beam multiplicity. \n"
938                "@item beamed-minimum-free-lengths \n"
939                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
940                "@item beamed-extreme-minimum-free-lengths\n"
941                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
942                "@item lengths\n"
943                "Default stem lengths. The list gives a length for each flag-count.\n"
944                "@item stem-shorten\n"
945                "How much a stem in a forced direction "
946                "should be shortened. The list gives an amount depending on the number "
947                "of flags/beams."
948                "@end table\n"
949
950                ,
951
952                /* properties */
953                
954                "avoid-note-head "
955                "beam "
956                "beaming "
957                "details "
958                "direction "
959                "duration-log "
960                "flag-style "
961                "french-beaming "
962                "length "
963                "length-fraction "
964                "neutral-direction "
965                "no-stem-extend "
966                "note-heads "
967                "positioning-done "
968                "rests "
969                "stem-end-position "
970                "stem-info "
971                "stemlet-length "
972                "stroke-style "
973                "thickness "
974                "tremolo-flag "
975                );
976
977 /****************************************************************/
978
979 Stem_info::Stem_info ()
980 {
981   ideal_y_ = shortest_y_ = 0;
982   dir_ = CENTER;
983 }
984
985 void
986 Stem_info::scale (Real x)
987 {
988   ideal_y_ *= x;
989   shortest_y_ *= x;
990 }