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