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