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