]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam.cc
release: 1.3.2
[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
39 Beam::Beam ()
40 {
41   slope_f_ = 0;
42   left_y_ = 0;
43   multiple_i_ = 0;
44 }
45
46 void
47 Beam::add_stem (Stem*s)
48 {
49 #if 0
50   /*
51     should figure out why this didn't work.
52
53     --hwn.
54    */
55   if (!stems_.size ())
56     {
57       set_parent (s, Y_AXIS);
58     }
59 #endif
60   stems_.push (s);
61   s->add_dependency (this);
62
63   assert (!s->beam_l_);
64   s->beam_l_ = this;
65
66   if (!spanned_drul_[LEFT])
67     set_bounds (LEFT,s);
68   else
69     set_bounds (RIGHT,s);
70 }
71
72 Stem_info
73 Beam::get_stem_info (Stem *s)
74 {
75   Stem_info i;
76   for (int i=0; i < sinfo_.size (); i++)
77     {
78       if (sinfo_[i].stem_l_ == s)
79         return sinfo_[i];
80     }
81   assert (false);
82   return i;
83 }
84
85 Molecule*
86 Beam::do_brew_molecule_p () const
87 {
88   Molecule *mol_p = new Molecule;
89   if (!sinfo_.size ())
90     return mol_p;
91   
92   Real x0 = stems_[0]->hpos_f ();
93   for (int j=0; j <stems_.size (); j++)
94     {
95       Stem *i = stems_[j];
96       Stem * prev = (j > 0)? stems_[j-1] : 0;
97       Stem * next = (j < stems_.size ()-1) ? stems_[j+1] :0;
98
99       Molecule sb = stem_beams (i, next, prev);
100       Real  x = i->hpos_f ()-x0;
101       sb.translate (Offset (x, (x * slope_f_ + left_y_) *
102                             i->staff_line_leading_f ()/2 ));
103       mol_p->add_molecule (sb);
104     }
105   mol_p->translate_axis (x0 
106     - spanned_drul_[LEFT]->relative_coordinate (0, X_AXIS), X_AXIS);
107
108   return mol_p;
109 }
110
111 Offset
112 Beam::center () const
113 {
114   Stem_info si = sinfo_[0];
115   
116   Real w= (si.stem_l_->note_delta_f () + extent (X_AXIS).length ())/2.0;
117   return Offset (w, ( w* slope_f_) *
118                  si.stem_l_->staff_line_leading_f ()/2);
119 }
120
121 /*
122   Simplistic auto-knees; only consider vertical gap between two
123   adjacent chords
124  */
125 bool
126 Beam::auto_knee (SCM gap, bool interstaff_b)
127 {
128   bool knee = false;
129   int knee_y = 0;
130   Real internote_f = stems_[0]->staff_line_leading_f ()/2;
131   if (gap != SCM_UNDEFINED)
132     {
133       int auto_gap_i = gh_scm2int (gap);
134       for (int i=1; i < stems_.size (); i++)
135         {
136           bool is_b = (bool)(sinfo_[i].interstaff_f_ - sinfo_[i-1].interstaff_f_);
137           int l_y = (int)(stems_[i-1]->chord_start_f () / internote_f)
138             + (int)sinfo_[i-1].interstaff_f_;
139           int r_y = (int)(stems_[i]->chord_start_f () / internote_f)
140             + (int)sinfo_[i].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 < stems_.size (); i++)
158         {
159           int y = (int)(stems_[i]->chord_start_f () / internote_f)
160             + (int)sinfo_[i].interstaff_f_;
161           stems_[i]->dir_ = y < knee_y ? UP : DOWN;
162           stems_[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 (!dir_)
186     dir_ = get_default_dir ();
187   
188   set_direction (dir_);
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 (stems_.size () < 2)
204     {
205       warning (_ ("beam with less than two stems"));
206       set_elt_property ("transparent", SCM_BOOL_T);
207       return;
208     }
209   set_steminfo ();
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       dir_ = get_default_dir ();
217       set_direction (dir_);
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       sinfo_.clear ();
225       set_steminfo ();
226     }
227   calculate_slope ();
228   set_stemlens ();
229 }
230
231 void
232 Beam::do_substitute_element_pointer (Score_element*o,Score_element*n)
233 {
234   if (Stem * os = dynamic_cast<Stem*> (o))
235     stems_.substitute (os,
236                        dynamic_cast<Stem *> (n));
237 }
238
239 Interval
240 Beam::do_width () const
241 {
242   return Interval (stems_[0]->hpos_f (),
243                    stems_.top ()->hpos_f ());
244 }
245
246 Direction
247 Beam::get_default_dir () const
248 {
249   Drul_array<int> total;
250   total[UP]  = total[DOWN] = 0;
251   Drul_array<int> count; 
252   count[UP]  = count[DOWN] = 0;
253   Direction d = DOWN;
254
255   for (int i=0; i <stems_.size (); i++)
256     do {
257       Stem *s = stems_[i];
258       int current = s->dir_ 
259         ? (1 + d * s->dir_)/2
260         : s->get_center_distance ((Direction)-d);
261
262       if (current)
263         {
264           total[d] += current;
265           count[d] ++;
266         }
267
268     } while (flip(&d) != DOWN);
269   
270   /* 
271      [Ross] states that the majority of the notes dictates the
272      direction (and not the mean of "center distance")
273
274      But is that because it really looks better, or because he wants
275      to provide some real simple hands-on rules?
276      
277      We have our doubts, so we simply provide all sensible alternatives.
278
279      If dir is not determined: up (see stem::get_default_dir ()) */
280
281   Direction beam_dir = CENTER;
282   Direction neutral_dir = (Direction)(int)paper_l ()->get_var ("stem_default_neutral_direction");
283
284   SCM a = get_elt_property ("beam-dir-algorithm");
285   
286   if (a == gh_symbol2scm ("majority")) // should get default from paper.
287     beam_dir = (count[UP] == count[DOWN]) ? neutral_dir 
288       : (count[UP] > count[DOWN]) ? UP : DOWN;
289   else if (a == gh_symbol2scm ("mean"))
290     // mean center distance
291     beam_dir = (total[UP] == total[DOWN]) ? neutral_dir
292       : (total[UP] > total[DOWN]) ? UP : DOWN;
293   else if (a == gh_symbol2scm ("median"))
294     {
295       // median center distance
296       if (count[DOWN] && count[UP])
297         {
298           beam_dir = (total[UP] / count[UP] == total[DOWN] / count[DOWN]) 
299             ? neutral_dir 
300             : (total[UP] / count[UP] > total[DOWN] / count[DOWN]) ? UP : DOWN;
301         }
302       else
303         {
304           beam_dir = (count[UP] == count[DOWN]) ? neutral_dir 
305             : (count[UP] > count[DOWN]) ? UP : DOWN;
306         }
307     }
308   
309   return beam_dir;
310 }
311
312 void
313 Beam::set_direction (Direction d)
314 {
315   dir_ = d;
316   for (int i=0; i <stems_.size (); i++)
317     {
318       Stem *s = stems_[i];
319       s->set_elt_property ("beam-dir", gh_int2scm (d));
320
321       SCM force = s->remove_elt_property ("dir-forced");
322       if (force == SCM_UNDEFINED)
323         s->dir_ = d;
324     }
325 }
326
327 /*
328   See Documentation/tex/fonts.doc
329  */
330
331 void
332 Beam::solve_slope ()
333 {
334   assert (sinfo_.size () > 1);
335
336   Least_squares l;
337   for (int i=0; i < sinfo_.size (); i++)
338     {
339       l.input.push (Offset (sinfo_[i].x_, sinfo_[i].idealy_f_));
340     }
341   l.minimise (slope_f_, left_y_);
342 }
343
344 /*
345   ugh. Naming: this doesn't check, but sets as well.
346  */
347   
348 Real
349 Beam::check_stemlengths_f (bool set_b)
350 {
351   Real interbeam_f = paper_l ()->interbeam_f (multiple_i_);
352
353   Real beam_f = paper_l ()->get_var ("beam_thickness");;
354   Real staffline_f = paper_l ()-> get_var ("stafflinethickness");
355   Real epsilon_f = staffline_f / 8;
356   Real dy_f = 0.0;
357   for (int i=0; i < sinfo_.size (); i++)
358     {
359       Real y = sinfo_[i].x_ * slope_f_ + left_y_;
360
361       // correct for knee
362       if (dir_ != sinfo_[i].dir_)
363         {
364           Real internote_f = sinfo_[i].stem_l_->staff_line_leading_f ()/2;
365           y -= dir_ * (beam_f / 2
366                        + (sinfo_[i].mult_i_ - 1) * interbeam_f) / internote_f;
367           if (!i && sinfo_[i].stem_l_->staff_symbol_l () !=
368               sinfo_.top ().stem_l_->staff_symbol_l ())
369             y += dir_ * (multiple_i_ - (sinfo_[i].stem_l_->flag_i_ - 2) >? 0)
370               * interbeam_f / internote_f;
371         }
372
373       if (set_b)
374         sinfo_[i].stem_l_->set_stemend (y - sinfo_[i].interstaff_f_);
375         
376       y *= dir_;
377       if (y > sinfo_[i].maxy_f_)
378         dy_f = dy_f <? sinfo_[i].maxy_f_ - y;
379       if (y < sinfo_[i].miny_f_)
380         { 
381           // when all too short, normal stems win..
382           if (dy_f < -epsilon_f)
383             warning (_ ("weird beam vertical offset"));
384           dy_f = dy_f >? sinfo_[i].miny_f_ - y; 
385         }
386     }
387   return dy_f;
388 }
389
390 void
391 Beam::set_steminfo ()
392 {
393   if(!stems_.size ())
394     return;
395   
396   assert (multiple_i_);
397
398   int total_count_i = 0;
399   int forced_count_i = 0;
400   for (int i=0; i < stems_.size (); i++)
401     {
402       Stem *s = stems_[i];
403
404       s->set_default_extents ();
405       if (s->invisible_b ())
406         continue;
407       if (((int)s->chord_start_f ()) && (s->dir_ != s->get_default_dir ()))
408         forced_count_i++;
409       total_count_i++;
410     }
411
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 (multiple_i_ <? stem_max));
417     
418   Real leftx = 0;
419   for (int i=0; i < stems_.size (); i++)
420     {
421       Stem *s = stems_[i];
422       /*
423         Chord tremolo needs to beam over invisible stems of wholes
424       */
425       SCM trem = get_elt_property ("chord-tremolo");
426       if (gh_boolean_p (trem) && gh_scm2bool (trem))
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