]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
e4a8950024ba52c3afdfc123c15c33484cfd778f
[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--2003 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 #include "stem-tremolo.hh"
37
38 void
39 Stem::set_beaming (Grob*me, int beam_count,  Direction d)
40 {
41   SCM pair = me->get_grob_property ("beaming");
42   
43   if (!gh_pair_p (pair))
44     {
45       pair = gh_cons (SCM_EOL, SCM_EOL);
46       me->set_grob_property ("beaming", pair);
47     }
48
49   SCM l = index_get_cell (pair, d);
50   for( int i = 0; i<  beam_count; i++)
51     {
52       l = gh_cons (gh_int2scm (i), l);
53     }
54   index_set_cell (pair, d, l);          
55 }
56
57
58 Interval
59 Stem::head_positions (Grob*me) 
60 {
61   if (!head_count (me))
62     {
63       Interval iv;
64       return iv;
65     }
66
67   Drul_array<Grob*> e (extremal_heads (me));
68
69   return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
70                    Staff_symbol_referencer::get_position (e[UP]));
71 }
72
73
74 Real
75 Stem::chord_start_y (Grob*me) 
76 {
77   return head_positions (me)[get_direction (me)]
78     * Staff_symbol_referencer::staff_space (me)/2.0;
79 }
80
81 Real
82 Stem::stem_end_position (Grob*me) 
83 {
84   SCM p =me->get_grob_property ("stem-end-position");
85   Real pos;
86   if (!gh_number_p (p))
87     {
88       pos = get_default_stem_end_position (me);
89       me->set_grob_property ("stem-end-position", gh_double2scm (pos));
90     }
91   else
92     pos = gh_scm2double (p);
93
94   return pos;
95 }
96
97 Direction
98 Stem::get_direction (Grob*me)
99 {
100   Direction d = get_grob_direction (me);
101
102   if (!d)
103     {
104        d = get_default_dir (me);
105        // urg, AAARGH!
106        set_grob_direction (me, d);
107     }
108   return d ;
109 }
110
111
112 void
113 Stem::set_stemend (Grob*me, Real se)
114 {
115   // todo: margins
116   Direction d= get_direction (me);
117   
118   if (d && d * head_positions (me)[get_direction (me)] >= se*d)
119     me->warning (_ ("Weird stem size; check for narrow beams"));
120
121   me->set_grob_property ("stem-end-position", gh_double2scm (se));
122 }
123
124
125 /*
126   Note head that determines hshift for upstems
127
128   WARNING: triggers direction
129 */ 
130 Grob*
131 Stem::support_head (Grob*me)
132 {
133   if (head_count (me) == 1)
134     {
135       /*
136         UGH.
137        */
138       
139       return unsmob_grob (ly_car (me->get_grob_property ("note-heads")));
140     }
141   else
142     return first_head (me);
143 }
144
145
146 int
147 Stem::head_count (Grob*me)
148 {
149   return  Pointer_group_interface::count (me, "note-heads");
150 }
151
152 /*
153   The note head which forms one end of the stem.  
154
155   WARNING: triggers direction
156 */
157 Grob*
158 Stem::first_head (Grob*me)
159 {
160   Direction d = get_direction (me);
161   if (!d)
162     return 0;
163   return extremal_heads (me)[-d];
164 }
165
166 /*
167   The note head opposite to the first head.
168  */
169 Grob*
170 Stem::last_head (Grob*me)
171 {
172   Direction d = get_direction (me);
173   if (!d)
174     return 0;
175   return extremal_heads (me)[d];
176 }
177
178 /*
179   START is part where stem reaches `last' head. 
180  */
181 Drul_array<Grob*>
182 Stem::extremal_heads (Grob*me) 
183 {
184   const int inf = 1000000;
185   Drul_array<int> extpos;
186   extpos[DOWN] = inf;
187   extpos[UP] = -inf;  
188   
189   Drul_array<Grob *> exthead;
190   exthead[LEFT] = exthead[RIGHT] =0;
191   
192   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
193     {
194       Grob * n = unsmob_grob (ly_car (s));
195
196       
197       int p = int (Staff_symbol_referencer::get_position (n));
198
199       Direction d = LEFT;
200       do {
201       if (d* p > d* extpos[d])
202         {
203           exthead[d] = n;
204           extpos[d] = p;
205         }
206       } while (flip (&d) != DOWN);
207     }
208
209   return exthead;
210 }
211
212 static int
213 icmp (int const &a, int const &b)
214 {
215   return a-b;
216 }
217
218 Array<int>
219 Stem::note_head_positions (Grob *me)
220 {
221   Array<int> ps ;
222   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
223     {
224       Grob * n = unsmob_grob (ly_car (s));
225       int p = int (Staff_symbol_referencer::get_position (n));
226
227       ps.push (p);
228     }
229   
230   ps.sort (icmp);
231   return ps; 
232 }
233
234
235 void
236 Stem::add_head (Grob*me, Grob *n)
237 {
238   n->set_grob_property ("stem", me->self_scm ());
239   n->add_dependency (me);
240
241   /*
242     TODO: why not store Rest pointers? 
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   Real ss = Staff_symbol_referencer::staff_space (me); 
280
281   int durlog = duration_log (me);
282     
283   SCM s;
284   Array<Real> a;
285
286   
287   Real length = 7;              // WARNING: IN HALF SPACES
288   SCM scm_len = me->get_grob_property ("length");
289   if (gh_number_p (scm_len))
290     {
291       length = gh_scm2double (scm_len);
292     }
293   else
294     {
295       s = me->get_grob_property ("lengths");
296       if (gh_pair_p (s))
297         {
298           length = 2* gh_scm2double (robust_list_ref (durlog -2, s));
299         }
300     }
301
302
303
304   /* URGURGURG
305      'set-default-stemlen' sets direction too
306    */
307   Direction dir = get_direction (me);
308   if (!dir)
309     {
310       dir = get_default_dir (me);
311       set_grob_direction (me, dir);
312     }
313
314
315   /* stems in unnatural (forced) direction should be shortened, 
316     according to [Roush & Gourlay] */
317   if (!chord_start_y (me)
318       || (get_direction (me) != get_default_dir (me)))
319     {
320       
321       Real shorten = 0.0;
322   
323       SCM sshorten = me->get_grob_property ("stem-shorten");
324       SCM scm_shorten = gh_pair_p (sshorten) ?
325         robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
326       if (gh_number_p (scm_shorten))
327         {
328           shorten = 2* gh_scm2double (scm_shorten);
329         }
330   
331       /* On boundary: shorten only half */
332       if (abs (head_positions (me)[get_direction (me)]) <= 1)
333         shorten *= 0.5;
334   
335       length -= shorten;
336     }
337
338   /*
339     Tremolo stuff: 
340   */
341   Grob * trem = unsmob_grob (me->get_grob_property ("tremolo-flag"));
342   if (trem &&  !unsmob_grob (me->get_grob_property ("beam")))
343     {
344       /*
345         Crude hack: add extra space if tremolo flag is there.
346
347         We can't do this for the beam, since we get into a loop
348         (Stem_tremolo::raw_molecule() looks at the beam.)
349         
350          --hwn 
351       */
352       
353       Real minlen =
354         1.0 + 2 * Stem_tremolo::raw_molecule (trem).extent (Y_AXIS).length  () / ss;
355       
356       if (durlog >= 3)
357         {
358           Interval flag_ext = flag (me).extent (Y_AXIS) ;
359           if (!flag_ext.empty_b())
360             minlen += 2 * flag_ext.length () / ss ;
361
362           /*
363             The clash is smaller for down stems (since the tremolo is
364             angled up.)
365            */
366           if (dir == DOWN)
367             minlen -= 1.0;
368         }
369       
370       length = length >? (minlen + 1.0);
371     }
372    
373   Interval hp = head_positions (me);  
374   Real st = hp[dir] + dir * length;
375
376   /*
377     TODO: change name  to extend-stems to staff/center/'()
378   */
379   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
380   if (!no_extend_b && dir * st < 0) // junkme?
381     st = 0.0;
382
383   /*
384     Make a little room if we have a upflag and there is a dot.
385     previous approach was to lengthen the stem. This is not
386     good typesetting practice. 
387     
388   */
389   if (!get_beam (me) && dir == UP
390       && durlog > 2)
391     {
392       Grob * closest_to_flag = extremal_heads (me)[dir];
393       Grob * dots = closest_to_flag
394         ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
395
396       if (dots)
397         {
398           Real dp = Staff_symbol_referencer::get_position (dots);
399           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
400             / ss;
401
402           /*
403             Very gory: add myself to the X-support of the parent,
404             which should be a dot-column.
405            */
406           if (dir * (st + flagy -  dp) < 0.5)
407             {
408               Grob *par = dots->get_parent (X_AXIS);
409
410               if (Dot_column::has_interface (par))
411                 {
412                   Side_position_interface::add_support (par, me);
413
414                   /*
415                     TODO: apply some better logic here. The flag is
416                     curved inwards, so this will typically be too
417                     much.
418                   */
419                 }
420             }
421         }
422     }
423
424
425   return st;
426 }
427
428
429
430 /*
431   
432   the log of the duration (Number of hooks on the flag minus two)
433  */
434 int
435 Stem::duration_log (Grob*me) 
436 {
437   SCM s = me->get_grob_property ("duration-log");
438   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
439 }
440
441 void
442 Stem::position_noteheads (Grob*me)
443 {
444   if (!head_count (me))
445     return;
446   
447   Link_array<Grob> heads =
448     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
449
450   heads.sort (compare_position);
451   Direction dir =get_direction (me);
452   
453   if (dir < 0)
454     heads.reverse ();
455
456
457   Real thick = gh_scm2double (me->get_grob_property ("thickness"))
458      * me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
459       
460   Grob *hed = support_head (me);
461   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
462   for (int i=0; i < heads.size (); i++)
463     {
464       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
465                                 X_AXIS);
466     }
467   
468   bool parity= true;
469   int lastpos = int (Staff_symbol_referencer::get_position (heads[0]));
470   for (int i=1; i < heads.size (); i ++)
471     {
472       Real p = Staff_symbol_referencer::get_position (heads[i]);
473       int dy =abs (lastpos- (int)p);
474
475       if (dy <= 1)
476         {
477           if (parity)
478             {
479               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
480
481               Direction d = get_direction (me);
482               /* reversed head should be shifted l-thickness, but this looks
483                  too crowded, so we only shift l-0.5*thickness.
484                  Notice that this leads to assymetry: Normal heads overlap
485                  the stem 100% whereas reversed heads only overlaps the stem
486                  50% */
487               #define magic 0.5
488               heads[i]->translate_axis ((l-thick*magic) * d, X_AXIS);
489
490               if (invisible_b(me))
491                 heads[i]->translate_axis (-thick*(2-magic) * d , X_AXIS);
492
493               
494              /* TODO:
495                  
496               For some cases we should kern some more: when the
497               distance between the next or prev note is too large, we'd 
498               get large white gaps, eg.
499               
500                |
501               X|
502                |X  <- kern this.
503                |
504               X
505               
506               */
507             }
508           parity = !parity;
509         }
510       else
511         parity = true;
512       
513       lastpos = int (p);
514     }
515 }
516
517 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
518 SCM
519 Stem::before_line_breaking (SCM smob)
520 {
521   Grob*me = unsmob_grob (smob);
522
523
524   /*
525     Do the calculations for visible stems, but also for invisible stems
526     with note heads (i.e. half notes.)
527    */
528   if (head_count (me))
529     {
530       stem_end_position (me);   // ugh. Trigger direction calc.
531       position_noteheads (me);
532     }
533   else
534     {
535       me->set_grob_property ("molecule-callback", SCM_EOL);
536     }
537   
538   return SCM_UNSPECIFIED;
539 }
540
541 /*
542   ugh.
543   When in a beam with tuplet brackets, brew_mol is called early,
544   caching a wrong value.
545  */
546 MAKE_SCHEME_CALLBACK (Stem, height, 2);
547 SCM
548 Stem::height (SCM smob, SCM ax)
549 {
550   Axis a = (Axis)gh_scm2int (ax);
551   Grob * me = unsmob_grob (smob);
552   assert (a == Y_AXIS);
553
554   SCM mol = me->get_uncached_molecule ();
555   Interval iv;
556   if (mol != SCM_EOL)
557     iv = unsmob_molecule (mol)->extent (a);
558   if (Grob *b =get_beam (me))
559     {
560       Direction d = get_direction (me);
561       iv[d] += d * Beam::get_thickness (b) /2.0 ;
562     }
563
564   return ly_interval2scm (iv);
565 }
566
567
568 Molecule
569 Stem::flag (Grob*me)
570 {
571   /* TODO: maybe property stroke-style should take different values,
572      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
573      '() or "grace").  */
574   String flag_style;
575   
576   SCM flag_style_scm = me->get_grob_property ("flag-style");
577   if (gh_symbol_p (flag_style_scm))
578     {
579       flag_style = ly_symbol2string (flag_style_scm);
580     }
581
582   if (flag_style == "no-flag")
583     {
584       return Molecule ();
585     }
586
587   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
588
589   String staffline_offs;
590   if (String::compare (flag_style, "mensural") == 0)
591     /* Mensural notation: For notes on staff lines, use different
592        flags than for notes between staff lines.  The idea is that
593        flags are always vertically aligned with the staff lines,
594        regardless if the note head is on a staff line or between two
595        staff lines.  In other words, the inner end of a flag always
596        touches a staff line.
597     */
598     {
599       if (adjust)
600         {
601           /* Urrgh!  We have to detect wether this stem ends on a staff
602              line or between two staff lines.  But we can not call
603              stem_end_position(me) or get_default_stem_end_position(me),
604              since this encounters the flag and hence results in an
605              infinite recursion.  However, in pure mensural notation,
606              there are no multiple note heads attached to a single stem,
607              neither is there usually need for using the stem_shorten
608              property (except for 32th and 64th notes, but that is not a
609              problem since the stem length in this case is augmented by
610              an integral multiple of staff_space).  Hence, it should be
611              sufficient to just take the first note head, assume it's
612              the only one, look if it's on a staff line, and select the
613              flag's shape accordingly.  In the worst case, the shape
614              looks slightly misplaced, but that will usually be the
615              programmer's fault (e.g. when trying to attach multiple
616              note heads to a single stem in mensural notation).
617           */
618
619           /*
620             perhaps the detection whether this correction is needed should
621             happen in a different place  to avoid the recursion.
622             
623             --hwn.
624           */
625           int p = (int)rint (Staff_symbol_referencer::get_position (first_head (me)));
626           staffline_offs = Staff_symbol_referencer::on_staffline (me, p) ?
627             "1" : "0";
628         }
629       else
630         {
631           staffline_offs = "2";
632         }
633     }
634   else
635     {
636       staffline_offs = "";
637     }
638
639   char dir = (get_direction (me) == UP) ? 'u' : 'd';
640   String font_char =
641     flag_style + to_string (dir) + staffline_offs + to_string (duration_log (me));
642   Font_metric *fm = Font_interface::get_default_font (me);
643   Molecule flag = fm->find_by_name ("flags-" + font_char);
644   if (flag.empty_b ())
645     {
646       me->warning (_f ("flag `%s' not found", font_char));
647     }
648
649   SCM stroke_style_scm = me->get_grob_property ("stroke-style");
650   if (gh_string_p (stroke_style_scm))
651     {
652       String stroke_style = ly_scm2string (stroke_style_scm);
653       if (!stroke_style.empty_b ())
654         {
655           String font_char = to_string (dir) + stroke_style;
656           Molecule stroke = fm->find_by_name ("flags-" + font_char);
657           if (stroke.empty_b ())
658             {
659               me->warning (_f ("flag stroke `%s' not found", font_char));
660             }
661           else
662             {
663               flag.add_molecule (stroke);
664             }
665         }
666     }
667
668   return flag;
669 }
670
671 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
672 SCM
673 Stem::dim_callback (SCM e, SCM ax)
674 {
675   Axis a = (Axis) gh_scm2int (ax);
676   assert (a == X_AXIS);
677   Grob *se = unsmob_grob (e);
678   Interval r (0, 0);
679   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
680     ;   // TODO!
681   else
682     {
683       r = flag (se).extent (X_AXIS);
684     }
685   return ly_interval2scm (r);
686 }
687  
688
689
690 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
691
692 SCM
693 Stem::brew_molecule (SCM smob) 
694 {
695   Grob*me = unsmob_grob (smob);
696   Molecule mol;
697   Direction d = get_direction (me);
698   
699   
700      
701   /*
702     TODO: make  the stem start a direction ?
703
704     This is required to avoid stems passing in tablature chords...
705   */
706   Grob *lh = to_boolean (me->get_grob_property ("avoid-note-head")) 
707     ? last_head (me) :  lh = first_head (me);
708
709   if (!lh)
710     return SCM_EOL;
711   
712   Real y1 = Staff_symbol_referencer::get_position (lh);
713   Real y2 = stem_end_position (me);
714   
715   Interval stem_y (y1 <? y2,y2 >? y1);
716
717
718   // dy?
719   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
720
721   if (Grob *hed = support_head (me))
722     {
723       /*
724         must not take ledgers into account.
725        */
726       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
727       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
728
729       y_attach = head_height.linear_combination (y_attach);
730       stem_y[Direction (-d)] += d * y_attach/dy;
731     }
732   
733   if (!invisible_b (me))
734     {
735       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
736         // URG
737         * me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
738       
739       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
740                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
741       mol.add_molecule (ss);
742     }
743
744   if (!get_beam (me) && abs (duration_log (me)) > 2)
745     {
746       Molecule fl = flag (me);
747       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
748       mol.add_molecule (fl);
749     }
750
751   return mol.smobbed_copy ();
752 }
753
754 /*
755   move the stem to right of the notehead if it is up.
756  */
757 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
758 SCM
759 Stem::off_callback (SCM element_smob, SCM)
760 {
761   Grob *me = unsmob_grob (element_smob);
762   
763   Real r=0;
764
765   if (head_count (me) == 0)
766     {
767       return gh_double2scm (0.0);
768     }
769   
770   if (Grob * f = first_head (me))
771     {
772       Interval head_wid = Note_head::head_extent(f, X_AXIS);
773
774       
775       Real attach =0.0;
776
777       if (invisible_b (me))
778         {
779           attach = 0.0;
780         }
781       else
782         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
783
784       Direction d = get_direction (me);
785
786       Real real_attach = head_wid.linear_combination (d * attach);
787
788       r = real_attach;
789
790       /*
791         If not centered: correct for stem thickness.
792        */
793       if (attach)
794         {
795           Real rule_thick
796             = gh_scm2double (me->get_grob_property ("thickness"))
797             * me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
798
799           
800           r += - d * rule_thick * 0.5;
801         }
802     }
803   return gh_double2scm (r);
804 }
805
806 Grob*
807 Stem::get_beam (Grob*me)
808 {
809   SCM b=  me->get_grob_property ("beam");
810   return unsmob_grob (b);
811 }
812
813 Stem_info
814 Stem::get_stem_info (Grob *me)
815 {
816   /* Return cached info if available */
817   SCM scm_info = me->get_grob_property ("stem-info");
818   if (!gh_pair_p (scm_info))
819     {
820       calc_stem_info (me);
821       scm_info = me->get_grob_property ("stem-info");
822     }
823   
824   Stem_info si;
825   si.dir_ = get_grob_direction (me); 
826   si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
827   si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
828   return si;
829 }
830
831
832 /*
833   TODO: add extra space for tremolos!
834  */
835 void
836 Stem::calc_stem_info (Grob *me)
837 {
838   Direction my_dir = get_grob_direction (me);
839   Real staff_space = Staff_symbol_referencer::staff_space (me);
840   Grob *beam = get_beam (me);
841   Real beam_translation = Beam::get_beam_translation (beam);
842   Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
843   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
844
845
846   /* Simple standard stem length */
847   SCM lengths = me->get_grob_property ("beamed-lengths");
848   Real ideal_length =
849     gh_scm2double (robust_list_ref (beam_count - 1,lengths))
850                 
851     * staff_space
852     /* stem only extends to center of beam */
853     - 0.5 * beam_thickness;
854   
855   /* Condition: sane minimum free stem length (chord to beams) */
856   lengths = me->get_grob_property ("beamed-minimum-free-lengths");
857   Real ideal_minimum_free =
858     gh_scm2double (robust_list_ref (beam_count - 1, lengths))
859     * staff_space;
860   
861
862   /* UGH
863      It seems that also for ideal minimum length, we must use
864      the maximum beam count (for this direction):
865      
866      \score{ \notes\relative c''{ [a8 a32] }}
867      
868      must be horizontal. */
869   Real height_of_my_beams = beam_thickness
870     + (beam_count - 1) * beam_translation;
871
872   Real ideal_minimum_length = ideal_minimum_free
873     + height_of_my_beams
874     /* stem only extends to center of beam */
875     - 0.5 * beam_thickness;
876
877   ideal_length = ideal_length >? ideal_minimum_length;
878
879   
880   /* Convert to Y position, calculate for dir == UP */
881   Real note_start =
882     /* staff positions */
883     head_positions (me)[my_dir] * 0.5
884     * my_dir;
885   Real ideal_y = note_start + ideal_length;
886
887
888   /* Conditions for Y position */
889
890   /* Lowest beam of (UP) beam must never be lower than second staffline
891  
892      Reference?
893  
894      Although this (additional) rule is probably correct,
895      I expect that highest beam (UP) should also never be lower
896      than middle staffline, just as normal stems.
897
898      Reference?
899
900      Obviously not for grace beams.
901      
902      Also, not for knees.  Seems to be a good thing. */
903   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
904   bool knee_b = to_boolean (beam->get_grob_property ("knee"));
905   if (!no_extend_b && !knee_b)
906     {
907       /* Highest beam of (UP) beam must never be lower than middle
908          staffline */
909       ideal_y = ideal_y >? 0;
910       /* Lowest beam of (UP) beam must never be lower than second staffline */
911       ideal_y = ideal_y >? (-staff_space
912                             - beam_thickness + height_of_my_beams);
913     }
914
915
916   SCM shorten = beam->get_grob_property ("shorten");
917   if (gh_number_p (shorten))
918     ideal_y -= gh_scm2double (shorten);
919
920   Real minimum_free =
921     gh_scm2double (robust_list_ref
922                    (beam_count - 1,
923                     me->get_grob_property
924                     ("beamed-extreme-minimum-free-lengths")))
925     * staff_space;
926
927   Real minimum_length = minimum_free
928     + height_of_my_beams
929     /* stem only extends to center of beam */
930     - 0.5 * beam_thickness;
931
932  Real minimum_y = note_start + minimum_length;
933   
934   
935   ideal_y *= my_dir;
936   Real shortest_y = minimum_y * my_dir; 
937   
938   me->set_grob_property ("stem-info",
939                          scm_list_n (gh_double2scm (ideal_y),
940                                      gh_double2scm (shortest_y),
941                                      SCM_UNDEFINED));
942 }
943
944 Slice
945 Stem::beam_multiplicity (Grob *stem)
946 {
947   SCM beaming= stem->get_grob_property ("beaming");
948   Slice l = int_list_to_slice (gh_car (beaming));
949   Slice r = int_list_to_slice (gh_cdr (beaming));
950   l.unite (r);
951
952   return l;
953 }
954
955
956 /*
957   these are too many props.
958  */
959 ADD_INTERFACE (Stem,"stem-interface",
960                "A stem",
961                "tremolo-flag french-beaming "
962                "avoid-note-head adjust-if-on-staffline thickness "
963                "stem-info beamed-lengths beamed-minimum-free-lengths "
964                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
965                "duration-log beaming neutral-direction stem-end-position "
966                "note-heads direction length flag-style "
967                "no-stem-extend stroke-style ");
968
969