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