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