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