]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
a5d0c00e73498c698e5bba73674b8b2780470baf
[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 "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 /*
219   The positions, in ascending order.
220  */
221 Array<int>
222 Stem::note_head_positions (Grob *me)
223 {
224   Array<int> ps ;
225   for (SCM s = me->get_grob_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
226     {
227       Grob * n = unsmob_grob (ly_car (s));
228       int p = int (Staff_symbol_referencer::get_position (n));
229
230       ps.push (p);
231     }
232   
233   ps.sort (icmp);
234   return ps; 
235 }
236
237
238 void
239 Stem::add_head (Grob*me, Grob *n)
240 {
241   n->set_grob_property ("stem", me->self_scm ());
242   n->add_dependency (me);
243
244   /*
245     TODO: why not store Rest pointers? 
246   */
247   if (Note_head::has_interface (n))
248     {
249       Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
250     }
251 }
252
253 bool
254 Stem::invisible_b (Grob*me)
255 {
256   return ! (head_count (me)
257             && gh_scm2int (me->get_grob_property ("duration-log")) >= 1);
258 }
259
260 Direction
261 Stem::get_default_dir (Grob*me) 
262 {
263   int staff_center = 0;
264   Interval hp = head_positions (me);
265   if (hp.is_empty ())
266     {
267       return CENTER;
268     }
269   
270   int udistance = (int) (UP * hp[UP] - staff_center);
271   int ddistance = (int) (DOWN* hp[DOWN] - staff_center);  
272   
273   if (sign (ddistance - udistance))
274     return Direction (sign (ddistance -udistance));
275
276   return to_dir (me->get_grob_property ("neutral-direction"));
277 }
278
279 Real
280 Stem::get_default_stem_end_position (Grob*me) 
281 {
282   Real ss = Staff_symbol_referencer::staff_space (me); 
283
284   int durlog = duration_log (me);
285     
286   SCM s;
287   Array<Real> a;
288
289   
290   Real length = 7;              // WARNING: IN HALF SPACES
291   SCM scm_len = me->get_grob_property ("length");
292   if (gh_number_p (scm_len))
293     {
294       length = gh_scm2double (scm_len);
295     }
296   else
297     {
298       s = me->get_grob_property ("lengths");
299       if (gh_pair_p (s))
300         {
301           length = 2* gh_scm2double (robust_list_ref (durlog -2, s));
302         }
303     }
304
305
306
307   /* URGURGURG
308      'set-default-stemlen' sets direction too
309    */
310   Direction dir = get_direction (me);
311   if (!dir)
312     {
313       dir = get_default_dir (me);
314       set_grob_direction (me, dir);
315     }
316
317
318   /* stems in unnatural (forced) direction should be shortened, 
319     according to [Roush & Gourlay] */
320   if (!chord_start_y (me)
321       || (get_direction (me) != get_default_dir (me)))
322     {
323       
324   
325       SCM sshorten = me->get_grob_property ("stem-shorten");
326       SCM scm_shorten = gh_pair_p (sshorten) ?
327         robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
328       Real shorten = 2* robust_scm2double (scm_shorten,0);
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.is_empty ())
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 = thickness (me);
458       
459   Grob *hed = support_head (me);
460   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
461   for (int i=0; i < heads.size (); i++)
462     {
463       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
464                                 X_AXIS);
465     }
466   
467   bool parity= true;
468   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
469   for (int i=1; i < heads.size (); i ++)
470     {
471       Real p = Staff_symbol_referencer::get_position (heads[i]);
472       Real dy =fabs (lastpos- p);
473
474       /*
475        dy should always be 0.5, 0.0, 1.0, but provide safety margin
476        for rounding errors.
477       */
478       if (dy < 1.1)             
479         {
480           if (parity)
481             {
482               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
483
484               Direction d = get_direction (me);
485               /*
486                 Reversed head should be shifted l-thickness, but this
487                 looks too crowded, so we only shift l-0.5*thickness.
488
489                 This leads to assymetry: Normal heads overlap the
490                 stem 100% whereas reversed heads only overlaps the
491                 stem 50%
492
493               */
494
495               Real reverse_overlap =0.5;
496               heads[i]->translate_axis ((l-thick*reverse_overlap) * d, X_AXIS);
497
498               if (invisible_b(me))
499                 heads[i]->translate_axis (-thick*(2 - reverse_overlap) * d , X_AXIS);
500
501               
502              /* TODO:
503                  
504               For some cases we should kern some more: when the
505               distance between the next or prev note is too large, we'd 
506               get large white gaps, eg.
507               
508                |
509               X|
510                |X  <- kern this.
511                |
512               X
513               
514               */
515             }
516           parity = !parity;
517         }
518       else
519         parity = true;
520       
521       lastpos = int (p);
522     }
523 }
524
525 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
526 SCM
527 Stem::before_line_breaking (SCM smob)
528 {
529   Grob*me = unsmob_grob (smob);
530
531
532   /*
533     Do the calculations for visible stems, but also for invisible stems
534     with note heads (i.e. half notes.)
535    */
536   if (head_count (me))
537     {
538       stem_end_position (me);   // ugh. Trigger direction calc.
539       position_noteheads (me);
540     }
541   else
542     {
543       me->set_grob_property ("print-function", SCM_EOL);
544     }
545   
546   return SCM_UNSPECIFIED;
547 }
548
549 /*
550   ugh.
551   When in a beam with tuplet brackets, brew_mol is called early,
552   caching a wrong value.
553  */
554 MAKE_SCHEME_CALLBACK (Stem, height, 2);
555 SCM
556 Stem::height (SCM smob, SCM ax)
557 {
558   Axis a = (Axis)gh_scm2int (ax);
559   Grob * me = unsmob_grob (smob);
560   assert (a == Y_AXIS);
561
562   SCM mol = me->get_uncached_molecule ();
563   Interval iv;
564   if (mol != SCM_EOL)
565     iv = unsmob_molecule (mol)->extent (a);
566   if (Grob *b =get_beam (me))
567     {
568       Direction d = get_direction (me);
569       iv[d] += d * Beam::get_thickness (b) /2.0 ;
570     }
571
572   return ly_interval2scm (iv);
573 }
574
575
576 Molecule
577 Stem::flag (Grob*me)
578 {
579   /* TODO: maybe property stroke-style should take different values,
580      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
581      '() or "grace").  */
582   String flag_style;
583   
584   SCM flag_style_scm = me->get_grob_property ("flag-style");
585   if (gh_symbol_p (flag_style_scm))
586     {
587       flag_style = ly_symbol2string (flag_style_scm);
588     }
589
590   if (flag_style == "no-flag")
591     {
592       return Molecule ();
593     }
594
595   bool adjust = to_boolean (me->get_grob_property ("adjust-if-on-staffline"));
596
597   String staffline_offs;
598   if (String::compare (flag_style, "mensural") == 0)
599     /* Mensural notation: For notes on staff lines, use different
600        flags than for notes between staff lines.  The idea is that
601        flags are always vertically aligned with the staff lines,
602        regardless if the note head is on a staff line or between two
603        staff lines.  In other words, the inner end of a flag always
604        touches a staff line.
605     */
606     {
607       if (adjust)
608         {
609           /* Urrgh!  We have to detect wether this stem ends on a staff
610              line or between two staff lines.  But we can not call
611              stem_end_position(me) or get_default_stem_end_position(me),
612              since this encounters the flag and hence results in an
613              infinite recursion.  However, in pure mensural notation,
614              there are no multiple note heads attached to a single stem,
615              neither is there usually need for using the stem_shorten
616              property (except for 32th and 64th notes, but that is not a
617              problem since the stem length in this case is augmented by
618              an integral multiple of staff_space).  Hence, it should be
619              sufficient to just take the first note head, assume it's
620              the only one, look if it's on a staff line, and select the
621              flag's shape accordingly.  In the worst case, the shape
622              looks slightly misplaced, but that will usually be the
623              programmer's fault (e.g. when trying to attach multiple
624              note heads to a single stem in mensural notation).
625           */
626
627           /*
628             perhaps the detection whether this correction is needed should
629             happen in a different place  to avoid the recursion.
630             
631             --hwn.
632           */
633           int p = (int)rint (Staff_symbol_referencer::get_position (first_head (me)));
634           staffline_offs = Staff_symbol_referencer::on_staffline (me, p) ?
635             "1" : "0";
636         }
637       else
638         {
639           staffline_offs = "2";
640         }
641     }
642   else
643     {
644       staffline_offs = "";
645     }
646
647   char dir = (get_direction (me) == UP) ? 'u' : 'd';
648   String font_char =
649     flag_style + to_string (dir) + staffline_offs + to_string (duration_log (me));
650   Font_metric *fm = Font_interface::get_default_font (me);
651   Molecule flag = fm->find_by_name ("flags-" + font_char);
652   if (flag.is_empty ())
653     {
654       me->warning (_f ("flag `%s' not found", font_char));
655     }
656
657   SCM stroke_style_scm = me->get_grob_property ("stroke-style");
658   if (gh_string_p (stroke_style_scm))
659     {
660       String stroke_style = ly_scm2string (stroke_style_scm);
661       if (!stroke_style.is_empty ())
662         {
663           String font_char = to_string (dir) + stroke_style;
664           Molecule stroke = fm->find_by_name ("flags-" + font_char);
665           if (stroke.is_empty ())
666             {
667               me->warning (_f ("flag stroke `%s' not found", font_char));
668             }
669           else
670             {
671               flag.add_molecule (stroke);
672             }
673         }
674     }
675
676   return flag;
677 }
678
679 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
680 SCM
681 Stem::dim_callback (SCM e, SCM ax)
682 {
683   Axis a = (Axis) gh_scm2int (ax);
684   assert (a == X_AXIS);
685   Grob *me = unsmob_grob (e);
686   Interval r (0, 0);
687   if (unsmob_grob (me->get_grob_property ("beam")) || abs (duration_log (me)) <= 2)
688     ;   // TODO!
689   else
690     {
691       r = flag (me).extent (X_AXIS)
692         + thickness (me)/2;
693     }
694   return ly_interval2scm (r);
695 }
696  
697 Real
698 Stem::thickness (Grob* me)
699 {
700   return gh_scm2double (me->get_grob_property ("thickness"))
701     * Staff_symbol_referencer::line_thickness (me);
702 }
703
704 MAKE_SCHEME_CALLBACK (Stem,print,1);
705
706 SCM
707 Stem::print (SCM smob) 
708 {
709   Grob*me = unsmob_grob (smob);
710   Molecule mol;
711   Direction d = get_direction (me);
712      
713   /*
714     TODO: make the stem start a direction ?
715
716     This is required to avoid stems passing in tablature chords...
717   */
718   Grob *lh = to_boolean (me->get_grob_property ("avoid-note-head")) 
719     ? last_head (me) :  lh = first_head (me);
720
721   if (!lh)
722     return SCM_EOL;
723
724   if (invisible_b (me))
725     return SCM_EOL;
726   
727   Real y1 = Staff_symbol_referencer::get_position (lh);
728   Real y2 = stem_end_position (me);
729   
730   Interval stem_y (y1 <? y2,y2 >? y1);
731  
732
733   // dy?
734   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
735
736   if (Grob *hed = support_head (me))
737     {
738       /*
739         must not take ledgers into account.
740        */
741       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
742       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
743
744       y_attach = head_height.linear_combination (y_attach);
745       stem_y[Direction (-d)] += d * y_attach/dy;
746     }
747
748   
749   // URG
750   Real stem_width = thickness (me);
751   Real blot = 
752         me->get_paper ()->get_realvar (ly_symbol2scm ("blotdiameter"));
753   
754   Box b = Box (Interval (-stem_width/2, stem_width/2),
755                Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
756
757   Molecule ss = Lookup::round_filled_box (b, blot);
758   mol.add_molecule (ss);
759
760   if (!get_beam (me) && abs (duration_log (me)) > 2)
761     {
762       Molecule fl = flag (me);
763       fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
764       fl.translate_axis (stem_width/2, X_AXIS);
765       mol.add_molecule (fl);
766     }
767
768   return mol.smobbed_copy ();
769 }
770
771 /*
772   move the stem to right of the notehead if it is up.
773  */
774 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
775 SCM
776 Stem::off_callback (SCM element_smob, SCM)
777 {
778   Grob *me = unsmob_grob (element_smob);
779   
780   Real r=0;
781
782   if (head_count (me) == 0)
783     {
784       return gh_double2scm (0.0);
785     }
786   
787   if (Grob * f = first_head (me))
788     {
789       Interval head_wid = Note_head::head_extent(f, X_AXIS);
790       
791       Real attach =0.0;
792
793       if (invisible_b (me))
794         {
795           attach = 0.0;
796         }
797       else
798         attach = Note_head::stem_attachment_coordinate(f, X_AXIS);
799
800       Direction d = get_direction (me);
801
802       Real real_attach = head_wid.linear_combination (d * attach);
803
804       r = real_attach;
805
806       /*
807         If not centered: correct for stem thickness.
808        */
809       if (attach)
810         {
811           Real rule_thick
812             = thickness (me);
813           
814           r += - d * rule_thick * 0.5;
815         }
816     }
817   return gh_double2scm (r);
818 }
819
820
821 Grob*
822 Stem::get_beam (Grob*me)
823 {
824   SCM b=  me->get_grob_property ("beam");
825   return unsmob_grob (b);
826 }
827
828 Stem_info
829 Stem::get_stem_info (Grob *me)
830 {
831   /* Return cached info if available */
832   SCM scm_info = me->get_grob_property ("stem-info");
833   if (!gh_pair_p (scm_info))
834     {
835       calc_stem_info (me);
836       scm_info = me->get_grob_property ("stem-info");
837     }
838   
839   Stem_info si;
840   si.dir_ = get_grob_direction (me); 
841   si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
842   si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
843   return si;
844 }
845
846
847 /*
848   TODO: add extra space for tremolos!
849  */
850 void
851 Stem::calc_stem_info (Grob *me)
852 {
853   Direction my_dir = get_grob_direction (me);
854   Real staff_space = Staff_symbol_referencer::staff_space (me);
855   Grob *beam = get_beam (me);
856   Real beam_translation = Beam::get_beam_translation (beam);
857   Real beam_thickness = Beam::get_thickness (beam);
858   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
859
860
861   /* Simple standard stem length */
862   SCM lengths = me->get_grob_property ("beamed-lengths");
863   Real ideal_length =
864     gh_scm2double (robust_list_ref (beam_count - 1,lengths))
865                 
866     * staff_space
867     /* stem only extends to center of beam */
868     - 0.5 * beam_thickness;
869   
870   /* Condition: sane minimum free stem length (chord to beams) */
871   lengths = me->get_grob_property ("beamed-minimum-free-lengths");
872   Real ideal_minimum_free =
873     gh_scm2double (robust_list_ref (beam_count - 1, lengths))
874     * staff_space;
875   
876
877   /* UGH
878      It seems that also for ideal minimum length, we must use
879      the maximum beam count (for this direction):
880      
881      \score{ \notes\relative c''{ [a8 a32] }}
882      
883      must be horizontal. */
884   Real height_of_my_beams = beam_thickness
885     + (beam_count - 1) * beam_translation;
886
887   Real ideal_minimum_length = ideal_minimum_free
888     + height_of_my_beams
889     /* stem only extends to center of beam */
890     - 0.5 * beam_thickness;
891
892   ideal_length = ideal_length >? ideal_minimum_length;
893
894   
895   /* Convert to Y position, calculate for dir == UP */
896   Real note_start =
897     /* staff positions */
898     head_positions (me)[my_dir] * 0.5
899     * my_dir * staff_space;
900   Real ideal_y = note_start + ideal_length;
901
902
903   /* Conditions for Y position */
904
905   /* Lowest beam of (UP) beam must never be lower than second staffline
906  
907      Reference?
908  
909      Although this (additional) rule is probably correct,
910      I expect that highest beam (UP) should also never be lower
911      than middle staffline, just as normal stems.
912
913      Reference?
914
915      Obviously not for grace beams.
916      
917      Also, not for knees.  Seems to be a good thing. */
918   bool no_extend_b = to_boolean (me->get_grob_property ("no-stem-extend"));
919   bool knee_b = to_boolean (beam->get_grob_property ("knee"));
920   if (!no_extend_b && !knee_b)
921     {
922       /* Highest beam of (UP) beam must never be lower than middle
923          staffline */
924       ideal_y = ideal_y >? 0;
925       /* Lowest beam of (UP) beam must never be lower than second staffline */
926       ideal_y = ideal_y >? (-staff_space
927                             - beam_thickness + height_of_my_beams);
928     }
929
930
931   ideal_y -= robust_scm2double (beam->get_grob_property ("shorten"), 0);
932
933   Real minimum_free =
934     gh_scm2double (robust_list_ref
935                    (beam_count - 1,
936                     me->get_grob_property
937                     ("beamed-extreme-minimum-free-lengths")))
938     * staff_space;
939
940   Real minimum_length = minimum_free
941     + height_of_my_beams
942     /* stem only extends to center of beam */
943     - 0.5 * beam_thickness;
944
945   Real minimum_y = note_start + minimum_length;
946   
947   
948   ideal_y *= my_dir;
949   Real shortest_y = minimum_y * my_dir; 
950   
951   me->set_grob_property ("stem-info",
952                          scm_list_n (gh_double2scm (ideal_y),
953                                      gh_double2scm (shortest_y),
954                                      SCM_UNDEFINED));
955 }
956
957 Slice
958 Stem::beam_multiplicity (Grob *stem)
959 {
960   SCM beaming= stem->get_grob_property ("beaming");
961   Slice l = int_list_to_slice (gh_car (beaming));
962   Slice r = int_list_to_slice (gh_cdr (beaming));
963   l.unite (r);
964
965   return l;
966 }
967
968
969 /*
970   these are too many props.
971  */
972 ADD_INTERFACE (Stem,"stem-interface",
973                "A stem",
974                "tremolo-flag french-beaming "
975                "avoid-note-head adjust-if-on-staffline thickness "
976                "stem-info beamed-lengths beamed-minimum-free-lengths "
977                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
978                "duration-log beaming neutral-direction stem-end-position "
979                "note-heads direction length flag-style "
980                "no-stem-extend stroke-style");
981
982
983
984 /****************************************************************/
985
986 Stem_info::Stem_info()
987 {
988   ideal_y_ = shortest_y_ =0;
989   dir_ = CENTER;
990 }
991
992 void
993 Stem_info::scale (Real x)
994 {
995   ideal_y_ *= x;
996   shortest_y_ *= x;
997 }