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