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