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