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