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