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