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