]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
*** empty log message ***
[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--2004 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 "output-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "stencil.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 #include "stem-tremolo.hh"
37
38 void
39 Stem::set_beaming (Grob *me, int beam_count, Direction d)
40 {
41   SCM pair = me->get_property ("beaming");
42
43   if (!scm_is_pair (pair))
44     {
45       pair = scm_cons (SCM_EOL, SCM_EOL);
46       me->set_property ("beaming", pair);
47     }
48
49   SCM lst = index_get_cell (pair, d);
50   for (int i = 0; i < beam_count; i++)
51     lst = scm_cons (scm_int2num (i), lst);
52   index_set_cell (pair, d, lst);
53 }
54
55 int
56 Stem::get_beaming (Grob *me, Direction d)
57 {
58   SCM pair = me->get_property ("beaming");
59   if (!scm_is_pair (pair))
60     return 0;
61
62   SCM lst = index_get_cell (pair, d);
63   return scm_ilength (lst);
64 }
65
66
67 Interval
68 Stem::head_positions (Grob *me)
69 {
70   if (head_count (me))
71     {
72       Drul_array<Grob*> e (extremal_heads (me));
73       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
74                        Staff_symbol_referencer::get_position (e[UP]));
75     }
76   return Interval ();
77 }
78
79 Real
80 Stem::chord_start_y (Grob *me)
81 {
82   Interval hp = head_positions (me);
83   if (!hp.is_empty ())
84     return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
85       * 0.5;
86   return 0;
87 }
88
89 Real
90 Stem::stem_end_position (Grob *me)
91 {
92   SCM p = me->get_property ("stem-end-position");
93   Real pos;
94   if (!scm_is_number (p))
95     {
96       pos = get_default_stem_end_position (me);
97       me->set_property ("stem-end-position", scm_make_real (pos));
98     }
99   else
100     pos = scm_to_double (p);
101
102   return pos;
103 }
104
105 Direction
106 Stem::get_direction (Grob *me)
107 {
108   Direction d = get_grob_direction (me);
109
110   if (!d)
111     {
112       d = get_default_dir (me);
113       // urg, AAARGH!
114       set_grob_direction (me, d);
115     }
116   return d;
117 }
118
119 void
120 Stem::set_stemend (Grob *me, Real se)
121 {
122   // todo: margins
123   Direction d = get_direction (me);
124
125   if (d && d * head_positions (me)[get_direction (me)] >= se*d)
126     me->warning (_ ("Weird stem size; check for narrow beams"));
127
128   me->set_property ("stem-end-position", scm_make_real (se));
129 }
130
131 /* Note head that determines hshift for upstems
132    WARNING: triggers direction  */
133 Grob *
134 Stem::support_head (Grob *me)
135 {
136   if (head_count (me) == 1)
137     /* UGH. */
138     return unsmob_grob (scm_car (me->get_property ("note-heads")));
139   return first_head (me);
140 }
141
142 int
143 Stem::head_count (Grob *me)
144 {
145   return Pointer_group_interface::count (me, "note-heads");
146 }
147
148 /* The note head which forms one end of the stem.
149    WARNING: triggers direction  */
150 Grob *
151 Stem::first_head (Grob *me)
152 {
153   Direction d = get_direction (me);
154   if (d)
155     return extremal_heads (me)[-d];
156   return 0;
157 }
158
159 /* The note head opposite to the first head.  */
160 Grob *
161 Stem::last_head (Grob *me)
162 {
163   Direction d = get_direction (me);
164   if (d)
165     return extremal_heads (me)[d];
166   return 0;  
167 }
168
169 /* START is part where stem reaches `last' head.
170
171 This function returns a drul with (bottom-head, top-head). 
172
173 */
174 Drul_array<Grob*>
175 Stem::extremal_heads (Grob *me)
176 {
177   const int inf = 1000000;
178   Drul_array<int> extpos;
179   extpos[DOWN] = inf;
180   extpos[UP] = -inf;
181
182   Drul_array<Grob *> exthead;
183   exthead[LEFT] = exthead[RIGHT] =0;
184
185   for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
186        s = scm_cdr (s))
187     {
188       Grob *n = unsmob_grob (scm_car (s));
189       int p = Staff_symbol_referencer::get_rounded_position (n);
190
191       Direction d = LEFT;
192       do
193         {
194           if (d * p > d * extpos[d])
195             {
196               exthead[d] = n;
197               extpos[d] = p;
198             }
199         } while (flip (&d) != DOWN);
200     }
201   return exthead;
202 }
203
204 static int
205 icmp (int const &a, int const &b)
206 {
207   return a - b;
208 }
209
210 /* The positions, in ascending order.  */
211 Array<int>
212 Stem::note_head_positions (Grob *me)
213 {
214   Array<int> ps ;
215   for (SCM s = me->get_property ("note-heads"); scm_is_pair (s);
216        s = scm_cdr (s))
217     {
218       Grob *n = unsmob_grob (scm_car (s));
219       int p = Staff_symbol_referencer::get_rounded_position (n);
220
221       ps.push (p);
222     }
223
224   ps.sort (icmp);
225   return ps;
226 }
227
228 void
229 Stem::add_head (Grob *me, Grob *n)
230 {
231   n->set_property ("stem", me->self_scm ());
232   n->add_dependency (me);
233
234   /* TODO: why not store Rest pointers? */
235   if (Note_head::has_interface (n))
236     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
237 }
238
239 bool
240 Stem::is_invisible (Grob *me)
241 {
242   return !(head_count (me)
243            && scm_to_int (me->get_property ("duration-log")) >= 1);
244 }
245
246 Direction
247 Stem::get_default_dir (Grob *me)
248 {
249   int staff_center = 0;
250   Interval hp = head_positions (me);
251   if (hp.is_empty ())
252     return CENTER;
253
254   int udistance = (int) (UP * hp[UP] - staff_center);
255   int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
256
257   if (sign (ddistance - udistance))
258     return Direction (sign (ddistance - udistance));
259
260   return to_dir (me->get_property ("neutral-direction"));
261 }
262
263 Real
264 Stem::get_default_stem_end_position (Grob *me)
265 {
266   Real ss = Staff_symbol_referencer::staff_space (me);
267   int durlog = duration_log (me);
268   SCM s;
269   Array<Real> a;
270
271   /* WARNING: IN HALF SPACES */
272   Real length = 7;
273   SCM scm_len = me->get_property ("length");
274   if (scm_is_number (scm_len))
275     length = scm_to_double (scm_len);
276   else
277     {
278       s = me->get_property ("lengths");
279       if (scm_is_pair (s))
280         length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
281     }
282
283   /* URGURGURG
284      'set-default-stemlen' sets direction too.   */
285   Direction dir = get_direction (me);
286   if (!dir)
287     {
288       dir = get_default_dir (me);
289       set_grob_direction (me, dir);
290     }
291
292   /* Stems in unnatural (forced) direction should be shortened,
293      according to [Roush & Gourlay] */
294   Interval hp = head_positions (me);
295   if (dir && dir * hp[dir] >= 0)
296     {
297       SCM sshorten = me->get_property ("stem-shorten");
298       SCM scm_shorten = scm_is_pair (sshorten) ?
299         robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
300       Real shorten = 2* robust_scm2double (scm_shorten,0);
301
302       /* On boundary: shorten only half */
303       if (abs (head_positions (me)[dir]) <= 1)
304         shorten *= 0.5;
305
306       length -= shorten;
307     }
308
309   /* Tremolo stuff.  */
310   Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
311   if (t_flag && !unsmob_grob (me->get_property ("beam")))
312     {
313       /* Crude hack: add extra space if tremolo flag is there.
314
315         We can't do this for the beam, since we get into a loop
316         (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
317
318       Real minlen = 1.0
319         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length  ()
320         / ss;
321
322       if (durlog >= 3)
323         {
324           Interval flag_ext = flag (me).extent (Y_AXIS);
325           if (!flag_ext.is_empty ())
326             minlen += 2 * flag_ext.length () / ss;
327
328           /* The clash is smaller for down stems (since the tremolo is
329              angled up.) */
330           if (dir == DOWN)
331             minlen -= 1.0;
332         }
333       length = length >? (minlen + 1.0);
334     }
335
336   Real st = dir ? hp[dir] + dir * length : 0;
337
338   /* TODO: change name  to extend-stems to staff/center/'()  */
339   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
340   if (!no_extend_b && dir * st < 0)
341     st = 0.0;
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   if (!get_beam (me) && dir == UP
347       && durlog > 2)
348     {
349       Grob * closest_to_flag = extremal_heads (me)[dir];
350       Grob * dots = closest_to_flag
351         ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
352
353       if (dots)
354         {
355           Real dp = Staff_symbol_referencer::get_position (dots);
356           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
357
358           /* Very gory: add myself to the X-support of the parent,
359              which should be a dot-column. */
360           if (dir * (st + flagy -  dp) < 0.5)
361             {
362               Grob *par = dots->get_parent (X_AXIS);
363
364               if (Dot_column::has_interface (par))
365                 {
366                   Side_position_interface::add_support (par, me);
367
368                   /* TODO: apply some better logic here. The flag is
369                      curved inwards, so this will typically be too
370                      much. */
371                 }
372             }
373         }
374     }
375   return st;
376 }
377
378 /* The log of the duration (Number of hooks on the flag minus two)  */
379 int
380 Stem::duration_log (Grob *me)
381 {
382   SCM s = me->get_property ("duration-log");
383   return (scm_is_number (s)) ? scm_to_int (s) : 2;
384 }
385
386 void
387 Stem::position_noteheads (Grob *me)
388 {
389   if (!head_count (me))
390     return;
391
392   Link_array<Grob> heads =
393     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-heads");
394
395   heads.sort (compare_position);
396   Direction dir = get_direction (me);
397
398   if (dir < 0)
399     heads.reverse ();
400
401   Real thick = thickness (me);
402
403   Grob *hed = support_head (me);
404   Real w = hed->extent (hed, X_AXIS)[dir];
405   for (int i = 0; i < heads.size (); i++)
406     heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
407                               X_AXIS);
408
409   bool parity = true;
410   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
411   for (int i = 1; i < heads.size (); i ++)
412     {
413       Real p = Staff_symbol_referencer::get_position (heads[i]);
414       Real dy =fabs (lastpos- p);
415
416       /*
417        dy should always be 0.5, 0.0, 1.0, but provide safety margin
418        for rounding errors.
419       */
420       if (dy < 1.1)             
421         {
422           if (parity)
423             {
424               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
425
426               Direction d = get_direction (me);
427               /*
428                 Reversed head should be shifted ell-thickness, but this
429                 looks too crowded, so we only shift ell-0.5*thickness.
430
431                 This leads to assymetry: Normal heads overlap the
432                 stem 100% whereas reversed heads only overlaps the
433                 stem 50%
434               */
435
436               Real reverse_overlap = 0.5;
437               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
438                                         X_AXIS);
439
440               if (is_invisible (me))
441                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
442                                           X_AXIS);
443
444              /* TODO:
445                 
446               For some cases we should kern some more: when the
447               distance between the next or prev note is too large, we'd
448               get large white gaps, eg.
449         
450                |
451               X|
452                |X  <- kern this.
453                |
454               X
455         
456               */
457             }
458           parity = !parity;
459         }
460       else
461         parity = true;
462
463       lastpos = int (p);
464     }
465 }
466
467 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
468 SCM
469 Stem::before_line_breaking (SCM smob)
470 {
471   Grob *me = unsmob_grob (smob);
472
473   /*
474     Do the calculations for visible stems, but also for invisible stems
475     with note heads (i.e. half notes.)
476    */
477   if (head_count (me))
478     {
479       stem_end_position (me);   // ugh. Trigger direction calc.
480       position_noteheads (me);
481     }
482   else
483     me->set_property ("print-function", SCM_EOL);
484   
485   return SCM_UNSPECIFIED;
486 }
487
488 /*
489   ugh.
490   When in a beam with tuplet brackets, brew_mol is called early,
491   caching a wrong value.
492  */
493 MAKE_SCHEME_CALLBACK (Stem, height, 2);
494 SCM
495 Stem::height (SCM smob, SCM ax)
496 {
497   Axis a = (Axis)scm_to_int (ax);
498   Grob *me = unsmob_grob (smob);
499   assert (a == Y_AXIS);
500
501   /*
502     ugh. - this dependency should be automatic.
503   */
504   Grob *beam= get_beam (me);
505   if (beam)
506     {
507       Beam::after_line_breaking (beam->self_scm ());
508     }
509   
510
511   SCM mol = me->get_uncached_stencil ();
512   Interval iv;
513   if (mol != SCM_EOL)
514     iv = unsmob_stencil (mol)->extent (a);
515   if (Grob *b =get_beam (me))
516     {
517       Direction d = get_direction (me);
518       iv[d] += d * Beam::get_thickness (b) * 0.5 ;
519     }
520
521   return ly_interval2scm (iv);
522 }
523
524
525 Stencil
526 Stem::flag (Grob *me)
527 {
528   /* TODO: maybe property stroke-style should take different values,
529      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
530      '() or "grace").  */
531   String flag_style;
532
533   SCM flag_style_scm = me->get_property ("flag-style");
534   if (scm_is_symbol (flag_style_scm))
535     flag_style = ly_symbol2string (flag_style_scm);
536   
537   if (flag_style == "no-flag")
538     return Stencil ();
539
540   bool adjust = true;
541
542   String staffline_offs;
543   if (String::compare (flag_style, "mensural") == 0)
544     /* Mensural notation: For notes on staff lines, use different
545        flags than for notes between staff lines.  The idea is that
546        flags are always vertically aligned with the staff lines,
547        regardless if the note head is on a staff line or between two
548        staff lines.  In other words, the inner end of a flag always
549        touches a staff line.
550     */
551     {
552       if (adjust)
553         {
554           int p = (int)(rint (stem_end_position (me)));
555           staffline_offs =
556             Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
557         }
558       else
559         {
560           staffline_offs = "2";
561         }
562     }
563   else
564     {
565       staffline_offs = "";
566     }
567
568   char dir = (get_direction (me) == UP) ? 'u' : 'd';
569   String font_char = flag_style
570     + to_string (dir) + staffline_offs + to_string (duration_log (me));
571   Font_metric *fm = Font_interface::get_default_font (me);
572   Stencil flag = fm->find_by_name ("flags-" + font_char);
573   if (flag.is_empty ())
574     me->warning (_f ("flag `%s' not found", font_char));
575
576   SCM stroke_style_scm = me->get_property ("stroke-style");
577   if (scm_is_string (stroke_style_scm))
578     {
579       String stroke_style = ly_scm2string (stroke_style_scm);
580       if (!stroke_style.is_empty ())
581         {
582           String font_char = to_string (dir) + stroke_style;
583           Stencil stroke = fm->find_by_name ("flags-" + font_char);
584           if (stroke.is_empty ())
585             me->warning (_f ("flag stroke `%s' not found", font_char));
586           else
587             flag.add_stencil (stroke);
588         }
589     }
590
591   return flag;
592 }
593
594 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
595 SCM
596 Stem::dim_callback (SCM e, SCM ax)
597 {
598   Axis a = (Axis) scm_to_int (ax);
599   assert (a == X_AXIS);
600   Grob *me = unsmob_grob (e);
601
602   Interval r;
603   
604   if (is_invisible (me))
605     {
606       r.set_empty ();
607     }    
608   else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
609     {
610       r = Interval (-1,1);
611       r *= thickness (me)/2; 
612     }
613   else
614     {
615       r = flag (me).extent (X_AXIS)
616         + thickness (me)/2;
617     }  
618   return ly_interval2scm (r);
619 }
620
621 Real
622 Stem::thickness (Grob *me)
623 {
624   return scm_to_double (me->get_property ("thickness"))
625     * Staff_symbol_referencer::line_thickness (me);
626 }
627
628 MAKE_SCHEME_CALLBACK (Stem, print, 1);
629 SCM
630 Stem::print (SCM smob)
631 {
632   Grob *me = unsmob_grob (smob);
633   Stencil mol;
634   Direction d = get_direction (me);
635
636   /* TODO: make the stem start a direction ?
637      This is required to avoid stems passing in tablature chords.  */
638   Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
639     ? last_head (me) :  lh = first_head (me);
640
641   if (!lh)
642     return SCM_EOL;
643
644   if (is_invisible (me))
645     return SCM_EOL;
646
647   Real y1 = Staff_symbol_referencer::get_position (lh);
648   Real y2 = stem_end_position (me);
649
650   Interval stem_y (y1 <? y2,y2 >? y1);
651
652   // dy?
653   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
654
655   if (Grob *hed = support_head (me))
656     {
657       /*
658         must not take ledgers into account.
659        */
660       Interval head_height = hed->extent (hed,Y_AXIS);
661       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
662
663       y_attach = head_height.linear_combination (y_attach);
664       stem_y[Direction (-d)] += d * y_attach/dy;
665     }
666
667
668   // URG
669   Real stem_width = thickness (me);
670   Real blot =
671         me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
672
673   Box b = Box (Interval (-stem_width/2, stem_width/2),
674                Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
675
676   Stencil ss = Lookup::round_filled_box (b, blot);
677   mol.add_stencil (ss);
678
679   if (!get_beam (me) && abs (duration_log (me)) > 2)
680     {
681       Stencil fl = flag (me);
682       fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
683       fl.translate_axis (stem_width/2, X_AXIS);
684       mol.add_stencil (fl);
685     }
686
687   return mol.smobbed_copy ();
688 }
689
690 /*
691   move the stem to right of the notehead if it is up.
692  */
693 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
694 SCM
695 Stem::off_callback (SCM element_smob, SCM)
696 {
697   Grob *me = unsmob_grob (element_smob);
698   Real r = 0.0;
699   
700   if (head_count (me))
701     if (Grob *f = first_head (me))
702       {
703         Interval head_wid = f->extent (f, X_AXIS);
704         Real attach = 0.0;
705         
706         if (is_invisible (me))
707           attach = 0.0;
708         else
709         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
710         
711         Direction d = get_direction (me);
712         Real real_attach = head_wid.linear_combination (d * attach);
713         r = real_attach;
714         
715         /* If not centered: correct for stem thickness.  */
716         if (attach)
717           {
718             Real rule_thick = thickness (me);
719             r += - d * rule_thick * 0.5;
720           }
721       }
722   return scm_make_real (r);
723 }
724
725 Spanner *
726 Stem::get_beam (Grob *me)
727 {
728   SCM b = me->get_property ("beam");
729   return dynamic_cast<Spanner*> (unsmob_grob (b));
730 }
731
732 Stem_info
733 Stem::get_stem_info (Grob *me)
734 {
735   /* Return cached info if available */
736   SCM scm_info = me->get_property ("stem-info");
737   if (!scm_is_pair (scm_info))
738     {
739       calc_stem_info (me);
740       scm_info = me->get_property ("stem-info");
741     }
742
743   Stem_info si;
744   si.dir_ = get_grob_direction (me);
745   si.ideal_y_ = scm_to_double (scm_car (scm_info));
746   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
747   return si;
748 }
749
750
751 /* TODO: add extra space for tremolos!  */
752 void
753 Stem::calc_stem_info (Grob *me)
754 {
755   Direction my_dir = get_grob_direction (me);
756
757   if (!my_dir)
758     {
759       programming_error ("No stem dir set?");
760       my_dir  = UP;
761     }
762
763   Real staff_space = Staff_symbol_referencer::staff_space (me);
764   Grob *beam = get_beam (me);
765   Real beam_translation = Beam::get_beam_translation (beam);
766   Real beam_thickness = Beam::get_thickness (beam);
767   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
768
769
770   /* Simple standard stem length */
771   SCM lengths = me->get_property ("beamed-lengths");
772   Real ideal_length =
773     scm_to_double (robust_list_ref (beam_count - 1,lengths))
774                 
775     * staff_space
776     /* stem only extends to center of beam
777     */
778     - 0.5 * beam_thickness
779     ;
780
781   /* Condition: sane minimum free stem length (chord to beams) */
782   lengths = me->get_property ("beamed-minimum-free-lengths");
783   Real ideal_minimum_free =
784     scm_to_double (robust_list_ref (beam_count - 1, lengths))
785     * staff_space;
786
787
788   /* UGH
789      It seems that also for ideal minimum length, we must use
790      the maximum beam count (for this direction):
791
792      \score{ \notes\relative c''{ [a8 a32] }}
793
794      must be horizontal. */
795   Real height_of_my_beams = beam_thickness
796     + (beam_count - 1) * beam_translation;
797
798   Real ideal_minimum_length = ideal_minimum_free
799     + height_of_my_beams
800     /* stem only extends to center of beam */
801     - 0.5 * beam_thickness;
802
803   ideal_length = ideal_length >? ideal_minimum_length;
804
805   /* Convert to Y position, calculate for dir == UP */
806   Real note_start =
807     /* staff positions */
808     head_positions (me)[my_dir] * 0.5
809     * my_dir * staff_space;
810   Real ideal_y = note_start + ideal_length;
811
812
813   /* Conditions for Y position */
814
815   /* Lowest beam of (UP) beam must never be lower than second staffline
816
817      Reference?
818
819      Although this (additional) rule is probably correct,
820      I expect that highest beam (UP) should also never be lower
821      than middle staffline, just as normal stems.
822
823      Reference?
824
825      Obviously not for grace beams.
826
827      Also, not for knees.  Seems to be a good thing. */
828   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
829   bool is_knee = to_boolean (beam->get_property ("knee"));
830   if (!no_extend_b && !is_knee)
831     {
832       /* Highest beam of (UP) beam must never be lower than middle
833          staffline */
834       ideal_y = ideal_y >? 0;
835       /* Lowest beam of (UP) beam must never be lower than second staffline */
836       ideal_y = ideal_y >? (-staff_space
837                             - beam_thickness + height_of_my_beams);
838     }
839
840
841   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
842
843   Real minimum_free =
844     scm_to_double (robust_list_ref
845                    (beam_count - 1,
846                     me->get_property
847                     ("beamed-extreme-minimum-free-lengths")))
848     * staff_space;
849
850   Real minimum_length = minimum_free
851     + height_of_my_beams
852     /* stem only extends to center of beam */
853     - 0.5 * beam_thickness;
854
855   ideal_y *= my_dir;
856   Real minimum_y = note_start + minimum_length;
857   Real shortest_y = minimum_y * my_dir;
858
859   me->set_property ("stem-info",
860                     scm_list_2 (scm_make_real (ideal_y),
861                                 scm_make_real (shortest_y)));
862 }
863
864 Slice
865 Stem::beam_multiplicity (Grob *stem)
866 {
867   SCM beaming= stem->get_property ("beaming");
868   Slice le = int_list_to_slice (scm_car (beaming));
869   Slice ri = int_list_to_slice (scm_cdr (beaming));
870   le.unite (ri);
871   return le;
872 }
873
874
875 /* FIXME:  Too many properties  */
876 ADD_INTERFACE (Stem, "stem-interface",
877                "The stem represent the graphical stem.  "
878                "In addition, it internally connects note heads, beams and"
879                "tremolos. "
880                "Rests and whole notes have invisible stems.",
881                "tremolo-flag french-beaming "
882                "avoid-note-head thickness "
883                "stem-info beamed-lengths beamed-minimum-free-lengths "
884                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
885                "duration-log beaming neutral-direction stem-end-position "
886                "note-heads direction length flag-style "
887                "no-stem-extend stroke-style");
888
889 /****************************************************************/
890
891 Stem_info::Stem_info ()
892 {
893   ideal_y_ = shortest_y_ = 0;
894   dir_ = CENTER;
895 }
896
897 void
898 Stem_info::scale (Real x)
899 {
900   ideal_y_ *= x;
901   shortest_y_ *= x;
902 }