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