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