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