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