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