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