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