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