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