]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* scm/define-grobs.scm: switch on new-slur by default.
[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   SCM mol = me->get_uncached_stencil ();
503   Interval iv;
504   if (mol != SCM_EOL)
505     iv = unsmob_stencil (mol)->extent (a);
506   if (Grob *b =get_beam (me))
507     {
508       Direction d = get_direction (me);
509       iv[d] += d * Beam::get_thickness (b) * 0.5 ;
510     }
511
512   return ly_interval2scm (iv);
513 }
514
515
516 Stencil
517 Stem::flag (Grob *me)
518 {
519   /* TODO: maybe property stroke-style should take different values,
520      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
521      '() or "grace").  */
522   String flag_style;
523
524   SCM flag_style_scm = me->get_property ("flag-style");
525   if (ly_c_symbol_p (flag_style_scm))
526     flag_style = ly_symbol2string (flag_style_scm);
527   
528   if (flag_style == "no-flag")
529     return Stencil ();
530
531   bool adjust = true;
532
533   String staffline_offs;
534   if (String::compare (flag_style, "mensural") == 0)
535     /* Mensural notation: For notes on staff lines, use different
536        flags than for notes between staff lines.  The idea is that
537        flags are always vertically aligned with the staff lines,
538        regardless if the note head is on a staff line or between two
539        staff lines.  In other words, the inner end of a flag always
540        touches a staff line.
541     */
542     {
543       if (adjust)
544         {
545           /* Urrgh!  We have to detect wether this stem ends on a staff
546              line or between two staff lines.  But we can not call
547              stem_end_position (me) or get_default_stem_end_position (me),
548              since this encounters the flag and hence results in an
549              infinite recursion.  However, in pure mensural notation,
550              there are no multiple note heads attached to a single stem,
551              neither is there usually need for using the stem_shorten
552              property (except for 32th and 64th notes, but that is not a
553              problem since the stem length in this case is augmented by
554              an integral multiple of staff_space).  Hence, it should be
555              sufficient to just take the first note head, assume it's
556              the only one, look if it's on a staff line, and select the
557              flag's shape accordingly.  In the worst case, the shape
558              looks slightly misplaced, but that will usually be the
559              programmer's fault (e.g. when trying to attach multiple
560              note heads to a single stem in mensural notation).
561           */
562
563           /*
564             perhaps the detection whether this correction is needed should
565             happen in a different place  to avoid the recursion.
566         
567             --hwn.
568           */
569           int p = Staff_symbol_referencer::get_rounded_position (me);
570           staffline_offs = Staff_symbol_referencer::on_staffline (me, p)
571             ? "1" : "0";
572         }
573       else
574         {
575           staffline_offs = "2";
576         }
577     }
578   else
579     {
580       staffline_offs = "";
581     }
582
583   char dir = (get_direction (me) == UP) ? 'u' : 'd';
584   String font_char = flag_style
585     + to_string (dir) + staffline_offs + to_string (duration_log (me));
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 (ly_c_string_p (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,dim_callback,2);
610 SCM
611 Stem::dim_callback (SCM e, SCM ax)
612 {
613   Axis a = (Axis) ly_scm2int (ax);
614   assert (a == X_AXIS);
615   Grob *me = unsmob_grob (e);
616   Interval r (0, 0);
617   if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
618     ;   // TODO!
619   else
620     r = flag (me).extent (X_AXIS)
621       + thickness (me)/2;
622   return ly_interval2scm (r);
623 }
624
625 Real
626 Stem::thickness (Grob *me)
627 {
628   return ly_scm2double (me->get_property ("thickness"))
629     * Staff_symbol_referencer::line_thickness (me);
630 }
631
632 MAKE_SCHEME_CALLBACK (Stem, print, 1);
633 SCM
634 Stem::print (SCM smob)
635 {
636   Grob *me = unsmob_grob (smob);
637   Stencil mol;
638   Direction d = get_direction (me);
639
640   /* TODO: make the stem start a direction ?
641      This is required to avoid stems passing in tablature chords.  */
642   Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
643     ? last_head (me) :  lh = first_head (me);
644
645   if (!lh)
646     return SCM_EOL;
647
648   if (is_invisible (me))
649     return SCM_EOL;
650
651   Real y1 = Staff_symbol_referencer::get_position (lh);
652   Real y2 = stem_end_position (me);
653
654   Interval stem_y (y1 <? y2,y2 >? y1);
655
656   // dy?
657   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
658
659   if (Grob *hed = support_head (me))
660     {
661       /*
662         must not take ledgers into account.
663        */
664       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
665       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
666
667       y_attach = head_height.linear_combination (y_attach);
668       stem_y[Direction (-d)] += d * y_attach/dy;
669     }
670
671
672   // URG
673   Real stem_width = thickness (me);
674   Real blot =
675         me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
676
677   Box b = Box (Interval (-stem_width/2, stem_width/2),
678                Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
679
680   Stencil ss = Lookup::round_filled_box (b, blot);
681   mol.add_stencil (ss);
682
683   if (!get_beam (me) && abs (duration_log (me)) > 2)
684     {
685       Stencil fl = flag (me);
686       fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
687       fl.translate_axis (stem_width/2, X_AXIS);
688       mol.add_stencil (fl);
689     }
690
691   return mol.smobbed_copy ();
692 }
693
694 /*
695   move the stem to right of the notehead if it is up.
696  */
697 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
698 SCM
699 Stem::off_callback (SCM element_smob, SCM)
700 {
701   Grob *me = unsmob_grob (element_smob);
702   Real r = 0.0;
703   
704   if (head_count (me))
705     if (Grob *f = first_head (me))
706       {
707         Interval head_wid = Note_head::head_extent (f, X_AXIS);
708         Real attach = 0.0;
709         
710         if (is_invisible (me))
711           attach = 0.0;
712         else
713         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
714         
715         Direction d = get_direction (me);
716         Real real_attach = head_wid.linear_combination (d * attach);
717         r = real_attach;
718         
719         /* If not centered: correct for stem thickness.  */
720         if (attach)
721           {
722             Real rule_thick = thickness (me);
723             r += - d * rule_thick * 0.5;
724           }
725       }
726   return scm_make_real (r);
727 }
728
729 Grob *
730 Stem::get_beam (Grob *me)
731 {
732   SCM b = me->get_property ("beam");
733   return unsmob_grob (b);
734 }
735
736 Stem_info
737 Stem::get_stem_info (Grob *me)
738 {
739   /* Return cached info if available */
740   SCM scm_info = me->get_property ("stem-info");
741   if (!ly_c_pair_p (scm_info))
742     {
743       calc_stem_info (me);
744       scm_info = me->get_property ("stem-info");
745     }
746
747   Stem_info si;
748   si.dir_ = get_grob_direction (me);
749   si.ideal_y_ = ly_scm2double (ly_car (scm_info));
750   si.shortest_y_ = ly_scm2double (ly_cadr (scm_info));
751   return si;
752 }
753
754
755 /* TODO: add extra space for tremolos!  */
756 void
757 Stem::calc_stem_info (Grob *me)
758 {
759   Direction my_dir = get_grob_direction (me);
760
761   if (!my_dir)
762     {
763       programming_error ("No stem dir set?");
764       my_dir  = UP;
765     }
766
767   Real staff_space = Staff_symbol_referencer::staff_space (me);
768   Grob *beam = get_beam (me);
769   Real beam_translation = Beam::get_beam_translation (beam);
770   Real beam_thickness = Beam::get_thickness (beam);
771   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
772
773
774   /* Simple standard stem length */
775   SCM lengths = me->get_property ("beamed-lengths");
776   Real ideal_length =
777     ly_scm2double (robust_list_ref (beam_count - 1,lengths))
778                 
779     * staff_space
780     /* stem only extends to center of beam */
781     - 0.5 * beam_thickness;
782
783   /* Condition: sane minimum free stem length (chord to beams) */
784   lengths = me->get_property ("beamed-minimum-free-lengths");
785   Real ideal_minimum_free =
786     ly_scm2double (robust_list_ref (beam_count - 1, lengths))
787     * staff_space;
788
789
790   /* UGH
791      It seems that also for ideal minimum length, we must use
792      the maximum beam count (for this direction):
793
794      \score{ \notes\relative c''{ [a8 a32] }}
795
796      must be horizontal. */
797   Real height_of_my_beams = beam_thickness
798     + (beam_count - 1) * beam_translation;
799
800   Real ideal_minimum_length = ideal_minimum_free
801     + height_of_my_beams
802     /* stem only extends to center of beam */
803     - 0.5 * beam_thickness;
804
805   ideal_length = ideal_length >? ideal_minimum_length;
806
807   /* Convert to Y position, calculate for dir == UP */
808   Real note_start =
809     /* staff positions */
810     head_positions (me)[my_dir] * 0.5
811     * my_dir * staff_space;
812   Real ideal_y = note_start + ideal_length;
813
814
815   /* Conditions for Y position */
816
817   /* Lowest beam of (UP) beam must never be lower than second staffline
818
819      Reference?
820
821      Although this (additional) rule is probably correct,
822      I expect that highest beam (UP) should also never be lower
823      than middle staffline, just as normal stems.
824
825      Reference?
826
827      Obviously not for grace beams.
828
829      Also, not for knees.  Seems to be a good thing. */
830   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
831   bool is_knee = to_boolean (beam->get_property ("knee"));
832   if (!no_extend_b && !is_knee)
833     {
834       /* Highest beam of (UP) beam must never be lower than middle
835          staffline */
836       ideal_y = ideal_y >? 0;
837       /* Lowest beam of (UP) beam must never be lower than second staffline */
838       ideal_y = ideal_y >? (-staff_space
839                             - beam_thickness + height_of_my_beams);
840     }
841
842
843   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
844
845   Real minimum_free =
846     ly_scm2double (robust_list_ref
847                    (beam_count - 1,
848                     me->get_property
849                     ("beamed-extreme-minimum-free-lengths")))
850     * staff_space;
851
852   Real minimum_length = minimum_free
853     + height_of_my_beams
854     /* stem only extends to center of beam */
855     - 0.5 * beam_thickness;
856
857   ideal_y *= my_dir;
858   Real minimum_y = note_start + minimum_length;
859   Real shortest_y = minimum_y * my_dir;
860
861   me->set_property ("stem-info",
862                     scm_list_2 (scm_make_real (ideal_y),
863                                 scm_make_real (shortest_y)));
864 }
865
866 Slice
867 Stem::beam_multiplicity (Grob *stem)
868 {
869   SCM beaming= stem->get_property ("beaming");
870   Slice le = int_list_to_slice (ly_car (beaming));
871   Slice ri = int_list_to_slice (ly_cdr (beaming));
872   le.unite (ri);
873   return le;
874 }
875
876
877 /* FIXME:  Too many properties  */
878 ADD_INTERFACE (Stem, "stem-interface",
879                "The stem represent the graphical stem.  "
880                "In addition, it internally connects note heads, beams and"
881                "tremolos. "
882                "Rests and whole notes have invisible stems.",
883                "tremolo-flag french-beaming "
884                "avoid-note-head thickness "
885                "stem-info beamed-lengths beamed-minimum-free-lengths "
886                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
887                "duration-log beaming neutral-direction stem-end-position "
888                "note-heads direction length flag-style "
889                "no-stem-extend stroke-style");
890
891 /****************************************************************/
892
893 Stem_info::Stem_info ()
894 {
895   ideal_y_ = shortest_y_ = 0;
896   dir_ = CENTER;
897 }
898
899 void
900 Stem_info::scale (Real x)
901 {
902   ideal_y_ *= x;
903   shortest_y_ *= x;
904 }