]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/stem.cc (calc_stem_info): trigger beaming calculation.
[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@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 "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 stem_end = 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 * stem_end < 0)
247     stem_end = 0.0;
248
249   
250   /* Make a little room if we have a upflag and there is a dot.
251      previous approach was to lengthen the stem. This is not
252      good typesetting practice.  */
253   if (!get_beam (me) && dir == UP
254       && durlog > 2)
255     {
256       Grob *closest_to_flag = extremal_heads (me)[dir];
257       Grob *dots = closest_to_flag
258         ? Rhythmic_head::get_dots (closest_to_flag) : 0;
259
260       if (dots)
261         {
262           Real dp = Staff_symbol_referencer::get_position (dots);
263           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
264
265           /* Very gory: add myself to the X-support of the parent,
266              which should be a dot-column. */
267           if (dir * (stem_end + flagy - dp) < 0.5)
268             {
269               Grob *par = dots->get_parent (X_AXIS);
270
271               if (Dot_column::has_interface (par))
272                 {
273                   Side_position_interface::add_support (par, me);
274
275                   /* TODO: apply some better logic here. The flag is
276                      curved inwards, so this will typically be too
277                      much. */
278                 }
279             }
280         }
281     }
282
283   return scm_from_double (stem_end);
284 }
285
286
287 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
288 SCM
289 Stem::calc_length (SCM smob)
290 {
291   Grob *me = unsmob_grob (smob);
292   
293   SCM details = me->get_property ("details");
294   int durlog = duration_log (me);
295
296   Real ss = Staff_symbol_referencer::staff_space (me);
297   Real length = 7;
298   SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
299   if (scm_is_pair (s))
300     length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
301
302   Direction dir = get_grob_direction (me);
303
304   /* Stems in unnatural (forced) direction should be shortened,
305      according to [Roush & Gourlay] */
306   Interval hp = head_positions (me);
307   if (dir && dir * hp[dir] >= 0)
308     {
309       SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
310       SCM scm_shorten = scm_is_pair (sshorten)
311         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
312       Real shorten = 2* robust_scm2double (scm_shorten, 0);
313
314       /* On boundary: shorten only half */
315       if (abs (head_positions (me)[dir]) <= 1)
316         shorten *= 0.5;
317
318       length -= shorten;
319     }
320
321   length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
322
323   /* Tremolo stuff.  */
324   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
325   if (t_flag && !unsmob_grob (me->get_object ("beam")))
326     {
327       /* Crude hack: add extra space if tremolo flag is there.
328
329       We can't do this for the beam, since we get into a loop
330       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
331
332       Real minlen = 1.0
333         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length ()
334         / ss;
335
336       if (durlog >= 3)
337         {
338           Interval flag_ext = flag (me).extent (Y_AXIS);
339           if (!flag_ext.is_empty ())
340             minlen += 2 * flag_ext.length () / ss;
341
342           /* The clash is smaller for down stems (since the tremolo is
343              angled up.) */
344           if (dir == DOWN)
345             minlen -= 1.0;
346         }
347       length = max (length, minlen + 1.0);
348     }
349   
350   return scm_from_double (length);
351 }
352 /* The log of the duration (Number of hooks on the flag minus two)  */
353 int
354 Stem::duration_log (Grob *me)
355 {
356   SCM s = me->get_property ("duration-log");
357   return (scm_is_number (s)) ? scm_to_int (s) : 2;
358 }
359
360 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
361 SCM
362 Stem::calc_positioning_done (SCM smob)
363 {
364   Grob *me = unsmob_grob (smob);  
365   if (!head_count (me))
366     return SCM_BOOL_T;
367
368   extract_grob_set (me, "note-heads", ro_heads);
369   Link_array<Grob> heads (ro_heads);
370   heads.sort (compare_position);
371   Direction dir = get_grob_direction (me);
372
373   if (dir < 0)
374     heads.reverse ();
375
376   Real thick = thickness (me);
377
378   Grob *hed = support_head (me);
379   if (!dir)
380     {
381       programming_error ("Stem dir must be up or down.");
382       dir = UP;
383       set_grob_direction (me, dir);
384     }
385   
386   Real w = hed->extent (hed, X_AXIS)[dir];
387   for (int i = 0; i < heads.size (); i++)
388     heads[i]->translate_axis (w - heads[i]->extent (heads[i], X_AXIS)[dir],
389                               X_AXIS);
390
391   bool parity = true;
392   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
393   for (int i = 1; i < heads.size (); i++)
394     {
395       Real p = Staff_symbol_referencer::get_position (heads[i]);
396       Real dy = fabs (lastpos- p);
397
398       /*
399         dy should always be 0.5, 0.0, 1.0, but provide safety margin
400         for rounding errors.
401       */
402       if (dy < 1.1)
403         {
404           if (parity)
405             {
406               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
407
408               Direction d = get_grob_direction (me);
409               /*
410                 Reversed head should be shifted ell-thickness, but this
411                 looks too crowded, so we only shift ell-0.5*thickness.
412
413                 This leads to assymetry: Normal heads overlap the
414                 stem 100% whereas reversed heads only overlaps the
415                 stem 50%
416               */
417
418               Real reverse_overlap = 0.5;
419               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
420                                         X_AXIS);
421
422               if (is_invisible (me))
423                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
424                                           X_AXIS);
425
426               /* TODO:
427
428               For some cases we should kern some more: when the
429               distance between the next or prev note is too large, we'd
430               get large white gaps, eg.
431
432               |
433               X|
434               |X  <- kern this.
435               |
436               X
437
438               */
439             }
440           parity = !parity;
441         }
442       else
443         parity = true;
444
445       lastpos = int (p);
446     }
447
448   return SCM_BOOL_T;
449 }
450
451 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
452 SCM
453 Stem::calc_direction (SCM smob)
454 {
455   Grob *me = unsmob_grob (smob);
456   Direction dir = CENTER;
457   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
458     {
459       SCM ignore_me = beam->get_property ("direction");
460       (void) ignore_me;
461       dir = get_grob_direction (me);
462     }
463   else
464     dir = get_default_dir (me);
465   
466   return scm_from_int (dir);
467 }
468
469 /* A separate function, since this is used elsewhere too.  */
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, 1);
492 SCM
493 Stem::height (SCM smob)
494 {
495   Grob *me = unsmob_grob (smob);
496
497   Direction dir = get_grob_direction (me);
498   
499   /* Trigger callback.
500
501   UGH. Should be automatic
502   */
503   Grob *beam = get_beam (me);
504   if (beam)
505     {
506       beam->get_property ("positions");
507     }
508
509   /*
510     Can't get_stencil(), since that would cache stencils too early.
511     This causes problems with beams.
512    */
513   Stencil *stencil = unsmob_stencil (print (smob));
514   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
515   if (beam)
516     {
517       if (dir == CENTER)
518         {
519           programming_error ("no stem direction");
520           dir = UP;
521         }
522       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
523     }
524
525   return ly_interval2scm (iv);
526 }
527
528 Real
529 Stem::stem_end_position (Grob *me)
530 {
531   return robust_scm2double (me->get_property ("stem-end-position"), 0);
532 }
533
534 Stencil
535 Stem::flag (Grob *me)
536 {
537   int log = duration_log (me);
538   if (log < 3
539       || unsmob_grob (me->get_object ("beam")))
540     return Stencil ();
541
542   /*
543     TODO: maybe property stroke-style should take different values,
544     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
545     '() or "grace").  */
546   String flag_style;
547
548   SCM flag_style_scm = me->get_property ("flag-style");
549   if (scm_is_symbol (flag_style_scm))
550     flag_style = ly_symbol2string (flag_style_scm);
551
552   if (flag_style == "no-flag")
553     return Stencil ();
554
555   bool adjust = true;
556
557   String staffline_offs;
558   if (String::compare (flag_style, "mensural") == 0)
559     /* Mensural notation: For notes on staff lines, use different
560        flags than for notes between staff lines.  The idea is that
561        flags are always vertically aligned with the staff lines,
562        regardless if the note head is on a staff line or between two
563        staff lines.  In other words, the inner end of a flag always
564        touches a staff line.
565     */
566     {
567       if (adjust)
568         {
569           int p = (int) (rint (stem_end_position (me)));
570           staffline_offs
571             = Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
572         }
573       else
574         staffline_offs = "2";
575     }
576   else
577     staffline_offs = "";
578
579   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
580   String font_char = flag_style
581     + to_string (dir) + staffline_offs + to_string (log);
582   Font_metric *fm = Font_interface::get_default_font (me);
583   Stencil flag = fm->find_by_name ("flags." + font_char);
584   if (flag.is_empty ())
585     me->warning (_f ("flag `%s' not found", font_char));
586
587   SCM stroke_style_scm = me->get_property ("stroke-style");
588   if (scm_is_string (stroke_style_scm))
589     {
590       String stroke_style = ly_scm2string (stroke_style_scm);
591       if (!stroke_style.is_empty ())
592         {
593           String font_char = to_string (dir) + stroke_style;
594           Stencil stroke = fm->find_by_name ("flags." + font_char);
595           if (stroke.is_empty ())
596             me->warning (_f ("flag stroke `%s' not found", font_char));
597           else
598             flag.add_stencil (stroke);
599         }
600     }
601
602   return flag;
603 }
604
605 MAKE_SCHEME_CALLBACK (Stem, width, 1);
606 SCM
607 Stem::width (SCM e)
608 {
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 *head = support_head (me))
686     {
687       /*
688         must not take ledgers into account.
689       */
690       Interval head_height = head->extent (head, Y_AXIS);
691       Real y_attach = Note_head::stem_attachment_coordinate (head, 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->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->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, 1);
736 SCM
737 Stem::offset_callback (SCM smob)
738 {
739   Grob *me = unsmob_grob (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
811   if (beam)
812     {
813       (void) beam->get_property ("beaming");
814     }
815   
816   Real beam_translation = Beam::get_beam_translation (beam);
817   Real beam_thickness = Beam::get_thickness (beam);
818   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
819   Real length_fraction
820     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
821
822   /* Simple standard stem length */
823   SCM details = me->get_property ("details");
824   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
825   
826   Real ideal_length
827     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
828     * staff_space
829     * length_fraction
830     
831     /* stem only extends to center of beam
832      */
833     - 0.5 * beam_thickness;
834
835   /* Condition: sane minimum free stem length (chord to beams) */
836   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
837
838   Real ideal_minimum_free
839     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
840     * staff_space
841     * length_fraction;
842
843   /* UGH
844      It seems that also for ideal minimum length, we must use
845      the maximum beam count (for this direction):
846
847      \score{ \notes\relative c''{ [a8 a32] }}
848
849      must be horizontal. */
850   Real height_of_my_beams = beam_thickness
851     + (beam_count - 1) * beam_translation;
852
853   Real ideal_minimum_length = ideal_minimum_free
854     + height_of_my_beams
855     /* stem only extends to center of beam */
856     - 0.5 * beam_thickness;
857
858   ideal_length = max (ideal_length, ideal_minimum_length);
859
860   /* Convert to Y position, calculate for dir == UP */
861   Real note_start
862     =     /* staff positions */
863     head_positions (me)[my_dir] * 0.5
864     * my_dir * staff_space;
865   Real ideal_y = note_start + ideal_length;
866
867   /* Conditions for Y position */
868
869   /* Lowest beam of (UP) beam must never be lower than second staffline
870
871   Reference?
872
873   Although this (additional) rule is probably correct,
874   I expect that highest beam (UP) should also never be lower
875   than middle staffline, just as normal stems.
876
877   Reference?
878
879   Obviously not for grace beams.
880
881   Also, not for knees.  Seems to be a good thing. */
882   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
883   bool is_knee = to_boolean (beam->get_property ("knee"));
884   if (!no_extend_b && !is_knee)
885     {
886       /* Highest beam of (UP) beam must never be lower than middle
887          staffline */
888       ideal_y = max (ideal_y, 0.0);
889       /* Lowest beam of (UP) beam must never be lower than second staffline */
890       ideal_y = max (ideal_y, (-staff_space
891                                - beam_thickness + height_of_my_beams));
892     }
893
894   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
895
896   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
897                                  details));
898   
899   Real minimum_free
900     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
901     * staff_space
902     * length_fraction;
903
904   Real minimum_length = minimum_free
905     + height_of_my_beams
906     /* stem only extends to center of beam */
907     - 0.5 * beam_thickness;
908
909   if (Grob *tremolo = unsmob_grob (me->get_object ("tremolo-flag")))
910     {
911       Interval y_ext = tremolo->extent (tremolo, Y_AXIS);
912       y_ext.widen (0.5);        // FIXME. Should be tunable? 
913       minimum_length = max (minimum_length, y_ext.length ());
914     }
915
916   ideal_y *= my_dir;
917   Real minimum_y = note_start + minimum_length;
918   Real shortest_y = minimum_y * my_dir;
919
920   return scm_list_2 (scm_from_double (ideal_y),
921                      scm_from_double (shortest_y));
922 }
923
924 Slice
925 Stem::beam_multiplicity (Grob *stem)
926 {
927   SCM beaming = stem->get_property ("beaming");
928   Slice le = int_list_to_slice (scm_car (beaming));
929   Slice ri = int_list_to_slice (scm_cdr (beaming));
930   le.unite (ri);
931   return le;
932 }
933
934 /* FIXME:  Too many properties  */
935 ADD_INTERFACE (Stem, "stem-interface",
936                "The stem represent the graphical stem.  "
937                "In addition, it internally connects note heads, beams and"
938                "tremolos. "
939                "Rests and whole notes have invisible stems."
940
941                "\n\nThe following properties may be set in the details list." 
942                "@table @code\n"
943                "@item  beamed-lengths \n"
944                "list of stem lengths given beam multiplicity. \n"
945                "@item beamed-minimum-free-lengths \n"
946                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
947                "@item beamed-extreme-minimum-free-lengths\n"
948                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
949                "@item lengths\n"
950                "Default stem lengths. The list gives a length for each flag-count.\n"
951                "@item stem-shorten\n"
952                "How much a stem in a forced direction "
953                "should be shortened. The list gives an amount depending on the number "
954                "of flags/beams."
955                "@end table\n"
956                ,
957
958                /* properties */
959                "avoid-note-head "
960                "beam "
961                "beaming "
962                "details "
963                "direction "
964                "duration-log "
965                "flag-style "
966                "french-beaming "
967                "length "
968                "length-fraction "
969                "neutral-direction "
970                "no-stem-extend "
971                "note-heads "
972                "positioning-done "
973                "rests "
974                "stem-end-position "
975                "stem-info "
976                "stemlet-length "
977                "stroke-style "
978                "thickness "
979                "tremolo-flag "
980                );
981
982 /****************************************************************/
983
984 Stem_info::Stem_info ()
985 {
986   ideal_y_ = shortest_y_ = 0;
987   dir_ = CENTER;
988 }
989
990 void
991 Stem_info::scale (Real x)
992 {
993   ideal_y_ *= x;
994   shortest_y_ *= x;
995 }