]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam.cc
release: 1.3.1
[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_BOOL_F)
133     {
134       int auto_gap_i = gh_scm2int (SCM_CDR (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_sym, 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_scm_sym), true))
173     return true;
174   
175   return auto_knee (get_elt_property (auto_knee_gap_scm_sym), 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_sym, 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_scm_sym, 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 (gh_symbol2scm ("beam_dir_algorithm"));
286   a= gh_cdr (a);
287   
288   if (a == gh_symbol2scm ("majority")) // should get default from paper.
289     beam_dir = (count[UP] == count[DOWN]) ? neutral_dir 
290       : (count[UP] > count[DOWN]) ? UP : DOWN;
291   else if (a == gh_symbol2scm ("mean"))
292     // mean center distance
293     beam_dir = (total[UP] == total[DOWN]) ? neutral_dir
294       : (total[UP] > total[DOWN]) ? UP : DOWN;
295   else if (a == gh_symbol2scm ("median"))
296     {
297       // median center distance
298       if (count[DOWN] && count[UP])
299         {
300           beam_dir = (total[UP] / count[UP] == total[DOWN] / count[DOWN]) 
301             ? neutral_dir 
302             : (total[UP] / count[UP] > total[DOWN] / count[DOWN]) ? UP : DOWN;
303         }
304       else
305         {
306           beam_dir = (count[UP] == count[DOWN]) ? neutral_dir 
307             : (count[UP] > count[DOWN]) ? UP : DOWN;
308         }
309     }
310   
311   return beam_dir;
312 }
313
314 void
315 Beam::set_direction (Direction d)
316 {
317   dir_ = d;
318   for (int i=0; i <stems_.size (); i++)
319     {
320       Stem *s = stems_[i];
321       s->set_elt_property (beam_dir_scm_sym, gh_int2scm (d));
322
323       SCM force = s->remove_elt_property (dir_forced_scm_sym);
324       if (force == SCM_BOOL_F)
325         s->dir_ = d;
326     }
327 }
328
329 /*
330   See Documentation/tex/fonts.doc
331  */
332
333 void
334 Beam::solve_slope ()
335 {
336   assert (sinfo_.size () > 1);
337
338   Least_squares l;
339   for (int i=0; i < sinfo_.size (); i++)
340     {
341       l.input.push (Offset (sinfo_[i].x_, sinfo_[i].idealy_f_));
342     }
343   l.minimise (slope_f_, left_y_);
344 }
345
346 /*
347   ugh. Naming: this doesn't check, but sets as well.
348  */
349   
350 Real
351 Beam::check_stemlengths_f (bool set_b)
352 {
353   Real interbeam_f = paper_l ()->interbeam_f (multiple_i_);
354
355   Real beam_f = paper_l ()->get_realvar (beam_thickness_scm_sym);;
356   Real staffline_f = paper_l ()-> get_var ("stafflinethickness");
357   Real epsilon_f = staffline_f / 8;
358   Real dy_f = 0.0;
359   for (int i=0; i < sinfo_.size (); i++)
360     {
361       Real y = sinfo_[i].x_ * slope_f_ + left_y_;
362
363       // correct for knee
364       if (dir_ != sinfo_[i].dir_)
365         {
366           Real internote_f = sinfo_[i].stem_l_->staff_line_leading_f ()/2;
367           y -= dir_ * (beam_f / 2
368                        + (sinfo_[i].mult_i_ - 1) * interbeam_f) / internote_f;
369           if (!i && sinfo_[i].stem_l_->staff_symbol_l () !=
370               sinfo_.top ().stem_l_->staff_symbol_l ())
371             y += dir_ * (multiple_i_ - (sinfo_[i].stem_l_->flag_i_ - 2) >? 0)
372               * interbeam_f / internote_f;
373         }
374
375       if (set_b)
376         sinfo_[i].stem_l_->set_stemend (y - sinfo_[i].interstaff_f_);
377         
378       y *= dir_;
379       if (y > sinfo_[i].maxy_f_)
380         dy_f = dy_f <? sinfo_[i].maxy_f_ - y;
381       if (y < sinfo_[i].miny_f_)
382         { 
383           // when all too short, normal stems win..
384           if (dy_f < -epsilon_f)
385             warning (_ ("weird beam vertical offset"));
386           dy_f = dy_f >? sinfo_[i].miny_f_ - y; 
387         }
388     }
389   return dy_f;
390 }
391
392 void
393 Beam::set_steminfo ()
394 {
395   if(!stems_.size ())
396     return;
397   
398   assert (multiple_i_);
399
400   int total_count_i = 0;
401   int forced_count_i = 0;
402   for (int i=0; i < stems_.size (); i++)
403     {
404       Stem *s = stems_[i];
405
406       s->set_default_extents ();
407       if (s->invisible_b ())
408         continue;
409       if (((int)s->chord_start_f ()) && (s->dir_ != s->get_default_dir ()))
410         forced_count_i++;
411       total_count_i++;
412     }
413
414   bool grace_b = get_elt_property (grace_scm_sym) != SCM_BOOL_F;
415   String type_str = grace_b ? "grace_" : "";
416   int stem_max = (int)rint(paper_l ()->get_var ("stem_max"));
417   Real shorten_f = paper_l ()->get_var (type_str + "forced_stem_shorten"
418                                         + to_str (multiple_i_ <? stem_max));
419     
420   Real leftx = 0;
421   for (int i=0; i < stems_.size (); i++)
422     {
423       Stem *s = stems_[i];
424       /*
425         Chord tremolo needs to beam over invisible stems of wholes
426       */
427       if (!dynamic_cast<Chord_tremolo*> (this))
428         {
429           if (s->invisible_b ())
430             continue;
431         }
432
433       Stem_info info (s, multiple_i_);
434       if (leftx == 0)
435         leftx = info.x_;
436       info.x_ -= leftx;
437       if (info.dir_ == dir_)
438         {
439           if (forced_count_i == total_count_i)
440             info.idealy_f_ -= shorten_f;
441           else if (forced_count_i > total_count_i / 2)
442             info.idealy_f_ -= shorten_f / 2;
443         }
444       sinfo_.push (info);
445     }
446 }
447
448 void
449 Beam::calculate_slope ()
450 {
451   if (!sinfo_.size ())
452     slope_f_ = left_y_ = 0;
453   else if (sinfo_[0].idealy_f_ == sinfo_.top ().idealy_f_)
454     {
455       slope_f_ = 0;
456       left_y_ = sinfo_[0].idealy_f_;
457       left_y_ *= dir_;
458     }
459   else
460     {
461       solve_slope ();
462       Real solved_slope_f = slope_f_;
463
464       /*
465         steep slope running against lengthened stem is suspect
466       */
467       Real dx_f = stems_.top ()->hpos_f () - stems_[0]->hpos_f ();
468
469       // urg, these y internote-y-dimensions
470       Real internote_f = stems_[0]->staff_line_leading_f ()/2;
471
472       Real lengthened = paper_l ()->get_var ("beam_lengthened") / internote_f;
473       Real steep = paper_l ()->get_var ("beam_steep_slope") / internote_f;
474       if (((left_y_ - sinfo_[0].idealy_f_ > lengthened)
475            && (slope_f_ > steep))
476           || ((left_y_ + slope_f_ * dx_f - sinfo_.top ().idealy_f_ > lengthened)
477               && (slope_f_ < -steep)))
478         {
479           slope_f_ = 0;
480         }
481
482       /*
483         This neat trick is by Werner Lemberg,
484         damped = tanh (slope_f_)
485         corresponds with some tables in [Wanske]
486       */
487       SCM damp = remove_elt_property (damping_scm_sym);
488       int damping = 1;          // ugh.
489       if (damp!= SCM_BOOL_F)
490         damping = gh_int2scm (SCM_CDR(damp));
491
492       if (damping)
493         slope_f_ = 0.6 * tanh (slope_f_) / damping;
494       
495       quantise_dy ();
496
497       Real damped_slope_dy_f = (solved_slope_f - slope_f_) * dx_f / 2;
498       left_y_ += damped_slope_dy_f;
499
500       left_y_ *= dir_;
501       slope_f_ *= dir_;
502     }
503 }
504
505 void
506 Beam::quantise_dy ()
507 {
508   /*
509     [Ross] (simplification of)
510     Try to set slope_f_ complying with y-span of:
511       - zero
512       - beam_f / 2 + staffline_f / 2
513       - beam_f + staffline_f
514     + n * interline
515     */
516
517   SCM q = get_elt_property (gh_symbol2scm ("slope_quantisation"));
518   q = gh_cdr (q);
519   
520   if (q == gh_symbol2scm ("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   Array<Real> allowed_fraction (3);
536   allowed_fraction[0] = 0;
537   allowed_fraction[1] = (beam_f / 2 + staffline_f / 2);
538   allowed_fraction[2] = (beam_f + staffline_f);
539
540
541   Interval iv = quantise_iv (allowed_fraction, interline_f, dy_f);
542   quanty_f = (dy_f - iv[SMALLER] <= iv[BIGGER] - dy_f)
543     ? iv[SMALLER]
544     : iv[BIGGER];
545
546
547   slope_f_ = (quanty_f / dx_f) / internote_f * sign (slope_f_);
548 }
549
550 /*
551   
552   Prevent interference from stafflines and beams.  See Documentation/tex/fonts.doc
553   
554  */
555 void
556 Beam::quantise_left_y (bool extend_b)
557 {
558    /*
559     we only need to quantise the start of the beam as dy is quantised too
560    if extend_b then stems must *not* get shorter
561    */
562   SCM q = get_elt_property (gh_symbol2scm ("slope_quantisation"));
563   q = gh_cdr (q);
564
565   /*
566     ----------------------------------------------------------
567                                                    ########
568                                         ########
569                              ########
570     --------------########------------------------------------
571        ########
572
573        hang       straddle   sit        inter      hang
574    */
575
576   Real space = stems_[0]->staff_line_leading_f ();
577   Real internote_f = space /2;
578   Real staffline_f = paper_l ()->get_var ("stafflinethickness");
579   Real beam_f = paper_l ()->get_realvar (beam_thickness_scm_sym);;
580
581   /*
582     [TODO]
583     it would be nice to have all allowed positions in a runtime matrix:
584     (multiplicity, minimum_beam_dy, maximum_beam_dy)
585    */
586
587   Real straddle = 0;
588   Real sit = beam_f / 2 - staffline_f / 2;
589   Real hang = space - beam_f / 2 + staffline_f / 2;
590
591   /*
592     Put all allowed positions into an array.
593     Whether a position is allowed or not depends on 
594     strictness of quantisation, multiplicity and direction.
595
596     For simplicity, we'll assume dir = UP and correct if 
597     dir = DOWN afterwards.
598    */
599   // isn't this asymmetric ? --hwn
600   
601   // dim(left_y_) = internote
602   Real dy_f = dir_ * left_y_ * internote_f;
603
604   Real beamdx_f = stems_.top ()->hpos_f () - stems_[0]->hpos_f ();
605   Real beamdy_f = beamdx_f * slope_f_ * internote_f;
606
607   Array<Real> allowed_position;
608   if (q == gh_symbol2scm ("normal"))
609     {
610       if ((multiple_i_ <= 2) || (abs (beamdy_f) >= staffline_f / 2))
611         allowed_position.push (straddle);
612       if ((multiple_i_ <= 1) || (abs (beamdy_f) >= staffline_f / 2))
613         allowed_position.push (sit);
614       allowed_position.push (hang);
615     }
616   else if (q == gh_symbol2scm ("traditional"))
617     {
618       // TODO: check and fix TRADITIONAL
619       if ((multiple_i_ <= 2) || (abs (beamdy_f) >= staffline_f / 2))
620         allowed_position.push (straddle);
621       if ((multiple_i_ <= 1) && (beamdy_f <= staffline_f / 2))
622         allowed_position.push (sit);
623       if (beamdy_f >= -staffline_f / 2)
624         allowed_position.push (hang);
625     }
626
627
628   Interval iv = quantise_iv (allowed_position, space, dy_f);
629
630   Real quanty_f = dy_f - iv[SMALLER] <= iv[BIGGER] - dy_f ? iv[SMALLER] : iv[BIGGER];
631   if (extend_b)
632     quanty_f = iv[BIGGER];
633
634   // dim(left_y_) = internote
635   left_y_ = dir_ * quanty_f / internote_f;
636 }
637
638 void
639 Beam::set_stemlens ()
640 {
641   Real staffline_f = paper_l ()->get_var ("stafflinethickness");
642   // enge floots
643   Real epsilon_f = staffline_f / 8;
644
645
646   // je bent zelf eng --hwn.
647   Real dy_f = check_stemlengths_f (false);
648   for (int i = 0; i < 2; i++)   // 2 ?
649     { 
650       left_y_ += dy_f * dir_;
651       quantise_left_y (dy_f);
652       dy_f = check_stemlengths_f (true);
653       if (abs (dy_f) <= epsilon_f)
654         {
655           break;
656         }
657     }
658 }
659
660 void
661 Beam::set_beaming (Beaming_info_list *beaming)
662 {
663   Direction d = LEFT;
664   for (int i=0; i  < stems_.size (); i++)
665     {
666       do
667         {
668           if (stems_[i]->beams_i_drul_[d] < 0)
669             stems_[i]->beams_i_drul_[d] = beaming->infos_.elem (i).beams_i_drul_[d];
670         }
671       while (flip (&d) != LEFT);
672     }
673 }
674
675
676 void
677 Beam::do_add_processing ()
678 {
679   for (int i=0; i < stems_.size () ; i++) 
680     {
681       Direction d = LEFT;
682       do {
683         multiple_i_ = multiple_i_ >? stems_[i]->beams_i_drul_[d];
684       } while ((flip (&d)) != LEFT);
685     }
686
687   if (stems_.size ())
688     {
689       stems_[0]->beams_i_drul_[LEFT] =0;
690       stems_.top()->beams_i_drul_[RIGHT] =0;
691     }
692 }
693
694
695
696 /*
697   beams to go with one stem.
698
699   clean  me up.
700   */
701 Molecule
702 Beam::stem_beams (Stem *here, Stem *next, Stem *prev) const
703 {
704   if ((next && !(next->hpos_f () > here->hpos_f ())) ||
705       (prev && !(prev->hpos_f () < here->hpos_f ())))
706       programming_error ("Beams are not left-to-right");
707
708   Real staffline_f = paper_l ()->get_var ("stafflinethickness");
709   Real interbeam_f = paper_l ()->interbeam_f (multiple_i_);
710
711   Real internote_f = here->staff_line_leading_f ()/2;
712   Real beam_f = paper_l ()->get_realvar (beam_thickness_scm_sym);;
713
714   Real dy = interbeam_f;
715   Real stemdx = staffline_f;
716   Real sl = slope_f_* internote_f;
717
718   Molecule leftbeams;
719   Molecule rightbeams;
720
721   // UGH
722   Real nw_f;
723   if (!here->head_l_arr_.size ())
724     nw_f = 0;
725   else if (here->type_i ()== 1)
726     nw_f = paper_l ()->get_var ("wholewidth");
727   else if (here->type_i () == 2)
728     nw_f = paper_l ()->get_var ("notewidth") * 0.8;
729   else
730     nw_f = paper_l ()->get_var ("quartwidth");
731
732   /* half beams extending to the left. */
733   if (prev)
734     {
735       int lhalfs= lhalfs = here->beams_i_drul_[LEFT] - prev->beams_i_drul_[RIGHT] ;
736       int lwholebeams= here->beams_i_drul_[LEFT] <? prev->beams_i_drul_[RIGHT] ;
737       /*
738        Half beam should be one note-width, 
739        but let's make sure two half-beams never touch
740        */
741       Real w = here->hpos_f () - prev->hpos_f ();
742       w = w/2 <? nw_f;
743       Molecule a;
744       if (lhalfs)               // generates warnings if not
745         a =  lookup_l ()->beam (sl, w, beam_f);
746       a.translate (Offset (-w, -w * sl));
747       for (int j = 0; j  < lhalfs; j++)
748         {
749           Molecule b (a);
750           b.translate_axis (-dir_ * dy * (lwholebeams+j), Y_AXIS);
751           leftbeams.add_molecule (b);
752         }
753     }
754
755   if (next)
756     {
757       int rhalfs = here->beams_i_drul_[RIGHT] - next->beams_i_drul_[LEFT];
758       int rwholebeams = here->beams_i_drul_[RIGHT] <? next->beams_i_drul_[LEFT];
759
760       Real w = next->hpos_f () - here->hpos_f ();
761       Molecule a = lookup_l ()->beam (sl, w + stemdx, beam_f);
762       a.translate_axis( - stemdx/2, X_AXIS);
763       int j = 0;
764       Real gap_f = 0;
765
766       SCM gap = get_elt_property (beam_gap_scm_sym);
767       if (gap != SCM_BOOL_F)
768         {
769           int gap_i = gh_scm2int (SCM_CDR (gap));
770           int nogap = rwholebeams - gap_i;
771           
772           for (; j  < nogap; j++)
773             {
774               Molecule b (a);
775               b.translate_axis (-dir_ * dy * j, Y_AXIS);
776               rightbeams.add_molecule (b);
777             }
778           // TODO: notehead widths differ for different types
779           gap_f = nw_f / 2;
780           w -= 2 * gap_f;
781           a = lookup_l ()->beam (sl, w + stemdx, beam_f);
782         }
783
784       for (; j  < rwholebeams; j++)
785         {
786           Molecule b (a);
787           if (!here->invisible_b ())
788             b.translate (Offset (gap_f, -dir_ * dy * j));
789           else
790             b.translate (Offset (0, -dir_ * dy * j));
791           rightbeams.add_molecule (b);
792         }
793
794       w = w/2 <? nw_f;
795       if (rhalfs)
796         a = lookup_l ()->beam (sl, w, beam_f);
797
798       for (; j  < rwholebeams + rhalfs; j++)
799         {
800           Molecule b (a);
801           b.translate_axis (-dir_ * dy * j, Y_AXIS);
802           rightbeams.add_molecule (b);
803         }
804
805     }
806   leftbeams.add_molecule (rightbeams);
807
808   /*
809     Does beam quanting think  of the asymetry of beams? 
810     Refpoint is on bottom of symbol. (FIXTHAT) --hwn.
811    */
812   return leftbeams;
813 }
814