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