]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* input/regression/key-signature-cancellation.ly (Module): new file.
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include <math.h>               // rint
17
18 #include "lookup.hh"
19 #include "directional-element-interface.hh"
20 #include "note-head.hh"
21 #include "stem.hh"
22 #include "warn.hh"
23 #include "output-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "stencil.hh"
27 #include "paper-column.hh"
28 #include "misc.hh"
29 #include "beam.hh"
30 #include "rest.hh"
31 #include "group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "spanner.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
36 #include "stem-tremolo.hh"
37
38 void
39 Stem::set_beaming (Grob *me, int beam_count, Direction d)
40 {
41   SCM pair = me->get_property ("beaming");
42
43   if (!ly_c_pair_p (pair))
44     {
45       pair = scm_cons (SCM_EOL, SCM_EOL);
46       me->set_property ("beaming", pair);
47     }
48
49   SCM lst = index_get_cell (pair, d);
50   for (int i = 0; i < beam_count; i++)
51     lst = scm_cons (scm_int2num (i), lst);
52   index_set_cell (pair, d, lst);
53 }
54
55 int
56 Stem::get_beaming (Grob *me, Direction d)
57 {
58   SCM pair = me->get_property ("beaming");
59   if (!ly_c_pair_p (pair))
60     return 0;
61
62   SCM lst = index_get_cell (pair, d);
63   return scm_ilength (lst);
64 }
65
66
67 Interval
68 Stem::head_positions (Grob *me)
69 {
70   if (head_count (me))
71     {
72       Drul_array<Grob*> e (extremal_heads (me));
73       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
74                        Staff_symbol_referencer::get_position (e[UP]));
75     }
76   return Interval ();
77 }
78
79 Real
80 Stem::chord_start_y (Grob *me)
81 {
82   Interval hp = head_positions (me);
83   if (!hp.is_empty ())
84     return hp[get_direction (me)] * Staff_symbol_referencer::staff_space (me)
85       * 0.5;
86   return 0;
87 }
88
89 Real
90 Stem::stem_end_position (Grob *me)
91 {
92   SCM p = me->get_property ("stem-end-position");
93   Real pos;
94   if (!scm_is_number (p))
95     {
96       pos = get_default_stem_end_position (me);
97       me->set_property ("stem-end-position", scm_make_real (pos));
98     }
99   else
100     pos = scm_to_double (p);
101
102   return pos;
103 }
104
105 Direction
106 Stem::get_direction (Grob *me)
107 {
108   Direction d = get_grob_direction (me);
109
110   if (!d)
111     {
112       d = get_default_dir (me);
113       // urg, AAARGH!
114       set_grob_direction (me, d);
115     }
116   return d;
117 }
118
119 void
120 Stem::set_stemend (Grob *me, Real se)
121 {
122   // todo: margins
123   Direction d = get_direction (me);
124
125   if (d && d * head_positions (me)[get_direction (me)] >= se*d)
126     me->warning (_ ("Weird stem size; check for narrow beams"));
127
128   me->set_property ("stem-end-position", scm_make_real (se));
129 }
130
131 /* Note head that determines hshift for upstems
132    WARNING: triggers direction  */
133 Grob *
134 Stem::support_head (Grob *me)
135 {
136   if (head_count (me) == 1)
137     /* UGH. */
138     return unsmob_grob (ly_car (me->get_property ("note-heads")));
139   return first_head (me);
140 }
141
142 int
143 Stem::head_count (Grob *me)
144 {
145   return Pointer_group_interface::count (me, "note-heads");
146 }
147
148 /* The note head which forms one end of the stem.
149    WARNING: triggers direction  */
150 Grob *
151 Stem::first_head (Grob *me)
152 {
153   Direction d = get_direction (me);
154   if (d)
155     return extremal_heads (me)[-d];
156   return 0;
157 }
158
159 /* The note head opposite to the first head.  */
160 Grob *
161 Stem::last_head (Grob *me)
162 {
163   Direction d = get_direction (me);
164   if (d)
165     return extremal_heads (me)[d];
166   return 0;  
167 }
168
169 /* 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;
183   exthead[LEFT] = exthead[RIGHT] =0;
184
185   for (SCM s = me->get_property ("note-heads"); ly_c_pair_p (s);
186        s = ly_cdr (s))
187     {
188       Grob *n = unsmob_grob (ly_car (s));
189       int p = Staff_symbol_referencer::get_rounded_position (n);
190
191       Direction d = LEFT;
192       do
193         {
194           if (d * p > d * extpos[d])
195             {
196               exthead[d] = n;
197               extpos[d] = p;
198             }
199         } while (flip (&d) != DOWN);
200     }
201   return exthead;
202 }
203
204 static int
205 icmp (int const &a, int const &b)
206 {
207   return a - b;
208 }
209
210 /* The positions, in ascending order.  */
211 Array<int>
212 Stem::note_head_positions (Grob *me)
213 {
214   Array<int> ps ;
215   for (SCM s = me->get_property ("note-heads"); ly_c_pair_p (s);
216        s = ly_cdr (s))
217     {
218       Grob *n = unsmob_grob (ly_car (s));
219       int p = Staff_symbol_referencer::get_rounded_position (n);
220
221       ps.push (p);
222     }
223
224   ps.sort (icmp);
225   return ps;
226 }
227
228 void
229 Stem::add_head (Grob *me, Grob *n)
230 {
231   n->set_property ("stem", me->self_scm ());
232   n->add_dependency (me);
233
234   /* TODO: why not store Rest pointers? */
235   if (Note_head::has_interface (n))
236     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
237 }
238
239 bool
240 Stem::is_invisible (Grob *me)
241 {
242   return !(head_count (me)
243            && scm_to_int (me->get_property ("duration-log")) >= 1);
244 }
245
246 Direction
247 Stem::get_default_dir (Grob *me)
248 {
249   int staff_center = 0;
250   Interval hp = head_positions (me);
251   if (hp.is_empty ())
252     return CENTER;
253
254   int udistance = (int) (UP * hp[UP] - staff_center);
255   int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
256
257   if (sign (ddistance - udistance))
258     return Direction (sign (ddistance - udistance));
259
260   return to_dir (me->get_property ("neutral-direction"));
261 }
262
263 Real
264 Stem::get_default_stem_end_position (Grob *me)
265 {
266   Real ss = Staff_symbol_referencer::staff_space (me);
267   int durlog = duration_log (me);
268   SCM s;
269   Array<Real> a;
270
271   /* WARNING: IN HALF SPACES */
272   Real length = 7;
273   SCM scm_len = me->get_property ("length");
274   if (scm_is_number (scm_len))
275     length = scm_to_double (scm_len);
276   else
277     {
278       s = me->get_property ("lengths");
279       if (ly_c_pair_p (s))
280         length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
281     }
282
283   /* URGURGURG
284      'set-default-stemlen' sets direction too.   */
285   Direction dir = get_direction (me);
286   if (!dir)
287     {
288       dir = get_default_dir (me);
289       set_grob_direction (me, dir);
290     }
291
292   /* Stems in unnatural (forced) direction should be shortened,
293      according to [Roush & Gourlay] */
294   Interval hp = head_positions (me);
295   if (dir && dir * hp[dir] >= 0)
296     {
297       SCM sshorten = me->get_property ("stem-shorten");
298       SCM scm_shorten = ly_c_pair_p (sshorten) ?
299         robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
300       Real shorten = 2* robust_scm2double (scm_shorten,0);
301
302       /* On boundary: shorten only half */
303       if (abs (head_positions (me)[dir]) <= 1)
304         shorten *= 0.5;
305
306       length -= shorten;
307     }
308
309   /* Tremolo stuff.  */
310   Grob *t_flag = unsmob_grob (me->get_property ("tremolo-flag"));
311   if (t_flag && !unsmob_grob (me->get_property ("beam")))
312     {
313       /* Crude hack: add extra space if tremolo flag is there.
314
315         We can't do this for the beam, since we get into a loop
316         (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
317
318       Real minlen = 1.0
319         + 2 * Stem_tremolo::raw_stencil (t_flag).extent (Y_AXIS).length  ()
320         / ss;
321
322       if (durlog >= 3)
323         {
324           Interval flag_ext = flag (me).extent (Y_AXIS);
325           if (!flag_ext.is_empty ())
326             minlen += 2 * flag_ext.length () / ss;
327
328           /* The clash is smaller for down stems (since the tremolo is
329              angled up.) */
330           if (dir == DOWN)
331             minlen -= 1.0;
332         }
333       length = length >? (minlen + 1.0);
334     }
335
336   Real st = dir ? hp[dir] + dir * length : 0;
337
338   /* TODO: change name  to extend-stems to staff/center/'()  */
339   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
340   if (!no_extend_b && dir * st < 0)
341     st = 0.0;
342
343   /* Make a little room if we have a upflag and there is a dot.
344      previous approach was to lengthen the stem. This is not
345      good typesetting practice.  */
346   if (!get_beam (me) && dir == UP
347       && durlog > 2)
348     {
349       Grob * closest_to_flag = extremal_heads (me)[dir];
350       Grob * dots = closest_to_flag
351         ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
352
353       if (dots)
354         {
355           Real dp = Staff_symbol_referencer::get_position (dots);
356           Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2 / ss;
357
358           /* Very gory: add myself to the X-support of the parent,
359              which should be a dot-column. */
360           if (dir * (st + flagy -  dp) < 0.5)
361             {
362               Grob *par = dots->get_parent (X_AXIS);
363
364               if (Dot_column::has_interface (par))
365                 {
366                   Side_position_interface::add_support (par, me);
367
368                   /* TODO: apply some better logic here. The flag is
369                      curved inwards, so this will typically be too
370                      much. */
371                 }
372             }
373         }
374     }
375   return st;
376 }
377
378 /* The log of the duration (Number of hooks on the flag minus two)  */
379 int
380 Stem::duration_log (Grob *me)
381 {
382   SCM s = me->get_property ("duration-log");
383   return (scm_is_number (s)) ? scm_to_int (s) : 2;
384 }
385
386 void
387 Stem::position_noteheads (Grob *me)
388 {
389   if (!head_count (me))
390     return;
391
392   Link_array<Grob> heads =
393     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-heads");
394
395   heads.sort (compare_position);
396   Direction dir = get_direction (me);
397
398   if (dir < 0)
399     heads.reverse ();
400
401   Real thick = thickness (me);
402
403   Grob *hed = support_head (me);
404   Real w = Note_head::head_extent (hed, X_AXIS)[dir];
405   for (int i = 0; i < heads.size (); i++)
406     heads[i]->translate_axis (w - Note_head::head_extent (heads[i],
407                                                           X_AXIS)[dir],
408                               X_AXIS);
409
410   bool parity = true;
411   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
412   for (int i = 1; i < heads.size (); i ++)
413     {
414       Real p = Staff_symbol_referencer::get_position (heads[i]);
415       Real dy =fabs (lastpos- p);
416
417       /*
418        dy should always be 0.5, 0.0, 1.0, but provide safety margin
419        for rounding errors.
420       */
421       if (dy < 1.1)             
422         {
423           if (parity)
424             {
425               Real ell = Note_head::head_extent (heads[i], X_AXIS).length ();
426
427               Direction d = get_direction (me);
428               /*
429                 Reversed head should be shifted ell-thickness, but this
430                 looks too crowded, so we only shift ell-0.5*thickness.
431
432                 This leads to assymetry: Normal heads overlap the
433                 stem 100% whereas reversed heads only overlaps the
434                 stem 50%
435               */
436
437               Real reverse_overlap = 0.5;
438               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
439                                         X_AXIS);
440
441               if (is_invisible (me))
442                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
443                                           X_AXIS);
444
445              /* TODO:
446                 
447               For some cases we should kern some more: when the
448               distance between the next or prev note is too large, we'd
449               get large white gaps, eg.
450         
451                |
452               X|
453                |X  <- kern this.
454                |
455               X
456         
457               */
458             }
459           parity = !parity;
460         }
461       else
462         parity = true;
463
464       lastpos = int (p);
465     }
466 }
467
468 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
469 SCM
470 Stem::before_line_breaking (SCM smob)
471 {
472   Grob *me = unsmob_grob (smob);
473
474   /*
475     Do the calculations for visible stems, but also for invisible stems
476     with note heads (i.e. half notes.)
477    */
478   if (head_count (me))
479     {
480       stem_end_position (me);   // ugh. Trigger direction calc.
481       position_noteheads (me);
482     }
483   else
484     me->set_property ("print-function", SCM_EOL);
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   /* TODO: make the stem start a direction ?
638      This is required to avoid stems passing in tablature chords.  */
639   Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
640     ? last_head (me) :  lh = first_head (me);
641
642   if (!lh)
643     return SCM_EOL;
644
645   if (is_invisible (me))
646     return SCM_EOL;
647
648   Real y1 = Staff_symbol_referencer::get_position (lh);
649   Real y2 = stem_end_position (me);
650
651   Interval stem_y (y1 <? y2,y2 >? y1);
652
653   // dy?
654   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
655
656   if (Grob *hed = support_head (me))
657     {
658       /*
659         must not take ledgers into account.
660        */
661       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
662       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
663
664       y_attach = head_height.linear_combination (y_attach);
665       stem_y[Direction (-d)] += d * y_attach/dy;
666     }
667
668
669   // URG
670   Real stem_width = thickness (me);
671   Real blot =
672         me->get_paper ()->get_dimension (ly_symbol2scm ("blotdiameter"));
673
674   Box b = Box (Interval (-stem_width/2, stem_width/2),
675                Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
676
677   Stencil ss = Lookup::round_filled_box (b, blot);
678   mol.add_stencil (ss);
679
680   if (!get_beam (me) && abs (duration_log (me)) > 2)
681     {
682       Stencil fl = flag (me);
683       fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
684       fl.translate_axis (stem_width/2, X_AXIS);
685       mol.add_stencil (fl);
686     }
687
688   return mol.smobbed_copy ();
689 }
690
691 /*
692   move the stem to right of the notehead if it is up.
693  */
694 MAKE_SCHEME_CALLBACK (Stem, off_callback, 2);
695 SCM
696 Stem::off_callback (SCM element_smob, SCM)
697 {
698   Grob *me = unsmob_grob (element_smob);
699   Real r = 0.0;
700   
701   if (head_count (me))
702     if (Grob *f = first_head (me))
703       {
704         Interval head_wid = Note_head::head_extent (f, X_AXIS);
705         Real attach = 0.0;
706         
707         if (is_invisible (me))
708           attach = 0.0;
709         else
710         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
711         
712         Direction d = get_direction (me);
713         Real real_attach = head_wid.linear_combination (d * attach);
714         r = real_attach;
715         
716         /* If not centered: correct for stem thickness.  */
717         if (attach)
718           {
719             Real rule_thick = thickness (me);
720             r += - d * rule_thick * 0.5;
721           }
722       }
723   return scm_make_real (r);
724 }
725
726 Spanner *
727 Stem::get_beam (Grob *me)
728 {
729   SCM b = me->get_property ("beam");
730   return dynamic_cast<Spanner*> (unsmob_grob (b));
731 }
732
733 Stem_info
734 Stem::get_stem_info (Grob *me)
735 {
736   /* Return cached info if available */
737   SCM scm_info = me->get_property ("stem-info");
738   if (!ly_c_pair_p (scm_info))
739     {
740       calc_stem_info (me);
741       scm_info = me->get_property ("stem-info");
742     }
743
744   Stem_info si;
745   si.dir_ = get_grob_direction (me);
746   si.ideal_y_ = scm_to_double (ly_car (scm_info));
747   si.shortest_y_ = scm_to_double (ly_cadr (scm_info));
748   return si;
749 }
750
751
752 /* TODO: add extra space for tremolos!  */
753 void
754 Stem::calc_stem_info (Grob *me)
755 {
756   Direction my_dir = get_grob_direction (me);
757
758   if (!my_dir)
759     {
760       programming_error ("No stem dir set?");
761       my_dir  = UP;
762     }
763
764   Real staff_space = Staff_symbol_referencer::staff_space (me);
765   Grob *beam = get_beam (me);
766   Real beam_translation = Beam::get_beam_translation (beam);
767   Real beam_thickness = Beam::get_thickness (beam);
768   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
769
770
771   /* Simple standard stem length */
772   SCM lengths = me->get_property ("beamed-lengths");
773   Real ideal_length =
774     scm_to_double (robust_list_ref (beam_count - 1,lengths))
775                 
776     * staff_space
777     /* stem only extends to center of beam
778     */
779     - 0.5 * beam_thickness
780     ;
781
782   /* Condition: sane minimum free stem length (chord to beams) */
783   lengths = me->get_property ("beamed-minimum-free-lengths");
784   Real ideal_minimum_free =
785     scm_to_double (robust_list_ref (beam_count - 1, lengths))
786     * staff_space;
787
788
789   /* UGH
790      It seems that also for ideal minimum length, we must use
791      the maximum beam count (for this direction):
792
793      \score{ \notes\relative c''{ [a8 a32] }}
794
795      must be horizontal. */
796   Real height_of_my_beams = beam_thickness
797     + (beam_count - 1) * beam_translation;
798
799   Real ideal_minimum_length = ideal_minimum_free
800     + height_of_my_beams
801     /* stem only extends to center of beam */
802     - 0.5 * beam_thickness;
803
804   ideal_length = ideal_length >? ideal_minimum_length;
805
806   /* Convert to Y position, calculate for dir == UP */
807   Real note_start =
808     /* staff positions */
809     head_positions (me)[my_dir] * 0.5
810     * my_dir * staff_space;
811   Real ideal_y = note_start + ideal_length;
812
813
814   /* Conditions for Y position */
815
816   /* Lowest beam of (UP) beam must never be lower than second staffline
817
818      Reference?
819
820      Although this (additional) rule is probably correct,
821      I expect that highest beam (UP) should also never be lower
822      than middle staffline, just as normal stems.
823
824      Reference?
825
826      Obviously not for grace beams.
827
828      Also, not for knees.  Seems to be a good thing. */
829   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
830   bool is_knee = to_boolean (beam->get_property ("knee"));
831   if (!no_extend_b && !is_knee)
832     {
833       /* Highest beam of (UP) beam must never be lower than middle
834          staffline */
835       ideal_y = ideal_y >? 0;
836       /* Lowest beam of (UP) beam must never be lower than second staffline */
837       ideal_y = ideal_y >? (-staff_space
838                             - beam_thickness + height_of_my_beams);
839     }
840
841
842   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
843
844   Real minimum_free =
845     scm_to_double (robust_list_ref
846                    (beam_count - 1,
847                     me->get_property
848                     ("beamed-extreme-minimum-free-lengths")))
849     * staff_space;
850
851   Real minimum_length = minimum_free
852     + height_of_my_beams
853     /* stem only extends to center of beam */
854     - 0.5 * beam_thickness;
855
856   ideal_y *= my_dir;
857   Real minimum_y = note_start + minimum_length;
858   Real shortest_y = minimum_y * my_dir;
859
860   me->set_property ("stem-info",
861                     scm_list_2 (scm_make_real (ideal_y),
862                                 scm_make_real (shortest_y)));
863 }
864
865 Slice
866 Stem::beam_multiplicity (Grob *stem)
867 {
868   SCM beaming= stem->get_property ("beaming");
869   Slice le = int_list_to_slice (ly_car (beaming));
870   Slice ri = int_list_to_slice (ly_cdr (beaming));
871   le.unite (ri);
872   return le;
873 }
874
875
876 /* FIXME:  Too many properties  */
877 ADD_INTERFACE (Stem, "stem-interface",
878                "The stem represent the graphical stem.  "
879                "In addition, it internally connects note heads, beams and"
880                "tremolos. "
881                "Rests and whole notes have invisible stems.",
882                "tremolo-flag french-beaming "
883                "avoid-note-head thickness "
884                "stem-info beamed-lengths beamed-minimum-free-lengths "
885                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
886                "duration-log beaming neutral-direction stem-end-position "
887                "note-heads direction length flag-style "
888                "no-stem-extend stroke-style");
889
890 /****************************************************************/
891
892 Stem_info::Stem_info ()
893 {
894   ideal_y_ = shortest_y_ = 0;
895   dir_ = CENTER;
896 }
897
898 void
899 Stem_info::scale (Real x)
900 {
901   ideal_y_ *= x;
902   shortest_y_ *= x;
903 }