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