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