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