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