]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
(parse_symbol_list): Bugfix.
[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     {
509       Beam::after_line_breaking (beam->self_scm ());
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       if (d == CENTER)
520         {
521           programming_error ("no stem direction");
522           d = UP;
523         }
524       iv[d] += d * Beam::get_thickness (b) * 0.5;
525     }
526
527   return ly_interval2scm (iv);
528 }
529
530 Stencil
531 Stem::flag (Grob *me)
532 {
533   /* TODO: maybe property stroke-style should take different values,
534      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
535      '() or "grace").  */
536   String flag_style;
537
538   SCM flag_style_scm = me->get_property ("flag-style");
539   if (scm_is_symbol (flag_style_scm))
540     flag_style = ly_symbol2string (flag_style_scm);
541
542   if (flag_style == "no-flag")
543     return Stencil ();
544
545   bool adjust = true;
546
547   String staffline_offs;
548   if (String::compare (flag_style, "mensural") == 0)
549     /* Mensural notation: For notes on staff lines, use different
550        flags than for notes between staff lines.  The idea is that
551        flags are always vertically aligned with the staff lines,
552        regardless if the note head is on a staff line or between two
553        staff lines.  In other words, the inner end of a flag always
554        touches a staff line.
555     */
556     {
557       if (adjust)
558         {
559           int p = (int) (rint (stem_end_position (me)));
560           staffline_offs
561             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
562         }
563       else
564         {
565           staffline_offs = "2";
566         }
567     }
568   else
569     {
570       staffline_offs = "";
571     }
572
573   char dir = (get_direction (me) == UP) ? 'u' : 'd';
574   String font_char = flag_style
575     + to_string (dir) + staffline_offs + to_string (duration_log (me));
576   Font_metric *fm = Font_interface::get_default_font (me);
577   Stencil flag = fm->find_by_name ("flags." + font_char);
578   if (flag.is_empty ())
579     me->warning (_f ("flag `%s' not found", font_char));
580
581   SCM stroke_style_scm = me->get_property ("stroke-style");
582   if (scm_is_string (stroke_style_scm))
583     {
584       String stroke_style = ly_scm2string (stroke_style_scm);
585       if (!stroke_style.is_empty ())
586         {
587           String font_char = to_string (dir) + stroke_style;
588           Stencil stroke = fm->find_by_name ("flags." + font_char);
589           if (stroke.is_empty ())
590             me->warning (_f ("flag stroke `%s' not found", font_char));
591           else
592             flag.add_stencil (stroke);
593         }
594     }
595
596   return flag;
597 }
598
599 MAKE_SCHEME_CALLBACK (Stem, width_callback, 2);
600 SCM
601 Stem::width_callback (SCM e, SCM ax)
602 {
603   (void) ax;
604   assert (scm_to_int (ax) == X_AXIS);
605   Grob *me = unsmob_grob (e);
606
607   Interval r;
608
609   if (is_invisible (me))
610     {
611       r.set_empty ();
612     }
613   else if (unsmob_grob (me->get_object ("beam")) || abs (duration_log (me)) <= 2)
614     {
615       r = Interval (-1, 1);
616       r *= thickness (me) / 2;
617     }
618   else
619     {
620       r = Interval (-1, 1) * thickness (me) * 0.5;
621       r.unite (flag (me).extent (X_AXIS));
622     }
623   return ly_interval2scm (r);
624 }
625
626 Real
627 Stem::thickness (Grob *me)
628 {
629   return scm_to_double (me->get_property ("thickness"))
630     * Staff_symbol_referencer::line_thickness (me);
631 }
632
633 MAKE_SCHEME_CALLBACK (Stem, print, 1);
634 SCM
635 Stem::print (SCM smob)
636 {
637   Grob *me = unsmob_grob (smob);
638   Stencil mol;
639   Direction d = get_direction (me);
640
641   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
642                                            0.0);
643   bool stemlet = stemlet_length > 0.0;
644
645   /* TODO: make the stem start a direction ?
646      This is required to avoid stems passing in tablature chords.  */
647   Grob *lh
648     = to_boolean (me->get_property ("avoid-note-head"))
649     ? last_head (me)
650     : first_head (me);
651   Grob *beam = get_beam (me);
652
653   if (!lh && !stemlet)
654     return SCM_EOL;
655
656   if (!lh && stemlet && !beam)
657     return SCM_EOL;
658
659   if (is_invisible (me))
660     return SCM_EOL;
661
662   Real y2 = stem_end_position (me);
663   Real y1 = y2;
664   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
665
666   if (lh)
667     y2 = Staff_symbol_referencer::get_position (lh);
668   else if (stemlet)
669     {
670       Real beam_translation = Beam::get_beam_translation (beam);
671       Real beam_thickness = Beam::get_thickness (beam);
672       int beam_count = beam_multiplicity (me).length () + 1;
673
674       y2 -= d
675         * (0.5 * beam_thickness
676            + beam_translation * max (0, (beam_count - 1))
677            + stemlet_length) / half_space;
678     }
679
680   Interval stem_y (min (y1, y2), max (y2, y1));
681
682   if (Grob *hed = support_head (me))
683     {
684       /*
685         must not take ledgers into account.
686       */
687       Interval head_height = hed->extent (hed, Y_AXIS);
688       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
689
690       y_attach = head_height.linear_combination (y_attach);
691       stem_y[Direction (-d)] += d * y_attach / half_space;
692     }
693
694   // URG
695   Real stem_width = thickness (me);
696   Real blot
697     = me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
698
699   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
700                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
701
702   Stencil ss = Lookup::round_filled_box (b, blot);
703   mol.add_stencil (ss);
704
705   if (!get_beam (me) && abs (duration_log (me)) > 2)
706     {
707       Stencil fl = flag (me);
708       fl.translate_axis (stem_y[d] * half_space - d * blot / 2, Y_AXIS);
709       fl.translate_axis (stem_width / 2, X_AXIS);
710       mol.add_stencil (fl);
711     }
712
713   return mol.smobbed_copy ();
714 }
715
716 /*
717   move the stem to right of the notehead if it is up.
718 */
719 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
720 SCM
721 Stem::offset_callback (SCM element_smob, SCM)
722 {
723   Grob *me = unsmob_grob (element_smob);
724   Real r = 0.0;
725
726   if (Grob *f = first_head (me))
727     {
728       Interval head_wid = f->extent (f, X_AXIS);
729       Real attach = 0.0;
730
731       if (is_invisible (me))
732         attach = 0.0;
733       else
734         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
735
736       Direction d = get_direction (me);
737       Real real_attach = head_wid.linear_combination (d * attach);
738       r = real_attach;
739
740       /* If not centered: correct for stem thickness.  */
741       if (attach)
742         {
743           Real rule_thick = thickness (me);
744           r += -d * rule_thick * 0.5;
745         }
746     }
747   else
748     {
749       extract_grob_set (me, "rests", rests);
750       if (rests.size ())
751         {
752           Grob *rest = rests.top ();
753           r = rest->extent (rest, X_AXIS).center ();
754         }
755     }
756   return scm_from_double (r);
757 }
758
759 Spanner *
760 Stem::get_beam (Grob *me)
761 {
762   SCM b = me->get_object ("beam");
763   return dynamic_cast<Spanner *> (unsmob_grob (b));
764 }
765
766 Stem_info
767 Stem::get_stem_info (Grob *me)
768 {
769   /* Return cached info if available */
770   SCM scm_info = me->get_property ("stem-info");
771   if (!scm_is_pair (scm_info))
772     {
773       calc_stem_info (me);
774       scm_info = me->get_property ("stem-info");
775     }
776
777   Stem_info si;
778   si.dir_ = get_grob_direction (me);
779   si.ideal_y_ = scm_to_double (scm_car (scm_info));
780   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
781   return si;
782 }
783
784 /* TODO: add extra space for tremolos!  */
785 void
786 Stem::calc_stem_info (Grob *me)
787 {
788   Direction my_dir = get_grob_direction (me);
789
790   if (!my_dir)
791     {
792       programming_error ("no stem dir set");
793       my_dir = UP;
794     }
795
796   Real staff_space = Staff_symbol_referencer::staff_space (me);
797   Grob *beam = get_beam (me);
798   Real beam_translation = Beam::get_beam_translation (beam);
799   Real beam_thickness = Beam::get_thickness (beam);
800   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
801
802   /* Simple standard stem length */
803   SCM lengths = me->get_property ("beamed-lengths");
804   Real ideal_length
805     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
806
807     * staff_space
808     /* stem only extends to center of beam
809      */
810     - 0.5 * beam_thickness;
811
812   /* Condition: sane minimum free stem length (chord to beams) */
813   lengths = me->get_property ("beamed-minimum-free-lengths");
814   Real ideal_minimum_free
815     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
816     * staff_space;
817
818   /* UGH
819      It seems that also for ideal minimum length, we must use
820      the maximum beam count (for this direction):
821
822      \score{ \notes\relative c''{ [a8 a32] }}
823
824      must be horizontal. */
825   Real height_of_my_beams = beam_thickness
826     + (beam_count - 1) * beam_translation;
827
828   Real ideal_minimum_length = ideal_minimum_free
829     + height_of_my_beams
830     /* stem only extends to center of beam */
831     - 0.5 * beam_thickness;
832
833   ideal_length = max (ideal_length, ideal_minimum_length);
834
835   /* Convert to Y position, calculate for dir == UP */
836   Real note_start
837     =     /* staff positions */
838     head_positions (me)[my_dir] * 0.5
839     * my_dir * staff_space;
840   Real ideal_y = note_start + ideal_length;
841
842   /* Conditions for Y position */
843
844   /* Lowest beam of (UP) beam must never be lower than second staffline
845
846   Reference?
847
848   Although this (additional) rule is probably correct,
849   I expect that highest beam (UP) should also never be lower
850   than middle staffline, just as normal stems.
851
852   Reference?
853
854   Obviously not for grace beams.
855
856   Also, not for knees.  Seems to be a good thing. */
857   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
858   bool is_knee = to_boolean (beam->get_property ("knee"));
859   if (!no_extend_b && !is_knee)
860     {
861       /* Highest beam of (UP) beam must never be lower than middle
862          staffline */
863       ideal_y = max (ideal_y, 0.0);
864       /* Lowest beam of (UP) beam must never be lower than second staffline */
865       ideal_y = max (ideal_y, (-staff_space
866                                - beam_thickness + height_of_my_beams));
867     }
868
869   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
870
871   Real minimum_free
872     = scm_to_double (robust_list_ref
873                      (beam_count - 1,
874                       me->get_property
875                       ("beamed-extreme-minimum-free-lengths")))
876     * staff_space;
877
878   Real minimum_length = minimum_free
879     + height_of_my_beams
880     /* stem only extends to center of beam */
881     - 0.5 * beam_thickness;
882
883   if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
884     {
885       Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
886       y_ext.widen (0.5);        // FIXME. Should be tunable? 
887       minimum_length = max (minimum_length, y_ext.length ());
888     }
889
890   ideal_y *= my_dir;
891   Real minimum_y = note_start + minimum_length;
892   Real shortest_y = minimum_y * my_dir;
893
894   me->set_property ("stem-info",
895                     scm_list_2 (scm_from_double (ideal_y),
896                                 scm_from_double (shortest_y)));
897 }
898
899 Slice
900 Stem::beam_multiplicity (Grob *stem)
901 {
902   SCM beaming = stem->get_property ("beaming");
903   Slice le = int_list_to_slice (scm_car (beaming));
904   Slice ri = int_list_to_slice (scm_cdr (beaming));
905   le.unite (ri);
906   return le;
907 }
908
909 /* FIXME:  Too many properties  */
910 ADD_INTERFACE (Stem, "stem-interface",
911                "The stem represent the graphical stem.  "
912                "In addition, it internally connects note heads, beams and"
913                "tremolos. "
914                "Rests and whole notes have invisible stems.",
915                "tremolo-flag french-beaming "
916                "avoid-note-head thickness "
917                "stemlet-length rests "
918                "stem-info beamed-lengths beamed-minimum-free-lengths "
919                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
920                "duration-log beaming neutral-direction stem-end-position "
921                "note-heads direction length flag-style "
922                "no-stem-extend stroke-style");
923
924 /****************************************************************/
925
926 Stem_info::Stem_info ()
927 {
928   ideal_y_ = shortest_y_ = 0;
929   dir_ = CENTER;
930 }
931
932 void
933 Stem_info::scale (Real x)
934 {
935   ideal_y_ *= x;
936   shortest_y_ *= x;
937 }