]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
d50bd50cda02d8de223bbfd97e31f33f3c0f0648
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include "stem.hh"
17 #include "spanner.hh"
18
19 #include <cmath>                // rint
20 using namespace std;
21
22 #include "beam.hh"
23 #include "directional-element-interface.hh"
24 #include "dot-column.hh"
25 #include "font-interface.hh"
26 #include "international.hh"
27 #include "lookup.hh"
28 #include "misc.hh"
29 #include "note-head.hh"
30 #include "output-def.hh"
31 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #include "rest.hh"
34 #include "rhythmic-head.hh"
35 #include "side-position-interface.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem-tremolo.hh"
38 #include "warn.hh"
39
40 void
41 Stem::set_beaming (Grob *me, int beam_count, Direction d)
42 {
43   SCM pair = me->get_property ("beaming");
44
45   if (!scm_is_pair (pair))
46     {
47       pair = scm_cons (SCM_EOL, SCM_EOL);
48       me->set_property ("beaming", pair);
49     }
50
51   SCM lst = index_get_cell (pair, d);
52   if (beam_count)
53     for (int i = 0; i < beam_count; i++)
54       lst = scm_cons (scm_from_int (i), lst);
55   else
56     lst = SCM_BOOL_F;
57   
58   index_set_cell (pair, d, lst);
59 }
60
61 int
62 Stem::get_beaming (Grob *me, Direction d)
63 {
64   SCM pair = me->get_property ("beaming");
65   if (!scm_is_pair (pair))
66     return 0;
67
68   SCM lst = index_get_cell (pair, d);
69
70   int len = scm_ilength (lst);
71   return max (len, 0);
72 }
73
74 Interval
75 Stem::head_positions (Grob *me)
76 {
77   if (head_count (me))
78     {
79       Drul_array<Grob *> e (extremal_heads (me));
80       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
81                        Staff_symbol_referencer::get_position (e[UP]));
82     }
83   return Interval ();
84 }
85
86 Real
87 Stem::chord_start_y (Grob *me)
88 {
89   Interval hp = head_positions (me);
90   if (!hp.is_empty ())
91     return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
92       * 0.5;
93   return 0;
94 }
95
96
97
98 void
99 Stem::set_stemend (Grob *me, Real se)
100 {
101   // todo: margins
102   Direction d = get_grob_direction (me);
103
104   if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
105     me->warning (_ ("weird stem size, check for narrow beams"));
106
107   me->set_property ("stem-end-position", scm_from_double (se));
108 }
109
110 /* Note head that determines hshift for upstems
111    WARNING: triggers direction  */
112 Grob *
113 Stem::support_head (Grob *me)
114 {
115   extract_grob_set (me, "note-heads", heads);
116   if (heads.size () == 1)
117     return heads[0];
118
119   return first_head (me);
120 }
121
122 int
123 Stem::head_count (Grob *me)
124 {
125   return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
126 }
127
128 /* The note head which forms one end of the stem.
129    WARNING: triggers direction  */
130 Grob *
131 Stem::first_head (Grob *me)
132 {
133   Direction d = get_grob_direction (me);
134   if (d)
135     return extremal_heads (me)[-d];
136   return 0;
137 }
138
139 /* The note head opposite to the first head.  */
140 Grob *
141 Stem::last_head (Grob *me)
142 {
143   Direction d = get_grob_direction (me);
144   if (d)
145     return extremal_heads (me)[d];
146   return 0;
147 }
148
149 /*
150   START is part where stem reaches `last' head.
151
152   This function returns a drul with (bottom-head, top-head).
153 */
154 Drul_array<Grob *>
155 Stem::extremal_heads (Grob *me)
156 {
157   const int inf = INT_MAX;
158   Drul_array<int> extpos;
159   extpos[DOWN] = inf;
160   extpos[UP] = -inf;
161
162   Drul_array<Grob *> exthead (0, 0);
163   extract_grob_set (me, "note-heads", heads);
164
165   for (vsize i = heads.size (); i--;)
166     {
167       Grob *n = heads[i];
168       int p = Staff_symbol_referencer::get_rounded_position (n);
169
170       Direction d = LEFT;
171       do
172         {
173           if (d * p > d * extpos[d])
174             {
175               exthead[d] = n;
176               extpos[d] = p;
177             }
178         }
179       while (flip (&d) != DOWN);
180     }
181   return exthead;
182 }
183
184 /* The positions, in ascending order.  */
185 vector<int>
186 Stem::note_head_positions (Grob *me)
187 {
188   vector<int> ps;
189   extract_grob_set (me, "note-heads", heads);
190
191   for (vsize i = heads.size (); i--;)
192     {
193       Grob *n = heads[i];
194       int p = Staff_symbol_referencer::get_rounded_position (n);
195
196       ps.push_back (p);
197     }
198
199   vector_sort (ps, less<int> ());
200   return ps;
201 }
202
203 void
204 Stem::add_head (Grob *me, Grob *n)
205 {
206   n->set_object ("stem", me->self_scm ());
207
208   if (Note_head::has_interface (n))
209     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
210   else if (Rest::has_interface (n))
211     Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
212 }
213
214 bool
215 Stem::is_invisible (Grob *me)
216 {
217   return !is_normal_stem (me)
218     && (robust_scm2double (me->get_property ("stemlet-length"),
219                            0.0) == 0.0);
220 }
221
222
223 bool
224 Stem::is_normal_stem (Grob *me)
225 {
226   return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
227 }
228
229
230 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
231 SCM
232 Stem::pure_height (SCM smob, SCM start, SCM end)
233 {
234   (void) start;
235   (void) end;
236
237   Grob *me = unsmob_grob (smob);
238   Interval iv;
239
240   if (!is_normal_stem (me))
241     return ly_interval2scm (iv);
242
243   Real ss = Staff_symbol_referencer::staff_space (me);
244   Real rad = Staff_symbol_referencer::staff_radius (me);
245
246   if (!to_boolean (me->get_property ("cross-staff")))
247     {
248       Real len = scm_to_double (calc_length (smob)) * ss / 2;
249       Direction dir = get_grob_direction (me);
250
251       Interval hp = head_positions (me);
252       if (dir == UP)
253         iv = Interval (0, len);
254       else
255         iv = Interval (-len, 0);
256
257       if (!hp.is_empty ())
258         iv.translate (hp[dir] * ss / 2);
259
260       /* extend the stem (away from the head) to cover the staff */
261       if (dir == UP)
262         iv[UP] = max (iv[UP], rad * ss);
263       else
264         iv[DOWN] = min (iv[DOWN], -rad * ss);
265     }
266   else
267     iv = Interval (-rad * ss, rad * ss);
268
269   return ly_interval2scm (iv);
270 }
271
272 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
273 SCM
274 Stem::calc_stem_end_position (SCM smob)
275 {
276   Grob *me = unsmob_grob (smob);
277
278   if (!head_count (me))
279     return scm_from_double (0.0);
280
281   if (Grob *beam = get_beam (me))
282     {
283       (void) beam->get_property ("quantized-positions");
284       return me->get_property ("stem-end-position");
285     }
286   
287   vector<Real> a;
288
289   /* WARNING: IN HALF SPACES */
290   Real length = robust_scm2double (me->get_property ("length"), 7);
291
292   Direction dir = get_grob_direction (me);
293   Interval hp = head_positions (me);
294   Real stem_end = dir ? hp[dir] + dir * length : 0;
295
296   /* TODO: change name  to extend-stems to staff/center/'()  */
297   bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
298   if (!no_extend && dir * stem_end < 0)
299     stem_end = 0.0;
300
301   return scm_from_double (stem_end);
302 }
303
304 /* Length is in half-spaces (or: positions) here. */
305 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
306 SCM
307 Stem::calc_length (SCM smob)
308 {
309   Grob *me = unsmob_grob (smob);
310   
311   SCM details = me->get_property ("details");
312   int durlog = duration_log (me);
313
314   Real ss = Staff_symbol_referencer::staff_space (me);
315   Real length = 7;
316   SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
317   if (scm_is_pair (s))
318     length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
319
320   Direction dir = get_grob_direction (me);
321
322   /* Stems in unnatural (forced) direction should be shortened,
323      according to [Roush & Gourlay] */
324   Interval hp = head_positions (me);
325   if (dir && dir * hp[dir] >= 0)
326     {
327       SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
328       SCM scm_shorten = scm_is_pair (sshorten)
329         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
330       Real shorten = 2* robust_scm2double (scm_shorten, 0);
331
332       /* On boundary: shorten only half */
333       if (abs (head_positions (me)[dir]) <= 1)
334         shorten *= 0.5;
335
336       length -= shorten;
337     }
338
339   length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
340
341   /* Tremolo stuff.  */
342   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
343   if (t_flag && !unsmob_grob (me->get_object ("beam")))
344     {
345       /* Crude hack: add extra space if tremolo flag is there.
346
347       We can't do this for the beam, since we get into a loop
348       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
349
350       Real minlen = 1.0
351         + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
352
353       /* We don't want to add the whole extent of the flag because the trem
354          and the flag can overlap partly. beam_translation gives a good
355          approximation */
356       if (durlog >= 3)
357         {
358           Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
359           /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
360           minlen += 2 * (durlog - 1.5) * beam_trans;
361
362           /* up-stems need even a little more space to avoid collisions. This
363              needs to be in sync with the tremolo positioning code in
364              Stem_tremolo::print */
365           if (dir == UP)
366             minlen += beam_trans;
367         }
368       length = max (length, minlen + 1.0);
369     }
370   
371   return scm_from_double (length);
372 }
373 /* The log of the duration (Number of hooks on the flag minus two)  */
374 int
375 Stem::duration_log (Grob *me)
376 {
377   SCM s = me->get_property ("duration-log");
378   return (scm_is_number (s)) ? scm_to_int (s) : 2;
379 }
380
381 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
382 SCM
383 Stem::calc_positioning_done (SCM smob)
384 {
385   Grob *me = unsmob_grob (smob);  
386   if (!head_count (me))
387     return SCM_BOOL_T;
388
389   me->set_property ("positioning-done", SCM_BOOL_T);
390   
391   extract_grob_set (me, "note-heads", ro_heads);
392   vector<Grob*> heads (ro_heads);
393   vector_sort (heads, position_less);
394   Direction dir = get_grob_direction (me);
395
396   if (dir < 0)
397     reverse (heads);
398
399   Real thick = thickness (me);
400
401   Grob *hed = support_head (me);
402   if (!dir)
403     {
404       programming_error ("Stem dir must be up or down.");
405       dir = UP;
406       set_grob_direction (me, dir);
407     }
408
409   bool is_harmonic_centered = false;
410   for (vsize i = 0; i < heads.size (); i++)
411     is_harmonic_centered = is_harmonic_centered 
412       || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
413   is_harmonic_centered = is_harmonic_centered && is_invisible (me);
414
415   Real w = hed->extent (hed, X_AXIS)[dir];
416   for (vsize i = 0; i < heads.size (); i++)
417     {
418       Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
419
420       if (is_harmonic_centered)
421         amount =
422           hed->extent (hed, X_AXIS).linear_combination (CENTER)
423           - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
424       
425       heads[i]->translate_axis (amount, X_AXIS);
426     }
427   bool parity = true;
428   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
429   for (vsize i = 1; i < heads.size (); i++)
430     {
431       Real p = Staff_symbol_referencer::get_position (heads[i]);
432       Real dy = fabs (lastpos- p);
433
434       /*
435         dy should always be 0.5, 0.0, 1.0, but provide safety margin
436         for rounding errors.
437       */
438       if (dy < 1.1)
439         {
440           if (parity)
441             {
442               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
443
444               Direction d = get_grob_direction (me);
445               /*
446                 Reversed head should be shifted ell-thickness, but this
447                 looks too crowded, so we only shift ell-0.5*thickness.
448
449                 This leads to assymetry: Normal heads overlap the
450                 stem 100% whereas reversed heads only overlaps the
451                 stem 50%
452               */
453
454               Real reverse_overlap = 0.5;
455               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
456                                         X_AXIS);
457
458               if (is_invisible (me))
459                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
460                                           X_AXIS);
461
462               /* TODO:
463
464               For some cases we should kern some more: when the
465               distance between the next or prev note is too large, we'd
466               get large white gaps, eg.
467
468               |
469               X|
470               |X  <- kern this.
471               |
472               X
473
474               */
475             }
476           parity = !parity;
477         }
478       else
479         parity = true;
480
481       lastpos = int (p);
482     }
483
484   return SCM_BOOL_T;
485 }
486
487 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
488 SCM
489 Stem::calc_direction (SCM smob)
490 {
491   Grob *me = unsmob_grob (smob);
492   Direction dir = CENTER;
493   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
494     {
495       SCM ignore_me = beam->get_property ("direction");
496       (void) ignore_me;
497       dir = get_grob_direction (me);
498     }
499   else
500     {
501       SCM dd = me->get_property ("default-direction");
502       dir = to_dir (dd);
503       if (!dir)
504         return me->get_property ("neutral-direction");
505     }
506   
507   return scm_from_int (dir);
508 }
509
510 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
511 SCM
512 Stem::calc_default_direction (SCM smob)
513 {
514   Grob *me = unsmob_grob (smob);
515
516   Direction dir = CENTER;
517   int staff_center = 0;
518   Interval hp = head_positions (me);
519   if (!hp.is_empty ())
520     {
521       int udistance = (int) (UP * hp[UP] - staff_center);
522       int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
523       
524       dir = Direction (sign (ddistance - udistance));
525     }
526   
527   return scm_from_int (dir);
528 }
529
530
531 MAKE_SCHEME_CALLBACK (Stem, height, 1);
532 SCM
533 Stem::height (SCM smob)
534 {
535   Grob *me = unsmob_grob (smob);
536   if (!is_normal_stem (me))
537     return ly_interval2scm (Interval ());
538   
539   Direction dir = get_grob_direction (me);
540   
541   Grob *beam = get_beam (me);
542   if (beam)
543     {
544       /* trigger set-stem-lengths. */
545       beam->get_property ("quantized-positions");
546     }
547
548   /*
549     Can't get_stencil (), since that would cache stencils too early.
550     This causes problems with beams.
551    */
552   Stencil *stencil = unsmob_stencil (print (smob));
553   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
554   if (beam)
555     {
556       if (dir == CENTER)
557         {
558           programming_error ("no stem direction");
559           dir = UP;
560         }
561       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
562     }
563
564   return ly_interval2scm (iv);
565 }
566
567 Real
568 Stem::stem_end_position (Grob *me)
569 {
570   return robust_scm2double (me->get_property ("stem-end-position"), 0);
571 }
572
573 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
574 SCM
575 Stem::calc_flag (SCM smob)
576 {
577   Grob *me = unsmob_grob (smob);
578
579   int log = duration_log (me);
580   /*
581     TODO: maybe property stroke-style should take different values,
582     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
583     '() or "grace").  */
584   string flag_style;
585
586    SCM flag_style_scm = me->get_property ("flag-style");
587   if (scm_is_symbol (flag_style_scm))
588     flag_style = ly_symbol2string (flag_style_scm);
589
590   if (flag_style == "no-flag")
591     return Stencil ().smobbed_copy ();
592
593   bool adjust = true;
594
595   string staffline_offs;
596   if (flag_style == "mensural")
597     /* Mensural notation: For notes on staff lines, use different
598        flags than for notes between staff lines.  The idea is that
599        flags are always vertically aligned with the staff lines,
600        regardless if the note head is on a staff line or between two
601        staff lines.  In other words, the inner end of a flag always
602        touches a staff line.
603     */
604     {
605       if (adjust)
606         {
607           int p = (int) (rint (stem_end_position (me)));
608           staffline_offs
609             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
610         }
611       else
612         staffline_offs = "2";
613      }
614   else
615     staffline_offs = "";
616
617   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
618   string font_char = flag_style
619     + to_string (dir) + staffline_offs + to_string (log);
620   Font_metric *fm = Font_interface::get_default_font (me);
621   Stencil flag = fm->find_by_name ("flags." + font_char);
622   if (flag.is_empty ())
623     me->warning (_f ("flag `%s' not found", font_char));
624
625   SCM stroke_style_scm = me->get_property ("stroke-style");
626   if (scm_is_string (stroke_style_scm))
627     {
628       string stroke_style = ly_scm2string (stroke_style_scm);
629       if (!stroke_style.empty ())
630         {
631           string font_char = flag_style + to_string (dir) + stroke_style;
632           Stencil stroke = fm->find_by_name ("flags." + font_char);
633           if (stroke.is_empty ())
634             {
635               font_char = to_string (dir) + stroke_style;
636               stroke = fm->find_by_name ("flags." + font_char);
637             }
638           if (stroke.is_empty ())
639             me->warning (_f ("flag stroke `%s' not found", font_char));
640           else
641             flag.add_stencil (stroke);
642         }
643      }
644
645   return flag.smobbed_copy ();
646 }
647
648
649 Stencil
650 Stem::flag (Grob *me)
651 {
652   int log = duration_log (me);
653   if (log < 3
654       || unsmob_grob (me->get_object ("beam")))
655     return Stencil ();
656
657   if (!is_normal_stem (me))
658     return Stencil ();
659
660   // This get_property call already evaluates the scheme function with
661   // the grob passed as argument! Thus, we only have to check if a valid
662   // stencil is returned.
663   SCM flag_style_scm = me->get_property ("flag");
664   if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
665     return *flag;
666   } else {
667     return Stencil ();
668   }
669 }
670
671 MAKE_SCHEME_CALLBACK (Stem, width, 1);
672 SCM
673 Stem::width (SCM e)
674 {
675   Grob *me = unsmob_grob (e);
676
677   Interval r;
678
679   if (is_invisible (me))
680     r.set_empty ();
681   else if (unsmob_grob (me->get_object ("beam"))
682            || abs (duration_log (me)) <= 2)
683     {
684       r = Interval (-1, 1);
685       r *= thickness (me) / 2;
686     }
687   else
688     {
689       r = Interval (-1, 1) * thickness (me) * 0.5;
690       r.unite (flag (me).extent (X_AXIS));
691     }
692   return ly_interval2scm (r);
693 }
694
695 Real
696 Stem::thickness (Grob *me)
697 {
698   return scm_to_double (me->get_property ("thickness"))
699     * Staff_symbol_referencer::line_thickness (me);
700 }
701
702 MAKE_SCHEME_CALLBACK (Stem, print, 1);
703 SCM
704 Stem::print (SCM smob)
705 {
706   Grob *me = unsmob_grob (smob);
707   Grob *beam = get_beam (me);
708     
709   Stencil mol;
710   Direction d = get_grob_direction (me);
711
712   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
713                                            0.0);
714   bool stemlet = stemlet_length > 0.0;
715
716   /* TODO: make the stem start a direction ?
717      This is required to avoid stems passing in tablature chords.  */
718   Grob *lh
719     = to_boolean (me->get_property ("avoid-note-head"))
720     ? last_head (me)
721     : first_head (me);
722
723   if (!lh && !stemlet)
724     return SCM_EOL;
725
726   if (!lh && stemlet && !beam)
727     return SCM_EOL;
728
729   if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1) 
730     return SCM_EOL;
731
732   if (is_invisible (me))
733     return SCM_EOL;
734
735   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
736   Real y1 = y2;
737   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
738
739   if (lh)
740     y2 = Staff_symbol_referencer::get_position (lh);
741   else if (stemlet)
742     {
743       Real beam_translation = Beam::get_beam_translation (beam);
744       Real beam_thickness = Beam::get_thickness (beam);
745       int beam_count = beam_multiplicity (me).length () + 1;
746
747       y2 -= d
748         * (0.5 * beam_thickness
749            + beam_translation * max (0, (beam_count - 1))
750            + stemlet_length) / half_space;
751     }
752
753   Interval stem_y (min (y1, y2), max (y2, y1));
754
755   if (Grob *head = support_head (me))
756     {
757       /*
758         must not take ledgers into account.
759       */
760       Interval head_height = head->extent (head, Y_AXIS);
761       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
762
763       y_attach = head_height.linear_combination (y_attach);
764       stem_y[Direction (-d)] += d * y_attach / half_space;
765     }
766
767   // URG
768   Real stem_width = thickness (me);
769   Real blot
770     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
771
772   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
773                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
774
775   Stencil ss = Lookup::round_filled_box (b, blot);
776   mol.add_stencil (ss);
777
778   mol.add_stencil (get_translated_flag (me));
779
780   return mol.smobbed_copy ();
781 }
782
783 Stencil
784 Stem::get_translated_flag (Grob *me)
785 {
786   Stencil fl = flag (me);
787   if (!fl.is_empty ())
788     {
789       Direction d = get_grob_direction (me);
790       Real blot
791         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
792       Real stem_width = thickness (me);
793       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
794       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
795       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
796       fl.translate_axis (stem_width / 2, X_AXIS);
797     }
798   return fl;
799 }
800
801
802 /*
803   move the stem to right of the notehead if it is up.
804 */
805 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
806 SCM
807 Stem::offset_callback (SCM smob)
808 {
809   Grob *me = unsmob_grob (smob);
810
811   extract_grob_set (me, "rests", rests);
812   if (rests.size ())
813     {
814       Grob *rest = rests.back ();
815       Real r = rest->extent (rest, X_AXIS).center ();
816       return scm_from_double (r);
817     }
818
819   
820   if (Grob *f = first_head (me))
821     {
822       Interval head_wid = f->extent (f, X_AXIS);
823       Real attach = 0.0;
824
825       if (is_invisible (me))
826         attach = 0.0;
827       else
828         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
829
830       Direction d = get_grob_direction (me);
831       Real real_attach = head_wid.linear_combination (d * attach);
832       Real r = real_attach;
833
834       /* If not centered: correct for stem thickness.  */
835       if (attach)
836         {
837           Real rule_thick = thickness (me);
838           r += -d * rule_thick * 0.5;
839         }
840       return scm_from_double (r);
841     }
842
843   programming_error ("Weird stem.");
844   return scm_from_double (0.0);
845 }
846
847 Spanner *
848 Stem::get_beam (Grob *me)
849 {
850   SCM b = me->get_object ("beam");
851   return dynamic_cast<Spanner *> (unsmob_grob (b));
852 }
853
854 Stem_info
855 Stem::get_stem_info (Grob *me)
856 {
857   Stem_info si;
858   si.dir_ = get_grob_direction (me);
859   
860   SCM scm_info = me->get_property ("stem-info");
861   si.ideal_y_ = scm_to_double (scm_car (scm_info));
862   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
863   return si;
864 }
865
866 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
867 SCM
868 Stem::calc_stem_info (SCM smob)
869 {
870   Grob *me = unsmob_grob (smob);
871   Direction my_dir = get_grob_direction (me);
872
873   if (!my_dir)
874     {
875       programming_error ("no stem dir set");
876       my_dir = UP;
877     }
878
879   Real staff_space = Staff_symbol_referencer::staff_space (me);
880   Grob *beam = get_beam (me);
881
882   if (beam)
883     {
884       (void) beam->get_property ("beaming");
885     }
886   
887   Real beam_translation = Beam::get_beam_translation (beam);
888   Real beam_thickness = Beam::get_thickness (beam);
889   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
890   Real length_fraction
891     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
892
893   /* Simple standard stem length */
894   SCM details = me->get_property ("details");
895   SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
896   
897   Real ideal_length
898     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
899     * staff_space
900     * length_fraction
901     
902     /* stem only extends to center of beam
903      */
904     - 0.5 * beam_thickness;
905
906   /* Condition: sane minimum free stem length (chord to beams) */
907   lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"), details, SCM_EOL);
908
909   Real ideal_minimum_free
910     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
911     * staff_space
912     * length_fraction;
913
914   Real height_of_my_trem = 0.0;
915   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
916   if (trem)
917     {
918       height_of_my_trem
919         = Stem_tremolo::vertical_length (trem)
920         /* hack a bit of space around the trem. */
921         + beam_translation;
922     }
923
924   
925   /* UGH
926      It seems that also for ideal minimum length, we must use
927      the maximum beam count (for this direction):
928
929      \score{ \notes\relative c''{ [a8 a32] }}
930
931      must be horizontal. */
932   Real height_of_my_beams = beam_thickness
933     + (beam_count - 1) * beam_translation;
934
935   Real ideal_minimum_length = ideal_minimum_free
936     + height_of_my_beams
937     + height_of_my_trem
938     /* stem only extends to center of beam */
939     - 0.5 * beam_thickness;
940
941   ideal_length = max (ideal_length, ideal_minimum_length);
942
943   /* Convert to Y position, calculate for dir == UP */
944   Real note_start
945     =     /* staff positions */
946     head_positions (me)[my_dir] * 0.5
947     * my_dir * staff_space;
948   Real ideal_y = note_start + ideal_length;
949
950   /* Conditions for Y position */
951
952   /* Lowest beam of (UP) beam must never be lower than second staffline
953
954   Reference?
955
956   Although this (additional) rule is probably correct,
957   I expect that highest beam (UP) should also never be lower
958   than middle staffline, just as normal stems.
959
960   Reference?
961
962   Obviously not for grace beams.
963
964   Also, not for knees.  Seems to be a good thing. */
965   bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
966   bool is_knee = to_boolean (beam->get_property ("knee"));
967   if (!no_extend && !is_knee)
968     {
969       /* Highest beam of (UP) beam must never be lower than middle
970          staffline */
971       ideal_y = max (ideal_y, 0.0);
972       /* Lowest beam of (UP) beam must never be lower than second staffline */
973       ideal_y = max (ideal_y, (-staff_space
974                                - beam_thickness + height_of_my_beams));
975     }
976
977   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
978
979   SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
980                             details, SCM_EOL);
981   
982   Real minimum_free
983     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
984     * staff_space
985     * length_fraction;
986
987   Real minimum_length = max (minimum_free, height_of_my_trem)
988     + height_of_my_beams
989     /* stem only extends to center of beam */
990     - 0.5 * beam_thickness;
991
992   ideal_y *= my_dir;
993   Real minimum_y = note_start + minimum_length;
994   Real shortest_y = minimum_y * my_dir;
995
996   return scm_list_2 (scm_from_double (ideal_y),
997                      scm_from_double (shortest_y));
998 }
999
1000 Slice
1001 Stem::beam_multiplicity (Grob *stem)
1002 {
1003   SCM beaming = stem->get_property ("beaming");
1004   Slice le = int_list_to_slice (scm_car (beaming));
1005   Slice ri = int_list_to_slice (scm_cdr (beaming));
1006   le.unite (ri);
1007   return le;
1008 }
1009
1010 bool
1011 Stem::is_cross_staff (Grob *stem)
1012 {
1013   Grob *beam = unsmob_grob (stem->get_object ("beam"));
1014   return beam && Beam::is_cross_staff (beam);
1015 }
1016
1017 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1018 SCM
1019 Stem::calc_cross_staff (SCM smob)
1020 {
1021   return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1022 }
1023
1024 /* FIXME:  Too many properties  */
1025 ADD_INTERFACE (Stem,
1026                "The stem represents the graphical stem.  In addition, it"
1027                " internally connects note heads, beams, and tremolos.  Rests"
1028                " and whole notes have invisible stems.\n"
1029                "\n"
1030                "The following properties may be set in the @code{details}"
1031                " list.\n"
1032                "\n"
1033                "@table @code\n"
1034                "@item beamed-lengths\n"
1035                "List of stem lengths given beam multiplicity.\n"
1036                "@item beamed-minimum-free-lengths\n"
1037                "List of normal minimum free stem lengths (chord to beams)"
1038                " given beam multiplicity.\n"
1039                "@item beamed-extreme-minimum-free-lengths\n"
1040                "List of extreme minimum free stem lengths (chord to beams)"
1041                " given beam multiplicity.\n"
1042                "@item lengths\n"
1043                "Default stem lengths.  The list gives a length for each"
1044                " flag count.\n"
1045                "@item stem-shorten\n"
1046                "How much a stem in a forced direction should be shortened."
1047                "  The list gives an amount depending on the number of flags"
1048                " and beams.\n"
1049                "@end table\n",
1050
1051                /* properties */
1052                "avoid-note-head "
1053                "beam "
1054                "beaming "
1055                "default-direction "
1056                "details "
1057                "direction "
1058                "duration-log "
1059                "flag "
1060                "flag-style "
1061                "french-beaming "
1062                "length "
1063                "length-fraction "
1064                "max-beam-connect "
1065                "neutral-direction "
1066                "no-stem-extend "
1067                "note-heads "
1068                "positioning-done "
1069                "rests "
1070                "stem-end-position "
1071                "stem-info "
1072                "stemlet-length "
1073                "stroke-style "
1074                "thickness "
1075                "tremolo-flag "
1076                );
1077
1078 /****************************************************************/
1079
1080 Stem_info::Stem_info ()
1081 {
1082   ideal_y_ = shortest_y_ = 0;
1083   dir_ = CENTER;
1084 }
1085
1086 void
1087 Stem_info::scale (Real x)
1088 {
1089   ideal_y_ *= x;
1090   shortest_y_ *= x;
1091 }