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