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