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