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