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