]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
ed154d38600eafea92d9b4f09e665ac08e8bd8d7
[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 + 1.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   if (to_boolean (me->get_grob_property ("avoid-note-head")))
648     {
649       y1 = Staff_symbol_referencer::position_f (last_head (me));
650     }
651   else
652     {
653       y1 = Staff_symbol_referencer::position_f (first_head (me));
654     }
655   
656   Real y2 = stem_end_position (me);
657   
658   Interval stem_y (y1 <? y2,y2 >? y1);
659
660
661   // dy?
662   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
663
664   if (Grob *hed = support_head (me))
665     {
666       /*
667         must not take ledgers into account.
668        */
669       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
670       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
671
672       y_attach = head_height.linear_combination (y_attach);
673       stem_y[Direction (-d)] += d * y_attach/dy;
674     }
675   
676   if (!invisible_b (me))
677     {
678       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
679         // URG
680         * me->paper_l ()->get_var ("linethickness");
681       
682       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
683                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
684       mol.add_molecule (ss);
685     }
686
687   if (!beam_l (me) && abs (duration_log (me)) > 2)
688     {
689       Molecule fl = flag (me);
690       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
691       mol.add_molecule (fl);
692     }
693
694   return mol.smobbed_copy ();
695 }
696
697 /*
698   move the stem to right of the notehead if it is up.
699  */
700 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
701 SCM
702 Stem::off_callback (SCM element_smob, SCM)
703 {
704   Grob *me = unsmob_grob (element_smob);
705   
706   Real r=0;
707
708   if (head_count (me) == 0)
709     {
710       return gh_double2scm (0.0);
711     }
712   
713   if (Grob * f = first_head (me))
714     {
715       Interval head_wid = Note_head::head_extent(f, X_AXIS);
716
717       
718       Real attach =0.0;
719
720       if (invisible_b (me))
721         {
722           attach = 0.0;
723         }
724       else
725         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
726
727       Direction d = get_direction (me);
728
729       Real real_attach = head_wid.linear_combination (d * attach);
730
731       r = real_attach;
732
733       /*
734         If not centered: correct for stem thickness.
735        */
736       if (attach)
737         {
738           Real rule_thick
739             = gh_scm2double (me->get_grob_property ("thickness"))
740             * me->paper_l ()->get_var ("linethickness");
741
742           
743           r += - d * rule_thick * 0.5;
744         }
745     }
746   return gh_double2scm (r);
747 }
748
749
750
751 Grob*
752 Stem::beam_l (Grob*me)
753 {
754   SCM b=  me->get_grob_property ("beam");
755   return unsmob_grob (b);
756 }
757
758
759 // ugh still very long.
760 Stem_info
761 Stem::calc_stem_info (Grob*me) 
762 {
763   SCM up_to_staff = me->get_grob_property ("up-to-staff");
764   if (gh_scm2bool(up_to_staff)) {
765     
766     // Up-to-staff : the stem end out of the staff.
767
768     /*
769       FIXME: duplicate code.
770      */
771     int line_count = Staff_symbol_referencer::line_count (me);
772     
773     Stem_info si ;
774     
775     Direction dir = get_direction (me);
776     
777     si.ideal_y_ = dir*  (line_count + 1.5);
778     si.dir_ = dir;
779     si.shortest_y_ = si.ideal_y_; 
780     
781     return si;
782   }
783   
784   SCM scm_info = me->get_grob_property ("stem-info");
785
786   if (gh_pair_p (scm_info ))
787     {
788       Stem_info si ;
789
790       si.dir_ = Directional_element_interface::get(me); 
791       si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
792       si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
793
794       return si;
795     }
796
797   Direction mydir = Directional_element_interface::get (me);
798   Real staff_space = Staff_symbol_referencer::staff_space (me);
799   Real half_space = staff_space / 2;
800
801   Grob * beam = beam_l (me);
802   int multiplicity = Beam::get_multiplicity (beam);
803   Real interbeam_f = Beam::get_interbeam (beam);
804
805   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
806   
807   Real ideal_y = chord_start_y (me);
808   
809   /* from here on, calculate as if dir == UP */
810   ideal_y *= mydir;
811   
812   SCM grace_prop = me->get_grob_property ("grace");
813   
814   bool grace_b = to_boolean (grace_prop);
815   
816   Array<Real> a;
817   SCM s;
818   
819   s = me->get_grob_property ("beamed-minimum-lengths");
820   a.clear ();
821   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
822     a.push (gh_scm2double (ly_car (q)));
823
824
825   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
826   s = me->get_grob_property ("beamed-lengths");
827
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   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
833
834   Grob *fvs = Beam::first_visible_stem (beam);
835
836   /*
837     Let's hope people don't use kneed tremolo beams.
838    */
839   Direction first_dir = fvs ? Directional_element_interface::get(fvs) : mydir;
840
841   // FIXME, hairy.  see beam::calc_stem_y, for knees it's not trival
842   // to calculate where secondary, ternary beams will go.
843   if (multiplicity && first_dir == mydir)
844     ideal_y += thick + (multiplicity - 1) * interbeam_f;
845
846   ideal_y += stem_length;
847
848   
849   Real shortest_y = ideal_y -stem_length + minimum_length; 
850
851   /*
852     lowest beam of (UP) beam must never be lower than second staffline
853
854     Hmm, reference (Wanske?)
855
856     Although this (additional) rule is probably correct,
857     I expect that highest beam (UP) should also never be lower
858     than middle staffline, just as normal stems.
859         
860   */
861   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
862   if (!grace_b && !no_extend_b)
863     {
864       /* highest beam of (UP) beam must never be lower than middle
865          staffline
866          lowest beam of (UP) beam must never be lower than second staffline
867       */
868       shortest_y =
869         shortest_y >? 0
870         >? (- 2 * half_space - thick
871             + (multiplicity > 0) * thick
872             + interbeam_f * (multiplicity - 1));
873     }
874     
875   
876   ideal_y = ideal_y >? shortest_y;
877
878   s = beam->get_grob_property ("shorten");
879   if (gh_number_p (s))
880     ideal_y -= gh_scm2double (s);
881
882   Grob *common = me->common_refpoint (beam, Y_AXIS);
883
884   /*
885     UGH -> THIS CAUSES ASYMETRY: the same beam can start/end on
886     different staffs.
887
888     TODO: the beam calculation should probably also use
889     relative_coordinate() for the Y positions of all beams.
890
891     
892    */
893   Real interstaff_f = mydir *
894     (me->relative_coordinate (common, Y_AXIS)
895      - beam->relative_coordinate (common, Y_AXIS));
896   
897   ideal_y += interstaff_f;
898   shortest_y += interstaff_f;
899
900   ideal_y *= mydir;
901   shortest_y *= mydir; 
902   
903   me->set_grob_property ("stem-info",
904                          scm_list_n (gh_double2scm (ideal_y),
905                                      gh_double2scm (shortest_y),
906                                      SCM_UNDEFINED));
907
908   Stem_info si;
909   si.dir_ = mydir;
910   si.shortest_y_ = shortest_y;
911   si.ideal_y_ = ideal_y;
912   
913   return si;
914 }
915
916 ADD_INTERFACE (Stem,"stem-interface",
917   "A stem",
918   "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");
919