]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* scripts/midi2ly.py (Key.dump): pychecker cleanups.
[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       /* trigger set-stem-lengths. */
513       beam->get_property ("quantized-positions");
514     }
515
516   /*
517     Can't get_stencil(), since that would cache stencils too early.
518     This causes problems with beams.
519    */
520   Stencil *stencil = unsmob_stencil (print (smob));
521   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
522   if (beam)
523     {
524       if (dir == CENTER)
525         {
526           programming_error ("no stem direction");
527           dir = UP;
528         }
529       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
530     }
531
532   return ly_interval2scm (iv);
533 }
534
535 Real
536 Stem::stem_end_position (Grob *me)
537 {
538   return robust_scm2double (me->get_property ("stem-end-position"), 0);
539 }
540
541 Stencil
542 Stem::flag (Grob *me)
543 {
544   int log = duration_log (me);
545   if (log < 3
546       || unsmob_grob (me->get_object ("beam")))
547     return Stencil ();
548
549   /*
550     TODO: maybe property stroke-style should take different values,
551     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
552     '() or "grace").  */
553   string flag_style;
554
555   SCM flag_style_scm = me->get_property ("flag-style");
556   if (scm_is_symbol (flag_style_scm))
557     flag_style = ly_symbol2string (flag_style_scm);
558
559   if (flag_style == "no-flag")
560     return Stencil ();
561
562   bool adjust = true;
563
564   string staffline_offs;
565   if (flag_style == "mensural")
566     /* Mensural notation: For notes on staff lines, use different
567        flags than for notes between staff lines.  The idea is that
568        flags are always vertically aligned with the staff lines,
569        regardless if the note head is on a staff line or between two
570        staff lines.  In other words, the inner end of a flag always
571        touches a staff line.
572     */
573     {
574       if (adjust)
575         {
576           int p = (int) (rint (stem_end_position (me)));
577           staffline_offs
578             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
579         }
580       else
581         staffline_offs = "2";
582     }
583   else
584     staffline_offs = "";
585
586   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
587   string font_char = flag_style
588     + to_string (dir) + staffline_offs + to_string (log);
589   Font_metric *fm = Font_interface::get_default_font (me);
590   Stencil flag = fm->find_by_name ("flags." + font_char);
591   if (flag.is_empty ())
592     me->warning (_f ("flag `%s' not found", font_char));
593
594   SCM stroke_style_scm = me->get_property ("stroke-style");
595   if (scm_is_string (stroke_style_scm))
596     {
597       string stroke_style = ly_scm2string (stroke_style_scm);
598       if (!stroke_style.empty ())
599         {
600           string font_char = to_string (dir) + stroke_style;
601           Stencil stroke = fm->find_by_name ("flags." + font_char);
602           if (stroke.is_empty ())
603             me->warning (_f ("flag stroke `%s' not found", font_char));
604           else
605             flag.add_stencil (stroke);
606         }
607     }
608
609   return flag;
610 }
611
612 MAKE_SCHEME_CALLBACK (Stem, width, 1);
613 SCM
614 Stem::width (SCM e)
615 {
616   Grob *me = unsmob_grob (e);
617
618   Interval r;
619
620   if (is_invisible (me))
621     r.set_empty ();
622   else if (unsmob_grob (me->get_object ("beam"))
623            || abs (duration_log (me)) <= 2)
624     {
625       r = Interval (-1, 1);
626       r *= thickness (me) / 2;
627     }
628   else
629     {
630       r = Interval (-1, 1) * thickness (me) * 0.5;
631       r.unite (flag (me).extent (X_AXIS));
632     }
633   return ly_interval2scm (r);
634 }
635
636 Real
637 Stem::thickness (Grob *me)
638 {
639   return scm_to_double (me->get_property ("thickness"))
640     * Staff_symbol_referencer::line_thickness (me);
641 }
642
643 MAKE_SCHEME_CALLBACK (Stem, print, 1);
644 SCM
645 Stem::print (SCM smob)
646 {
647   Grob *me = unsmob_grob (smob);
648   Stencil mol;
649   Direction d = get_grob_direction (me);
650
651   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
652                                            0.0);
653   bool stemlet = stemlet_length > 0.0;
654
655   /* TODO: make the stem start a direction ?
656      This is required to avoid stems passing in tablature chords.  */
657   Grob *lh
658     = to_boolean (me->get_property ("avoid-note-head"))
659     ? last_head (me)
660     : first_head (me);
661   Grob *beam = get_beam (me);
662
663   if (!lh && !stemlet)
664     return SCM_EOL;
665
666   if (!lh && stemlet && !beam)
667     return SCM_EOL;
668
669   if (is_invisible (me))
670     return SCM_EOL;
671
672   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
673   Real y1 = y2;
674   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
675
676   if (lh)
677     y2 = Staff_symbol_referencer::get_position (lh);
678   else if (stemlet)
679     {
680       Real beam_translation = Beam::get_beam_translation (beam);
681       Real beam_thickness = Beam::get_thickness (beam);
682       int beam_count = beam_multiplicity (me).length () + 1;
683
684       y2 -= d
685         * (0.5 * beam_thickness
686            + beam_translation * max (0, (beam_count - 1))
687            + stemlet_length) / half_space;
688     }
689
690   Interval stem_y (min (y1, y2), max (y2, y1));
691
692   if (Grob *head = support_head (me))
693     {
694       /*
695         must not take ledgers into account.
696       */
697       Interval head_height = head->extent (head, Y_AXIS);
698       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
699
700       y_attach = head_height.linear_combination (y_attach);
701       stem_y[Direction (-d)] += d * y_attach / half_space;
702     }
703
704   // URG
705   Real stem_width = thickness (me);
706   Real blot
707     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
708
709   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
710                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
711
712   Stencil ss = Lookup::round_filled_box (b, blot);
713   mol.add_stencil (ss);
714
715   mol.add_stencil (get_translated_flag (me));
716
717   return mol.smobbed_copy ();
718 }
719
720 Stencil
721 Stem::get_translated_flag (Grob *me)
722 {
723   Stencil fl = flag (me);
724   if (!fl.is_empty ())
725     {
726       Direction d = get_grob_direction (me);
727       Real blot
728         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
729       Real stem_width = thickness (me);
730       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
731       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
732       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
733       fl.translate_axis (stem_width / 2, X_AXIS);
734     }
735   return fl;
736 }
737
738
739 /*
740   move the stem to right of the notehead if it is up.
741 */
742 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
743 SCM
744 Stem::offset_callback (SCM smob)
745 {
746   Grob *me = unsmob_grob (smob);
747   Real r = 0.0;
748
749   if (Grob *f = first_head (me))
750     {
751       Interval head_wid = f->extent (f, X_AXIS);
752       Real attach = 0.0;
753
754       if (is_invisible (me))
755         attach = 0.0;
756       else
757         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
758
759       Direction d = get_grob_direction (me);
760       Real real_attach = head_wid.linear_combination (d * attach);
761       r = real_attach;
762
763       /* If not centered: correct for stem thickness.  */
764       if (attach)
765         {
766           Real rule_thick = thickness (me);
767           r += -d * rule_thick * 0.5;
768         }
769     }
770   else
771     {
772       extract_grob_set (me, "rests", rests);
773       if (rests.size ())
774         {
775           Grob *rest = rests.back ();
776           r = rest->extent (rest, X_AXIS).center ();
777         }
778     }
779   return scm_from_double (r);
780 }
781
782 Spanner *
783 Stem::get_beam (Grob *me)
784 {
785   SCM b = me->get_object ("beam");
786   return dynamic_cast<Spanner *> (unsmob_grob (b));
787 }
788
789 Stem_info
790 Stem::get_stem_info (Grob *me)
791 {
792   Stem_info si;
793   si.dir_ = get_grob_direction (me);
794   
795   SCM scm_info = me->get_property ("stem-info");
796   si.ideal_y_ = scm_to_double (scm_car (scm_info));
797   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
798   return si;
799 }
800
801 /* TODO: add extra space for tremolos!  */
802 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
803 SCM
804 Stem::calc_stem_info (SCM smob)
805 {
806   Grob *me = unsmob_grob (smob);
807   Direction my_dir = get_grob_direction (me);
808
809   if (!my_dir)
810     {
811       programming_error ("no stem dir set");
812       my_dir = UP;
813     }
814
815   Real staff_space = Staff_symbol_referencer::staff_space (me);
816   Grob *beam = get_beam (me);
817
818   if (beam)
819     {
820       (void) beam->get_property ("beaming");
821     }
822   
823   Real beam_translation = Beam::get_beam_translation (beam);
824   Real beam_thickness = Beam::get_thickness (beam);
825   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
826   Real length_fraction
827     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
828
829   /* Simple standard stem length */
830   SCM details = me->get_property ("details");
831   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
832   
833   Real ideal_length
834     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
835     * staff_space
836     * length_fraction
837     
838     /* stem only extends to center of beam
839      */
840     - 0.5 * beam_thickness;
841
842   /* Condition: sane minimum free stem length (chord to beams) */
843   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
844
845   Real ideal_minimum_free
846     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
847     * staff_space
848     * length_fraction;
849
850   /* UGH
851      It seems that also for ideal minimum length, we must use
852      the maximum beam count (for this direction):
853
854      \score{ \notes\relative c''{ [a8 a32] }}
855
856      must be horizontal. */
857   Real height_of_my_beams = beam_thickness
858     + (beam_count - 1) * beam_translation;
859
860   Real ideal_minimum_length = ideal_minimum_free
861     + height_of_my_beams
862     /* stem only extends to center of beam */
863     - 0.5 * beam_thickness;
864
865   ideal_length = max (ideal_length, ideal_minimum_length);
866
867   /* Convert to Y position, calculate for dir == UP */
868   Real note_start
869     =     /* staff positions */
870     head_positions (me)[my_dir] * 0.5
871     * my_dir * staff_space;
872   Real ideal_y = note_start + ideal_length;
873
874   /* Conditions for Y position */
875
876   /* Lowest beam of (UP) beam must never be lower than second staffline
877
878   Reference?
879
880   Although this (additional) rule is probably correct,
881   I expect that highest beam (UP) should also never be lower
882   than middle staffline, just as normal stems.
883
884   Reference?
885
886   Obviously not for grace beams.
887
888   Also, not for knees.  Seems to be a good thing. */
889   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
890   bool is_knee = to_boolean (beam->get_property ("knee"));
891   if (!no_extend_b && !is_knee)
892     {
893       /* Highest beam of (UP) beam must never be lower than middle
894          staffline */
895       ideal_y = max (ideal_y, 0.0);
896       /* Lowest beam of (UP) beam must never be lower than second staffline */
897       ideal_y = max (ideal_y, (-staff_space
898                                - beam_thickness + height_of_my_beams));
899     }
900
901   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
902
903   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
904                                  details));
905   
906   Real minimum_free
907     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
908     * staff_space
909     * length_fraction;
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                "\n\nThe following properties may be set in the details list." 
949                "@table @code\n"
950                "@item  beamed-lengths \n"
951                "list of stem lengths given beam multiplicity. \n"
952                "@item beamed-minimum-free-lengths \n"
953                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
954                "@item beamed-extreme-minimum-free-lengths\n"
955                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
956                "@item lengths\n"
957                "Default stem lengths. The list gives a length for each flag-count.\n"
958                "@item stem-shorten\n"
959                "How much a stem in a forced direction "
960                "should be shortened. The list gives an amount depending on the number "
961                "of flags/beams."
962                "@end table\n"
963                ,
964
965                /* properties */
966                "avoid-note-head "
967                "beam "
968                "beaming "
969                "default-direction "
970                "details "
971                "direction "
972                "duration-log "
973                "flag-style "
974                "french-beaming "
975                "length "
976                "length-fraction "
977                "neutral-direction "
978                "no-stem-extend "
979                "note-heads "
980                "positioning-done "
981                "rests "
982                "stem-end-position "
983                "stem-info "
984                "stemlet-length "
985                "stroke-style "
986                "thickness "
987                "tremolo-flag "
988                );
989
990 /****************************************************************/
991
992 Stem_info::Stem_info ()
993 {
994   ideal_y_ = shortest_y_ = 0;
995   dir_ = CENTER;
996 }
997
998 void
999 Stem_info::scale (Real x)
1000 {
1001   ideal_y_ *= x;
1002   shortest_y_ *= x;
1003 }