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