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