]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
Merge branch 'master' of ssh+git://hanwen@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include "stem.hh"
17 #include "spanner.hh"
18
19 #include <cmath>                // rint
20 using namespace std;
21
22 #include "beam.hh"
23 #include "directional-element-interface.hh"
24 #include "dot-column.hh"
25 #include "font-interface.hh"
26 #include "international.hh"
27 #include "lookup.hh"
28 #include "misc.hh"
29 #include "note-head.hh"
30 #include "output-def.hh"
31 #include "paper-column.hh"
32 #include "pointer-group-interface.hh"
33 #include "rest.hh"
34 #include "rhythmic-head.hh"
35 #include "side-position-interface.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem-tremolo.hh"
38 #include "warn.hh"
39
40 void
41 Stem::set_beaming (Grob *me, int beam_count, Direction d)
42 {
43   SCM pair = me->get_property ("beaming");
44
45   if (!scm_is_pair (pair))
46     {
47       pair = scm_cons (SCM_EOL, SCM_EOL);
48       me->set_property ("beaming", pair);
49     }
50
51   SCM lst = index_get_cell (pair, d);
52   if (beam_count)
53     for (int i = 0; i < beam_count; i++)
54       lst = scm_cons (scm_from_int (i), lst);
55   else
56     lst = SCM_BOOL_F;
57   
58   index_set_cell (pair, d, lst);
59 }
60
61 int
62 Stem::get_beaming (Grob *me, Direction d)
63 {
64   SCM pair = me->get_property ("beaming");
65   if (!scm_is_pair (pair))
66     return 0;
67
68   SCM lst = index_get_cell (pair, d);
69
70   int len = scm_ilength (lst);
71   return max (len, 0);
72 }
73
74 Interval
75 Stem::head_positions (Grob *me)
76 {
77   if (head_count (me))
78     {
79       Drul_array<Grob *> e (extremal_heads (me));
80       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
81                        Staff_symbol_referencer::get_position (e[UP]));
82     }
83   return Interval ();
84 }
85
86 Real
87 Stem::chord_start_y (Grob *me)
88 {
89   Interval hp = head_positions (me);
90   if (!hp.is_empty ())
91     return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
92       * 0.5;
93   return 0;
94 }
95
96
97
98 void
99 Stem::set_stemend (Grob *me, Real se)
100 {
101   // todo: margins
102   Direction d = get_grob_direction (me);
103
104   if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
105     me->warning (_ ("weird stem size, check for narrow beams"));
106
107   me->set_property ("stem-end-position", scm_from_double (se));
108 }
109
110 /* Note head that determines hshift for upstems
111    WARNING: triggers direction  */
112 Grob *
113 Stem::support_head (Grob *me)
114 {
115   extract_grob_set (me, "note-heads", heads);
116   if (heads.size () == 1)
117     return heads[0];
118
119   return first_head (me);
120 }
121
122 int
123 Stem::head_count (Grob *me)
124 {
125   return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
126 }
127
128 /* The note head which forms one end of the stem.
129    WARNING: triggers direction  */
130 Grob *
131 Stem::first_head (Grob *me)
132 {
133   Direction d = get_grob_direction (me);
134   if (d)
135     return extremal_heads (me)[-d];
136   return 0;
137 }
138
139 /* The note head opposite to the first head.  */
140 Grob *
141 Stem::last_head (Grob *me)
142 {
143   Direction d = get_grob_direction (me);
144   if (d)
145     return extremal_heads (me)[d];
146   return 0;
147 }
148
149 /*
150   START is part where stem reaches `last' head.
151
152   This function returns a drul with (bottom-head, top-head).
153 */
154 Drul_array<Grob *>
155 Stem::extremal_heads (Grob *me)
156 {
157   const int inf = INT_MAX;
158   Drul_array<int> extpos;
159   extpos[DOWN] = inf;
160   extpos[UP] = -inf;
161
162   Drul_array<Grob *> exthead (0, 0);
163   extract_grob_set (me, "note-heads", heads);
164
165   for (vsize i = heads.size (); i--;)
166     {
167       Grob *n = heads[i];
168       int p = Staff_symbol_referencer::get_rounded_position (n);
169
170       Direction d = LEFT;
171       do
172         {
173           if (d * p > d * extpos[d])
174             {
175               exthead[d] = n;
176               extpos[d] = p;
177             }
178         }
179       while (flip (&d) != DOWN);
180     }
181   return exthead;
182 }
183
184 /* The positions, in ascending order.  */
185 vector<int>
186 Stem::note_head_positions (Grob *me)
187 {
188   vector<int> ps;
189   extract_grob_set (me, "note-heads", heads);
190
191   for (vsize i = heads.size (); i--;)
192     {
193       Grob *n = heads[i];
194       int p = Staff_symbol_referencer::get_rounded_position (n);
195
196       ps.push_back (p);
197     }
198
199   vector_sort (ps, less<int> ());
200   return ps;
201 }
202
203 void
204 Stem::add_head (Grob *me, Grob *n)
205 {
206   n->set_object ("stem", me->self_scm ());
207
208   if (Note_head::has_interface (n))
209     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
210   else if (Rest::has_interface (n))
211     Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
212 }
213
214 bool
215 Stem::is_invisible (Grob *me)
216 {
217   return !is_normal_stem (me)
218     && (robust_scm2double (me->get_property ("stemlet-length"),
219                            0.0) == 0.0);
220 }
221
222
223 bool
224 Stem::is_normal_stem (Grob *me)
225 {
226   return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
227 }
228
229
230 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
231 SCM
232 Stem::pure_height (SCM smob, SCM start, SCM end)
233 {
234   (void) start;
235   (void) end;
236
237   Grob *me = unsmob_grob (smob);
238   Interval iv;
239
240   if (!is_normal_stem (me))
241     return ly_interval2scm (iv);
242
243   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 = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
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 = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
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 Stencil
574 Stem::flag (Grob *me)
575 {
576   int log = duration_log (me);
577   if (log < 3
578       || unsmob_grob (me->get_object ("beam")))
579     return Stencil ();
580
581   if (!is_normal_stem (me))
582     return Stencil ();
583   
584   /*
585     TODO: maybe property stroke-style should take different values,
586     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
587     '() or "grace").  */
588   string flag_style;
589
590   SCM flag_style_scm = me->get_property ("flag-style");
591   if (scm_is_symbol (flag_style_scm))
592     flag_style = ly_symbol2string (flag_style_scm);
593
594   if (flag_style == "no-flag")
595     return Stencil ();
596
597   bool adjust = true;
598
599   string staffline_offs;
600   if (flag_style == "mensural")
601     /* Mensural notation: For notes on staff lines, use different
602        flags than for notes between staff lines.  The idea is that
603        flags are always vertically aligned with the staff lines,
604        regardless if the note head is on a staff line or between two
605        staff lines.  In other words, the inner end of a flag always
606        touches a staff line.
607     */
608     {
609       if (adjust)
610         {
611           int p = (int) (rint (stem_end_position (me)));
612           staffline_offs
613             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
614         }
615       else
616         staffline_offs = "2";
617     }
618   else
619     staffline_offs = "";
620
621   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
622   string font_char = flag_style
623     + to_string (dir) + staffline_offs + to_string (log);
624   Font_metric *fm = Font_interface::get_default_font (me);
625   Stencil flag = fm->find_by_name ("flags." + font_char);
626   if (flag.is_empty ())
627     me->warning (_f ("flag `%s' not found", font_char));
628
629   SCM stroke_style_scm = me->get_property ("stroke-style");
630   if (scm_is_string (stroke_style_scm))
631     {
632       string stroke_style = ly_scm2string (stroke_style_scm);
633       if (!stroke_style.empty ())
634         {
635           string font_char = to_string (dir) + stroke_style;
636           Stencil stroke = fm->find_by_name ("flags." + font_char);
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;
645 }
646
647 MAKE_SCHEME_CALLBACK (Stem, width, 1);
648 SCM
649 Stem::width (SCM e)
650 {
651   Grob *me = unsmob_grob (e);
652
653   Interval r;
654
655   if (is_invisible (me))
656     r.set_empty ();
657   else if (unsmob_grob (me->get_object ("beam"))
658            || abs (duration_log (me)) <= 2)
659     {
660       r = Interval (-1, 1);
661       r *= thickness (me) / 2;
662     }
663   else
664     {
665       r = Interval (-1, 1) * thickness (me) * 0.5;
666       r.unite (flag (me).extent (X_AXIS));
667     }
668   return ly_interval2scm (r);
669 }
670
671 Real
672 Stem::thickness (Grob *me)
673 {
674   return scm_to_double (me->get_property ("thickness"))
675     * Staff_symbol_referencer::line_thickness (me);
676 }
677
678 MAKE_SCHEME_CALLBACK (Stem, print, 1);
679 SCM
680 Stem::print (SCM smob)
681 {
682   Grob *me = unsmob_grob (smob);
683   Grob *beam = get_beam (me);
684     
685   Stencil mol;
686   Direction d = get_grob_direction (me);
687
688   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
689                                            0.0);
690   bool stemlet = stemlet_length > 0.0;
691
692   /* TODO: make the stem start a direction ?
693      This is required to avoid stems passing in tablature chords.  */
694   Grob *lh
695     = to_boolean (me->get_property ("avoid-note-head"))
696     ? last_head (me)
697     : first_head (me);
698
699   if (!lh && !stemlet)
700     return SCM_EOL;
701
702   if (!lh && stemlet && !beam)
703     return SCM_EOL;
704
705   if (is_invisible (me))
706     return SCM_EOL;
707
708   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
709   Real y1 = y2;
710   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
711
712   if (lh)
713     y2 = Staff_symbol_referencer::get_position (lh);
714   else if (stemlet)
715     {
716       Real beam_translation = Beam::get_beam_translation (beam);
717       Real beam_thickness = Beam::get_thickness (beam);
718       int beam_count = beam_multiplicity (me).length () + 1;
719
720       y2 -= d
721         * (0.5 * beam_thickness
722            + beam_translation * max (0, (beam_count - 1))
723            + stemlet_length) / half_space;
724     }
725
726   Interval stem_y (min (y1, y2), max (y2, y1));
727
728   if (Grob *head = support_head (me))
729     {
730       /*
731         must not take ledgers into account.
732       */
733       Interval head_height = head->extent (head, Y_AXIS);
734       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
735
736       y_attach = head_height.linear_combination (y_attach);
737       stem_y[Direction (-d)] += d * y_attach / half_space;
738     }
739
740   // URG
741   Real stem_width = thickness (me);
742   Real blot
743     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
744
745   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
746                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
747
748   Stencil ss = Lookup::round_filled_box (b, blot);
749   mol.add_stencil (ss);
750
751   mol.add_stencil (get_translated_flag (me));
752
753   return mol.smobbed_copy ();
754 }
755
756 Stencil
757 Stem::get_translated_flag (Grob *me)
758 {
759   Stencil fl = flag (me);
760   if (!fl.is_empty ())
761     {
762       Direction d = get_grob_direction (me);
763       Real blot
764         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
765       Real stem_width = thickness (me);
766       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
767       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
768       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
769       fl.translate_axis (stem_width / 2, X_AXIS);
770     }
771   return fl;
772 }
773
774
775 /*
776   move the stem to right of the notehead if it is up.
777 */
778 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
779 SCM
780 Stem::offset_callback (SCM smob)
781 {
782   Grob *me = unsmob_grob (smob);
783
784   extract_grob_set (me, "rests", rests);
785   if (rests.size ())
786     {
787       Grob *rest = rests.back ();
788       Real r = rest->extent (rest, X_AXIS).center ();
789       return scm_from_double (r);
790     }
791
792   
793   if (Grob *f = first_head (me))
794     {
795       Interval head_wid = f->extent (f, X_AXIS);
796       Real attach = 0.0;
797
798       if (is_invisible (me))
799         attach = 0.0;
800       else
801         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
802
803       Direction d = get_grob_direction (me);
804       Real real_attach = head_wid.linear_combination (d * attach);
805       Real r = real_attach;
806
807       /* If not centered: correct for stem thickness.  */
808       if (attach)
809         {
810           Real rule_thick = thickness (me);
811           r += -d * rule_thick * 0.5;
812         }
813       return scm_from_double (r);
814     }
815
816   programming_error ("Weird stem.");
817   return scm_from_double (0.0);
818 }
819
820 Spanner *
821 Stem::get_beam (Grob *me)
822 {
823   SCM b = me->get_object ("beam");
824   return dynamic_cast<Spanner *> (unsmob_grob (b));
825 }
826
827 Stem_info
828 Stem::get_stem_info (Grob *me)
829 {
830   Stem_info si;
831   si.dir_ = get_grob_direction (me);
832   
833   SCM scm_info = me->get_property ("stem-info");
834   si.ideal_y_ = scm_to_double (scm_car (scm_info));
835   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
836   return si;
837 }
838
839 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
840 SCM
841 Stem::calc_stem_info (SCM smob)
842 {
843   Grob *me = unsmob_grob (smob);
844   Direction my_dir = get_grob_direction (me);
845
846   if (!my_dir)
847     {
848       programming_error ("no stem dir set");
849       my_dir = UP;
850     }
851
852   Real staff_space = Staff_symbol_referencer::staff_space (me);
853   Grob *beam = get_beam (me);
854
855   if (beam)
856     {
857       (void) beam->get_property ("beaming");
858     }
859   
860   Real beam_translation = Beam::get_beam_translation (beam);
861   Real beam_thickness = Beam::get_thickness (beam);
862   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
863   Real length_fraction
864     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
865
866   /* Simple standard stem length */
867   SCM details = me->get_property ("details");
868   SCM lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
869   
870   Real ideal_length
871     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
872     * staff_space
873     * length_fraction
874     
875     /* stem only extends to center of beam
876      */
877     - 0.5 * beam_thickness;
878
879   /* Condition: sane minimum free stem length (chord to beams) */
880   lengths = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
881
882   Real ideal_minimum_free
883     = scm_to_double (robust_list_ref (beam_count - 1, lengths))
884     * staff_space
885     * length_fraction;
886
887   Real height_of_my_trem = 0.0;
888   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
889   if (trem)
890     {
891       height_of_my_trem
892         = Stem_tremolo::vertical_length (trem)
893         /* hack a bit of space around the trem. */
894         + beam_translation;
895     }
896
897   
898   /* UGH
899      It seems that also for ideal minimum length, we must use
900      the maximum beam count (for this direction):
901
902      \score{ \notes\relative c''{ [a8 a32] }}
903
904      must be horizontal. */
905   Real height_of_my_beams = beam_thickness
906     + (beam_count - 1) * beam_translation;
907
908   Real ideal_minimum_length = ideal_minimum_free
909     + height_of_my_beams
910     + height_of_my_trem
911     /* stem only extends to center of beam */
912     - 0.5 * beam_thickness;
913
914   ideal_length = max (ideal_length, ideal_minimum_length);
915
916   /* Convert to Y position, calculate for dir == UP */
917   Real note_start
918     =     /* staff positions */
919     head_positions (me)[my_dir] * 0.5
920     * my_dir * staff_space;
921   Real ideal_y = note_start + ideal_length;
922
923   /* Conditions for Y position */
924
925   /* Lowest beam of (UP) beam must never be lower than second staffline
926
927   Reference?
928
929   Although this (additional) rule is probably correct,
930   I expect that highest beam (UP) should also never be lower
931   than middle staffline, just as normal stems.
932
933   Reference?
934
935   Obviously not for grace beams.
936
937   Also, not for knees.  Seems to be a good thing. */
938   bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
939   bool is_knee = to_boolean (beam->get_property ("knee"));
940   if (!no_extend && !is_knee)
941     {
942       /* Highest beam of (UP) beam must never be lower than middle
943          staffline */
944       ideal_y = max (ideal_y, 0.0);
945       /* Lowest beam of (UP) beam must never be lower than second staffline */
946       ideal_y = max (ideal_y, (-staff_space
947                                - beam_thickness + height_of_my_beams));
948     }
949
950   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
951
952   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
953                                  details));
954   
955   Real minimum_free
956     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
957     * staff_space
958     * length_fraction;
959
960   Real minimum_length = max (minimum_free, height_of_my_trem)
961     + height_of_my_beams
962     /* stem only extends to center of beam */
963     - 0.5 * beam_thickness;
964
965   ideal_y *= my_dir;
966   Real minimum_y = note_start + minimum_length;
967   Real shortest_y = minimum_y * my_dir;
968
969   return scm_list_2 (scm_from_double (ideal_y),
970                      scm_from_double (shortest_y));
971 }
972
973 Slice
974 Stem::beam_multiplicity (Grob *stem)
975 {
976   SCM beaming = stem->get_property ("beaming");
977   Slice le = int_list_to_slice (scm_car (beaming));
978   Slice ri = int_list_to_slice (scm_cdr (beaming));
979   le.unite (ri);
980   return le;
981 }
982
983 bool
984 Stem::is_cross_staff (Grob *stem)
985 {
986   Grob *beam = unsmob_grob (stem->get_object ("beam"));
987   return beam && Beam::is_cross_staff (beam);
988 }
989
990 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
991 SCM
992 Stem::calc_cross_staff (SCM smob)
993 {
994   return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
995 }
996
997 /* FIXME:  Too many properties  */
998 ADD_INTERFACE (Stem,
999                "The stem represent the graphical stem.  "
1000                "In addition, it internally connects note heads, beams and"
1001                "tremolos. "
1002                "Rests and whole notes have invisible stems."
1003
1004                "\n\nThe following properties may be set in the details list." 
1005                "@table @code\n"
1006                "@item  beamed-lengths \n"
1007                "list of stem lengths given beam multiplicity. \n"
1008                "@item beamed-minimum-free-lengths \n"
1009                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1010                "@item beamed-extreme-minimum-free-lengths\n"
1011                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1012                "@item lengths\n"
1013                "Default stem lengths. The list gives a length for each flag-count.\n"
1014                "@item stem-shorten\n"
1015                "How much a stem in a forced direction "
1016                "should be shortened. The list gives an amount depending on the number "
1017                "of flags/beams."
1018                "@end table\n"
1019                ,
1020
1021                /* properties */
1022                "avoid-note-head "
1023                "beam "
1024                "beaming "
1025                "default-direction "
1026                "details "
1027                "direction "
1028                "duration-log "
1029                "flag-style "
1030                "french-beaming "
1031                "length "
1032                "length-fraction "
1033                "max-beam-connect "
1034                "neutral-direction "
1035                "no-stem-extend "
1036                "note-heads "
1037                "positioning-done "
1038                "rests "
1039                "stem-end-position "
1040                "stem-info "
1041                "stemlet-length "
1042                "stroke-style "
1043                "thickness "
1044                "tremolo-flag "
1045                );
1046
1047 /****************************************************************/
1048
1049 Stem_info::Stem_info ()
1050 {
1051   ideal_y_ = shortest_y_ = 0;
1052   dir_ = CENTER;
1053 }
1054
1055 void
1056 Stem_info::scale (Real x)
1057 {
1058   ideal_y_ *= x;
1059   shortest_y_ *= x;
1060 }