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