]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/new-slur.cc: Resolve conflicts.
[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 (!ly_c_pair_p (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 (!ly_c_pair_p (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 (!ly_c_number_p (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 = ly_scm2double (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 (ly_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"); ly_c_pair_p (s);
186        s = ly_cdr (s))
187     {
188       Grob *n = unsmob_grob (ly_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"); ly_c_pair_p (s);
216        s = ly_cdr (s))
217     {
218       Grob *n = unsmob_grob (ly_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            && ly_scm2int (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 (ly_c_number_p (scm_len))
275     length = ly_scm2double (scm_len);
276   else
277     {
278       s = me->get_property ("lengths");
279       if (ly_c_pair_p (s))
280         length = 2 * ly_scm2double (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 = ly_c_pair_p (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 (ly_c_number_p (s)) ? ly_scm2int (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 = Note_head::head_extent (hed,X_AXIS)[dir];
405   for (int i = 0; i < heads.size (); i++)
406     heads[i]->translate_axis (w - Note_head::head_extent (heads[i],
407                                                           X_AXIS)[dir],
408                               X_AXIS);
409
410   bool parity = true;
411   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
412   for (int i = 1; i < heads.size (); i ++)
413     {
414       Real p = Staff_symbol_referencer::get_position (heads[i]);
415       Real dy =fabs (lastpos- p);
416
417       /*
418        dy should always be 0.5, 0.0, 1.0, but provide safety margin
419        for rounding errors.
420       */
421       if (dy < 1.1)             
422         {
423           if (parity)
424             {
425               Real ell = Note_head::head_extent (heads[i], X_AXIS).length ();
426
427               Direction d = get_direction (me);
428               /*
429                 Reversed head should be shifted ell-thickness, but this
430                 looks too crowded, so we only shift ell-0.5*thickness.
431
432                 This leads to assymetry: Normal heads overlap the
433                 stem 100% whereas reversed heads only overlaps the
434                 stem 50%
435               */
436
437               Real reverse_overlap = 0.5;
438               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
439                                         X_AXIS);
440
441               if (is_invisible (me))
442                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
443                                           X_AXIS);
444
445              /* TODO:
446                 
447               For some cases we should kern some more: when the
448               distance between the next or prev note is too large, we'd
449               get large white gaps, eg.
450         
451                |
452               X|
453                |X  <- kern this.
454                |
455               X
456         
457               */
458             }
459           parity = !parity;
460         }
461       else
462         parity = true;
463
464       lastpos = int (p);
465     }
466 }
467
468 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
469 SCM
470 Stem::before_line_breaking (SCM smob)
471 {
472   Grob *me = unsmob_grob (smob);
473
474   /*
475     Do the calculations for visible stems, but also for invisible stems
476     with note heads (i.e. half notes.)
477    */
478   if (head_count (me))
479     {
480       stem_end_position (me);   // ugh. Trigger direction calc.
481       position_noteheads (me);
482     }
483   else
484     me->set_property ("print-function", SCM_EOL);
485   
486   return SCM_UNSPECIFIED;
487 }
488
489 /*
490   ugh.
491   When in a beam with tuplet brackets, brew_mol is called early,
492   caching a wrong value.
493  */
494 MAKE_SCHEME_CALLBACK (Stem, height, 2);
495 SCM
496 Stem::height (SCM smob, SCM ax)
497 {
498   Axis a = (Axis)ly_scm2int (ax);
499   Grob *me = unsmob_grob (smob);
500   assert (a == Y_AXIS);
501
502   /*
503     ugh. - this dependency should be automatic.
504   */
505   Grob *beam= get_beam (me);
506   if (beam)
507     {
508       Beam::after_line_breaking (beam->self_scm ());
509     }
510   
511
512   SCM mol = me->get_uncached_stencil ();
513   Interval iv;
514   if (mol != SCM_EOL)
515     iv = unsmob_stencil (mol)->extent (a);
516   if (Grob *b =get_beam (me))
517     {
518       Direction d = get_direction (me);
519       iv[d] += d * Beam::get_thickness (b) * 0.5 ;
520     }
521
522   return ly_interval2scm (iv);
523 }
524
525
526 Stencil
527 Stem::flag (Grob *me)
528 {
529   /* TODO: maybe property stroke-style should take different values,
530      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
531      '() or "grace").  */
532   String flag_style;
533
534   SCM flag_style_scm = me->get_property ("flag-style");
535   if (ly_c_symbol_p (flag_style_scm))
536     flag_style = ly_symbol2string (flag_style_scm);
537   
538   if (flag_style == "no-flag")
539     return Stencil ();
540
541   bool adjust = true;
542
543   String staffline_offs;
544   if (String::compare (flag_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           /*
574             perhaps the detection whether this correction is needed should
575             happen in a different place  to avoid the recursion.
576         
577             --hwn.
578           */
579           int p = Staff_symbol_referencer::get_rounded_position (me);
580           staffline_offs = Staff_symbol_referencer::on_staffline (me, p)
581             ? "1" : "0";
582         }
583       else
584         {
585           staffline_offs = "2";
586         }
587     }
588   else
589     {
590       staffline_offs = "";
591     }
592
593   char dir = (get_direction (me) == UP) ? 'u' : 'd';
594   String font_char = flag_style
595     + to_string (dir) + staffline_offs + to_string (duration_log (me));
596   Font_metric *fm = Font_interface::get_default_font (me);
597   Stencil flag = fm->find_by_name ("flags-" + font_char);
598   if (flag.is_empty ())
599     me->warning (_f ("flag `%s' not found", font_char));
600
601   SCM stroke_style_scm = me->get_property ("stroke-style");
602   if (ly_c_string_p (stroke_style_scm))
603     {
604       String stroke_style = ly_scm2string (stroke_style_scm);
605       if (!stroke_style.is_empty ())
606         {
607           String font_char = to_string (dir) + stroke_style;
608           Stencil stroke = fm->find_by_name ("flags-" + font_char);
609           if (stroke.is_empty ())
610             me->warning (_f ("flag stroke `%s' not found", font_char));
611           else
612             flag.add_stencil (stroke);
613         }
614     }
615
616   return flag;
617 }
618
619 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
620 SCM
621 Stem::dim_callback (SCM e, SCM ax)
622 {
623   Axis a = (Axis) ly_scm2int (ax);
624   assert (a == X_AXIS);
625   Grob *me = unsmob_grob (e);
626   Interval r (0, 0);
627   if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
628     ;   // TODO!
629   else
630     r = flag (me).extent (X_AXIS)
631       + thickness (me)/2;
632   return ly_interval2scm (r);
633 }
634
635 Real
636 Stem::thickness (Grob *me)
637 {
638   return ly_scm2double (me->get_property ("thickness"))
639     * Staff_symbol_referencer::line_thickness (me);
640 }
641
642 MAKE_SCHEME_CALLBACK (Stem, print, 1);
643 SCM
644 Stem::print (SCM smob)
645 {
646   Grob *me = unsmob_grob (smob);
647   Stencil mol;
648   Direction d = get_direction (me);
649
650   /* TODO: make the stem start a direction ?
651      This is required to avoid stems passing in tablature chords.  */
652   Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
653     ? last_head (me) :  lh = first_head (me);
654
655   if (!lh)
656     return SCM_EOL;
657
658   if (is_invisible (me))
659     return SCM_EOL;
660
661   Real y1 = Staff_symbol_referencer::get_position (lh);
662   Real y2 = stem_end_position (me);
663
664   Interval stem_y (y1 <? y2,y2 >? y1);
665
666   // dy?
667   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
668
669   if (Grob *hed = support_head (me))
670     {
671       /*
672         must not take ledgers into account.
673        */
674       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
675       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
676
677       y_attach = head_height.linear_combination (y_attach);
678       stem_y[Direction (-d)] += d * y_attach/dy;
679     }
680
681
682   // URG
683   Real stem_width = thickness (me);
684   Real blot =
685         me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
686
687   Box b = Box (Interval (-stem_width/2, stem_width/2),
688                Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
689
690   Stencil ss = Lookup::round_filled_box (b, blot);
691   mol.add_stencil (ss);
692
693   if (!get_beam (me) && abs (duration_log (me)) > 2)
694     {
695       Stencil fl = flag (me);
696       fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
697       fl.translate_axis (stem_width/2, X_AXIS);
698       mol.add_stencil (fl);
699     }
700
701   return mol.smobbed_copy ();
702 }
703
704 /*
705   move the stem to right of the notehead if it is up.
706  */
707 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
708 SCM
709 Stem::off_callback (SCM element_smob, SCM)
710 {
711   Grob *me = unsmob_grob (element_smob);
712   Real r = 0.0;
713   
714   if (head_count (me))
715     if (Grob *f = first_head (me))
716       {
717         Interval head_wid = Note_head::head_extent (f, X_AXIS);
718         Real attach = 0.0;
719         
720         if (is_invisible (me))
721           attach = 0.0;
722         else
723         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
724         
725         Direction d = get_direction (me);
726         Real real_attach = head_wid.linear_combination (d * attach);
727         r = real_attach;
728         
729         /* If not centered: correct for stem thickness.  */
730         if (attach)
731           {
732             Real rule_thick = thickness (me);
733             r += - d * rule_thick * 0.5;
734           }
735       }
736   return scm_make_real (r);
737 }
738
739 Grob *
740 Stem::get_beam (Grob *me)
741 {
742   SCM b = me->get_property ("beam");
743   return unsmob_grob (b);
744 }
745
746 Stem_info
747 Stem::get_stem_info (Grob *me)
748 {
749   /* Return cached info if available */
750   SCM scm_info = me->get_property ("stem-info");
751   if (!ly_c_pair_p (scm_info))
752     {
753       calc_stem_info (me);
754       scm_info = me->get_property ("stem-info");
755     }
756
757   Stem_info si;
758   si.dir_ = get_grob_direction (me);
759   si.ideal_y_ = ly_scm2double (ly_car (scm_info));
760   si.shortest_y_ = ly_scm2double (ly_cadr (scm_info));
761   return si;
762 }
763
764
765 /* TODO: add extra space for tremolos!  */
766 void
767 Stem::calc_stem_info (Grob *me)
768 {
769   Direction my_dir = get_grob_direction (me);
770
771   if (!my_dir)
772     {
773       programming_error ("No stem dir set?");
774       my_dir  = UP;
775     }
776
777   Real staff_space = Staff_symbol_referencer::staff_space (me);
778   Grob *beam = get_beam (me);
779   Real beam_translation = Beam::get_beam_translation (beam);
780   Real beam_thickness = Beam::get_thickness (beam);
781   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
782
783
784   /* Simple standard stem length */
785   SCM lengths = me->get_property ("beamed-lengths");
786   Real ideal_length =
787     ly_scm2double (robust_list_ref (beam_count - 1,lengths))
788                 
789     * staff_space
790     /* stem only extends to center of beam */
791     - 0.5 * beam_thickness;
792
793   /* Condition: sane minimum free stem length (chord to beams) */
794   lengths = me->get_property ("beamed-minimum-free-lengths");
795   Real ideal_minimum_free =
796     ly_scm2double (robust_list_ref (beam_count - 1, lengths))
797     * staff_space;
798
799
800   /* UGH
801      It seems that also for ideal minimum length, we must use
802      the maximum beam count (for this direction):
803
804      \score{ \notes\relative c''{ [a8 a32] }}
805
806      must be horizontal. */
807   Real height_of_my_beams = beam_thickness
808     + (beam_count - 1) * beam_translation;
809
810   Real ideal_minimum_length = ideal_minimum_free
811     + height_of_my_beams
812     /* stem only extends to center of beam */
813     - 0.5 * beam_thickness;
814
815   ideal_length = ideal_length >? ideal_minimum_length;
816
817   /* Convert to Y position, calculate for dir == UP */
818   Real note_start =
819     /* staff positions */
820     head_positions (me)[my_dir] * 0.5
821     * my_dir * staff_space;
822   Real ideal_y = note_start + ideal_length;
823
824
825   /* Conditions for Y position */
826
827   /* Lowest beam of (UP) beam must never be lower than second staffline
828
829      Reference?
830
831      Although this (additional) rule is probably correct,
832      I expect that highest beam (UP) should also never be lower
833      than middle staffline, just as normal stems.
834
835      Reference?
836
837      Obviously not for grace beams.
838
839      Also, not for knees.  Seems to be a good thing. */
840   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
841   bool is_knee = to_boolean (beam->get_property ("knee"));
842   if (!no_extend_b && !is_knee)
843     {
844       /* Highest beam of (UP) beam must never be lower than middle
845          staffline */
846       ideal_y = ideal_y >? 0;
847       /* Lowest beam of (UP) beam must never be lower than second staffline */
848       ideal_y = ideal_y >? (-staff_space
849                             - beam_thickness + height_of_my_beams);
850     }
851
852
853   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
854
855   Real minimum_free =
856     ly_scm2double (robust_list_ref
857                    (beam_count - 1,
858                     me->get_property
859                     ("beamed-extreme-minimum-free-lengths")))
860     * staff_space;
861
862   Real minimum_length = minimum_free
863     + height_of_my_beams
864     /* stem only extends to center of beam */
865     - 0.5 * beam_thickness;
866
867   ideal_y *= my_dir;
868   Real minimum_y = note_start + minimum_length;
869   Real shortest_y = minimum_y * my_dir;
870
871   me->set_property ("stem-info",
872                     scm_list_2 (scm_make_real (ideal_y),
873                                 scm_make_real (shortest_y)));
874 }
875
876 Slice
877 Stem::beam_multiplicity (Grob *stem)
878 {
879   SCM beaming= stem->get_property ("beaming");
880   Slice le = int_list_to_slice (ly_car (beaming));
881   Slice ri = int_list_to_slice (ly_cdr (beaming));
882   le.unite (ri);
883   return le;
884 }
885
886
887 /* FIXME:  Too many properties  */
888 ADD_INTERFACE (Stem, "stem-interface",
889                "The stem represent the graphical stem.  "
890                "In addition, it internally connects note heads, beams and"
891                "tremolos. "
892                "Rests and whole notes have invisible stems.",
893                "tremolo-flag french-beaming "
894                "avoid-note-head thickness "
895                "stem-info beamed-lengths beamed-minimum-free-lengths "
896                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
897                "duration-log beaming neutral-direction stem-end-position "
898                "note-heads direction length flag-style "
899                "no-stem-extend stroke-style");
900
901 /****************************************************************/
902
903 Stem_info::Stem_info ()
904 {
905   ideal_y_ = shortest_y_ = 0;
906   dir_ = CENTER;
907 }
908
909 void
910 Stem_info::scale (Real x)
911 {
912   ideal_y_ *= x;
913   shortest_y_ *= x;
914 }