]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
* lily/include/global-context.hh (Context): clean-up data hiding.
[lilypond.git] / lily / stem.cc
1 /*
2   stem.cc -- implement Stem
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8
9   TODO: This is way too hairy
10
11   TODO: fix naming.
12
13   Stem-end, chord-start, etc. is all confusing naming.
14 */
15
16 #include <math.h>               // rint
17
18 #include "lookup.hh"
19 #include "directional-element-interface.hh"
20 #include "note-head.hh"
21 #include "stem.hh"
22 #include "warn.hh"
23 #include "paper-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "stencil.hh"
27 #include "paper-column.hh"
28 #include "misc.hh"
29 #include "beam.hh"
30 #include "rest.hh"
31 #include "group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "spanner.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
36 #include "stem-tremolo.hh"
37
38 void
39 Stem::set_beaming (Grob*me, int beam_count,  Direction d)
40 {
41   SCM pair = me->get_property ("beaming");
42   
43   if (!gh_pair_p (pair))
44     {
45       pair = gh_cons (SCM_EOL, SCM_EOL);
46       me->set_property ("beaming", pair);
47     }
48
49   SCM l = index_get_cell (pair, d);
50   for (int i = 0; i<  beam_count; i++)
51     {
52       l = 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_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_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_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_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_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_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_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::is_invisible (Grob*me)
255 {
256   return ! (head_count (me)
257             && gh_scm2int (me->get_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_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_property ("length");
292   if (gh_number_p (scm_len))
293     {
294       length = gh_scm2double (scm_len);
295     }
296   else
297     {
298       s = me->get_property ("lengths");
299       if (gh_pair_p (s))
300         {
301           length = 2* gh_scm2double (robust_list_ref (durlog -2, s));
302         }
303     }
304
305   /* URGURGURG
306      'set-default-stemlen' sets direction too
307    */
308   Direction dir = get_direction (me);
309   if (!dir)
310     {
311       dir = get_default_dir (me);
312       set_grob_direction (me, dir);
313     }
314
315   /* stems in unnatural (forced) direction should be shortened, 
316     according to [Roush & Gourlay] */
317   if (dir * head_positions (me)[dir] >= 0)
318     {
319       SCM sshorten = me->get_property ("stem-shorten");
320       SCM scm_shorten = gh_pair_p (sshorten) ?
321         robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
322       Real shorten = 2* robust_scm2double (scm_shorten,0);
323       
324   
325       /* On boundary: shorten only half */
326       if (abs (head_positions (me)[dir]) <= 1)
327         shorten *= 0.5;
328   
329       length -= shorten;
330     }
331
332   /*
333     Tremolo stuff: 
334   */
335   Grob * trem = unsmob_grob (me->get_property ("tremolo-flag"));
336   if (trem &&  !unsmob_grob (me->get_property ("beam")))
337     {
338       /*
339         Crude hack: add extra space if tremolo flag is there.
340
341         We can't do this for the beam, since we get into a loop
342         (Stem_tremolo::raw_stencil () looks at the beam.)
343         
344          --hwn 
345       */
346       
347       Real minlen =
348         1.0 + 2 * Stem_tremolo::raw_stencil (trem).extent (Y_AXIS).length  () / ss;
349       
350       if (durlog >= 3)
351         {
352           Interval flag_ext = flag (me).extent (Y_AXIS) ;
353           if (!flag_ext.is_empty ())
354             minlen += 2 * flag_ext.length () / ss ;
355
356           /*
357             The clash is smaller for down stems (since the tremolo is
358             angled up.)
359            */
360           if (dir == DOWN)
361             minlen -= 1.0;
362         }
363       
364       length = length >? (minlen + 1.0);
365     }
366    
367   Interval hp = head_positions (me);  
368   Real st = hp[dir] + dir * length;
369
370   /*
371     TODO: change name  to extend-stems to staff/center/'()
372   */
373   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
374   if (!no_extend_b && dir * st < 0) // junkme?
375     st = 0.0;
376
377   /*
378     Make a little room if we have a upflag and there is a dot.
379     previous approach was to lengthen the stem. This is not
380     good typesetting practice. 
381     
382   */
383   if (!get_beam (me) && dir == UP
384       && durlog > 2)
385     {
386       Grob * closest_to_flag = extremal_heads (me)[dir];
387       Grob * dots = closest_to_flag
388         ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
389
390       if (dots)
391         {
392           Real dp = Staff_symbol_referencer::get_position (dots);
393           Real flagy =  flag (me).extent (Y_AXIS)[-dir] * 2
394             / ss;
395
396           /*
397             Very gory: add myself to the X-support of the parent,
398             which should be a dot-column.
399            */
400           if (dir * (st + flagy -  dp) < 0.5)
401             {
402               Grob *par = dots->get_parent (X_AXIS);
403
404               if (Dot_column::has_interface (par))
405                 {
406                   Side_position_interface::add_support (par, me);
407
408                   /*
409                     TODO: apply some better logic here. The flag is
410                     curved inwards, so this will typically be too
411                     much.
412                   */
413                 }
414             }
415         }
416     }
417
418
419   return st;
420 }
421
422
423
424 /*
425   
426   the log of the duration (Number of hooks on the flag minus two)
427  */
428 int
429 Stem::duration_log (Grob*me) 
430 {
431   SCM s = me->get_property ("duration-log");
432   return (gh_number_p (s)) ? gh_scm2int (s) : 2;
433 }
434
435 void
436 Stem::position_noteheads (Grob*me)
437 {
438   if (!head_count (me))
439     return;
440   
441   Link_array<Grob> heads =
442     Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
443
444   heads.sort (compare_position);
445   Direction dir =get_direction (me);
446   
447   if (dir < 0)
448     heads.reverse ();
449
450
451   Real thick = thickness (me);
452       
453   Grob *hed = support_head (me);
454   Real w = Note_head::head_extent (hed,X_AXIS)[dir];
455   for (int i=0; i < heads.size (); i++)
456     {
457       heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
458                                 X_AXIS);
459     }
460   
461   bool parity= true;
462   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
463   for (int i=1; i < heads.size (); i ++)
464     {
465       Real p = Staff_symbol_referencer::get_position (heads[i]);
466       Real dy =fabs (lastpos- p);
467
468       /*
469        dy should always be 0.5, 0.0, 1.0, but provide safety margin
470        for rounding errors.
471       */
472       if (dy < 1.1)             
473         {
474           if (parity)
475             {
476               Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
477
478               Direction d = get_direction (me);
479               /*
480                 Reversed head should be shifted l-thickness, but this
481                 looks too crowded, so we only shift l-0.5*thickness.
482
483                 This leads to assymetry: Normal heads overlap the
484                 stem 100% whereas reversed heads only overlaps the
485                 stem 50%
486
487               */
488
489               Real reverse_overlap =0.5;
490               heads[i]->translate_axis ((l-thick*reverse_overlap) * d, X_AXIS);
491
492               if (is_invisible (me))
493                 heads[i]->translate_axis (-thick*(2 - reverse_overlap) * d , X_AXIS);
494
495               
496              /* TODO:
497                  
498               For some cases we should kern some more: when the
499               distance between the next or prev note is too large, we'd 
500               get large white gaps, eg.
501               
502                |
503               X|
504                |X  <- kern this.
505                |
506               X
507               
508               */
509             }
510           parity = !parity;
511         }
512       else
513         parity = true;
514       
515       lastpos = int (p);
516     }
517 }
518
519 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
520 SCM
521 Stem::before_line_breaking (SCM smob)
522 {
523   Grob*me = unsmob_grob (smob);
524
525
526   /*
527     Do the calculations for visible stems, but also for invisible stems
528     with note heads (i.e. half notes.)
529    */
530   if (head_count (me))
531     {
532       stem_end_position (me);   // ugh. Trigger direction calc.
533       position_noteheads (me);
534     }
535   else
536     {
537       me->set_property ("print-function", SCM_EOL);
538     }
539   
540   return SCM_UNSPECIFIED;
541 }
542
543 /*
544   ugh.
545   When in a beam with tuplet brackets, brew_mol is called early,
546   caching a wrong value.
547  */
548 MAKE_SCHEME_CALLBACK (Stem, height, 2);
549 SCM
550 Stem::height (SCM smob, SCM ax)
551 {
552   Axis a = (Axis)gh_scm2int (ax);
553   Grob * me = unsmob_grob (smob);
554   assert (a == Y_AXIS);
555
556   SCM mol = me->get_uncached_stencil ();
557   Interval iv;
558   if (mol != SCM_EOL)
559     iv = unsmob_stencil (mol)->extent (a);
560   if (Grob *b =get_beam (me))
561     {
562       Direction d = get_direction (me);
563       iv[d] += d * Beam::get_thickness (b) /2.0 ;
564     }
565
566   return ly_interval2scm (iv);
567 }
568
569
570 Stencil
571 Stem::flag (Grob*me)
572 {
573   /* TODO: maybe property stroke-style should take different values,
574      e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
575      '() or "grace").  */
576   String flag_style;
577   
578   SCM flag_style_scm = me->get_property ("flag-style");
579   if (gh_symbol_p (flag_style_scm))
580     {
581       flag_style = ly_symbol2string (flag_style_scm);
582     }
583
584   if (flag_style == "no-flag")
585     {
586       return Stencil ();
587     }
588
589   bool adjust = true;
590
591   String staffline_offs;
592   if (String::compare (flag_style, "mensural") == 0)
593     /* Mensural notation: For notes on staff lines, use different
594        flags than for notes between staff lines.  The idea is that
595        flags are always vertically aligned with the staff lines,
596        regardless if the note head is on a staff line or between two
597        staff lines.  In other words, the inner end of a flag always
598        touches a staff line.
599     */
600     {
601       if (adjust)
602         {
603           /* Urrgh!  We have to detect wether this stem ends on a staff
604              line or between two staff lines.  But we can not call
605              stem_end_position (me) or get_default_stem_end_position (me),
606              since this encounters the flag and hence results in an
607              infinite recursion.  However, in pure mensural notation,
608              there are no multiple note heads attached to a single stem,
609              neither is there usually need for using the stem_shorten
610              property (except for 32th and 64th notes, but that is not a
611              problem since the stem length in this case is augmented by
612              an integral multiple of staff_space).  Hence, it should be
613              sufficient to just take the first note head, assume it's
614              the only one, look if it's on a staff line, and select the
615              flag's shape accordingly.  In the worst case, the shape
616              looks slightly misplaced, but that will usually be the
617              programmer's fault (e.g. when trying to attach multiple
618              note heads to a single stem in mensural notation).
619           */
620
621           /*
622             perhaps the detection whether this correction is needed should
623             happen in a different place  to avoid the recursion.
624             
625             --hwn.
626           */
627           int p = (int)rint (Staff_symbol_referencer::get_position (first_head (me)));
628           staffline_offs = Staff_symbol_referencer::on_staffline (me, p) ?
629             "1" : "0";
630         }
631       else
632         {
633           staffline_offs = "2";
634         }
635     }
636   else
637     {
638       staffline_offs = "";
639     }
640
641   char dir = (get_direction (me) == UP) ? 'u' : 'd';
642   String font_char =
643     flag_style + to_string (dir) + staffline_offs + to_string (duration_log (me));
644   Font_metric *fm = Font_interface::get_default_font (me);
645   Stencil flag = fm->find_by_name ("flags-" + font_char);
646   if (flag.is_empty ())
647     {
648       me->warning (_f ("flag `%s' not found", font_char));
649     }
650
651   SCM stroke_style_scm = me->get_property ("stroke-style");
652   if (gh_string_p (stroke_style_scm))
653     {
654       String stroke_style = ly_scm2string (stroke_style_scm);
655       if (!stroke_style.is_empty ())
656         {
657           String font_char = to_string (dir) + stroke_style;
658           Stencil stroke = fm->find_by_name ("flags-" + font_char);
659           if (stroke.is_empty ())
660             {
661               me->warning (_f ("flag stroke `%s' not found", font_char));
662             }
663           else
664             {
665               flag.add_stencil (stroke);
666             }
667         }
668     }
669
670   return flag;
671 }
672
673 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
674 SCM
675 Stem::dim_callback (SCM e, SCM ax)
676 {
677   Axis a = (Axis) gh_scm2int (ax);
678   assert (a == X_AXIS);
679   Grob *me = unsmob_grob (e);
680   Interval r (0, 0);
681   if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
682     ;   // TODO!
683   else
684     {
685       r = flag (me).extent (X_AXIS)
686         + thickness (me)/2;
687     }
688   return ly_interval2scm (r);
689 }
690  
691 Real
692 Stem::thickness (Grob* me)
693 {
694   return gh_scm2double (me->get_property ("thickness"))
695     * Staff_symbol_referencer::line_thickness (me);
696 }
697
698 MAKE_SCHEME_CALLBACK (Stem,print,1);
699
700 SCM
701 Stem::print (SCM smob) 
702 {
703   Grob*me = unsmob_grob (smob);
704   Stencil mol;
705   Direction d = get_direction (me);
706      
707   /*
708     TODO: make the stem start a direction ?
709
710     This is required to avoid stems passing in tablature chords...
711   */
712   Grob *lh = to_boolean (me->get_property ("avoid-note-head")) 
713     ? last_head (me) :  lh = first_head (me);
714
715   if (!lh)
716     return SCM_EOL;
717
718   if (is_invisible (me))
719     return SCM_EOL;
720   
721   Real y1 = Staff_symbol_referencer::get_position (lh);
722   Real y2 = stem_end_position (me);
723   
724   Interval stem_y (y1 <? y2,y2 >? y1);
725  
726
727   // dy?
728   Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
729
730   if (Grob *hed = support_head (me))
731     {
732       /*
733         must not take ledgers into account.
734        */
735       Interval head_height = Note_head::head_extent (hed,Y_AXIS);
736       Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
737
738       y_attach = head_height.linear_combination (y_attach);
739       stem_y[Direction (-d)] += d * y_attach/dy;
740     }
741
742   
743   // URG
744   Real stem_width = thickness (me);
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   Stencil ss = Lookup::round_filled_box (b, blot);
752   mol.add_stencil (ss);
753
754   if (!get_beam (me) && abs (duration_log (me)) > 2)
755     {
756       Stencil 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_stencil (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       Real attach =0.0;
786
787       if (is_invisible (me))
788         {
789           attach = 0.0;
790         }
791       else
792         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
793
794       Direction d = get_direction (me);
795
796       Real real_attach = head_wid.linear_combination (d * attach);
797
798       r = real_attach;
799
800       /*
801         If not centered: correct for stem thickness.
802        */
803       if (attach)
804         {
805           Real rule_thick
806             = thickness (me);
807           
808           r += - d * rule_thick * 0.5;
809         }
810     }
811   return gh_double2scm (r);
812 }
813
814
815 Grob*
816 Stem::get_beam (Grob*me)
817 {
818   SCM b=  me->get_property ("beam");
819   return unsmob_grob (b);
820 }
821
822 Stem_info
823 Stem::get_stem_info (Grob *me)
824 {
825   /* Return cached info if available */
826   SCM scm_info = me->get_property ("stem-info");
827   if (!gh_pair_p (scm_info))
828     {
829       calc_stem_info (me);
830       scm_info = me->get_property ("stem-info");
831     }
832   
833   Stem_info si;
834   si.dir_ = get_grob_direction (me); 
835   si.ideal_y_ = gh_scm2double (gh_car (scm_info)); 
836   si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
837   return si;
838 }
839
840
841 /*
842   TODO: add extra space for tremolos!
843  */
844 void
845 Stem::calc_stem_info (Grob *me)
846 {
847   Direction my_dir = get_grob_direction (me);
848
849   if (!my_dir)
850     {
851       programming_error ("No stem dir set?");
852       my_dir  = UP;
853     }
854   
855   Real staff_space = Staff_symbol_referencer::staff_space (me);
856   Grob *beam = get_beam (me);
857   Real beam_translation = Beam::get_beam_translation (beam);
858   Real beam_thickness = Beam::get_thickness (beam);
859   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
860
861
862   /* Simple standard stem length */
863   SCM lengths = me->get_property ("beamed-lengths");
864   Real ideal_length =
865     gh_scm2double (robust_list_ref (beam_count - 1,lengths))
866                 
867     * staff_space
868     /* stem only extends to center of beam */
869     - 0.5 * beam_thickness;
870   
871   /* Condition: sane minimum free stem length (chord to beams) */
872   lengths = me->get_property ("beamed-minimum-free-lengths");
873   Real ideal_minimum_free =
874     gh_scm2double (robust_list_ref (beam_count - 1, lengths))
875     * staff_space;
876   
877
878   /* UGH
879      It seems that also for ideal minimum length, we must use
880      the maximum beam count (for this direction):
881      
882      \score{ \notes\relative c''{ [a8 a32] }}
883      
884      must be horizontal. */
885   Real height_of_my_beams = beam_thickness
886     + (beam_count - 1) * beam_translation;
887
888   Real ideal_minimum_length = ideal_minimum_free
889     + height_of_my_beams
890     /* stem only extends to center of beam */
891     - 0.5 * beam_thickness;
892
893   ideal_length = ideal_length >? ideal_minimum_length;
894
895   
896   /* Convert to Y position, calculate for dir == UP */
897   Real note_start =
898     /* staff positions */
899     head_positions (me)[my_dir] * 0.5
900     * my_dir * staff_space;
901   Real ideal_y = note_start + ideal_length;
902
903
904   /* Conditions for Y position */
905
906   /* Lowest beam of (UP) beam must never be lower than second staffline
907  
908      Reference?
909  
910      Although this (additional) rule is probably correct,
911      I expect that highest beam (UP) should also never be lower
912      than middle staffline, just as normal stems.
913
914      Reference?
915
916      Obviously not for grace beams.
917      
918      Also, not for knees.  Seems to be a good thing. */
919   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
920   bool is_knee = to_boolean (beam->get_property ("knee"));
921   if (!no_extend_b && !is_knee)
922     {
923       /* Highest beam of (UP) beam must never be lower than middle
924          staffline */
925       ideal_y = ideal_y >? 0;
926       /* Lowest beam of (UP) beam must never be lower than second staffline */
927       ideal_y = ideal_y >? (-staff_space
928                             - beam_thickness + height_of_my_beams);
929     }
930
931
932   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
933
934   Real minimum_free =
935     gh_scm2double (robust_list_ref
936                    (beam_count - 1,
937                     me->get_property
938                     ("beamed-extreme-minimum-free-lengths")))
939     * staff_space;
940
941   Real minimum_length = minimum_free
942     + height_of_my_beams
943     /* stem only extends to center of beam */
944     - 0.5 * beam_thickness;
945
946   Real minimum_y = note_start + minimum_length;
947   
948   
949   ideal_y *= my_dir;
950   Real shortest_y = minimum_y * my_dir; 
951   
952   me->set_property ("stem-info",
953                          scm_list_n (gh_double2scm (ideal_y),
954                                      gh_double2scm (shortest_y),
955                                      SCM_UNDEFINED));
956 }
957
958 Slice
959 Stem::beam_multiplicity (Grob *stem)
960 {
961   SCM beaming= stem->get_property ("beaming");
962   Slice l = int_list_to_slice (gh_car (beaming));
963   Slice r = int_list_to_slice (gh_cdr (beaming));
964   l.unite (r);
965
966   return l;
967 }
968
969
970 /*
971   these are too many props.
972  */
973 ADD_INTERFACE (Stem,"stem-interface",
974                "The stem represent the graphical  stem. "
975                "  In addition, it internally connects note heads, beams, tremolos. Rests "
976                " and whole notes have invisible stems."
977
978 ,
979                
980                "tremolo-flag french-beaming "
981                "avoid-note-head thickness "
982                "stem-info beamed-lengths beamed-minimum-free-lengths "
983                "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
984                "duration-log beaming neutral-direction stem-end-position "
985                "note-heads direction length flag-style "
986                "no-stem-extend stroke-style");
987
988
989
990 /****************************************************************/
991
992 Stem_info::Stem_info ()
993 {
994   ideal_y_ = shortest_y_ =0;
995   dir_ = CENTER;
996 }
997
998 void
999 Stem_info::scale (Real x)
1000 {
1001   ideal_y_ *= x;
1002   shortest_y_ *= x;
1003 }