]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
(stem_dir_correction): don't inspect
[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 = unsmob_grob (me->get_object ("beam")))
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   Stencil mol;
688   Direction d = get_grob_direction (me);
689
690   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
691                                            0.0);
692   bool stemlet = stemlet_length > 0.0;
693
694   /* TODO: make the stem start a direction ?
695      This is required to avoid stems passing in tablature chords.  */
696   Grob *lh
697     = to_boolean (me->get_property ("avoid-note-head"))
698     ? last_head (me)
699     : first_head (me);
700   Grob *beam = get_beam (me);
701
702   if (!lh && !stemlet)
703     return SCM_EOL;
704
705   if (!lh && stemlet && !beam)
706     return SCM_EOL;
707
708   if (is_invisible (me))
709     return SCM_EOL;
710
711   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
712   Real y1 = y2;
713   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
714
715   if (lh)
716     y2 = Staff_symbol_referencer::get_position (lh);
717   else if (stemlet)
718     {
719       Real beam_translation = Beam::get_beam_translation (beam);
720       Real beam_thickness = Beam::get_thickness (beam);
721       int beam_count = beam_multiplicity (me).length () + 1;
722
723       y2 -= d
724         * (0.5 * beam_thickness
725            + beam_translation * max (0, (beam_count - 1))
726            + stemlet_length) / half_space;
727     }
728
729   Interval stem_y (min (y1, y2), max (y2, y1));
730
731   if (Grob *head = support_head (me))
732     {
733       /*
734         must not take ledgers into account.
735       */
736       Interval head_height = head->extent (head, Y_AXIS);
737       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
738
739       y_attach = head_height.linear_combination (y_attach);
740       stem_y[Direction (-d)] += d * y_attach / half_space;
741     }
742
743   // URG
744   Real stem_width = thickness (me);
745   Real blot
746     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
747
748   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
749                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
750
751   Stencil ss = Lookup::round_filled_box (b, blot);
752   mol.add_stencil (ss);
753
754   mol.add_stencil (get_translated_flag (me));
755
756   return mol.smobbed_copy ();
757 }
758
759 Stencil
760 Stem::get_translated_flag (Grob *me)
761 {
762   Stencil fl = flag (me);
763   if (!fl.is_empty ())
764     {
765       Direction d = get_grob_direction (me);
766       Real blot
767         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
768       Real stem_width = thickness (me);
769       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
770       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
771       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
772       fl.translate_axis (stem_width / 2, X_AXIS);
773     }
774   return fl;
775 }
776
777
778 /*
779   move the stem to right of the notehead if it is up.
780 */
781 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
782 SCM
783 Stem::offset_callback (SCM smob)
784 {
785   Grob *me = unsmob_grob (smob);
786   Real r = 0.0;
787
788   if (Grob *f = first_head (me))
789     {
790       Interval head_wid = f->extent (f, X_AXIS);
791       Real attach = 0.0;
792
793       if (is_invisible (me))
794         attach = 0.0;
795       else
796         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
797
798       Direction d = get_grob_direction (me);
799       Real real_attach = head_wid.linear_combination (d * attach);
800       r = real_attach;
801
802       /* If not centered: correct for stem thickness.  */
803       if (attach)
804         {
805           Real rule_thick = thickness (me);
806           r += -d * rule_thick * 0.5;
807         }
808     }
809   else
810     {
811       extract_grob_set (me, "rests", rests);
812       if (rests.size ())
813         {
814           Grob *rest = rests.back ();
815           r = rest->extent (rest, X_AXIS).center ();
816         }
817     }
818   return scm_from_double (r);
819 }
820
821 Spanner *
822 Stem::get_beam (Grob *me)
823 {
824   SCM b = me->get_object ("beam");
825   return dynamic_cast<Spanner *> (unsmob_grob (b));
826 }
827
828 Stem_info
829 Stem::get_stem_info (Grob *me)
830 {
831   Stem_info si;
832   si.dir_ = get_grob_direction (me);
833   
834   SCM scm_info = me->get_property ("stem-info");
835   si.ideal_y_ = scm_to_double (scm_car (scm_info));
836   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
837   return si;
838 }
839
840 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
841 SCM
842 Stem::calc_stem_info (SCM smob)
843 {
844   Grob *me = unsmob_grob (smob);
845   Direction my_dir = get_grob_direction (me);
846
847   if (!my_dir)
848     {
849       programming_error ("no stem dir set");
850       my_dir = UP;
851     }
852
853   Real staff_space = Staff_symbol_referencer::staff_space (me);
854   Grob *beam = get_beam (me);
855
856   if (beam)
857     {
858       (void) beam->get_property ("beaming");
859     }
860   
861   Real beam_translation = Beam::get_beam_translation (beam);
862   Real beam_thickness = Beam::get_thickness (beam);
863   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
864   Real length_fraction
865     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
866
867   /* Simple standard stem length */
868   SCM details = me->get_property ("details");
869   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
870   
871   Real ideal_length
872     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
873     * staff_space
874     * length_fraction
875     
876     /* stem only extends to center of beam
877      */
878     - 0.5 * beam_thickness;
879
880   /* Condition: sane minimum free stem length (chord to beams) */
881   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
882
883   Real ideal_minimum_free
884     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
885     * staff_space
886     * length_fraction;
887
888   Real height_of_my_trem = 0.0;
889   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
890   if (trem)
891       height_of_my_trem = trem->extent (trem, Y_AXIS).length ()
892         /* hack a bit of space around the trem. */
893         + beam_translation;
894
895   /* UGH
896      It seems that also for ideal minimum length, we must use
897      the maximum beam count (for this direction):
898
899      \score{ \notes\relative c''{ [a8 a32] }}
900
901      must be horizontal. */
902   Real height_of_my_beams = beam_thickness
903     + (beam_count - 1) * beam_translation;
904
905   Real ideal_minimum_length = ideal_minimum_free
906     + height_of_my_beams
907     + height_of_my_trem
908     /* stem only extends to center of beam */
909     - 0.5 * beam_thickness;
910
911   ideal_length = max (ideal_length, ideal_minimum_length);
912
913   /* Convert to Y position, calculate for dir == UP */
914   Real note_start
915     =     /* staff positions */
916     head_positions (me)[my_dir] * 0.5
917     * my_dir * staff_space;
918   Real ideal_y = note_start + ideal_length;
919
920   /* Conditions for Y position */
921
922   /* Lowest beam of (UP) beam must never be lower than second staffline
923
924   Reference?
925
926   Although this (additional) rule is probably correct,
927   I expect that highest beam (UP) should also never be lower
928   than middle staffline, just as normal stems.
929
930   Reference?
931
932   Obviously not for grace beams.
933
934   Also, not for knees.  Seems to be a good thing. */
935   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
936   bool is_knee = to_boolean (beam->get_property ("knee"));
937   if (!no_extend_b && !is_knee)
938     {
939       /* Highest beam of (UP) beam must never be lower than middle
940          staffline */
941       ideal_y = max (ideal_y, 0.0);
942       /* Lowest beam of (UP) beam must never be lower than second staffline */
943       ideal_y = max (ideal_y, (-staff_space
944                                - beam_thickness + height_of_my_beams));
945     }
946
947   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
948
949   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
950                                  details));
951   
952   Real minimum_free
953     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
954     * staff_space
955     * length_fraction;
956
957   Real minimum_length = max (minimum_free, height_of_my_trem)
958     + height_of_my_beams
959     /* stem only extends to center of beam */
960     - 0.5 * beam_thickness;
961
962   ideal_y *= my_dir;
963   Real minimum_y = note_start + minimum_length;
964   Real shortest_y = minimum_y * my_dir;
965
966   return scm_list_2 (scm_from_double (ideal_y),
967                      scm_from_double (shortest_y));
968 }
969
970 Slice
971 Stem::beam_multiplicity (Grob *stem)
972 {
973   SCM beaming = stem->get_property ("beaming");
974   Slice le = int_list_to_slice (scm_car (beaming));
975   Slice ri = int_list_to_slice (scm_cdr (beaming));
976   le.unite (ri);
977   return le;
978 }
979
980 /* FIXME:  Too many properties  */
981 ADD_INTERFACE (Stem, "stem-interface",
982                "The stem represent the graphical stem.  "
983                "In addition, it internally connects note heads, beams and"
984                "tremolos. "
985                "Rests and whole notes have invisible stems."
986
987                "\n\nThe following properties may be set in the details list." 
988                "@table @code\n"
989                "@item  beamed-lengths \n"
990                "list of stem lengths given beam multiplicity. \n"
991                "@item beamed-minimum-free-lengths \n"
992                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
993                "@item beamed-extreme-minimum-free-lengths\n"
994                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
995                "@item lengths\n"
996                "Default stem lengths. The list gives a length for each flag-count.\n"
997                "@item stem-shorten\n"
998                "How much a stem in a forced direction "
999                "should be shortened. The list gives an amount depending on the number "
1000                "of flags/beams."
1001                "@end table\n"
1002                ,
1003
1004                /* properties */
1005                "avoid-note-head "
1006                "beam "
1007                "beaming "
1008                "default-direction "
1009                "details "
1010                "direction "
1011                "duration-log "
1012                "flag-style "
1013                "french-beaming "
1014                "length "
1015                "length-fraction "
1016                "max-beam-connect "
1017                "neutral-direction "
1018                "no-stem-extend "
1019                "note-heads "
1020                "positioning-done "
1021                "rests "
1022                "stem-end-position "
1023                "stem-info "
1024                "stemlet-length "
1025                "stroke-style "
1026                "thickness "
1027                "tremolo-flag "
1028                );
1029
1030 /****************************************************************/
1031
1032 Stem_info::Stem_info ()
1033 {
1034   ideal_y_ = shortest_y_ = 0;
1035   dir_ = CENTER;
1036 }
1037
1038 void
1039 Stem_info::scale (Real x)
1040 {
1041   ideal_y_ *= x;
1042   shortest_y_ *= x;
1043 }