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