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