]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam.cc
release: 1.3.24
[lilypond.git] / lily / beam.cc
1 /*
2   beam.cc -- implement Beam
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8
9 */
10
11 /*
12   [TODO]
13     * less hairy code
14     * move paper vars to scm
15
16 */
17
18
19 #include <math.h>               // tanh.
20
21 #include "directional-element-interface.hh"
22 #include "beaming.hh"
23 #include "dimensions.hh"
24 #include "beam.hh"
25 #include "misc.hh"
26 #include "debug.hh"
27 #include "least-squares.hh"
28 #include "stem.hh"
29 #include "paper-def.hh"
30 #include "lookup.hh"
31 #include "group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "cross-staff.hh"
34 #include "lily-guile.icc"
35
36 Beam::Beam ()
37 {
38   Group_interface g (this, "stems");
39   g.set_interface ();
40 }
41
42 void
43 Beam::add_stem (Stem*s)
44 {
45   Group_interface gi (this, "stems");
46   gi.add_element (s);
47   
48   s->add_dependency (this);
49
50   assert (!s->beam_l ());
51   s->set_elt_property ("beam", self_scm_);
52
53   if (!spanned_drul_[LEFT])
54     set_bounds (LEFT,s);
55   else
56     set_bounds (RIGHT,s);
57 }
58
59 int
60 Beam::get_multiplicity () const
61 {
62   int m = 0;
63   for (SCM s = get_elt_property ("stems"); gh_pair_p (s); s = gh_cdr (s))
64     {
65       Score_element * sc = unsmob_element (gh_car (s));
66
67       if (Stem * st = dynamic_cast<Stem*> (sc))
68         m = m >? st->beam_count (LEFT) >? st->beam_count (RIGHT);
69     }
70   return m;
71 }
72
73 /*
74   After pre-processing all directions should be set.
75   Several post-processing routines (stem, slur, script) need stem/beam
76   direction.
77   Currenly, this means that beam has set all stem's directions.
78   [Alternatively, stems could set its own directions, according to
79    their beam, during 'final-pre-processing'.]
80  */
81 void
82 Beam::do_pre_processing ()
83 {
84   // Why?
85   if (visible_stem_count () < 2)
86     {
87       warning (_ ("beam has less than two stems"));
88       //      set_elt_property ("transparent", SCM_BOOL_T);
89     }
90
91   if (!directional_element (this).get ())
92     directional_element (this).set (get_default_dir ());
93
94   auto_knees ();
95   set_stem_directions ();
96   set_stem_shorten (); 
97 }
98
99 /*
100  FIXME
101  */
102 Direction
103 Beam::get_default_dir () const
104 {
105   Drul_array<int> total;
106   total[UP]  = total[DOWN] = 0;
107   Drul_array<int> count; 
108   count[UP]  = count[DOWN] = 0;
109   Direction d = DOWN;
110
111   for (int i=0; i <stem_count (); i++)
112     do { // HUH -- waar slaat dit op?
113       Stem *s = stem (i);
114       Direction sd = directional_element (s).get ();
115       int current = sd  ? (1 + d * sd)/2
116         : s->get_center_distance ((Direction)-d);
117
118       if (current)
119         {
120           total[d] += current;
121           count[d] ++;
122         }
123
124     } while (flip(&d) != DOWN);
125   
126
127   SCM s = scm_eval (gh_list (ly_symbol2scm ("beam-dir-algorithm"),
128                              ly_quote_scm (gh_cons (gh_int2scm (count[UP]),
129                                                     gh_int2scm (count[DOWN]))),
130                              ly_quote_scm (gh_cons (gh_int2scm (total[UP]),
131                                                     gh_int2scm (total[DOWN]))),
132                              SCM_UNDEFINED));
133   if (gh_number_p (s) && gh_scm2int (s))
134     return to_dir (s);
135   
136   /*
137     If dir is not determined: get from paper
138   */
139   return (Direction)(int)
140     paper_l ()->get_var ("stem_default_neutral_direction");
141 }
142
143
144 /*
145   Set all stems with non-forced direction to beam direction.
146   Urg: non-forced should become `without/with unforced' direction,
147        once stem gets cleaned-up.
148  */
149 void
150 Beam::set_stem_directions ()
151 {
152   Direction d = directional_element (this).get ();
153   for (int i=0; i <stem_count (); i++)
154     {
155       Stem *s = stem (i);
156       SCM force = s->remove_elt_property ("dir-forced");
157       if (!gh_boolean_p (force) || !gh_scm2bool (force))
158         directional_element (s).set (d);
159     }
160
161
162 void
163 Beam::auto_knees ()
164 {
165   if (!auto_knee ("auto-interstaff-knee-gap", true))
166     auto_knee ("auto-knee-gap", false);
167 }
168
169 /*
170   Simplistic auto-knees; only consider vertical gap between two
171   adjacent chords.
172
173   `Forced' stem directions are ignored.  If you don't want auto-knees,
174   don't set, or unset autoKneeGap/autoInterstaffKneeGap.
175  */
176 bool
177 Beam::auto_knee (String gap_str, bool interstaff_b)
178 {
179   bool knee_b = false;
180   int knee_y = 0;
181   SCM gap = get_elt_property (gap_str);
182   Direction d = directional_element (this).get ();
183   
184   if (gh_number_p (gap))
185     {
186       int auto_gap_i = gh_scm2int (gap);
187       for (int i=1; i < stem_count (); i++)
188         {
189           bool is_b = (bool)(calc_interstaff_dist (stem (i), this) 
190             - calc_interstaff_dist (stem (i-1), this));
191           int l_y = (int)(stem (i-1)->head_positions()[d])
192             + (int)calc_interstaff_dist (stem (i-1), this);
193           int r_y = (int)(stem (i)->head_positions()[d])
194             + (int)calc_interstaff_dist (stem (i), this);
195           int gap_i = r_y - l_y;
196
197           if ((abs (gap_i) >= auto_gap_i) && (!interstaff_b || is_b))
198             {
199               knee_y = (r_y + l_y) / 2;
200               knee_b = true;
201               break;
202             }
203         }
204     }
205   if (knee_b)
206     {
207       for (int i=0; i < stem_count (); i++)
208         {
209           int y = (int)(stem (i)->head_positions()[d])
210             + (int)calc_interstaff_dist (stem (i), this);
211           directional_element (stem (i)).set (y < knee_y ? UP : DOWN);
212           stem (i)->set_elt_property ("dir-forced", SCM_BOOL_T);
213         }
214     }
215   return knee_b;
216 }
217
218 /*
219  Set stem's shorten property if unset.
220  TODO:
221     take some y-position (chord/beam/nearest?) into account
222     scmify forced-fraction
223  */
224 void
225 Beam::set_stem_shorten ()
226 {
227   if (!visible_stem_count ())
228     return;
229
230   Real forced_fraction = forced_stem_count () / visible_stem_count ();
231   if (forced_fraction < 0.5)
232     return;
233
234   int multiplicity = get_multiplicity ();
235   // grace stems?
236   SCM shorten = ly_eval_str ("beamed-stem-shorten");
237
238   Array<Real> a;
239   scm_to_array (shorten, &a);
240   if (!a.size ())
241     return;
242
243   Staff_symbol_referencer_interface st (this);
244   Real staff_space = st.staff_space ();
245   Real shorten_f = a[multiplicity <? (a.size () - 1)] * staff_space;
246
247   /* cute, but who invented this -- how to customise ? */
248   if (forced_fraction < 1)
249     shorten_f /= 2;
250
251   for (int i=0; i < stem_count (); i++)
252     {
253       Stem* s = stem (i);
254       if (s->invisible_b ())
255         continue;
256       if (gh_number_p (s->get_elt_property ("shorten")))
257         s->set_elt_property ("shorten", gh_double2scm (shorten_f));
258     }
259 }
260
261 /*
262   Set elt properties height and y-position if not set.
263   Adjust stem lengths to reach beam.
264  */
265 void
266 Beam::do_post_processing ()
267 {
268   /* first, calculate y, dy */
269   Real y, dy;
270   calc_position_and_height (&y, &dy);
271   if (visible_stem_count ())
272     {
273       if (suspect_slope_b (y, dy))
274         dy = 0;
275
276       Real damped_dy = calc_slope_damping_f (dy);
277       Real quantised_dy = quantise_dy_f (damped_dy);
278
279       y += (dy - quantised_dy) / 2;
280       dy = quantised_dy;
281     }
282   /*
283     until here, we used only stem_info, which acts as if dir=up
284    */
285   y *= directional_element (this).get ();
286   dy *= directional_element (this).get ();
287
288   Staff_symbol_referencer_interface st (this);
289   Real half_space = st.staff_space () / 2;
290
291   /* check for user-override of dy */
292   SCM s = remove_elt_property ("height-hs");
293   if (gh_number_p (s))
294     {
295       dy = gh_scm2double (s) * half_space;
296     }
297   set_elt_property ("height", gh_double2scm (dy));
298
299   /* check for user-override of y */
300   s = remove_elt_property ("y-position-hs");
301   if (gh_number_p (s))
302     {
303       y = gh_scm2double (s) * half_space;
304     }
305   else
306     { 
307       /* we can modify y, so we should quantise y */
308       Real y_shift = check_stem_length_f (y, dy);
309       y += y_shift;
310       y = quantise_y_f (y, dy, 0);
311       set_stem_length (y, dy);
312       y_shift = check_stem_length_f (y, dy);
313
314       if (y_shift > half_space / 4)
315         {
316           y += y_shift;
317
318           /*
319             for significantly lengthened or shortened stems,
320             request quanting the other way.
321           */
322           int quant_dir = 0;
323           if (abs (y_shift) > half_space / 2)
324             quant_dir = sign (y_shift) * directional_element (this).get ();
325           y = quantise_y_f (y, dy, quant_dir);
326         }
327     }
328   // UGH. Y is not in staff position unit?
329   // Ik dacht datwe daar juist van weg wilden?
330   set_stem_length (y, dy);
331   set_elt_property ("y-position", gh_double2scm (y)); 
332 }
333
334 /*
335   See Documentation/tex/fonts.doc
336  */
337 void
338 Beam::calc_position_and_height (Real* y, Real* dy) const
339 {
340   *y = *dy = 0;
341   if (visible_stem_count () <= 1)
342     return;
343
344   Real first_ideal = first_visible_stem ()->calc_stem_info ().idealy_f_;
345   if (first_ideal == last_visible_stem ()->calc_stem_info ().idealy_f_)
346     {
347       *dy = 0;
348       *y = first_ideal;
349       return;
350     }
351
352   Array<Offset> ideals;
353   Real x0 = first_visible_stem ()->hpos_f ();
354   for (int i=0; i < stem_count (); i++)
355     {
356       Stem* s = stem (i);
357       if (s->invisible_b ())
358         continue;
359       ideals.push (Offset (s->hpos_f () - x0, 
360                            s->calc_stem_info ().idealy_f_));
361     }
362   Real dydx;
363   minimise_least_squares (&dydx, y, ideals); // duh, takes references
364
365   Real dx = last_visible_stem ()->hpos_f () - x0;
366   *dy = dydx * dx;
367 }
368
369 bool
370 Beam::suspect_slope_b (Real y, Real dy) const
371 {
372   /* first, calculate y, dy */
373   /*
374     steep slope running against lengthened stem is suspect
375   */
376   Real first_ideal = first_visible_stem ()->calc_stem_info ().idealy_f_;
377   Real last_ideal = last_visible_stem ()->calc_stem_info ().idealy_f_;
378   Real lengthened = paper_l ()->get_var ("beam_lengthened");
379   Real steep = paper_l ()->get_var ("beam_steep_slope");
380
381   Real dx = last_visible_stem ()->hpos_f () - first_visible_stem ()->hpos_f ();
382   Real dydx = dy && dx ? dy/dx : 0;
383
384   if (((y - first_ideal > lengthened) && (dydx > steep))
385       || ((y + dy - last_ideal > lengthened) && (dydx < -steep)))
386     {
387       return true;
388     }
389   return false;
390 }
391
392 /*
393   This neat trick is by Werner Lemberg,
394   damped = tanh (slope)
395   corresponds with some tables in [Wanske]
396 */
397 Real
398 Beam::calc_slope_damping_f (Real dy) const
399 {
400   SCM damp = get_elt_property ("damping"); // remove?
401   int damping = 1;              // ugh.
402   if (gh_number_p (damp))
403     damping = gh_scm2int (damp);
404
405   if (damping)
406     {
407       Real dx = last_visible_stem ()->hpos_f ()
408         - first_visible_stem ()->hpos_f ();
409       Real dydx = dy && dx ? dy/dx : 0;
410       dydx = 0.6 * tanh (dydx) / damping;
411       return dydx * dx;
412     }
413   return dy;
414 }
415
416 Real
417 Beam::calc_stem_y_f (Stem* s, Real y, Real dy) const
418 {
419   Real thick = gh_scm2double (get_elt_property ("beam-thickness"));
420   int beam_multiplicity = get_multiplicity ();
421   int stem_multiplicity = (s->flag_i () - 2) >? 0;
422
423   Real interbeam_f = paper_l ()->interbeam_f (beam_multiplicity);
424   Real x0 = first_visible_stem ()->hpos_f ();
425   Real dx = last_visible_stem ()->hpos_f () - x0;
426   Real stem_y = (dy && dx ? (s->hpos_f () - x0) / dx * dy : 0) + y;
427
428   /* knee */
429    Direction dir  = directional_element(this).get ();
430    Direction sdir = directional_element (s).get ();
431    
432     /* knee */
433    if (dir!= sdir)
434       {
435        stem_y -= dir 
436         * (thick / 2 + (beam_multiplicity - 1) * interbeam_f);
437
438       Staff_symbol_referencer_interface me (s);
439       Staff_symbol_referencer_interface last (last_visible_stem ());
440       
441       // huh, why not for first visible?
442       if (//(s != first_visible_stem ()) &&
443           me.staff_symbol_l () != last.staff_symbol_l ())
444         stem_y += directional_element (this).get ()
445           * (beam_multiplicity - stem_multiplicity) * interbeam_f;
446     }
447   return stem_y;
448 }
449
450 Real
451 Beam::check_stem_length_f (Real y, Real dy) const
452 {
453   Real shorten = 0;
454   Real lengthen = 0;
455   Direction dir = directional_element (this).get ();
456   
457   for (int i=0; i < stem_count (); i++)
458     {
459       Stem* s = stem (i);
460       if (s->invisible_b ())
461         continue;
462
463       Real stem_y = calc_stem_y_f (s, y, dy);
464         
465       stem_y *= dir;
466       Stem_info info = s->calc_stem_info ();
467
468       // if (0 > info.maxy_f_ - stem_y)
469       shorten = shorten <? info.maxy_f_ - stem_y;
470       // if (0 < info.miny_f_ - stem_y)
471       lengthen = lengthen >? info.miny_f_ - stem_y; 
472     }
473
474   if (lengthen && shorten)
475     warning (_ ("weird beam vertical offset"));
476
477   /* when all stems are too short, normal stems win */
478   return dir * ((shorten) ?  shorten : lengthen);
479 }
480
481 /*
482   Hmm.  At this time, beam position and slope are determined.  Maybe,
483   stem directions and length should set to relative to the chord's
484   position of the beam.  */
485 void
486 Beam::set_stem_length (Real y, Real dy)
487 {
488   Staff_symbol_referencer_interface st (this);
489   Real half_space = st.staff_space ()/2;
490   for (int i=0; i < stem_count (); i++)
491     {
492       Stem* s = stem (i);
493       if (s->invisible_b ())
494         continue;
495
496       Real stem_y = calc_stem_y_f (s, y, dy);
497
498       /* caution: stem measures in staff-positions */
499       s->set_stemend ((stem_y + calc_interstaff_dist (s, this)) / half_space);
500     }
501 }
502
503 /*
504   [Ross] (simplification of)
505   Set dy complying with:
506     - zero
507     - thick / 2 + staffline_f / 2
508     - thick + staffline_f
509   + n * staff_space
510 */
511 Real
512 Beam::quantise_dy_f (Real dy) const
513 {
514   SCM quants = ly_eval_str ("beam-height-quants");
515
516   Array<Real> a;
517   scm_to_array (quants, &a);
518   if (a.size () <= 1)
519     return dy;
520
521   Staff_symbol_referencer_interface st (this);
522   Real staff_space = st.staff_space ();
523   
524   Interval iv = quantise_iv (a, abs (dy)/staff_space) * staff_space;
525   Real q = (abs (dy) - iv[SMALLER] <= iv[BIGGER] - abs (dy))
526     ? iv[SMALLER]
527     : iv[BIGGER];
528   
529   return q * sign (dy);
530 }
531
532 /*
533   Prevent interference from stafflines and beams.
534   See Documentation/tex/fonts.doc
535
536   We only need to quantise the (left) y-position of the beam,
537   since dy is quantised too.
538   if extend_b then stems must *not* get shorter
539  */
540 Real
541 Beam::quantise_y_f (Real y, Real dy, int quant_dir)
542 {
543   int multiplicity = get_multiplicity ();
544   Staff_symbol_referencer_interface st (this);
545   Real staff_space = st.staff_space ();
546   SCM quants = scm_eval (gh_list (
547                                   ly_symbol2scm ("beam-vertical-position-quants"),
548                                   gh_int2scm (multiplicity),
549                                   gh_double2scm (dy/staff_space),
550                                   SCM_UNDEFINED));
551   Array<Real> a;
552   scm_to_array (quants, &a);
553   if (a.size () <= 1)
554     return y;
555
556   Real up_y = directional_element (this).get () * y;
557   Interval iv = quantise_iv (a, up_y/staff_space) * staff_space;
558
559   Real q = up_y - iv[SMALLER] <= iv[BIGGER] - up_y 
560     ? iv[SMALLER] : iv[BIGGER];
561   if (quant_dir)
562     q = iv[(Direction)quant_dir];
563
564   return q * directional_element (this).get ();
565 }
566
567 void
568 Beam::set_beaming (Beaming_info_list *beaming)
569 {
570   Direction d = LEFT;
571   for (int i=0; i  < stem_count (); i++)
572     {
573       do
574         {
575           if (stem (i)->beam_count (d) == 0)
576             stem (i)->set_beaming ( beaming->infos_.elem (i).beams_i_drul_[d],d);
577         }
578       while (flip (&d) != LEFT);
579     }
580 }
581
582
583
584 /*
585   beams to go with one stem.
586
587   BURP
588   clean  me up.
589   */
590 Molecule
591 Beam::stem_beams (Stem *here, Stem *next, Stem *prev) const
592 {
593   if ((next && !(next->hpos_f () > here->hpos_f ())) ||
594       (prev && !(prev->hpos_f () < here->hpos_f ())))
595       programming_error ("Beams are not left-to-right");
596
597   Real staffline_f = paper_l ()->get_var ("stafflinethickness");
598   int   multiplicity = get_multiplicity ();
599
600
601   Real interbeam_f = paper_l ()->interbeam_f (multiplicity);
602   Real thick = gh_scm2double (get_elt_property ("beam-thickness"));;
603
604   Real bdy = interbeam_f;
605   Real stemdx = staffline_f;
606
607   Real dx = visible_stem_count () ?
608     last_visible_stem ()->hpos_f () - first_visible_stem ()->hpos_f ()
609     : 0.0;
610   Real dy = get_real ("height");
611   Real dydx = dy && dx ? dy/dx : 0;
612
613   Molecule leftbeams;
614   Molecule rightbeams;
615
616   // UGH
617   Real nw_f;
618   if (!here->first_head ())
619     nw_f = 0;
620   else if (here->type_i ()== 1)
621     nw_f = paper_l ()->get_var ("wholewidth");
622   else if (here->type_i () == 2)
623     nw_f = paper_l ()->get_var ("notewidth") * 0.8;
624   else
625     nw_f = paper_l ()->get_var ("quartwidth");
626
627
628   Direction dir = directional_element (this).get ();
629   
630   /* half beams extending to the left. */
631   if (prev)
632     {
633       int lhalfs= lhalfs = here->beam_count (LEFT) - prev->beam_count (RIGHT);
634       int lwholebeams= here->beam_count (LEFT) <? prev->beam_count (RIGHT) ;
635       /*
636        Half beam should be one note-width, 
637        but let's make sure two half-beams never touch
638        */
639       Real w = here->hpos_f () - prev->hpos_f ();
640       w = w/2 <? nw_f;
641       Molecule a;
642       if (lhalfs)               // generates warnings if not
643         a =  lookup_l ()->beam (dydx, w, thick);
644       a.translate (Offset (-w, -w * dydx));
645       for (int j = 0; j  < lhalfs; j++)
646         {
647           Molecule b (a);
648           b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
649           leftbeams.add_molecule (b);
650         }
651     }
652
653   if (next)
654     {
655       int rhalfs  = here->beam_count (RIGHT) - next->beam_count (LEFT);
656       int rwholebeams= here->beam_count (RIGHT) <? next->beam_count (LEFT) ;
657
658       Real w = next->hpos_f () - here->hpos_f ();
659       Molecule a = lookup_l ()->beam (dydx, w + stemdx, thick);
660       a.translate_axis( - stemdx/2, X_AXIS);
661       int j = 0;
662       Real gap_f = 0;
663
664       SCM gap = get_elt_property ("beam-gap");
665       if (gh_number_p (gap))
666         {
667           int gap_i = gh_scm2int ( (gap));
668           int nogap = rwholebeams - gap_i;
669           
670           for (; j  < nogap; j++)
671             {
672               Molecule b (a);
673               b.translate_axis (-dir  * bdy * j, Y_AXIS);
674               rightbeams.add_molecule (b);
675             }
676           // TODO: notehead widths differ for different types
677           gap_f = nw_f / 2;
678           w -= 2 * gap_f;
679           a = lookup_l ()->beam (dydx, w + stemdx, thick);
680         }
681
682       for (; j  < rwholebeams; j++)
683         {
684           Molecule b (a);
685           b.translate (Offset (here->invisible_b () ? 0 : gap_f, -dir * bdy * j));
686           rightbeams.add_molecule (b);
687         }
688
689       w = w/2 <? nw_f;
690       if (rhalfs)
691         a = lookup_l ()->beam (dydx, w, thick);
692
693       for (; j  < rwholebeams + rhalfs; j++)
694         {
695           Molecule b (a);
696           b.translate_axis (- dir * bdy * j, Y_AXIS);
697           rightbeams.add_molecule (b);
698         }
699
700     }
701   leftbeams.add_molecule (rightbeams);
702
703   /*
704     Does beam quanting think  of the asymetry of beams? 
705     Refpoint is on bottom of symbol. (FIXTHAT) --hwn.
706    */
707   return leftbeams;
708 }
709
710
711 Molecule*
712 Beam::do_brew_molecule_p () const
713 {
714   Molecule *mol_p = new Molecule;
715   if (!stem_count ())
716     return mol_p;
717   Real x0,dx;
718   if (visible_stem_count ())
719     {
720       x0 = first_visible_stem ()->hpos_f ();
721       dx = last_visible_stem ()->hpos_f () - x0;
722     }
723   else
724     {
725       x0 = stem (0)->hpos_f ();
726       dx = stem_top ()->hpos_f () - x0;
727     }
728   
729   
730   Real dy = get_real ("height");
731   Real dydx = dy && dx ? dy/dx : 0;
732   Real y = get_real ("y-position");
733   for (int j=0; j <stem_count (); j++)
734     {
735       Stem *i = stem (j);
736       Stem * prev = (j > 0)? stem (j-1) : 0;
737       Stem * next = (j < stem_count ()-1) ? stem (j+1) :0;
738
739       Molecule sb = stem_beams (i, next, prev);
740       Real x = i->hpos_f ()-x0;
741       sb.translate (Offset (x, x * dydx + y));
742       mol_p->add_molecule (sb);
743     }
744   mol_p->translate_axis (x0 
745     - spanned_drul_[LEFT]->relative_coordinate (0, X_AXIS), X_AXIS);
746
747   return mol_p;
748 }
749
750 int
751 Beam::forced_stem_count () const
752 {
753   int f = 0;
754   for (int i=0; i < stem_count (); i++)
755     {
756       Stem *s = stem (i);
757
758       if (s->invisible_b ())
759         continue;
760
761       if (((int)s->chord_start_f ()) 
762         && (s->get_direction () != s->get_default_dir ()))
763         f++;
764     }
765   return f;
766 }
767
768
769
770 /*
771   TODO: Fix this class. This is wildly inefficient.
772   And it sux.  Yet another array/list 'interface'.
773  */
774 Stem *
775 Beam::stem (int i) const
776 {
777   return Group_interface__extract_elements ((Beam*) this, (Stem*) 0, "stems")[i];
778 }
779
780 int
781 Beam::stem_count () const
782 {
783   Group_interface gi (this, "stems");
784   return gi.count ();
785 }
786
787 Stem*
788 Beam::stem_top () const
789 {
790   SCM s = get_elt_property ("stems");
791   
792   return gh_pair_p (s) ? dynamic_cast<Stem*> (unsmob_element (gh_car (s))) : 0;
793     
794   //Group_interface__extract_elements ((Beam*) this, (Stem*) 0, "stems")[stem_count () - 1];
795 }
796
797 /* burp */
798 int
799 Beam::visible_stem_count () const
800 {
801   int c = 0;
802   for (int i = 0; i < stem_count (); i++)
803     {
804       if (!stem (i)->invisible_b ())
805         c++;
806     }
807   return c;
808 }
809
810 Stem*
811 Beam::first_visible_stem () const
812 {
813   for (int i = 0; i < stem_count (); i++)
814     {
815       Stem* s = stem (i);
816       if (!s->invisible_b ())
817         return s;
818     }
819   return 0;
820 }
821
822 Stem*
823 Beam::last_visible_stem () const
824 {
825   for (int i = stem_count (); i > 0; i--)
826     {
827       Stem* s = stem (i - 1);
828       if (!s->invisible_b ())
829         return s;
830     }
831   return 0;
832 }