]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
stem tremolo fix
[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--2002 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 "debug.hh"
23 #include "paper-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "molecule.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
37 void
38 Stem::set_beaming (Grob*me ,int i,  Direction d)
39 {
40   SCM pair = me->get_grob_property ("beaming");
41   
42   if (!gh_pair_p (pair))
43     {
44       pair = gh_cons (gh_int2scm (-1),gh_int2scm (-1));
45       me->      set_grob_property ("beaming", pair);
46     }
47   index_set_cell (pair, d, gh_int2scm (i));
48 }
49
50 int
51 Stem::beam_count (Grob*me,Direction d)
52 {
53   SCM p=me->get_grob_property ("beaming");
54   if (gh_pair_p (p))
55     return gh_scm2int (index_cell (p,d));
56   else
57     return -1;
58 }
59
60 Interval
61 Stem::head_positions (Grob*me) 
62 {
63   if (!head_count (me))
64     {
65       Interval iv;
66       return iv;
67     }
68
69   Drul_array<Grob*> e (extremal_heads (me));
70
71   return Interval (Staff_symbol_referencer::position_f (e[DOWN]),
72                    Staff_symbol_referencer::position_f (e[UP]));
73 }
74
75
76 Real
77 Stem::chord_start_y (Grob*me) 
78 {
79   return head_positions (me)[get_direction (me)]
80     * Staff_symbol_referencer::staff_space (me)/2.0;
81 }
82
83 Real
84 Stem::stem_end_position (Grob*me) 
85 {
86   SCM p =me->get_grob_property ("stem-end-position");
87   Real pos;
88   if (!gh_number_p (p))
89     {
90       pos = get_default_stem_end_position (me);
91       me->set_grob_property ("stem-end-position", gh_double2scm (pos));
92     }
93   else
94     pos = gh_scm2double (p);
95
96   return pos;
97 }
98
99 Direction
100 Stem::get_direction (Grob*me)
101 {
102   Direction d = Directional_element_interface::get (me);
103
104   if (!d)
105     {
106        d = get_default_dir (me);
107        // urg, AAARGH!
108        Directional_element_interface::set (me, d);
109     }
110   return d ;
111 }
112
113
114 void
115 Stem::set_stemend (Grob*me, Real se)
116 {
117   // todo: margins
118   Direction d= get_direction (me);
119   
120   if (d && d * head_positions (me)[get_direction (me)] >= se*d)
121     me->warning (_ ("Weird stem size; check for narrow beams"));
122
123   me->set_grob_property ("stem-end-position", gh_double2scm (se));
124 }
125
126
127 /*
128   Note head that determines hshift for upstems
129  */ 
130 Grob*
131 Stem::support_head (Grob*me)
132 {
133   SCM h = me->get_grob_property ("support-head");
134   Grob * nh = unsmob_grob (h);
135   if (nh)
136     return nh;
137   else if (head_count (me) == 1)
138     {
139       /*
140         UGH.
141        */
142       
143       return unsmob_grob (ly_car (me->get_grob_property ("note-heads")));
144     }
145   else
146     return first_head (me);
147 }
148
149
150 int
151 Stem::head_count (Grob*me)
152 {
153   return  Pointer_group_interface::count (me, "note-heads");
154 }
155
156 /*
157   The note head which forms one end of the stem.  
158  */
159 Grob*
160 Stem::first_head (Grob*me)
161 {
162   Direction d = get_direction (me);
163   if (!d)
164     return 0;
165   return extremal_heads (me)[-d];
166 }
167
168 /*
169   START is part where stem reaches `last' head. 
170  */
171 Drul_array<Grob*>
172 Stem::extremal_heads (Grob*me) 
173 {
174   const int inf = 1000000;
175   Drul_array<int> extpos;
176   extpos[DOWN] = inf;
177   extpos[UP] = -inf;  
178   
179   Drul_array<Grob *> exthead;
180   exthead[LEFT] = exthead[RIGHT] =0;
181   
182   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
183     {
184       Grob * n = unsmob_grob (ly_car (s));
185
186       
187       int p = int (Staff_symbol_referencer::position_f (n));
188
189       Direction d = LEFT;
190       do {
191       if (d* p > d* extpos[d])
192         {
193           exthead[d] = n;
194           extpos[d] = p;
195         }
196       } while (flip (&d) != DOWN);
197     }
198
199   return exthead;
200 }
201
202 static int
203 icmp (int const &a, int const &b)
204 {
205   return a-b;
206 }
207
208 Array<int>
209 Stem::note_head_positions (Grob *me)
210 {
211   Array<int> ps ;
212   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
213     {
214       Grob * n = unsmob_grob (ly_car (s));
215       int p = int (Staff_symbol_referencer::position_f (n));
216
217       ps.push (p);
218     }
219
220   ps.sort (icmp);
221   return ps; 
222 }
223
224
225 void
226 Stem::add_head (Grob*me, Grob *n)
227 {
228   n->set_grob_property ("stem", me->self_scm ());
229   n->add_dependency (me);
230
231   if (Note_head::has_interface (n))
232     {
233       Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
234     }
235 }
236
237 bool
238 Stem::invisible_b (Grob*me)
239 {
240   return ! (head_count (me) && Note_head::balltype_i (support_head (me)) >= 1);
241 }
242
243 Direction
244 Stem::get_default_dir (Grob*me) 
245 {
246   int staff_center = 0;
247   Interval hp = head_positions (me);
248   if (hp.empty_b())
249     {
250       return CENTER;
251     }
252   
253   int udistance = (int) (UP * hp[UP] - staff_center);
254   int ddistance = (int) (DOWN* hp[DOWN] - staff_center);  
255   
256   if (sign (ddistance - udistance))
257     return Direction (sign (ddistance -udistance));
258
259   return to_dir (me->get_grob_property ("neutral-direction"));
260 }
261
262 Real
263 Stem::get_default_stem_end_position (Grob*me) 
264 {
265   bool grace_b = to_boolean (me->get_grob_property ("grace"));
266   SCM s;
267   Array<Real> a;
268
269   Real length_f = 0.;
270   SCM scm_len = me->get_grob_property ("length");
271   if (gh_number_p (scm_len))
272     {
273       length_f = gh_scm2double (scm_len);
274     }
275   else
276     {
277       s = me->get_grob_property ("lengths");
278       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
279         a.push (gh_scm2double (ly_car (q)));
280                 
281       // stem uses half-spaces
282       length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
283     }
284
285
286   a.clear ();
287   s = me->get_grob_property ("stem-shorten");
288   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
289     a.push (gh_scm2double (ly_car (q)));
290
291
292   // stem uses half-spaces
293
294   // fixme: use scm_list_n_ref () iso. array[]
295   Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
296
297   /* On boundary: shorten only half */
298   if (abs (chord_start_y (me)) == 0.5)
299     shorten_f *= 0.5;
300
301   /* URGURGURG
302      'set-default-stemlen' sets direction too
303    */
304   Direction dir = get_direction (me);
305   if (!dir)
306     {
307       dir = get_default_dir (me);
308       Directional_element_interface::set (me, dir);
309     }
310   
311   /* stems in unnatural (forced) direction should be shortened, 
312     according to [Roush & Gourlay] */
313   if (chord_start_y (me)
314       && (get_direction (me) != get_default_dir (me)))
315     length_f -= shorten_f;
316
317   Interval hp = head_positions (me);  
318   Real st = hp[dir] + dir * length_f;
319
320   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
321   if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
322     st = 0.0;
323
324   /*
325     Make a little room if we have a upflag and there is a dot.
326     previous approach was to lengthen the stem. This is not
327     good typesetting practice. 
328     
329   */
330   if (!beam_l (me) && dir == UP
331       && duration_log (me) > 2)
332     {
333       Grob * closest_to_flag = extremal_heads (me)[dir];
334       Grob * dots = closest_to_flag
335         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
336
337       if (dots)
338         {
339           Real dp = Staff_symbol_referencer::position_f  (dots);
340           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
341             / Staff_symbol_referencer::staff_space (me); 
342
343           /*
344             Very gory: add myself to the X-support of the parent,
345             which should be a dot-column.
346            */
347           if (dir * (st + flagy -  dp) < 0.5)
348             {
349               Grob *par = dots->get_parent (X_AXIS);
350
351               if (Dot_column::has_interface (par))
352                 {
353                   Side_position_interface::add_support (par, me);
354
355                   /*
356                     TODO: apply some better logic here. The flag is
357                     curved inwards, so this will typically be too
358                     much.
359                   */
360                 }
361             }
362         }
363     }
364
365
366   return st;
367 }
368
369
370
371 /*
372   
373   the log of the duration (Number of hooks on the flag minus two)
374  */
375 int
376 Stem::duration_log (Grob*me) 
377 {
378   SCM s = me->get_grob_property ("duration-log");
379   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
380 }
381
382 void
383 Stem::position_noteheads (Grob*me)
384 {
385   if (!head_count (me))
386     return;
387   
388   Link_array<Grob> heads =
389     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
390
391   heads.sort (compare_position);
392   Direction dir =get_direction (me);
393   
394   if (dir < 0)
395     heads.reverse ();
396
397
398   bool invisible = invisible_b (me);
399   Real thick = 0.0;
400   if (invisible)
401         thick = gh_scm2double (me->get_grob_property ("thickness"))
402           * me->paper_l ()->get_var ("linethickness");
403       
404
405   Grob *hed = support_head (me);
406   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
407   for (int i=0; i < heads.size (); i++)
408     {
409       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
410                                 X_AXIS);
411     }
412   
413   bool parity= true;            // todo: make me settable.
414   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
415   for (int i=1; i < heads.size (); i ++)
416     {
417       Real p = Staff_symbol_referencer::position_f (heads[i]);
418       int dy =abs (lastpos- (int)p);
419
420       if (dy <= 1)
421         {
422           if (parity)
423             {
424               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
425
426               Direction d = get_direction (me);
427               heads[i]->translate_axis (l * d, X_AXIS);
428
429               if (invisible_b(me))
430                 heads[i]->translate_axis (-thick *2* d , X_AXIS);
431
432               
433              /* TODO:
434                  
435               For some cases we should kern some more: when the
436               distance between the next or prev note is too large, we'd 
437               get large white gaps, eg.
438               
439                |
440               X|
441                |X  <- kern this.
442                |
443               X
444               
445               */
446             }
447           parity = !parity;
448         }
449       else
450         parity = true;
451       
452       lastpos = int (p);
453     }
454 }
455
456 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
457 SCM
458 Stem::before_line_breaking (SCM smob)
459 {
460   Grob*me = unsmob_grob (smob);
461
462
463   /*
464     Do the calculations for visible stems, but also for invisible stems
465     with note heads (i.e. half notes.)
466    */
467   if (head_count (me))
468     {
469       stem_end_position (me);   // ugh. Trigger direction calc.
470       position_noteheads (me);
471     }
472   else
473     {
474       me->remove_grob_property ("molecule-callback");
475     }
476   
477   return SCM_UNSPECIFIED;
478 }
479
480 /*
481   ugh.
482   When in a beam with tuplet brackets, brew_mol is called early,
483   caching a wrong value.
484  */
485 MAKE_SCHEME_CALLBACK (Stem, height, 2);
486 SCM
487 Stem::height (SCM smob, SCM ax)
488 {
489   Axis a = (Axis)gh_scm2int (ax);
490   Grob * me = unsmob_grob (smob);
491   assert (a == Y_AXIS);
492
493   SCM mol = me->get_uncached_molecule ();
494   Interval iv;
495   if (mol != SCM_EOL)
496     iv = unsmob_molecule (mol)->extent (a);
497   return ly_interval2scm (iv);
498 }
499
500
501 Molecule
502 Stem::flag (Grob*me)
503 {
504   /* TODO: rename flag-style into something more appropriate,
505    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
506    "single" and "double".  Needs more discussion.
507   */
508   String style, fstyle, staffline_offs;
509   SCM fst = me->get_grob_property ("flag-style");
510   if (gh_string_p (fst))
511     {
512       fstyle = ly_scm2string (fst);
513     }
514
515   SCM st = me->get_grob_property ("style");
516   if (gh_symbol_p (st))
517     {
518       style = (ly_scm2string (scm_symbol_to_string (st)));
519     }
520   else
521     {
522       style = "";
523     }
524   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
525
526   if (String::compare_i (style, "mensural") == 0)
527     /* Mensural notation: For notes on staff lines, use different
528        flags than for notes between staff lines.  The idea is that
529        flags are always vertically aligned with the staff lines,
530        regardless if the note head is on a staff line or between two
531        staff lines.  In other words, the inner end of a flag always
532        touches a staff line.
533     */
534     {
535       if (adjust)
536         {
537           /* Urrgh!  We have to detect wether this stem ends on a staff
538              line or between two staff lines.  But we can not call
539              stem_end_position(me) or get_default_stem_end_position(me),
540              since this encounters the flag and hence results in an
541              infinite recursion.  However, in pure mensural notation,
542              there are no multiple note heads attached to a single stem,
543              neither is there usually need for using the stem_shorten
544              property (except for 32th and 64th notes, but that is not a
545              problem since the stem length in this case is augmented by
546              an integral multiple of staff_space).  Hence, it should be
547              sufficient to just take the first note head, assume it's
548              the only one, look if it's on a staff line, and select the
549              flag's shape accordingly.  In the worst case, the shape
550              looks slightly misplaced, but that will usually be the
551              programmer's fault (e.g. when trying to attach multiple
552              note heads to a single stem in mensural notation).  */
553
554           /*
555             perhaps the detection whether this correction is needed should
556             happen in a different place  to avoid the recursion.
557             
558             --hwn.
559           */
560           Grob *first = first_head(me);
561           int sz = Staff_symbol_referencer::line_count (me)-1;
562           int p = (int)rint (Staff_symbol_referencer::position_f (first));
563           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
564         }
565       else
566         {
567           staffline_offs = "2";
568         }
569     }
570   else
571     {
572       staffline_offs = "";
573     }
574   char c = (get_direction (me) == UP) ? 'u' : 'd';
575   String index_str
576     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
577   Molecule m
578     = Font_interface::get_default_font (me)->find_by_name (index_str);
579   if (!fstyle.empty_b ())
580     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
581   return m;
582 }
583
584 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
585 SCM
586 Stem::dim_callback (SCM e, SCM ax)
587 {
588   Axis a = (Axis) gh_scm2int (ax);
589   assert (a == X_AXIS);
590   Grob *se = unsmob_grob (e);
591   Interval r (0, 0);
592   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
593     ;   // TODO!
594   else
595     {
596       r = flag (se).extent (X_AXIS);
597     }
598   return ly_interval2scm (r);
599 }
600  
601
602
603 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
604
605 SCM
606 Stem::brew_molecule (SCM smob) 
607 {
608   Grob*me = unsmob_grob (smob);
609   Molecule mol;
610   Direction d = get_direction (me);
611   
612   
613   Real y1 = Staff_symbol_referencer::position_f (first_head (me));
614   Real y2 = stem_end_position (me);
615   
616   Interval stem_y (y1 <? y2,y2 >? y1);
617
618
619   // dy?
620   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
621
622   if (Grob *hed = support_head (me))
623     {
624       /*
625         must not take ledgers into account.
626        */
627       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
628       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
629
630       y_attach = head_height.linear_combination (y_attach);
631       stem_y[Direction (-d)] += d * y_attach/dy;
632     }
633   
634   if (!invisible_b (me))
635     {
636       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
637         // URG
638         * me->paper_l ()->get_var ("linethickness");
639       
640       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
641                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
642       mol.add_molecule (ss);
643     }
644
645   if (!beam_l (me) && abs (duration_log (me)) > 2)
646     {
647       Molecule fl = flag (me);
648       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
649       mol.add_molecule (fl);
650     }
651
652   return mol.smobbed_copy ();
653 }
654
655 /*
656   move the stem to right of the notehead if it is up.
657  */
658 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
659 SCM
660 Stem::off_callback (SCM element_smob, SCM)
661 {
662   Grob *me = unsmob_grob (element_smob);
663   
664   Real r=0;
665
666   if (head_count (me) == 0)
667     {
668       return gh_double2scm (0.0);
669     }
670   
671   if (Grob * f = first_head (me))
672     {
673       Interval head_wid = Note_head::head_extent(f, X_AXIS);
674
675       
676       Real attach =0.0;
677
678       if (invisible_b (me))
679         {
680           attach = 0.0;
681         }
682       else
683         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
684
685       Direction d = get_direction (me);
686
687       Real real_attach = head_wid.linear_combination (d * attach);
688
689       r = real_attach;
690
691       /*
692         If not centered: correct for stem thickness.
693        */
694       if (attach)
695         {
696           Real rule_thick
697             = gh_scm2double (me->get_grob_property ("thickness"))
698             * me->paper_l ()->get_var ("linethickness");
699
700           
701           r += - d * rule_thick * 0.5;
702         }
703     }
704   return gh_double2scm (r);
705 }
706
707
708
709 Grob*
710 Stem::beam_l (Grob*me)
711 {
712   SCM b=  me->get_grob_property ("beam");
713   return unsmob_grob (b);
714 }
715
716
717 // ugh still very long.
718 Stem_info
719 Stem::calc_stem_info (Grob*me) 
720 {
721   SCM scm_info = me->get_grob_property ("stem-info");
722
723   if (gh_pair_p (scm_info ))
724     {
725       Stem_info si ;
726
727       si.ideal_y = gh_scm2double (gh_car (scm_info)); 
728       si.max_y = gh_scm2double (gh_cadr (scm_info)); 
729       si.min_y = gh_scm2double (gh_caddr (scm_info));
730
731       return si;
732     }
733     
734   Grob * beam = beam_l (me);
735
736   Direction beam_dir = Directional_element_interface::get (beam);
737   if (!beam_dir)
738     {
739       programming_error ("Beam dir not set.");
740       beam_dir = UP;
741     }
742     
743
744   Real staff_space = Staff_symbol_referencer::staff_space (me);
745   Real half_space = staff_space / 2;
746   
747   int multiplicity = Beam::get_multiplicity (beam);
748   Real interbeam_f = Beam::get_interbeam (beam);
749
750   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
751   Stem_info info; 
752   info.ideal_y = chord_start_y (me);
753
754   // for simplicity, we calculate as if dir == UP
755
756   /*
757     UGH. This confuses issues more. fixme. --hwn
758    */
759   info.ideal_y *= beam_dir;
760   SCM grace_prop = me->get_grob_property ("grace");
761
762   bool grace_b = to_boolean (grace_prop);
763   
764   Array<Real> a;
765   SCM s;
766   
767   s = me->get_grob_property ("beamed-minimum-lengths");
768   a.clear ();
769   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
770     a.push (gh_scm2double (ly_car (q)));
771
772
773   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
774   s = me->get_grob_property ("beamed-lengths");
775
776   a.clear ();
777   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
778     a.push (gh_scm2double (ly_car (q)));
779
780   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
781
782
783   /*
784     This sucks -- On a kneed beam, *all* stems are kneed, not half of them.
785    */
786   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
787     /* normal beamed stem */
788     {
789       if (multiplicity)
790         {
791           info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
792         }
793       info.min_y = info.ideal_y;
794       info.max_y = 1000;  // INT_MAX;
795
796       info.ideal_y += stem_length;
797       info.min_y += minimum_length;
798
799       /*
800         lowest beam of (UP) beam must never be lower than second staffline
801
802         Hmm, reference (Wanske?)
803
804         Although this (additional) rule is probably correct,
805         I expect that highest beam (UP) should also never be lower
806         than middle staffline, just as normal stems.
807         
808       */
809       bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
810       if (!grace_b && !no_extend_b)
811         {
812           /* highest beam of (UP) beam must never be lower than middle
813              staffline
814              lowest beam of (UP) beam must never be lower than second staffline
815            */
816           info.min_y =
817             info.min_y >? 0
818             >? (- 2 * half_space - thick
819                 + (multiplicity > 0) * thick
820                 + interbeam_f * (multiplicity - 1));
821         }
822     }
823   else
824     /* knee */
825     {
826       info.ideal_y -= thick + stem_length;
827       info.max_y = info.ideal_y - minimum_length;
828
829       /*
830         We shouldn't invert the stems, so we set minimum at 0. 
831        */
832       info.min_y = 0.5;
833     }
834   
835   info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
836
837   s = beam->get_grob_property ("shorten");
838   if (gh_number_p (s))
839     info.ideal_y -= gh_scm2double (s);
840
841   Grob *common = me->common_refpoint (beam, Y_AXIS);
842   Real interstaff_f = beam_dir *
843     (me->relative_coordinate (common, Y_AXIS)
844      - beam->relative_coordinate (common, Y_AXIS));
845   
846   info.ideal_y += interstaff_f;
847   info.min_y += interstaff_f;
848   info.max_y += interstaff_f ;
849
850   me->set_grob_property ("stem-info",
851                          scm_list_n (gh_double2scm (info.ideal_y),
852                                      gh_double2scm (info.max_y),
853                                      gh_double2scm (info.min_y),
854                                      SCM_UNDEFINED));
855   
856   return info;
857 }
858
859 ADD_INTERFACE (Stem,"stem-interface",
860   "A stem",
861   "adjust-if-on-staffline thickness stem-info beamed-lengths beamed-minimum-lengths lengths beam stem-shorten duration-log beaming neutral-direction stem-end-position support-head note-heads direction length style no-stem-extend flag-style dir-forced");
862