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