]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/parser.yy (command_element): move clef stuff into Scheme.
[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   String flag_style, staffline_offs;
536
537   SCM flag_style_scm = me->get_grob_property ("flag-style");
538   if (gh_symbol_p (flag_style_scm))
539     {
540       flag_style = (ly_scm2string (scm_symbol_to_string (flag_style_scm)));
541     }
542   else
543     {
544       flag_style = "";
545     }
546   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
547
548   if (String::compare (flag_style, "mensural") == 0)
549     /* Mensural notation: For notes on staff lines, use different
550        flags than for notes between staff lines.  The idea is that
551        flags are always vertically aligned with the staff lines,
552        regardless if the note head is on a staff line or between two
553        staff lines.  In other words, the inner end of a flag always
554        touches a staff line.
555     */
556     {
557       if (adjust)
558         {
559           /* Urrgh!  We have to detect wether this stem ends on a staff
560              line or between two staff lines.  But we can not call
561              stem_end_position(me) or get_default_stem_end_position(me),
562              since this encounters the flag and hence results in an
563              infinite recursion.  However, in pure mensural notation,
564              there are no multiple note heads attached to a single stem,
565              neither is there usually need for using the stem_shorten
566              property (except for 32th and 64th notes, but that is not a
567              problem since the stem length in this case is augmented by
568              an integral multiple of staff_space).  Hence, it should be
569              sufficient to just take the first note head, assume it's
570              the only one, look if it's on a staff line, and select the
571              flag's shape accordingly.  In the worst case, the shape
572              looks slightly misplaced, but that will usually be the
573              programmer's fault (e.g. when trying to attach multiple
574              note heads to a single stem in mensural notation).  */
575
576           /*
577             perhaps the detection whether this correction is needed should
578             happen in a different place  to avoid the recursion.
579             
580             --hwn.
581           */
582           int p = (int)rint (Staff_symbol_referencer::get_position (first_head (me)));
583           staffline_offs = Staff_symbol_referencer::on_staffline (me, p) ?
584             "1" : "0";
585         }
586       else
587         {
588           staffline_offs = "2";
589         }
590     }
591   else
592     {
593       staffline_offs = "";
594     }
595   char dir = (get_direction (me) == UP) ? 'u' : 'd';
596   String font_char =
597     flag_style + to_string (dir) + staffline_offs + to_string (duration_log (me));
598   Font_metric *fm = Font_interface::get_default_font (me);
599   Molecule flag = fm->find_by_name ("flags-" + font_char);
600   if (flag.empty_b ())
601     {
602       me->warning (_f ("flag `%s' not found", font_char));
603     }
604
605   SCM stroke_style_scm = me->get_grob_property ("stroke-style");
606   if (gh_string_p (stroke_style_scm))
607     {
608       String stroke_style = ly_scm2string (stroke_style_scm);
609       if (!stroke_style.empty_b ())
610         {
611           String font_char = to_string (dir) + stroke_style;
612           Molecule stroke = fm->find_by_name ("flags-" + font_char);
613           if (stroke.empty_b ())
614             {
615               me->warning (_f ("flag stroke `%s' not found", font_char));
616             }
617           else
618             {
619               flag.add_molecule (stroke);
620             }
621         }
622     }
623
624   return flag;
625 }
626
627 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
628 SCM
629 Stem::dim_callback (SCM e, SCM ax)
630 {
631   Axis a = (Axis) gh_scm2int (ax);
632   assert (a == X_AXIS);
633   Grob *se = unsmob_grob (e);
634   Interval r (0, 0);
635   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
636     ;   // TODO!
637   else
638     {
639       r = flag (se).extent (X_AXIS);
640     }
641   return ly_interval2scm (r);
642 }
643  
644
645
646 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
647
648 SCM
649 Stem::brew_molecule (SCM smob) 
650 {
651   Grob*me = unsmob_grob (smob);
652   Molecule mol;
653   Direction d = get_direction (me);
654   
655   
656      
657   Real y1;
658
659   /*
660     This is required to avoid stems passing in tablature chords...
661    */
662
663
664   /*
665     TODO: make  the stem start a direction ?
666   */
667   
668
669   
670   if (to_boolean (me->get_grob_property ("avoid-note-head")))
671     {
672       Grob * lh = last_head (me);
673       if (!lh)
674         return SCM_EOL;
675       y1 = Staff_symbol_referencer::get_position (lh);
676     }
677   else
678     {
679       Grob * lh = first_head (me);
680       if (!lh)
681         return SCM_EOL;
682       y1 = Staff_symbol_referencer::get_position (lh);
683     }
684   
685   Real y2 = stem_end_position (me);
686   
687   Interval stem_y (y1 <? y2,y2 >? y1);
688
689
690   // dy?
691   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
692
693   if (Grob *hed = support_head (me))
694     {
695       /*
696         must not take ledgers into account.
697        */
698       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
699       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
700
701       y_attach = head_height.linear_combination (y_attach);
702       stem_y[Direction (-d)] += d * y_attach/dy;
703     }
704   
705   if (!invisible_b (me))
706     {
707       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
708         // URG
709         * me->get_paper ()->get_var ("linethickness");
710       
711       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
712                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
713       mol.add_molecule (ss);
714     }
715
716   if (!get_beam (me) && abs (duration_log (me)) > 2)
717     {
718       Molecule fl = flag (me);
719       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
720       mol.add_molecule (fl);
721     }
722
723   return mol.smobbed_copy ();
724 }
725
726 /*
727   move the stem to right of the notehead if it is up.
728  */
729 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
730 SCM
731 Stem::off_callback (SCM element_smob, SCM)
732 {
733   Grob *me = unsmob_grob (element_smob);
734   
735   Real r=0;
736
737   if (head_count (me) == 0)
738     {
739       return gh_double2scm (0.0);
740     }
741   
742   if (Grob * f = first_head (me))
743     {
744       Interval head_wid = Note_head::head_extent(f, X_AXIS);
745
746       
747       Real attach =0.0;
748
749       if (invisible_b (me))
750         {
751           attach = 0.0;
752         }
753       else
754         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
755
756       Direction d = get_direction (me);
757
758       Real real_attach = head_wid.linear_combination (d * attach);
759
760       r = real_attach;
761
762       /*
763         If not centered: correct for stem thickness.
764        */
765       if (attach)
766         {
767           Real rule_thick
768             = gh_scm2double (me->get_grob_property ("thickness"))
769             * me->get_paper ()->get_var ("linethickness");
770
771           
772           r += - d * rule_thick * 0.5;
773         }
774     }
775   return gh_double2scm (r);
776 }
777
778 Grob*
779 Stem::get_beam (Grob*me)
780 {
781   SCM b=  me->get_grob_property ("beam");
782   return unsmob_grob (b);
783 }
784
785 Stem_info
786 Stem::get_stem_info (Grob *me)
787 {
788   /* Return cached info if available */
789   SCM scm_info = me->get_grob_property ("stem-info");
790   if (!gh_pair_p (scm_info))
791     {
792       calc_stem_info (me);
793       scm_info = me->get_grob_property ("stem-info");
794     }
795   
796   Stem_info si;
797   si.dir_ = Directional_element_interface::get (me); 
798   si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
799   si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
800   return si;
801 }
802
803 void
804 Stem::calc_stem_info (Grob *me)
805 {
806   /* Tab notation feature: make stem end extend out of staff. */
807   SCM up_to_staff = me->get_grob_property ("up-to-staff");
808   if (to_boolean (up_to_staff))
809     {
810       int line_count = Staff_symbol_referencer::line_count (me);
811       Direction dir = get_direction (me);
812       Real ideal_y = dir * (line_count + 1.5);
813       Real shortest_y = ideal_y;
814       
815       me->set_grob_property ("stem-info",
816                              scm_list_n (gh_double2scm (ideal_y),
817                                          gh_double2scm (shortest_y),
818                                          SCM_UNDEFINED));
819       return;
820     }
821
822   Direction my_dir = Directional_element_interface::get (me);
823   Real staff_space = Staff_symbol_referencer::staff_space (me);
824   Grob *beam = get_beam (me);
825   Real beam_translation = Beam::get_beam_translation (beam);
826   Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
827   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
828
829
830   /* Simple standard stem length */
831   Real ideal_length =
832     gh_scm2double (robust_list_ref
833                    (beam_count - 1,
834                     me->get_grob_property ("beamed-lengths")))
835     * staff_space
836     /* stem only extends to center of beam */
837     - 0.5 * beam_thickness;
838
839   
840   /* Condition: sane minimum free stem length (chord to beams) */
841   Real ideal_minimum_free =
842     gh_scm2double (robust_list_ref
843                    (beam_count - 1,
844                     me->get_grob_property ("beamed-minimum-free-lengths")))
845     * staff_space;
846   
847
848   /* UGH
849      It seems that also for ideal minimum length, we must use
850      the maximum beam count (for this direction):
851      
852      \score{ \notes\relative c''{ [a8 a32] }}
853      
854      must be horizontal. */
855   Real height_of_my_beams = beam_thickness
856     + (beam_count - 1) * beam_translation;
857
858   Real ideal_minimum_length = ideal_minimum_free
859     + height_of_my_beams
860     /* stem only extends to center of beam */
861     - 0.5 * beam_thickness;
862
863   ideal_length = ideal_length >? ideal_minimum_length;
864
865   
866   /* Convert to Y position, calculate for dir == UP */
867   Real note_start =
868     /* staff positions */
869     head_positions (me)[my_dir] * 0.5
870     * my_dir;
871   Real ideal_y = note_start + ideal_length;
872
873
874   /* Conditions for Y position */
875
876   /* Lowest beam of (UP) beam must never be lower than second staffline
877  
878      Reference?
879  
880      Although this (additional) rule is probably correct,
881      I expect that highest beam (UP) should also never be lower
882      than middle staffline, just as normal stems.
883
884      Reference?
885
886      Obviously not for grace beams.
887      
888      Also, not for knees.  Seems to be a good thing. */
889   SCM grace = me->get_grob_property ("grace");
890   bool grace_b = to_boolean (grace);
891   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
892   bool knee_b = to_boolean (beam->get_grob_property ("knee"));
893   if (!grace_b && !no_extend_b && !knee_b)
894     {
895       /* Highest beam of (UP) beam must never be lower than middle
896          staffline */
897       ideal_y = ideal_y >? 0;
898       /* Lowest beam of (UP) beam must never be lower than second staffline */
899       ideal_y = ideal_y >? (-staff_space
900                             - beam_thickness + height_of_my_beams);
901     }
902
903
904   SCM shorten = beam->get_grob_property ("shorten");
905   if (gh_number_p (shorten))
906     ideal_y -= gh_scm2double (shorten);
907
908
909   Real minimum_free =
910     gh_scm2double (robust_list_ref
911                    (beam_count - 1,
912                     me->get_grob_property
913                     ("beamed-extreme-minimum-free-lengths")))
914     * staff_space;
915
916   Real minimum_length = minimum_free
917     + height_of_my_beams
918     /* stem only extends to center of beam */
919     - 0.5 * beam_thickness;
920
921   Real minimum_y = note_start + minimum_length;
922   
923   
924   ideal_y *= my_dir;
925   Real shortest_y = minimum_y * my_dir; 
926   
927   me->set_grob_property ("stem-info",
928                          scm_list_n (gh_double2scm (ideal_y),
929                                      gh_double2scm (shortest_y),
930                                      SCM_UNDEFINED));
931 }
932
933 Slice
934 Stem::beam_multiplicity (Grob *stem)
935 {
936   SCM beaming= stem->get_grob_property ("beaming");
937   Slice l = int_list_to_slice (gh_car (beaming));
938   Slice r = int_list_to_slice (gh_cdr (beaming));
939   l.unite (r);
940
941   return l;
942 }
943
944
945 ADD_INTERFACE (Stem,"stem-interface",
946   "A stem",
947   "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");
948
949