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