]> 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   me->set_property ("positioning-done", SCM_BOOL_T);
418   
419   extract_grob_set (me, "note-heads", ro_heads);
420   vector<Grob*> heads (ro_heads);
421   vector_sort (heads, position_less);
422   Direction dir = get_grob_direction (me);
423
424   if (dir < 0)
425     reverse (heads);
426
427   Real thick = thickness (me);
428
429   Grob *hed = support_head (me);
430   if (!dir)
431     {
432       programming_error ("Stem dir must be up or down.");
433       dir = UP;
434       set_grob_direction (me, dir);
435     }
436
437   bool is_harmonic_centered = false;
438   for (vsize i = 0; i < heads.size (); i++)
439     is_harmonic_centered = is_harmonic_centered 
440       || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
441   is_harmonic_centered = is_harmonic_centered && is_invisible (me);
442
443   Real w = hed->extent (hed, X_AXIS)[dir];
444   for (vsize i = 0; i < heads.size (); i++)
445     {
446       Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
447
448       if (is_harmonic_centered)
449         amount =
450           hed->extent (hed, X_AXIS).linear_combination (CENTER)
451           - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
452       
453       heads[i]->translate_axis (amount, X_AXIS);
454     }
455   bool parity = true;
456   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
457   for (vsize i = 1; i < heads.size (); i++)
458     {
459       Real p = Staff_symbol_referencer::get_position (heads[i]);
460       Real dy = fabs (lastpos- p);
461
462       /*
463         dy should always be 0.5, 0.0, 1.0, but provide safety margin
464         for rounding errors.
465       */
466       if (dy < 1.1)
467         {
468           if (parity)
469             {
470               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
471
472               Direction d = get_grob_direction (me);
473               /*
474                 Reversed head should be shifted ell-thickness, but this
475                 looks too crowded, so we only shift ell-0.5*thickness.
476
477                 This leads to assymetry: Normal heads overlap the
478                 stem 100% whereas reversed heads only overlaps the
479                 stem 50%
480               */
481
482               Real reverse_overlap = 0.5;
483               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
484                                         X_AXIS);
485
486               if (is_invisible (me))
487                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
488                                           X_AXIS);
489
490               /* TODO:
491
492               For some cases we should kern some more: when the
493               distance between the next or prev note is too large, we'd
494               get large white gaps, eg.
495
496               |
497               X|
498               |X  <- kern this.
499               |
500               X
501
502               */
503             }
504           parity = !parity;
505         }
506       else
507         parity = true;
508
509       lastpos = int (p);
510     }
511
512   return SCM_BOOL_T;
513 }
514
515 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
516 SCM
517 Stem::calc_direction (SCM smob)
518 {
519   Grob *me = unsmob_grob (smob);
520   Direction dir = CENTER;
521   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
522     {
523       SCM ignore_me = beam->get_property ("direction");
524       (void) ignore_me;
525       dir = get_grob_direction (me);
526     }
527   else
528     {
529       SCM dd = me->get_property ("default-direction");
530       dir = to_dir (dd);
531       if (!dir)
532         return me->get_property ("neutral-direction");
533     }
534   
535   return scm_from_int (dir);
536 }
537
538 MAKE_SCHEME_CALLBACK(Stem, calc_default_direction, 1);
539 SCM
540 Stem::calc_default_direction (SCM smob)
541 {
542   Grob *me = unsmob_grob (smob);
543
544   Direction dir = CENTER;
545   int staff_center = 0;
546   Interval hp = head_positions (me);
547   if (!hp.is_empty ())
548     {
549       int udistance = (int) (UP * hp[UP] - staff_center);
550       int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
551       
552       dir = Direction (sign (ddistance - udistance));
553     }
554   
555   return scm_from_int (dir);
556 }
557
558
559 MAKE_SCHEME_CALLBACK (Stem, height, 1);
560 SCM
561 Stem::height (SCM smob)
562 {
563   Grob *me = unsmob_grob (smob);
564   if (!is_normal_stem (me))
565     return ly_interval2scm (Interval ());
566   
567   Direction dir = get_grob_direction (me);
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     {
916       height_of_my_trem
917         = Stem_tremolo::vertical_length (trem)
918         /* hack a bit of space around the trem. */
919         + beam_translation;
920     }
921
922   
923   /* UGH
924      It seems that also for ideal minimum length, we must use
925      the maximum beam count (for this direction):
926
927      \score{ \notes\relative c''{ [a8 a32] }}
928
929      must be horizontal. */
930   Real height_of_my_beams = beam_thickness
931     + (beam_count - 1) * beam_translation;
932
933   Real ideal_minimum_length = ideal_minimum_free
934     + height_of_my_beams
935     + height_of_my_trem
936     /* stem only extends to center of beam */
937     - 0.5 * beam_thickness;
938
939   ideal_length = max (ideal_length, ideal_minimum_length);
940
941   /* Convert to Y position, calculate for dir == UP */
942   Real note_start
943     =     /* staff positions */
944     head_positions (me)[my_dir] * 0.5
945     * my_dir * staff_space;
946   Real ideal_y = note_start + ideal_length;
947
948   /* Conditions for Y position */
949
950   /* Lowest beam of (UP) beam must never be lower than second staffline
951
952   Reference?
953
954   Although this (additional) rule is probably correct,
955   I expect that highest beam (UP) should also never be lower
956   than middle staffline, just as normal stems.
957
958   Reference?
959
960   Obviously not for grace beams.
961
962   Also, not for knees.  Seems to be a good thing. */
963   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
964   bool is_knee = to_boolean (beam->get_property ("knee"));
965   if (!no_extend_b && !is_knee)
966     {
967       /* Highest beam of (UP) beam must never be lower than middle
968          staffline */
969       ideal_y = max (ideal_y, 0.0);
970       /* Lowest beam of (UP) beam must never be lower than second staffline */
971       ideal_y = max (ideal_y, (-staff_space
972                                - beam_thickness + height_of_my_beams));
973     }
974
975   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
976
977   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
978                                  details));
979   
980   Real minimum_free
981     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
982     * staff_space
983     * length_fraction;
984
985   Real minimum_length = max (minimum_free, height_of_my_trem)
986     + height_of_my_beams
987     /* stem only extends to center of beam */
988     - 0.5 * beam_thickness;
989
990   ideal_y *= my_dir;
991   Real minimum_y = note_start + minimum_length;
992   Real shortest_y = minimum_y * my_dir;
993
994   return scm_list_2 (scm_from_double (ideal_y),
995                      scm_from_double (shortest_y));
996 }
997
998 Slice
999 Stem::beam_multiplicity (Grob *stem)
1000 {
1001   SCM beaming = stem->get_property ("beaming");
1002   Slice le = int_list_to_slice (scm_car (beaming));
1003   Slice ri = int_list_to_slice (scm_cdr (beaming));
1004   le.unite (ri);
1005   return le;
1006 }
1007
1008 bool
1009 Stem::is_cross_staff (Grob *stem)
1010 {
1011   Grob *beam = unsmob_grob (stem->get_object ("beam"));
1012   return beam && Beam::is_cross_staff (beam);
1013 }
1014
1015 MAKE_SCHEME_CALLBACK (Stem, cross_staff, 1)
1016 SCM
1017 Stem::cross_staff (SCM smob)
1018 {
1019   return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1020 }
1021
1022 /* FIXME:  Too many properties  */
1023 ADD_INTERFACE (Stem,
1024                "The stem represent the graphical stem.  "
1025                "In addition, it internally connects note heads, beams and"
1026                "tremolos. "
1027                "Rests and whole notes have invisible stems."
1028
1029                "\n\nThe following properties may be set in the details list." 
1030                "@table @code\n"
1031                "@item  beamed-lengths \n"
1032                "list of stem lengths given beam multiplicity. \n"
1033                "@item beamed-minimum-free-lengths \n"
1034                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1035                "@item beamed-extreme-minimum-free-lengths\n"
1036                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1037                "@item lengths\n"
1038                "Default stem lengths. The list gives a length for each flag-count.\n"
1039                "@item stem-shorten\n"
1040                "How much a stem in a forced direction "
1041                "should be shortened. The list gives an amount depending on the number "
1042                "of flags/beams."
1043                "@end table\n"
1044                ,
1045
1046                /* properties */
1047                "avoid-note-head "
1048                "beam "
1049                "beaming "
1050                "default-direction "
1051                "details "
1052                "direction "
1053                "duration-log "
1054                "flag-style "
1055                "french-beaming "
1056                "length "
1057                "length-fraction "
1058                "max-beam-connect "
1059                "neutral-direction "
1060                "no-stem-extend "
1061                "note-heads "
1062                "positioning-done "
1063                "rests "
1064                "stem-end-position "
1065                "stem-info "
1066                "stemlet-length "
1067                "stroke-style "
1068                "thickness "
1069                "tremolo-flag "
1070                );
1071
1072 /****************************************************************/
1073
1074 Stem_info::Stem_info ()
1075 {
1076   ideal_y_ = shortest_y_ = 0;
1077   dir_ = CENTER;
1078 }
1079
1080 void
1081 Stem_info::scale (Real x)
1082 {
1083   ideal_y_ *= x;
1084   shortest_y_ *= x;
1085 }