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