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