]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
''
[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   SCM up_to_staff = me->get_grob_property ("up-to-staff");
278   if (to_boolean(up_to_staff))
279     {
280       int line_count = Staff_symbol_referencer::line_count (me);
281     
282       Direction dir = get_direction (me);
283     
284       return dir*  (line_count + 3.5);
285     }
286   
287   bool grace_b = to_boolean (me->get_grob_property ("grace"));
288   SCM s;
289   Array<Real> a;
290
291   Real length_f = 0.;
292   SCM scm_len = me->get_grob_property ("length");
293   if (gh_number_p (scm_len))
294     {
295       length_f = gh_scm2double (scm_len);
296     }
297   else
298     {
299       s = me->get_grob_property ("lengths");
300       for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
301         a.push (gh_scm2double (ly_car (q)));
302                 
303       // stem uses half-spaces
304       length_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
305     }
306
307
308   a.clear ();
309   s = me->get_grob_property ("stem-shorten");
310   for (SCM q = s; gh_pair_p (q); q = ly_cdr (q))
311     a.push (gh_scm2double (ly_car (q)));
312
313
314   // stem uses half-spaces
315
316   // fixme: use scm_list_n_ref () iso. array[]
317   Real shorten_f = a[ ((duration_log (me) - 2) >? 0) <? (a.size () - 1)] * 2;
318
319   /* On boundary: shorten only half */
320   if (abs (chord_start_y (me)) == 0.5)
321     shorten_f *= 0.5;
322
323   /* URGURGURG
324      'set-default-stemlen' sets direction too
325    */
326   Direction dir = get_direction (me);
327   if (!dir)
328     {
329       dir = get_default_dir (me);
330       Directional_element_interface::set (me, dir);
331     }
332   
333   /* stems in unnatural (forced) direction should be shortened, 
334     according to [Roush & Gourlay] */
335   if (chord_start_y (me)
336       && (get_direction (me) != get_default_dir (me)))
337     length_f -= shorten_f;
338
339   Interval hp = head_positions (me);  
340   Real st = hp[dir] + dir * length_f;
341
342   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
343   if (!grace_b && !no_extend_b && dir * st < 0) // junkme?
344     st = 0.0;
345
346   /*
347     Make a little room if we have a upflag and there is a dot.
348     previous approach was to lengthen the stem. This is not
349     good typesetting practice. 
350     
351   */
352   if (!beam_l (me) && dir == UP
353       && duration_log (me) > 2)
354     {
355       Grob * closest_to_flag = extremal_heads (me)[dir];
356       Grob * dots = closest_to_flag
357         ? Rhythmic_head::dots_l (closest_to_flag ) : 0;
358
359       if (dots)
360         {
361           Real dp = Staff_symbol_referencer::position_f  (dots);
362           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
363             / Staff_symbol_referencer::staff_space (me); 
364
365           /*
366             Very gory: add myself to the X-support of the parent,
367             which should be a dot-column.
368            */
369           if (dir * (st + flagy -  dp) < 0.5)
370             {
371               Grob *par = dots->get_parent (X_AXIS);
372
373               if (Dot_column::has_interface (par))
374                 {
375                   Side_position_interface::add_support (par, me);
376
377                   /*
378                     TODO: apply some better logic here. The flag is
379                     curved inwards, so this will typically be too
380                     much.
381                   */
382                 }
383             }
384         }
385     }
386
387
388   return st;
389 }
390
391
392
393 /*
394   
395   the log of the duration (Number of hooks on the flag minus two)
396  */
397 int
398 Stem::duration_log (Grob*me) 
399 {
400   SCM s = me->get_grob_property ("duration-log");
401   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
402 }
403
404 void
405 Stem::position_noteheads (Grob*me)
406 {
407   if (!head_count (me))
408     return;
409   
410   Link_array<Grob> heads =
411     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
412
413   heads.sort (compare_position);
414   Direction dir =get_direction (me);
415   
416   if (dir < 0)
417     heads.reverse ();
418
419
420   bool invisible = invisible_b (me);
421   Real thick = 0.0;
422   if (invisible)
423         thick = gh_scm2double (me->get_grob_property ("thickness"))
424           * me->paper_l ()->get_var ("linethickness");
425       
426
427   Grob *hed = support_head (me);
428   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
429   for (int i=0; i < heads.size (); i++)
430     {
431       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
432                                 X_AXIS);
433     }
434   
435   bool parity= true;            // todo: make me settable.
436   int lastpos = int (Staff_symbol_referencer::position_f (heads[0]));
437   for (int i=1; i < heads.size (); i ++)
438     {
439       Real p = Staff_symbol_referencer::position_f (heads[i]);
440       int dy =abs (lastpos- (int)p);
441
442       if (dy <= 1)
443         {
444           if (parity)
445             {
446               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
447
448               Direction d = get_direction (me);
449               heads[i]->translate_axis (l * d, X_AXIS);
450
451               if (invisible_b(me))
452                 heads[i]->translate_axis (-thick *2* d , X_AXIS);
453
454               
455              /* TODO:
456                  
457               For some cases we should kern some more: when the
458               distance between the next or prev note is too large, we'd 
459               get large white gaps, eg.
460               
461                |
462               X|
463                |X  <- kern this.
464                |
465               X
466               
467               */
468             }
469           parity = !parity;
470         }
471       else
472         parity = true;
473       
474       lastpos = int (p);
475     }
476 }
477
478 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
479 SCM
480 Stem::before_line_breaking (SCM smob)
481 {
482   Grob*me = unsmob_grob (smob);
483
484
485   /*
486     Do the calculations for visible stems, but also for invisible stems
487     with note heads (i.e. half notes.)
488    */
489   if (head_count (me))
490     {
491       stem_end_position (me);   // ugh. Trigger direction calc.
492       position_noteheads (me);
493     }
494   else
495     {
496       me->remove_grob_property ("molecule-callback");
497     }
498   
499   return SCM_UNSPECIFIED;
500 }
501
502 /*
503   ugh.
504   When in a beam with tuplet brackets, brew_mol is called early,
505   caching a wrong value.
506  */
507 MAKE_SCHEME_CALLBACK (Stem, height, 2);
508 SCM
509 Stem::height (SCM smob, SCM ax)
510 {
511   Axis a = (Axis)gh_scm2int (ax);
512   Grob * me = unsmob_grob (smob);
513   assert (a == Y_AXIS);
514
515   SCM mol = me->get_uncached_molecule ();
516   Interval iv;
517   if (mol != SCM_EOL)
518     iv = unsmob_molecule (mol)->extent (a);
519   return ly_interval2scm (iv);
520 }
521
522
523 Molecule
524 Stem::flag (Grob*me)
525 {
526   /* TODO: rename flag-style into something more appropriate,
527    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
528    "single" and "double".  Needs more discussion.
529   */
530   String style, fstyle, staffline_offs;
531   SCM fst = me->get_grob_property ("flag-style");
532   if (gh_string_p (fst))
533     {
534       fstyle = ly_scm2string (fst);
535     }
536
537   SCM st = me->get_grob_property ("style");
538   if (gh_symbol_p (st))
539     {
540       style = (ly_scm2string (scm_symbol_to_string (st)));
541     }
542   else
543     {
544       style = "";
545     }
546   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
547
548   if (String::compare_i (style, "mensural") == 0)
549     /* Mensural notation: For notes on staff lines, use different
550        flags than for notes between staff lines.  The idea is that
551        flags are always vertically aligned with the staff lines,
552        regardless if the note head is on a staff line or between two
553        staff lines.  In other words, the inner end of a flag always
554        touches a staff line.
555     */
556     {
557       if (adjust)
558         {
559           /* Urrgh!  We have to detect wether this stem ends on a staff
560              line or between two staff lines.  But we can not call
561              stem_end_position(me) or get_default_stem_end_position(me),
562              since this encounters the flag and hence results in an
563              infinite recursion.  However, in pure mensural notation,
564              there are no multiple note heads attached to a single stem,
565              neither is there usually need for using the stem_shorten
566              property (except for 32th and 64th notes, but that is not a
567              problem since the stem length in this case is augmented by
568              an integral multiple of staff_space).  Hence, it should be
569              sufficient to just take the first note head, assume it's
570              the only one, look if it's on a staff line, and select the
571              flag's shape accordingly.  In the worst case, the shape
572              looks slightly misplaced, but that will usually be the
573              programmer's fault (e.g. when trying to attach multiple
574              note heads to a single stem in mensural notation).  */
575
576           /*
577             perhaps the detection whether this correction is needed should
578             happen in a different place  to avoid the recursion.
579             
580             --hwn.
581           */
582           Grob *first = first_head(me);
583           int sz = Staff_symbol_referencer::line_count (me)-1;
584           int p = (int)rint (Staff_symbol_referencer::position_f (first));
585           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
586         }
587       else
588         {
589           staffline_offs = "2";
590         }
591     }
592   else
593     {
594       staffline_offs = "";
595     }
596   char c = (get_direction (me) == UP) ? 'u' : 'd';
597   String index_str
598     = String ("flags-") + style + to_str (c) + staffline_offs + to_str (duration_log (me));
599   Molecule m
600     = Font_interface::get_default_font (me)->find_by_name (index_str);
601   if (!fstyle.empty_b ())
602     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_str (c) + fstyle));
603   return m;
604 }
605
606 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
607 SCM
608 Stem::dim_callback (SCM e, SCM ax)
609 {
610   Axis a = (Axis) gh_scm2int (ax);
611   assert (a == X_AXIS);
612   Grob *se = unsmob_grob (e);
613   Interval r (0, 0);
614   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
615     ;   // TODO!
616   else
617     {
618       r = flag (se).extent (X_AXIS);
619     }
620   return ly_interval2scm (r);
621 }
622  
623
624
625 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
626
627 SCM
628 Stem::brew_molecule (SCM smob) 
629 {
630   Grob*me = unsmob_grob (smob);
631   Molecule mol;
632   Direction d = get_direction (me);
633   
634   
635      
636   Real y1;
637
638   /*
639     This is required to avoid stems passing in tablature chords...
640    */
641
642
643   /*
644     TODO: make  the stem start a direction ?
645   */
646   
647
648   
649   if (to_boolean (me->get_grob_property ("avoid-note-head")))
650     {
651       Grob * lh = last_head (me);
652       if (!lh)
653         return SCM_EOL;
654       y1 = Staff_symbol_referencer::position_f (lh);
655     }
656   else
657     {
658       Grob * lh = first_head (me);
659       if (!lh)
660         return SCM_EOL;
661       y1 = Staff_symbol_referencer::position_f (lh);
662     }
663   
664   Real y2 = stem_end_position (me);
665   
666   Interval stem_y (y1 <? y2,y2 >? y1);
667
668
669   // dy?
670   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
671
672   if (Grob *hed = support_head (me))
673     {
674       /*
675         must not take ledgers into account.
676        */
677       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
678       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
679
680       y_attach = head_height.linear_combination (y_attach);
681       stem_y[Direction (-d)] += d * y_attach/dy;
682     }
683   
684   if (!invisible_b (me))
685     {
686       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
687         // URG
688         * me->paper_l ()->get_var ("linethickness");
689       
690       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
691                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
692       mol.add_molecule (ss);
693     }
694
695   if (!beam_l (me) && abs (duration_log (me)) > 2)
696     {
697       Molecule fl = flag (me);
698       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
699       mol.add_molecule (fl);
700     }
701
702   return mol.smobbed_copy ();
703 }
704
705 /*
706   move the stem to right of the notehead if it is up.
707  */
708 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
709 SCM
710 Stem::off_callback (SCM element_smob, SCM)
711 {
712   Grob *me = unsmob_grob (element_smob);
713   
714   Real r=0;
715
716   if (head_count (me) == 0)
717     {
718       return gh_double2scm (0.0);
719     }
720   
721   if (Grob * f = first_head (me))
722     {
723       Interval head_wid = Note_head::head_extent(f, X_AXIS);
724
725       
726       Real attach =0.0;
727
728       if (invisible_b (me))
729         {
730           attach = 0.0;
731         }
732       else
733         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
734
735       Direction d = get_direction (me);
736
737       Real real_attach = head_wid.linear_combination (d * attach);
738
739       r = real_attach;
740
741       /*
742         If not centered: correct for stem thickness.
743        */
744       if (attach)
745         {
746           Real rule_thick
747             = gh_scm2double (me->get_grob_property ("thickness"))
748             * me->paper_l ()->get_var ("linethickness");
749
750           
751           r += - d * rule_thick * 0.5;
752         }
753     }
754   return gh_double2scm (r);
755 }
756
757
758
759 Grob*
760 Stem::beam_l (Grob*me)
761 {
762   SCM b=  me->get_grob_property ("beam");
763   return unsmob_grob (b);
764 }
765
766
767 // ugh still very long.
768 Stem_info
769 Stem::calc_stem_info (Grob*me) 
770 {
771   SCM up_to_staff = me->get_grob_property ("up-to-staff");
772   if (gh_scm2bool(up_to_staff)) {
773     
774     // Up-to-staff : the stem end out of the staff.
775
776     /*
777       FIXME: duplicate code.
778      */
779     int line_count = Staff_symbol_referencer::line_count (me);
780     
781     Stem_info si ;
782     
783     Direction dir = get_direction (me);
784     
785     si.ideal_y_ = dir*  (line_count + 1.5);
786     si.dir_ = dir;
787     si.shortest_y_ = si.ideal_y_; 
788     
789     return si;
790   }
791   
792   SCM scm_info = me->get_grob_property ("stem-info");
793
794   if (gh_pair_p (scm_info ))
795     {
796       Stem_info si ;
797
798       si.dir_ = Directional_element_interface::get(me); 
799       si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
800       si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
801
802       return si;
803     }
804
805   Direction mydir = Directional_element_interface::get (me);
806   Real staff_space = Staff_symbol_referencer::staff_space (me);
807   Real half_space = staff_space / 2;
808
809   Grob * beam = beam_l (me);
810   int multiplicity = Beam::get_multiplicity (beam);
811   Real interbeam_f = Beam::get_interbeam (beam);
812
813   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
814   
815   Real ideal_y = chord_start_y (me);
816   
817   /* from here on, calculate as if dir == UP */
818   ideal_y *= mydir;
819   
820   SCM grace_prop = me->get_grob_property ("grace");
821   
822   bool grace_b = to_boolean (grace_prop);
823   
824   Array<Real> a;
825   SCM s;
826   
827   s = me->get_grob_property ("beamed-minimum-lengths");
828   a.clear ();
829   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
830     a.push (gh_scm2double (ly_car (q)));
831
832
833   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
834   s = me->get_grob_property ("beamed-lengths");
835
836   a.clear ();
837   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
838     a.push (gh_scm2double (ly_car (q)));
839
840   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
841
842   Grob *fvs = Beam::first_visible_stem (beam);
843
844   /*
845     Let's hope people don't use kneed tremolo beams.
846    */
847   Direction first_dir = fvs ? Directional_element_interface::get(fvs) : mydir;
848
849   // FIXME, hairy.  see beam::calc_stem_y, for knees it's not trival
850   // to calculate where secondary, ternary beams will go.
851   if (multiplicity && first_dir == mydir)
852     ideal_y += thick + (multiplicity - 1) * interbeam_f;
853
854   ideal_y += stem_length;
855
856   
857   Real shortest_y = ideal_y -stem_length + minimum_length; 
858
859   /*
860     lowest beam of (UP) beam must never be lower than second staffline
861
862     Hmm, reference (Wanske?)
863
864     Although this (additional) rule is probably correct,
865     I expect that highest beam (UP) should also never be lower
866     than middle staffline, just as normal stems.
867         
868   */
869   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
870   if (!grace_b && !no_extend_b)
871     {
872       /* highest beam of (UP) beam must never be lower than middle
873          staffline
874          lowest beam of (UP) beam must never be lower than second staffline
875       */
876       shortest_y =
877         shortest_y >? 0
878         >? (- 2 * half_space - thick
879             + (multiplicity > 0) * thick
880             + interbeam_f * (multiplicity - 1));
881     }
882     
883   
884   ideal_y = ideal_y >? shortest_y;
885
886   s = beam->get_grob_property ("shorten");
887   if (gh_number_p (s))
888     ideal_y -= gh_scm2double (s);
889
890   Grob *common = me->common_refpoint (beam, Y_AXIS);
891
892   /*
893     UGH -> THIS CAUSES ASYMETRY: the same beam can start/end on
894     different staffs.
895
896     TODO: the beam calculation should probably also use
897     relative_coordinate() for the Y positions of all beams.
898
899     
900    */
901   Real interstaff_f = mydir *
902     (me->relative_coordinate (common, Y_AXIS)
903      - beam->relative_coordinate (common, Y_AXIS));
904   
905   ideal_y += interstaff_f;
906   shortest_y += interstaff_f;
907
908   ideal_y *= mydir;
909   shortest_y *= mydir; 
910   
911   me->set_grob_property ("stem-info",
912                          scm_list_n (gh_double2scm (ideal_y),
913                                      gh_double2scm (shortest_y),
914                                      SCM_UNDEFINED));
915
916   Stem_info si;
917   si.dir_ = mydir;
918   si.shortest_y_ = shortest_y;
919   si.ideal_y_ = ideal_y;
920   
921   return si;
922 }
923
924 ADD_INTERFACE (Stem,"stem-interface",
925   "A stem",
926   "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");
927