]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
fa3401e2a923348ff7b9dd8b2861b3d8302be108
[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   return ly_interval2scm (iv);
520 }
521
522
523 Molecule
524 Stem::flag (Grob*me)
525 {
526   /* TODO: rename flag-style into something more appropriate,
527    e.g. "stroke-style", maybe with values "" (i.e. no stroke),
528    "single" and "double".  Needs more discussion.
529   */
530   String style, fstyle, staffline_offs;
531   SCM fst = me->get_grob_property ("flag-style");
532   if (gh_string_p (fst))
533     {
534       fstyle = ly_scm2string (fst);
535     }
536
537   SCM st = me->get_grob_property ("style");
538   if (gh_symbol_p (st))
539     {
540       style = (ly_scm2string (scm_symbol_to_string (st)));
541     }
542   else
543     {
544       style = "";
545     }
546   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
547
548   if (String::compare (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           Grob *first = first_head(me);
583           int sz = Staff_symbol_referencer::line_count (me)-1;
584           int p = (int)rint (Staff_symbol_referencer::get_position (first));
585           staffline_offs = (((p ^ sz) & 0x1) == 0) ? "1" : "0";
586         }
587       else
588         {
589           staffline_offs = "2";
590         }
591     }
592   else
593     {
594       staffline_offs = "";
595     }
596   char c = (get_direction (me) == UP) ? 'u' : 'd';
597   String index_string
598     = String ("flags-") + style + to_string (c) + staffline_offs + to_string (duration_log (me));
599   Molecule m
600     = Font_interface::get_default_font (me)->find_by_name (index_string);
601   if (!fstyle.empty_b ())
602     m.add_molecule (Font_interface::get_default_font (me)->find_by_name (String ("flags-") + to_string (c) + fstyle));
603   return m;
604 }
605
606 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
607 SCM
608 Stem::dim_callback (SCM e, SCM ax)
609 {
610   Axis a = (Axis) gh_scm2int (ax);
611   assert (a == X_AXIS);
612   Grob *se = unsmob_grob (e);
613   Interval r (0, 0);
614   if (unsmob_grob (se->get_grob_property ("beam")) || abs (duration_log (se)) <= 2)
615     ;   // TODO!
616   else
617     {
618       r = flag (se).extent (X_AXIS);
619     }
620   return ly_interval2scm (r);
621 }
622  
623
624
625 MAKE_SCHEME_CALLBACK (Stem,brew_molecule,1);
626
627 SCM
628 Stem::brew_molecule (SCM smob) 
629 {
630   Grob*me = unsmob_grob (smob);
631   Molecule mol;
632   Direction d = get_direction (me);
633   
634   
635      
636   Real y1;
637
638   /*
639     This is required to avoid stems passing in tablature chords...
640    */
641
642
643   /*
644     TODO: make  the stem start a direction ?
645   */
646   
647
648   
649   if (to_boolean (me->get_grob_property ("avoid-note-head")))
650     {
651       Grob * lh = last_head (me);
652       if (!lh)
653         return SCM_EOL;
654       y1 = Staff_symbol_referencer::get_position (lh);
655     }
656   else
657     {
658       Grob * lh = first_head (me);
659       if (!lh)
660         return SCM_EOL;
661       y1 = Staff_symbol_referencer::get_position (lh);
662     }
663   
664   Real y2 = stem_end_position (me);
665   
666   Interval stem_y (y1 <? y2,y2 >? y1);
667
668
669   // dy?
670   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
671
672   if (Grob *hed = support_head (me))
673     {
674       /*
675         must not take ledgers into account.
676        */
677       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
678       Real y_attach = Note_head::stem_attachment_coordinate ( hed, Y_AXIS);
679
680       y_attach = head_height.linear_combination (y_attach);
681       stem_y[Direction (-d)] += d * y_attach/dy;
682     }
683   
684   if (!invisible_b (me))
685     {
686       Real stem_width = gh_scm2double (me->get_grob_property ("thickness"))
687         // URG
688         * me->get_paper ()->get_var ("linethickness");
689       
690       Molecule ss =Lookup::filledbox (Box (Interval (-stem_width/2, stem_width/2),
691                                            Interval (stem_y[DOWN]*dy, stem_y[UP]*dy)));
692       mol.add_molecule (ss);
693     }
694
695   if (!get_beam (me) && abs (duration_log (me)) > 2)
696     {
697       Molecule fl = flag (me);
698       fl.translate_axis (stem_y[d]*dy, Y_AXIS);
699       mol.add_molecule (fl);
700     }
701
702   return mol.smobbed_copy ();
703 }
704
705 /*
706   move the stem to right of the notehead if it is up.
707  */
708 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
709 SCM
710 Stem::off_callback (SCM element_smob, SCM)
711 {
712   Grob *me = unsmob_grob (element_smob);
713   
714   Real r=0;
715
716   if (head_count (me) == 0)
717     {
718       return gh_double2scm (0.0);
719     }
720   
721   if (Grob * f = first_head (me))
722     {
723       Interval head_wid = Note_head::head_extent(f, X_AXIS);
724
725       
726       Real attach =0.0;
727
728       if (invisible_b (me))
729         {
730           attach = 0.0;
731         }
732       else
733         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
734
735       Direction d = get_direction (me);
736
737       Real real_attach = head_wid.linear_combination (d * attach);
738
739       r = real_attach;
740
741       /*
742         If not centered: correct for stem thickness.
743        */
744       if (attach)
745         {
746           Real rule_thick
747             = gh_scm2double (me->get_grob_property ("thickness"))
748             * me->get_paper ()->get_var ("linethickness");
749
750           
751           r += - d * rule_thick * 0.5;
752         }
753     }
754   return gh_double2scm (r);
755 }
756
757 Grob*
758 Stem::get_beam (Grob*me)
759 {
760   SCM b=  me->get_grob_property ("beam");
761   return unsmob_grob (b);
762 }
763
764 Stem_info
765 Stem::get_stem_info (Grob *me)
766 {
767   /* Return cached info if available */
768   SCM scm_info = me->get_grob_property ("stem-info");
769   if (!gh_pair_p (scm_info))
770     {
771       calc_stem_info (me);
772       scm_info = me->get_grob_property ("stem-info");
773     }
774   
775   Stem_info si;
776   si.dir_ = Directional_element_interface::get (me); 
777   si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
778   si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
779   return si;
780 }
781
782 void
783 Stem::calc_stem_info (Grob *me)
784 {
785   /* Tab notation feature: make stem end extend out of staff. */
786   SCM up_to_staff = me->get_grob_property ("up-to-staff");
787   if (to_boolean (up_to_staff))
788     {
789       int line_count = Staff_symbol_referencer::line_count (me);
790       Direction dir = get_direction (me);
791       Real ideal_y = dir * (line_count + 1.5);
792       Real shortest_y = ideal_y;
793       
794       me->set_grob_property ("stem-info",
795                              scm_list_n (gh_double2scm (ideal_y),
796                                          gh_double2scm (shortest_y),
797                                          SCM_UNDEFINED));
798       return;
799     }
800
801   Direction my_dir = Directional_element_interface::get (me);
802   Real staff_space = Staff_symbol_referencer::staff_space (me);
803   Grob *beam = get_beam (me);
804   Real beam_translation = Beam::get_beam_translation (beam);
805   Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
806   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
807
808
809   /* Simple standard stem length */
810   Real ideal_length =
811     gh_scm2double (robust_list_ref
812                    (beam_count - 1,
813                     me->get_grob_property ("beamed-lengths")))
814     * staff_space
815     /* stem only extends to center of beam */
816     - 0.5 * beam_thickness;
817
818   
819   /* Condition: sane minimum free stem length (chord to beams) */
820   Real ideal_minimum_free =
821     gh_scm2double (robust_list_ref
822                    (beam_count - 1,
823                     me->get_grob_property ("beamed-minimum-free-lengths")))
824     * staff_space;
825   
826   int my_beam_count = Stem::beam_multiplicity (me).length () + 1;
827 #if 0
828   Real height_of_my_beams = beam_thickness
829     + (my_beam_count - 1) * beam_translation;
830 #else
831   /* UGH
832      It seems that also for ideal minimum length, we must use
833      the maximum beam count (for this direction):
834      
835      \score{ \notes\relative c''{ [a8 a32] }}
836      
837      must be horizontal. */
838   Real height_of_my_beams = beam_thickness
839     + (beam_count - 1) * beam_translation;
840 #endif
841
842   Real ideal_minimum_length = ideal_minimum_free
843     + height_of_my_beams
844     /* stem only extends to center of beam */
845     - 0.5 * beam_thickness;
846
847   ideal_length = ideal_length >? ideal_minimum_length;
848
849   
850   /* Convert to Y position, calculate for dir == UP */
851   Real note_start =
852     /* staff positions */
853     head_positions (me)[my_dir] * 0.5
854     * my_dir;
855   Real ideal_y = note_start + ideal_length;
856
857
858   /* Conditions for Y position */
859
860   /* Lowest beam of (UP) beam must never be lower than second staffline
861  
862      Reference?
863  
864      Although this (additional) rule is probably correct,
865      I expect that highest beam (UP) should also never be lower
866      than middle staffline, just as normal stems.
867
868      Reference?
869
870      Obviously not for grace beams.
871      
872      Also, not for knees.  Seems to be a good thing. */
873   SCM grace = me->get_grob_property ("grace");
874   bool grace_b = to_boolean (grace);
875   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
876   bool knee_b = to_boolean (beam->get_grob_property ("knee"));
877   if (!grace_b && !no_extend_b && !knee_b)
878     {
879       /* Highest beam of (UP) beam must never be lower than middle
880          staffline */
881       ideal_y = ideal_y >? 0;
882       /* Lowest beam of (UP) beam must never be lower than second staffline */
883       ideal_y = ideal_y >? (-staff_space
884                             - beam_thickness + height_of_my_beams);
885     }
886
887
888   SCM shorten = beam->get_grob_property ("shorten");
889   if (gh_number_p (shorten))
890     ideal_y -= gh_scm2double (shorten);
891
892
893   Real minimum_free =
894     gh_scm2double (robust_list_ref
895                    (beam_count - 1,
896                     me->get_grob_property
897                     ("beamed-extreme-minimum-free-lengths")))
898     * staff_space;
899
900   Real minimum_length = minimum_free
901     + height_of_my_beams
902     /* stem only extends to center of beam */
903     - 0.5 * beam_thickness;
904
905   Real minimum_y = note_start + minimum_length;
906   
907   
908   ideal_y *= my_dir;
909   Real shortest_y = minimum_y * my_dir; 
910   
911   me->set_grob_property ("stem-info",
912                          scm_list_n (gh_double2scm (ideal_y),
913                                      gh_double2scm (shortest_y),
914                                      SCM_UNDEFINED));
915 }
916
917 Slice
918 Stem::beam_multiplicity (Grob *stem)
919 {
920   SCM beaming= stem->get_grob_property ("beaming");
921   Slice l = int_list_to_slice (gh_car (beaming));
922   Slice r = int_list_to_slice (gh_cdr (beaming));
923   l.unite (r);
924
925   return l;
926 }
927
928
929 ADD_INTERFACE (Stem,"stem-interface",
930   "A stem",
931   "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 style no-stem-extend flag-style");
932
933