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