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