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