]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
biem 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   The note head opposite to the first head.
170  */
171 Grob*
172 Stem::last_head (Grob*me)
173 {
174   Direction d = get_direction (me);
175   if (!d)
176     return 0;
177   return extremal_heads (me)[d];
178 }
179
180 /*
181   START is part where stem reaches `last' head. 
182  */
183 Drul_array<Grob*>
184 Stem::extremal_heads (Grob*me) 
185 {
186   const int inf = 1000000;
187   Drul_array<int> extpos;
188   extpos[DOWN] = inf;
189   extpos[UP] = -inf;  
190   
191   Drul_array<Grob *> exthead;
192   exthead[LEFT] = exthead[RIGHT] =0;
193   
194   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
195     {
196       Grob * n = unsmob_grob (ly_car (s));
197
198       
199       int p = int (Staff_symbol_referencer::position_f (n));
200
201       Direction d = LEFT;
202       do {
203       if (d* p > d* extpos[d])
204         {
205           exthead[d] = n;
206           extpos[d] = p;
207         }
208       } while (flip (&d) != DOWN);
209     }
210
211   return exthead;
212 }
213
214 static int
215 icmp (int const &a, int const &b)
216 {
217   return a-b;
218 }
219
220 Array<int>
221 Stem::note_head_positions (Grob *me)
222 {
223   Array<int> ps ;
224   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
225     {
226       Grob * n = unsmob_grob (ly_car (s));
227       int p = int (Staff_symbol_referencer::position_f (n));
228
229       ps.push (p);
230     }
231
232   ps.sort (icmp);
233   return ps; 
234 }
235
236
237 void
238 Stem::add_head (Grob*me, Grob *n)
239 {
240   n->set_grob_property ("stem", me->self_scm ());
241   n->add_dependency (me);
242
243   if (Note_head::has_interface (n))
244     {
245       Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
246     }
247 }
248
249 bool
250 Stem::invisible_b (Grob*me)
251 {
252   return ! (head_count (me) && Note_head::balltype_i (support_head (me)) >= 1);
253 }
254
255 Direction
256 Stem::get_default_dir (Grob*me) 
257 {
258   int staff_center = 0;
259   Interval hp = head_positions (me);
260   if (hp.empty_b())
261     {
262       return CENTER;
263     }
264   
265   int udistance = (int) (UP * hp[UP] - staff_center);
266   int ddistance = (int) (DOWN* hp[DOWN] - staff_center);  
267   
268   if (sign (ddistance - udistance))
269     return Direction (sign (ddistance -udistance));
270
271   return to_dir (me->get_grob_property ("neutral-direction"));
272 }
273
274 Real
275 Stem::get_default_stem_end_position (Grob*me) 
276 {
277   bool grace_b = to_boolean (me->get_grob_property ("grace"));
278   SCM s;
279   Array<Real> a;
280
281   Real length_f = 0.;
282   SCM scm_len = me->get_grob_property ("length");
283   if (gh_number_p (scm_len))
284     {
285       length_f = gh_scm2double (scm_len);
286     }
287   else
288     {
289       s = me->get_grob_property ("lengths");
290       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
291         a.push (gh_scm2double (ly_car (q)));
292                 
293       // stem uses half-spaces
294       length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
295     }
296
297
298   a.clear ();
299   s = me->get_grob_property ("stem-shorten");
300   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
301     a.push (gh_scm2double (ly_car (q)));
302
303
304   // stem uses half-spaces
305
306   // fixme: use scm_list_n_ref () iso. array[]
307   Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
308
309   /* On boundary: shorten only half */
310   if (abs (chord_start_y (me)) == 0.5)
311     shorten_f *= 0.5;
312
313   /* URGURGURG
314      'set-default-stemlen' sets direction too
315    */
316   Direction dir = get_direction (me);
317   if (!dir)
318     {
319       dir = get_default_dir (me);
320       Directional_element_interface::set (me, dir);
321     }
322   
323   /* stems in unnatural (forced) direction should be shortened, 
324     according to [Roush & Gourlay] */
325   if (chord_start_y (me)
326       && (get_direction (me) != get_default_dir (me)))
327     length_f -= shorten_f;
328
329   Interval hp = head_positions (me);  
330   Real st = hp[dir] + dir * length_f;
331
332   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
333   if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
334     st = 0.0;
335
336   /*
337     Make a little room if we have a upflag and there is a dot.
338     previous approach was to lengthen the stem. This is not
339     good typesetting practice. 
340     
341   */
342   if (!beam_l (me) && dir == UP
343       && duration_log (me) > 2)
344     {
345       Grob * closest_to_flag = extremal_heads (me)[dir];
346       Grob * dots = closest_to_flag
347         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
348
349       if (dots)
350         {
351           Real dp = Staff_symbol_referencer::position_f  (dots);
352           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
353             / Staff_symbol_referencer::staff_space (me); 
354
355           /*
356             Very gory: add myself to the X-support of the parent,
357             which should be a dot-column.
358            */
359           if (dir * (st + flagy -  dp) < 0.5)
360             {
361               Grob *par = dots->get_parent (X_AXIS);
362
363               if (Dot_column::has_interface (par))
364                 {
365                   Side_position_interface::add_support (par, me);
366
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     }
376
377
378   return st;
379 }
380
381
382
383 /*
384   
385   the log of the duration (Number of hooks on the flag minus two)
386  */
387 int
388 Stem::duration_log (Grob*me) 
389 {
390   SCM s = me->get_grob_property ("duration-log");
391   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
392 }
393
394 void
395 Stem::position_noteheads (Grob*me)
396 {
397   if (!head_count (me))
398     return;
399   
400   Link_array<Grob> heads =
401     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
402
403   heads.sort (compare_position);
404   Direction dir =get_direction (me);
405   
406   if (dir < 0)
407     heads.reverse ();
408
409
410   bool invisible = invisible_b (me);
411   Real thick = 0.0;
412   if (invisible)
413         thick = gh_scm2double (me->get_grob_property ("thickness"))
414           * me->paper_l ()->get_var ("linethickness");
415       
416
417   Grob *hed = support_head (me);
418   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
419   for (int i=0; i < heads.size (); i++)
420     {
421       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
422                                 X_AXIS);
423     }
424   
425   bool parity= true;            // todo: make me settable.
426   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
427   for (int i=1; i < heads.size (); i ++)
428     {
429       Real p = Staff_symbol_referencer::position_f (heads[i]);
430       int dy =abs (lastpos- (int)p);
431
432       if (dy <= 1)
433         {
434           if (parity)
435             {
436               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
437
438               Direction d = get_direction (me);
439               heads[i]->translate_axis (l * d, X_AXIS);
440
441               if (invisible_b(me))
442                 heads[i]->translate_axis (-thick *2* d , X_AXIS);
443
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   /*
476     Do the calculations for visible stems, but also for invisible stems
477     with note heads (i.e. half notes.)
478    */
479   if (head_count (me))
480     {
481       stem_end_position (me);   // ugh. Trigger direction calc.
482       position_noteheads (me);
483     }
484   else
485     {
486       me->remove_grob_property ("molecule-callback");
487     }
488   
489   return SCM_UNSPECIFIED;
490 }
491
492 /*
493   ugh.
494   When in a beam with tuplet brackets, brew_mol is called early,
495   caching a wrong value.
496  */
497 MAKE_SCHEME_CALLBACK (Stem, height, 2);
498 SCM
499 Stem::height (SCM smob, SCM ax)
500 {
501   Axis a = (Axis)gh_scm2int (ax);
502   Grob * me = unsmob_grob (smob);
503   assert (a == Y_AXIS);
504
505   SCM mol = me->get_uncached_molecule ();
506   Interval iv;
507   if (mol != SCM_EOL)
508     iv = unsmob_molecule (mol)->extent (a);
509   return ly_interval2scm (iv);
510 }
511
512
513 Molecule
514 Stem::flag (Grob*me)
515 {
516   /* TODO: rename flag-style into something more appropriate,
517    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
518    "single" and "double".  Needs more discussion.
519   */
520   String style, fstyle, staffline_offs;
521   SCM fst = me->get_grob_property ("flag-style");
522   if (gh_string_p (fst))
523     {
524       fstyle = ly_scm2string (fst);
525     }
526
527   SCM st = me->get_grob_property ("style");
528   if (gh_symbol_p (st))
529     {
530       style = (ly_scm2string (scm_symbol_to_string (st)));
531     }
532   else
533     {
534       style = "";
535     }
536   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
537
538   if (String::compare_i (style, "mensural") == 0)
539     /* Mensural notation: For notes on staff lines, use different
540        flags than for notes between staff lines.  The idea is that
541        flags are always vertically aligned with the staff lines,
542        regardless if the note head is on a staff line or between two
543        staff lines.  In other words, the inner end of a flag always
544        touches a staff line.
545     */
546     {
547       if (adjust)
548         {
549           /* Urrgh!  We have to detect wether this stem ends on a staff
550              line or between two staff lines.  But we can not call
551              stem_end_position(me) or get_default_stem_end_position(me),
552              since this encounters the flag and hence results in an
553              infinite recursion.  However, in pure mensural notation,
554              there are no multiple note heads attached to a single stem,
555              neither is there usually need for using the stem_shorten
556              property (except for 32th and 64th notes, but that is not a
557              problem since the stem length in this case is augmented by
558              an integral multiple of staff_space).  Hence, it should be
559              sufficient to just take the first note head, assume it's
560              the only one, look if it's on a staff line, and select the
561              flag's shape accordingly.  In the worst case, the shape
562              looks slightly misplaced, but that will usually be the
563              programmer's fault (e.g. when trying to attach multiple
564              note heads to a single stem in mensural notation).  */
565
566           /*
567             perhaps the detection whether this correction is needed should
568             happen in a different place  to avoid the recursion.
569             
570             --hwn.
571           */
572           Grob *first = first_head(me);
573           int sz = Staff_symbol_referencer::line_count (me)-1;
574           int p = (int)rint (Staff_symbol_referencer::position_f (first));
575           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
576         }
577       else
578         {
579           staffline_offs = "2";
580         }
581     }
582   else
583     {
584       staffline_offs = "";
585     }
586   char c = (get_direction (me) == UP) ? 'u' : 'd';
587   String index_str
588     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
589   Molecule m
590     = Font_interface::get_default_font (me)->find_by_name (index_str);
591   if (!fstyle.empty_b ())
592     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
593   return m;
594 }
595
596 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
597 SCM
598 Stem::dim_callback (SCM e, SCM ax)
599 {
600   Axis a = (Axis) gh_scm2int (ax);
601   assert (a == X_AXIS);
602   Grob *se = unsmob_grob (e);
603   Interval r (0, 0);
604   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
605     ;   // TODO!
606   else
607     {
608       r = flag (se).extent (X_AXIS);
609     }
610   return ly_interval2scm (r);
611 }
612  
613
614
615 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
616
617 SCM
618 Stem::brew_molecule (SCM smob) 
619 {
620   Grob*me = unsmob_grob (smob);
621   Molecule mol;
622   Direction d = get_direction (me);
623   
624   
625      
626   Real y1;
627
628   /*
629     This is required to avoid stems passing in tablature chords...
630    */
631
632
633   /*
634     TODO: make  the stem start a direction ?
635     
636   */
637   if (to_boolean (me->get_grob_property ("avoid-note-head")))
638     {
639       y1 = Staff_symbol_referencer::position_f (last_head (me));
640     }
641   else
642     {
643       y1 = Staff_symbol_referencer::position_f (first_head (me));
644     }
645   
646   Real y2 = stem_end_position (me);
647   
648   Interval stem_y (y1 <? y2,y2 >? y1);
649
650
651   // dy?
652   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
653
654   if (Grob *hed = support_head (me))
655     {
656       /*
657         must not take ledgers into account.
658        */
659       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
660       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
661
662       y_attach = head_height.linear_combination (y_attach);
663       stem_y[Direction (-d)] += d * y_attach/dy;
664     }
665   
666   if (!invisible_b (me))
667     {
668       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
669         // URG
670         * me->paper_l ()->get_var ("linethickness");
671       
672       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
673                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
674       mol.add_molecule (ss);
675     }
676
677   if (!beam_l (me) && abs (duration_log (me)) > 2)
678     {
679       Molecule fl = flag (me);
680       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
681       mol.add_molecule (fl);
682     }
683
684   return mol.smobbed_copy ();
685 }
686
687 /*
688   move the stem to right of the notehead if it is up.
689  */
690 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
691 SCM
692 Stem::off_callback (SCM element_smob, SCM)
693 {
694   Grob *me = unsmob_grob (element_smob);
695   
696   Real r=0;
697
698   if (head_count (me) == 0)
699     {
700       return gh_double2scm (0.0);
701     }
702   
703   if (Grob * f = first_head (me))
704     {
705       Interval head_wid = Note_head::head_extent(f, X_AXIS);
706
707       
708       Real attach =0.0;
709
710       if (invisible_b (me))
711         {
712           attach = 0.0;
713         }
714       else
715         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
716
717       Direction d = get_direction (me);
718
719       Real real_attach = head_wid.linear_combination (d * attach);
720
721       r = real_attach;
722
723       /*
724         If not centered: correct for stem thickness.
725        */
726       if (attach)
727         {
728           Real rule_thick
729             = gh_scm2double (me->get_grob_property ("thickness"))
730             * me->paper_l ()->get_var ("linethickness");
731
732           
733           r += - d * rule_thick * 0.5;
734         }
735     }
736   return gh_double2scm (r);
737 }
738
739
740
741 Grob*
742 Stem::beam_l (Grob*me)
743 {
744   SCM b=  me->get_grob_property ("beam");
745   return unsmob_grob (b);
746 }
747
748
749 // ugh still very long.
750 Stem_info
751 Stem::calc_stem_info (Grob*me) 
752 {
753   SCM scm_info = me->get_grob_property ("stem-info");
754
755   if (gh_pair_p (scm_info ))
756     {
757       Stem_info si ;
758
759       si.dir_ = Directional_element_interface::get(me); 
760       si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
761       si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
762
763       return si;
764     }
765
766   Direction mydir = Directional_element_interface::get (me);
767   Real staff_space = Staff_symbol_referencer::staff_space (me);
768   Real half_space = staff_space / 2;
769
770   Grob * beam = beam_l (me);
771   int multiplicity = Beam::get_multiplicity (beam);
772   Real interbeam_f = Beam::get_interbeam (beam);
773
774   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
775   
776   Real ideal_y = chord_start_y (me);
777   
778   /* from here on, calculate as if dir == UP */
779   ideal_y *= mydir;
780   
781   SCM grace_prop = me->get_grob_property ("grace");
782   
783   bool grace_b = to_boolean (grace_prop);
784   
785   Array<Real> a;
786   SCM s;
787   
788   s = me->get_grob_property ("beamed-minimum-lengths");
789   a.clear ();
790   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
791     a.push (gh_scm2double (ly_car (q)));
792
793
794   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
795   s = me->get_grob_property ("beamed-lengths");
796
797   a.clear ();
798   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
799     a.push (gh_scm2double (ly_car (q)));
800
801   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
802
803   Direction first_dir = Directional_element_interface::get
804     (Beam::first_visible_stem (beam));
805
806   // FIXME, hairy.  see beam::calc_stem_y, for knees it's not trival
807   // to calculate where secondary, ternary beams will go.
808   if (multiplicity && first_dir == mydir)
809     ideal_y += thick + (multiplicity - 1) * interbeam_f;
810
811   ideal_y += stem_length;
812
813   
814   Real shortest_y = ideal_y -stem_length + minimum_length; 
815
816   /*
817     lowest beam of (UP) beam must never be lower than second staffline
818
819     Hmm, reference (Wanske?)
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   */
826   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
827   if (!grace_b && !no_extend_b)
828     {
829       /* highest beam of (UP) beam must never be lower than middle
830          staffline
831          lowest beam of (UP) beam must never be lower than second staffline
832       */
833       shortest_y =
834         shortest_y >? 0
835         >? (- 2 * half_space - thick
836             + (multiplicity > 0) * thick
837             + interbeam_f * (multiplicity - 1));
838     }
839     
840   
841   ideal_y = ideal_y >? shortest_y;
842
843   s = beam->get_grob_property ("shorten");
844   if (gh_number_p (s))
845     ideal_y -= gh_scm2double (s);
846
847   Grob *common = me->common_refpoint (beam, Y_AXIS);
848
849   /*
850     UGH -> THIS CAUSES ASYMETRY: the same beam can start/end on
851     different staffs.
852
853     TODO: the beam calculation should probably also use
854     relative_coordinate() for the Y positions of all beams.
855
856     
857    */
858   Real interstaff_f = mydir *
859     (me->relative_coordinate (common, Y_AXIS)
860      - beam->relative_coordinate (common, Y_AXIS));
861   
862   ideal_y += interstaff_f;
863   shortest_y += interstaff_f;
864
865   ideal_y *= mydir;
866   shortest_y *= mydir; 
867   
868   me->set_grob_property ("stem-info",
869                          scm_list_n (gh_double2scm (ideal_y),
870                                      gh_double2scm (shortest_y),
871                                      SCM_UNDEFINED));
872
873   Stem_info si;
874   si.dir_ = mydir;
875   si.shortest_y_ = shortest_y;
876   si.ideal_y_ = ideal_y;
877   
878   return si;
879 }
880
881 ADD_INTERFACE (Stem,"stem-interface",
882   "A stem",
883   "avoid-note-head 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");
884