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