]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
fix tuplet example
[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   if (!is_normal_stem (me))
561     return ly_interval2scm (Interval ());
562   
563   Direction dir = get_grob_direction (me);
564   
565   Grob *beam = get_beam (me);
566   if (beam)
567     {
568       /* trigger set-stem-lengths. */
569       beam->get_property ("quantized-positions");
570     }
571
572   /*
573     Can't get_stencil(), since that would cache stencils too early.
574     This causes problems with beams.
575    */
576   Stencil *stencil = unsmob_stencil (print (smob));
577   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
578   if (beam)
579     {
580       if (dir == CENTER)
581         {
582           programming_error ("no stem direction");
583           dir = UP;
584         }
585       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
586     }
587
588   return ly_interval2scm (iv);
589 }
590
591 Real
592 Stem::stem_end_position (Grob *me)
593 {
594   return robust_scm2double (me->get_property ("stem-end-position"), 0);
595 }
596
597 Stencil
598 Stem::flag (Grob *me)
599 {
600   int log = duration_log (me);
601   if (log < 3
602       || unsmob_grob (me->get_object ("beam")))
603     return Stencil ();
604
605   /*
606     TODO: maybe property stroke-style should take different values,
607     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
608     '() or "grace").  */
609   string flag_style;
610
611   SCM flag_style_scm = me->get_property ("flag-style");
612   if (scm_is_symbol (flag_style_scm))
613     flag_style = ly_symbol2string (flag_style_scm);
614
615   if (flag_style == "no-flag")
616     return Stencil ();
617
618   bool adjust = true;
619
620   string staffline_offs;
621   if (flag_style == "mensural")
622     /* Mensural notation: For notes on staff lines, use different
623        flags than for notes between staff lines.  The idea is that
624        flags are always vertically aligned with the staff lines,
625        regardless if the note head is on a staff line or between two
626        staff lines.  In other words, the inner end of a flag always
627        touches a staff line.
628     */
629     {
630       if (adjust)
631         {
632           int p = (int) (rint (stem_end_position (me)));
633           staffline_offs
634             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
635         }
636       else
637         staffline_offs = "2";
638     }
639   else
640     staffline_offs = "";
641
642   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
643   string font_char = flag_style
644     + to_string (dir) + staffline_offs + to_string (log);
645   Font_metric *fm = Font_interface::get_default_font (me);
646   Stencil flag = fm->find_by_name ("flags." + font_char);
647   if (flag.is_empty ())
648     me->warning (_f ("flag `%s' not found", font_char));
649
650   SCM stroke_style_scm = me->get_property ("stroke-style");
651   if (scm_is_string (stroke_style_scm))
652     {
653       string stroke_style = ly_scm2string (stroke_style_scm);
654       if (!stroke_style.empty ())
655         {
656           string font_char = to_string (dir) + stroke_style;
657           Stencil stroke = fm->find_by_name ("flags." + font_char);
658           if (stroke.is_empty ())
659             me->warning (_f ("flag stroke `%s' not found", font_char));
660           else
661             flag.add_stencil (stroke);
662         }
663     }
664
665   return flag;
666 }
667
668 MAKE_SCHEME_CALLBACK (Stem, width, 1);
669 SCM
670 Stem::width (SCM e)
671 {
672   Grob *me = unsmob_grob (e);
673
674   Interval r;
675
676   if (is_invisible (me))
677     r.set_empty ();
678   else if (unsmob_grob (me->get_object ("beam"))
679            || abs (duration_log (me)) <= 2)
680     {
681       r = Interval (-1, 1);
682       r *= thickness (me) / 2;
683     }
684   else
685     {
686       r = Interval (-1, 1) * thickness (me) * 0.5;
687       r.unite (flag (me).extent (X_AXIS));
688     }
689   return ly_interval2scm (r);
690 }
691
692 Real
693 Stem::thickness (Grob *me)
694 {
695   return scm_to_double (me->get_property ("thickness"))
696     * Staff_symbol_referencer::line_thickness (me);
697 }
698
699 MAKE_SCHEME_CALLBACK (Stem, print, 1);
700 SCM
701 Stem::print (SCM smob)
702 {
703   Grob *me = unsmob_grob (smob);
704   Grob *beam = get_beam (me);
705     
706   Stencil mol;
707   Direction d = get_grob_direction (me);
708
709   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
710                                            0.0);
711   bool stemlet = stemlet_length > 0.0;
712
713   /* TODO: make the stem start a direction ?
714      This is required to avoid stems passing in tablature chords.  */
715   Grob *lh
716     = to_boolean (me->get_property ("avoid-note-head"))
717     ? last_head (me)
718     : first_head (me);
719
720   if (!lh && !stemlet)
721     return SCM_EOL;
722
723   if (!lh && stemlet && !beam)
724     return SCM_EOL;
725
726   if (is_invisible (me))
727     return SCM_EOL;
728
729   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
730   Real y1 = y2;
731   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
732
733   if (lh)
734     y2 = Staff_symbol_referencer::get_position (lh);
735   else if (stemlet)
736     {
737       Real beam_translation = Beam::get_beam_translation (beam);
738       Real beam_thickness = Beam::get_thickness (beam);
739       int beam_count = beam_multiplicity (me).length () + 1;
740
741       y2 -= d
742         * (0.5 * beam_thickness
743            + beam_translation * max (0, (beam_count - 1))
744            + stemlet_length) / half_space;
745     }
746
747   Interval stem_y (min (y1, y2), max (y2, y1));
748
749   if (Grob *head = support_head (me))
750     {
751       /*
752         must not take ledgers into account.
753       */
754       Interval head_height = head->extent (head, Y_AXIS);
755       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
756
757       y_attach = head_height.linear_combination (y_attach);
758       stem_y[Direction (-d)] += d * y_attach / half_space;
759     }
760
761   // URG
762   Real stem_width = thickness (me);
763   Real blot
764     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
765
766   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
767                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
768
769   Stencil ss = Lookup::round_filled_box (b, blot);
770   mol.add_stencil (ss);
771
772   mol.add_stencil (get_translated_flag (me));
773
774   return mol.smobbed_copy ();
775 }
776
777 Stencil
778 Stem::get_translated_flag (Grob *me)
779 {
780   Stencil fl = flag (me);
781   if (!fl.is_empty ())
782     {
783       Direction d = get_grob_direction (me);
784       Real blot
785         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
786       Real stem_width = thickness (me);
787       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
788       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
789       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
790       fl.translate_axis (stem_width / 2, X_AXIS);
791     }
792   return fl;
793 }
794
795
796 /*
797   move the stem to right of the notehead if it is up.
798 */
799 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
800 SCM
801 Stem::offset_callback (SCM smob)
802 {
803   Grob *me = unsmob_grob (smob);
804
805   extract_grob_set (me, "rests", rests);
806   if (rests.size ())
807     {
808       Grob *rest = rests.back ();
809       Real r = rest->extent (rest, X_AXIS).center ();
810       return scm_from_double (r);
811     }
812
813   
814   if (Grob *f = first_head (me))
815     {
816       Interval head_wid = f->extent (f, X_AXIS);
817       Real attach = 0.0;
818
819       if (is_invisible (me))
820         attach = 0.0;
821       else
822         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
823
824       Direction d = get_grob_direction (me);
825       Real real_attach = head_wid.linear_combination (d * attach);
826       Real r = real_attach;
827
828       /* If not centered: correct for stem thickness.  */
829       if (attach)
830         {
831           Real rule_thick = thickness (me);
832           r += -d * rule_thick * 0.5;
833         }
834       return scm_from_double (r);
835     }
836
837   programming_error ("Weird stem.");
838   return scm_from_double (0.0);
839 }
840
841 Spanner *
842 Stem::get_beam (Grob *me)
843 {
844   SCM b = me->get_object ("beam");
845   return dynamic_cast<Spanner *> (unsmob_grob (b));
846 }
847
848 Stem_info
849 Stem::get_stem_info (Grob *me)
850 {
851   Stem_info si;
852   si.dir_ = get_grob_direction (me);
853   
854   SCM scm_info = me->get_property ("stem-info");
855   si.ideal_y_ = scm_to_double (scm_car (scm_info));
856   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
857   return si;
858 }
859
860 MAKE_SCHEME_CALLBACK(Stem, calc_stem_info, 1);
861 SCM
862 Stem::calc_stem_info (SCM smob)
863 {
864   Grob *me = unsmob_grob (smob);
865   Direction my_dir = get_grob_direction (me);
866
867   if (!my_dir)
868     {
869       programming_error ("no stem dir set");
870       my_dir = UP;
871     }
872
873   Real staff_space = Staff_symbol_referencer::staff_space (me);
874   Grob *beam = get_beam (me);
875
876   if (beam)
877     {
878       (void) beam->get_property ("beaming");
879     }
880   
881   Real beam_translation = Beam::get_beam_translation (beam);
882   Real beam_thickness = Beam::get_thickness (beam);
883   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
884   Real length_fraction
885     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
886
887   /* Simple standard stem length */
888   SCM details = me->get_property ("details");
889   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
890   
891   Real ideal_length
892     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
893     * staff_space
894     * length_fraction
895     
896     /* stem only extends to center of beam
897      */
898     - 0.5 * beam_thickness;
899
900   /* Condition: sane minimum free stem length (chord to beams) */
901   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
902
903   Real ideal_minimum_free
904     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
905     * staff_space
906     * length_fraction;
907
908   Real height_of_my_trem = 0.0;
909   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
910   if (trem)
911     {
912       height_of_my_trem
913         = Stem_tremolo::vertical_length (trem)
914         /* hack a bit of space around the trem. */
915         + beam_translation;
916     }
917
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 /* FIXME:  Too many properties  */
1005 ADD_INTERFACE (Stem,
1006                "The stem represent the graphical stem.  "
1007                "In addition, it internally connects note heads, beams and"
1008                "tremolos. "
1009                "Rests and whole notes have invisible stems."
1010
1011                "\n\nThe following properties may be set in the details list." 
1012                "@table @code\n"
1013                "@item  beamed-lengths \n"
1014                "list of stem lengths given beam multiplicity. \n"
1015                "@item beamed-minimum-free-lengths \n"
1016                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1017                "@item beamed-extreme-minimum-free-lengths\n"
1018                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1019                "@item lengths\n"
1020                "Default stem lengths. The list gives a length for each flag-count.\n"
1021                "@item stem-shorten\n"
1022                "How much a stem in a forced direction "
1023                "should be shortened. The list gives an amount depending on the number "
1024                "of flags/beams."
1025                "@end table\n"
1026                ,
1027
1028                /* properties */
1029                "avoid-note-head "
1030                "beam "
1031                "beaming "
1032                "default-direction "
1033                "details "
1034                "direction "
1035                "duration-log "
1036                "flag-style "
1037                "french-beaming "
1038                "length "
1039                "length-fraction "
1040                "max-beam-connect "
1041                "neutral-direction "
1042                "no-stem-extend "
1043                "note-heads "
1044                "positioning-done "
1045                "rests "
1046                "stem-end-position "
1047                "stem-info "
1048                "stemlet-length "
1049                "stroke-style "
1050                "thickness "
1051                "tremolo-flag "
1052                );
1053
1054 /****************************************************************/
1055
1056 Stem_info::Stem_info ()
1057 {
1058   ideal_y_ = shortest_y_ = 0;
1059   dir_ = CENTER;
1060 }
1061
1062 void
1063 Stem_info::scale (Real x)
1064 {
1065   ideal_y_ *= x;
1066   shortest_y_ *= x;
1067 }