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