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