]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
e79aabc56ef6efccedbae1bed456ac624a73dc56
[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 "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
512   SCM mol = me->get_uncached_stencil ();
513   Interval iv;
514   if (mol != SCM_EOL)
515     iv = unsmob_stencil (mol)->extent (a);
516   if (Grob *b = get_beam (me))
517     {
518       Direction d = get_direction (me);
519       if (d == CENTER)
520         {
521           programming_error ("No stem direction");
522           d = UP;
523         }
524       iv[d] += d * Beam::get_thickness (b) * 0.5 ;
525     }
526
527   return ly_interval2scm (iv);
528 }
529
530
531 Stencil
532 Stem::flag (Grob *me)
533 {
534   /* TODO: maybe property stroke-style should take different values,
535      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
536      '() or "grace").  */
537   String flag_style;
538
539   SCM flag_style_scm = me->get_property ("flag-style");
540   if (scm_is_symbol (flag_style_scm))
541     flag_style = ly_symbol2string (flag_style_scm);
542   
543   if (flag_style == "no-flag")
544     return Stencil ();
545
546   bool adjust = true;
547
548   String staffline_offs;
549   if (String::compare (flag_style, "mensural") == 0)
550     /* Mensural notation: For notes on staff lines, use different
551        flags than for notes between staff lines.  The idea is that
552        flags are always vertically aligned with the staff lines,
553        regardless if the note head is on a staff line or between two
554        staff lines.  In other words, the inner end of a flag always
555        touches a staff line.
556     */
557     {
558       if (adjust)
559         {
560           int p = (int)(rint (stem_end_position (me)));
561           staffline_offs =
562             Staff_symbol_referencer::on_staffline (me, p) ? "0" : "1";
563         }
564       else
565         {
566           staffline_offs = "2";
567         }
568     }
569   else
570     {
571       staffline_offs = "";
572     }
573
574   char dir = (get_direction (me) == UP) ? 'u' : 'd';
575   String font_char = flag_style
576     + to_string (dir) + staffline_offs + to_string (duration_log (me));
577   Font_metric *fm = Font_interface::get_default_font (me);
578   Stencil flag = fm->find_by_name ("flags." + font_char);
579   if (flag.is_empty ())
580     me->warning (_f ("flag `%s' not found", font_char));
581
582   SCM stroke_style_scm = me->get_property ("stroke-style");
583   if (scm_is_string (stroke_style_scm))
584     {
585       String stroke_style = ly_scm2string (stroke_style_scm);
586       if (!stroke_style.is_empty ())
587         {
588           String font_char = to_string (dir) + stroke_style;
589           Stencil stroke = fm->find_by_name ("flags." + font_char);
590           if (stroke.is_empty ())
591             me->warning (_f ("flag stroke `%s' not found", font_char));
592           else
593             flag.add_stencil (stroke);
594         }
595     }
596
597   return flag;
598 }
599
600 MAKE_SCHEME_CALLBACK (Stem,width_callback,2);
601 SCM
602 Stem::width_callback (SCM e, SCM ax)
603 {
604   Axis a = (Axis) scm_to_int (ax);
605   assert (a == X_AXIS);
606   Grob *me = unsmob_grob (e);
607
608   Interval r;
609   
610   if (is_invisible (me))
611     {
612       r.set_empty ();
613     }    
614   else if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
615     {
616       r = Interval (-1,1);
617       r *= thickness (me)/2; 
618     }
619   else
620     {
621       r = flag (me).extent (X_AXIS)
622         + thickness (me)/2;
623     }  
624   return ly_interval2scm (r);
625 }
626
627 Real
628 Stem::thickness (Grob *me)
629 {
630   return scm_to_double (me->get_property ("thickness"))
631     * Staff_symbol_referencer::line_thickness (me);
632 }
633
634 MAKE_SCHEME_CALLBACK (Stem, print, 1);
635 SCM
636 Stem::print (SCM smob)
637 {
638   Grob *me = unsmob_grob (smob);
639   Stencil mol;
640   Direction d = get_direction (me);
641
642   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
643                                            0.0);
644   bool stemlet = stemlet_length > 0.0;
645   
646   /* TODO: make the stem start a direction ?
647      This is required to avoid stems passing in tablature chords.  */
648   Grob *lh =
649     to_boolean (me->get_property ("avoid-note-head"))
650     ? last_head (me)
651     : lh = first_head (me);
652   Grob *beam = get_beam (me);
653
654   if (!lh && !stemlet)
655     return SCM_EOL;
656
657   if (stemlet && !beam)
658     return SCM_EOL;
659     
660   if (is_invisible (me))
661     return SCM_EOL;
662
663   Real y2 = stem_end_position (me);
664   Real y1 = y2;
665   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
666
667
668   if (lh)
669     y2 = Staff_symbol_referencer::get_position (lh);
670   else if (stemlet)
671     {
672       Real beam_translation = Beam::get_beam_translation (beam);
673       Real beam_thickness = Beam::get_thickness (beam);
674       int beam_count = beam_multiplicity (me).length () + 1;
675
676       y2 -= d
677         * (0.5 * beam_thickness
678            + beam_translation * (0 >? (beam_count - 1))
679            + stemlet_length) / half_space;
680     }
681
682   Interval stem_y (y1 <? y2,y2 >? y1);
683
684   if (Grob *hed = support_head (me))
685     {
686       /*
687         must not take ledgers into account.
688        */
689       Interval head_height = hed->extent (hed,Y_AXIS);
690       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
691
692       y_attach = head_height.linear_combination (y_attach);
693       stem_y[Direction (-d)] += d * y_attach/half_space;
694     }
695
696
697   // URG
698   Real stem_width = thickness (me);
699   Real blot =
700         me->get_layout ()->get_dimension (ly_symbol2scm ("blotdiameter"));
701
702   Box b = Box (Interval (-stem_width/2, stem_width/2),
703                Interval (stem_y[DOWN]*half_space, stem_y[UP]*half_space));
704
705   Stencil ss = Lookup::round_filled_box (b, blot);
706   mol.add_stencil (ss);
707
708   if (!get_beam (me) && abs (duration_log (me)) > 2)
709     {
710       Stencil fl = flag (me);
711       fl.translate_axis (stem_y[d]*half_space - d * blot/2, Y_AXIS);
712       fl.translate_axis (stem_width/2, X_AXIS);
713       mol.add_stencil (fl);
714     }
715
716   return mol.smobbed_copy ();
717 }
718
719 /*
720   move the stem to right of the notehead if it is up.
721  */
722 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 2);
723 SCM
724 Stem::offset_callback (SCM element_smob, SCM)
725 {
726   Grob *me = unsmob_grob (element_smob);
727   Real r = 0.0;
728   
729   if (Grob *f = first_head (me))
730     {
731       Interval head_wid = f->extent (f, X_AXIS);
732       Real attach = 0.0;
733         
734       if (is_invisible (me))
735         attach = 0.0;
736       else
737         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
738         
739       Direction d = get_direction (me);
740       Real real_attach = head_wid.linear_combination (d * attach);
741       r = real_attach;
742         
743       /* If not centered: correct for stem thickness.  */
744       if (attach)
745         {
746           Real rule_thick = thickness (me);
747           r += - d * rule_thick * 0.5;
748         }
749     }
750   else
751     {
752       SCM rests = me->get_property ("rests");
753       if (scm_is_pair (rests))
754         {
755           Grob * rest = unsmob_grob (scm_car (rests));
756           r = rest->extent (rest, X_AXIS).center ();
757         }
758     }
759   return scm_make_real (r);
760 }
761
762 Spanner *
763 Stem::get_beam (Grob *me)
764 {
765   SCM b = me->get_property ("beam");
766   return dynamic_cast<Spanner*> (unsmob_grob (b));
767 }
768
769 Stem_info
770 Stem::get_stem_info (Grob *me)
771 {
772   /* Return cached info if available */
773   SCM scm_info = me->get_property ("stem-info");
774   if (!scm_is_pair (scm_info))
775     {
776       calc_stem_info (me);
777       scm_info = me->get_property ("stem-info");
778     }
779
780   Stem_info si;
781   si.dir_ = get_grob_direction (me);
782   si.ideal_y_ = scm_to_double (scm_car (scm_info));
783   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
784   return si;
785 }
786
787
788 /* TODO: add extra space for tremolos!  */
789 void
790 Stem::calc_stem_info (Grob *me)
791 {
792   Direction my_dir = get_grob_direction (me);
793
794   if (!my_dir)
795     {
796       programming_error ("No stem dir set?");
797       my_dir  = UP;
798     }
799
800   Real staff_space = Staff_symbol_referencer::staff_space (me);
801   Grob *beam = get_beam (me);
802   Real beam_translation = Beam::get_beam_translation (beam);
803   Real beam_thickness = Beam::get_thickness (beam);
804   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
805
806
807   /* Simple standard stem length */
808   SCM lengths = me->get_property ("beamed-lengths");
809   Real ideal_length =
810     scm_to_double (robust_list_ref (beam_count - 1,lengths))
811                 
812     * staff_space
813     /* stem only extends to center of beam
814     */
815     - 0.5 * beam_thickness
816     ;
817
818   /* Condition: sane minimum free stem length (chord to beams) */
819   lengths = me->get_property ("beamed-minimum-free-lengths");
820   Real ideal_minimum_free =
821     scm_to_double (robust_list_ref (beam_count - 1, lengths))
822     * staff_space;
823
824
825   /* UGH
826      It seems that also for ideal minimum length, we must use
827      the maximum beam count (for this direction):
828
829      \score{ \notes\relative c''{ [a8 a32] }}
830
831      must be horizontal. */
832   Real height_of_my_beams = beam_thickness
833     + (beam_count - 1) * beam_translation;
834
835   Real ideal_minimum_length = ideal_minimum_free
836     + height_of_my_beams
837     /* stem only extends to center of beam */
838     - 0.5 * beam_thickness;
839
840   ideal_length = ideal_length >? ideal_minimum_length;
841
842   /* Convert to Y position, calculate for dir == UP */
843   Real note_start =
844     /* staff positions */
845     head_positions (me)[my_dir] * 0.5
846     * my_dir * staff_space;
847   Real ideal_y = note_start + ideal_length;
848
849
850   /* Conditions for Y position */
851
852   /* Lowest beam of (UP) beam must never be lower than second staffline
853
854      Reference?
855
856      Although this (additional) rule is probably correct,
857      I expect that highest beam (UP) should also never be lower
858      than middle staffline, just as normal stems.
859
860      Reference?
861
862      Obviously not for grace beams.
863
864      Also, not for knees.  Seems to be a good thing. */
865   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
866   bool is_knee = to_boolean (beam->get_property ("knee"));
867   if (!no_extend_b && !is_knee)
868     {
869       /* Highest beam of (UP) beam must never be lower than middle
870          staffline */
871       ideal_y = ideal_y >? 0;
872       /* Lowest beam of (UP) beam must never be lower than second staffline */
873       ideal_y = ideal_y >? (-staff_space
874                             - beam_thickness + height_of_my_beams);
875     }
876
877
878   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
879
880   Real minimum_free =
881     scm_to_double (robust_list_ref
882                    (beam_count - 1,
883                     me->get_property
884                     ("beamed-extreme-minimum-free-lengths")))
885     * staff_space;
886
887   Real minimum_length = minimum_free
888     + height_of_my_beams
889     /* stem only extends to center of beam */
890     - 0.5 * beam_thickness;
891
892   ideal_y *= my_dir;
893   Real minimum_y = note_start + minimum_length;
894   Real shortest_y = minimum_y * my_dir;
895
896   me->set_property ("stem-info",
897                     scm_list_2 (scm_make_real (ideal_y),
898                                 scm_make_real (shortest_y)));
899 }
900
901 Slice
902 Stem::beam_multiplicity (Grob *stem)
903 {
904   SCM beaming = stem->get_property ("beaming");
905   Slice le = int_list_to_slice (scm_car (beaming));
906   Slice ri = int_list_to_slice (scm_cdr (beaming));
907   le.unite (ri);
908   return le;
909 }
910
911
912 /* FIXME:  Too many properties  */
913 ADD_INTERFACE (Stem, "stem-interface",
914                "The stem represent the graphical stem.  "
915                "In addition, it internally connects note heads, beams and"
916                "tremolos. "
917                "Rests and whole notes have invisible stems.",
918                "tremolo-flag french-beaming "
919                "avoid-note-head thickness "
920                "stemlet-length rests "
921                "stem-info beamed-lengths beamed-minimum-free-lengths "
922                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
923                "duration-log beaming neutral-direction stem-end-position "
924                "note-heads direction length flag-style "
925                "no-stem-extend stroke-style");
926
927 /****************************************************************/
928
929 Stem_info::Stem_info ()
930 {
931   ideal_y_ = shortest_y_ = 0;
932   dir_ = CENTER;
933 }
934
935 void
936 Stem_info::scale (Real x)
937 {
938   ideal_y_ *= x;
939   shortest_y_ *= x;
940 }