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