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