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