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