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