]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
jiba tab
[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.ideal_y = gh_scm2double (gh_car (scm_info)); 
760       si.max_y = gh_scm2double (gh_cadr (scm_info)); 
761       si.min_y = gh_scm2double (gh_caddr (scm_info));
762
763       return si;
764     }
765     
766   Grob * beam = beam_l (me);
767
768   Direction beam_dir = Directional_element_interface::get (beam);
769   if (!beam_dir)
770     {
771       programming_error ("Beam dir not set.");
772       beam_dir = UP;
773     }
774     
775
776   Real staff_space = Staff_symbol_referencer::staff_space (me);
777   Real half_space = staff_space / 2;
778   
779   int multiplicity = Beam::get_multiplicity (beam);
780   Real interbeam_f = Beam::get_interbeam (beam);
781
782   Real thick = gh_scm2double (beam->get_grob_property ("thickness"));
783   Stem_info info; 
784   info.ideal_y = chord_start_y (me);
785
786   // for simplicity, we calculate as if dir == UP
787
788   /*
789     UGH. This confuses issues more. fixme. --hwn
790    */
791   info.ideal_y *= beam_dir;
792   SCM grace_prop = me->get_grob_property ("grace");
793
794   bool grace_b = to_boolean (grace_prop);
795   
796   Array<Real> a;
797   SCM s;
798   
799   s = me->get_grob_property ("beamed-minimum-lengths");
800   a.clear ();
801   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
802     a.push (gh_scm2double (ly_car (q)));
803
804
805   Real minimum_length = a[multiplicity <? (a.size () - 1)] * staff_space;
806   s = me->get_grob_property ("beamed-lengths");
807
808   a.clear ();
809   for (SCM q = s; q != SCM_EOL; q = ly_cdr (q))
810     a.push (gh_scm2double (ly_car (q)));
811
812   Real stem_length =  a[multiplicity <? (a.size () - 1)] * staff_space;
813
814
815   /*
816     This sucks -- On a kneed beam, *all* stems are kneed, not half of them.
817    */
818   if (!beam_dir || (beam_dir == Directional_element_interface::get (me)))
819     /* normal beamed stem */
820     {
821       if (multiplicity)
822         {
823           info.ideal_y += thick + (multiplicity - 1) * interbeam_f;
824         }
825       info.min_y = info.ideal_y;
826       info.max_y = 1000;  // INT_MAX;
827
828       info.ideal_y += stem_length;
829       info.min_y += minimum_length;
830
831       /*
832         lowest beam of (UP) beam must never be lower than second staffline
833
834         Hmm, reference (Wanske?)
835
836         Although this (additional) rule is probably correct,
837         I expect that highest beam (UP) should also never be lower
838         than middle staffline, just as normal stems.
839         
840       */
841       bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
842       if (!grace_b && !no_extend_b)
843         {
844           /* highest beam of (UP) beam must never be lower than middle
845              staffline
846              lowest beam of (UP) beam must never be lower than second staffline
847            */
848           info.min_y =
849             info.min_y >? 0
850             >? (- 2 * half_space - thick
851                 + (multiplicity > 0) * thick
852                 + interbeam_f * (multiplicity - 1));
853         }
854     }
855   else
856     /* knee */
857     {
858       info.ideal_y -= thick + stem_length;
859       info.max_y = info.ideal_y - minimum_length;
860
861       /*
862         We shouldn't invert the stems, so we set minimum at 0. 
863        */
864       info.min_y = 0.5;
865     }
866   
867   info.ideal_y = (info.max_y <? info.ideal_y) >? info.min_y;
868
869   s = beam->get_grob_property ("shorten");
870   if (gh_number_p (s))
871     info.ideal_y -= gh_scm2double (s);
872
873   Grob *common = me->common_refpoint (beam, Y_AXIS);
874   Real interstaff_f = beam_dir *
875     (me->relative_coordinate (common, Y_AXIS)
876      - beam->relative_coordinate (common, Y_AXIS));
877   
878   info.ideal_y += interstaff_f;
879   info.min_y += interstaff_f;
880   info.max_y += interstaff_f ;
881
882   me->set_grob_property ("stem-info",
883                          scm_list_n (gh_double2scm (info.ideal_y),
884                                      gh_double2scm (info.max_y),
885                                      gh_double2scm (info.min_y),
886                                      SCM_UNDEFINED));
887   
888   return info;
889 }
890
891 ADD_INTERFACE (Stem,"stem-interface",
892   "A stem",
893   "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");
894