]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam.cc
biempats
[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--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8   
9 */
10
11 /*
12   [TODO]
13
14   * Fix TODO
15   
16   * Junk stem_info.
17   
18   * Remove #'direction from beam.  A beam has no direction per se.
19     It may only set directions for stems.
20
21   * Rewrite stem_beams.
22
23   * Use Number_pair i.s.o Interval to represent (yl, yr).
24   
25   */
26
27
28
29
30 #include <math.h> // tanh.
31
32 #include "molecule.hh" 
33 #include "directional-element-interface.hh"
34 #include "beaming.hh"
35 #include "beam.hh"
36 #include "misc.hh"
37 #include "least-squares.hh"
38 #include "stem.hh"
39 #include "paper-def.hh"
40 #include "lookup.hh"
41 #include "group-interface.hh"
42 #include "staff-symbol-referencer.hh"
43 #include "item.hh"
44 #include "spanner.hh"
45 #include "warn.hh"
46
47
48 #define DEBUG_QUANTING 0
49
50
51 #if DEBUG_QUANTING
52 #include "text-item.hh"  // debug output.
53 #include "font-interface.hh"  // debug output.
54 #endif
55
56
57 const int INTER_QUANT_PENALTY = 1000; 
58 const int SECONDARY_BEAM_DEMERIT  = 15;
59 const int STEM_LENGTH_DEMERIT_FACTOR = 5;
60 const int STEM_LENGTH_LIMIT_PENALTY = 500;
61 const int DAMPING_DIRECTIION_PENALTY = 800;
62 const int MUSICAL_DIRECTION_FACTOR = 400;
63 const int IDEAL_SLOPE_FACTOR = 10;
64
65
66 static Real
67 shrink_extra_weight (Real x)
68 {
69   return fabs (x) * ((x < 0) ? 1.5 : 1.0);
70 }
71
72 void
73 Beam::add_stem (Grob *me, Grob *s)
74 {
75   Pointer_group_interface::add_grob (me, ly_symbol2scm ("stems"), s);
76   
77   s->add_dependency (me);
78
79   assert (!Stem::beam_l (s));
80   s->set_grob_property ("beam", me->self_scm ());
81
82   add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (s));
83 }
84
85 Real
86 Beam::get_interbeam (Grob *me)
87 {
88   SCM func = me->get_grob_property ("space-function");
89   SCM s = gh_call2 (func, me->self_scm (), gh_int2scm (get_multiplicity (me)));
90   return gh_scm2double (s);
91 }
92
93 /*
94   Maximum multiplicity.
95  */
96 int
97 Beam::get_multiplicity (Grob *me) 
98 {
99   int m = 0;
100   for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
101     {
102       Grob *sc = unsmob_grob (ly_car (s));
103
104       if (Stem::has_interface (sc))
105         m = m >? Stem::beam_count (sc, LEFT) >? Stem::beam_count (sc, RIGHT);
106     }
107   return m;
108 }
109
110 MAKE_SCHEME_CALLBACK (Beam, space_function, 2);
111 SCM
112 Beam::space_function (SCM smob, SCM multiplicity)
113 {
114   Grob *me = unsmob_grob (smob);
115   
116   Real staff_space = Staff_symbol_referencer::staff_space (me);
117   Real line = me->paper_l ()->get_var ("linethickness");
118   Real thickness = gh_scm2double (me->get_grob_property ("thickness"))
119     * staff_space;
120   
121   Real interbeam = gh_scm2int (multiplicity) < 4
122     ? (2*staff_space + line - thickness) / 2.0
123     : (3*staff_space + line - thickness) / 3.0;
124   
125   return gh_double2scm (interbeam);
126 }
127
128
129 /* After pre-processing all directions should be set.
130    Several post-processing routines (stem, slur, script) need stem/beam
131    direction.
132    Currenly, this means that beam has set all stem's directions.
133    [Alternatively, stems could set its own directions, according to
134    their beam, during 'final-pre-processing'.] */
135 MAKE_SCHEME_CALLBACK (Beam, before_line_breaking, 1);
136 SCM
137 Beam::before_line_breaking (SCM smob)
138 {
139   Grob *me =  unsmob_grob (smob);
140
141   /* Beams with less than 2 two stems don't make much sense, but could happen
142      when you do
143      
144      [r8 c8 r8].
145      
146     For a beam that  only has one stem, we try to do some disappearance magic:
147     we revert the flag, and move on to The Eternal Engraving Fields. */
148
149   int count = visible_stem_count (me);
150   if (count < 2)
151     {
152       me->warning (_ ("beam has less than two visible stems"));
153
154       SCM stems = me->get_grob_property ("stems");
155       if (scm_ilength (stems) == 1)
156         {
157           me->warning (_ ("Beam has less than two stems. Removing beam."));
158
159           unsmob_grob (gh_car (stems))->remove_grob_property ("beam");
160           me->suicide ();
161
162           return SCM_UNSPECIFIED;
163         }
164       else if (scm_ilength (stems) == 0)
165         {
166           me->suicide ();
167           return SCM_UNSPECIFIED;         
168         }
169     }
170   if (count >= 1)
171     {
172       Direction d = get_default_dir (me);
173
174       consider_auto_knees (me, d);
175       set_stem_directions (me, d);
176       set_stem_shorten (me);
177     }
178
179   return SCM_EOL;
180 }
181
182 Direction
183 Beam::get_default_dir (Grob *me) 
184 {
185   Drul_array<int> total;
186   total[UP]  = total[DOWN] = 0;
187   Drul_array<int> count; 
188   count[UP]  = count[DOWN] = 0;
189   Direction d = DOWN;
190
191   Link_array<Item> stems=
192         Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
193
194   for (int i=0; i <stems.size (); i++)
195     do {
196       Grob *s = stems[i];
197       Direction sd = Directional_element_interface::get (s);
198
199       int center_distance = int(- d * Stem::head_positions (s) [-d]) >? 0;
200       int current = sd  ? (1 + d * sd)/2 : center_distance;
201
202       if (current)
203         {
204           total[d] += current;
205           count[d] ++;
206         }
207     } while (flip (&d) != DOWN);
208   
209   SCM func = me->get_grob_property ("dir-function");
210   SCM s = gh_call2 (func,
211                     gh_cons (gh_int2scm (count[UP]),
212                              gh_int2scm (count[DOWN])),
213                     gh_cons (gh_int2scm (total[UP]),
214                              gh_int2scm (total[DOWN])));
215
216   if (gh_number_p (s) && gh_scm2int (s))
217     return to_dir (s);
218   
219   /* If dir is not determined: get default */
220   return to_dir (me->get_grob_property ("neutral-direction"));
221 }
222
223
224 /* Set all stems with non-forced direction to beam direction.
225    Urg: non-forced should become `without/with unforced' direction,
226    once stem gets cleaned-up. */
227 void
228 Beam::set_stem_directions (Grob *me, Direction d)
229 {
230   Link_array<Item> stems
231     =Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
232   
233   for (int i=0; i <stems.size (); i++)
234     {
235       Grob *s = stems[i];
236       SCM force = s->remove_grob_property ("dir-forced");
237       if (!gh_boolean_p (force) || !gh_scm2bool (force))
238         Directional_element_interface::set (s, d);
239     }
240
241
242 /* Simplistic auto-knees; only consider vertical gap between two
243    adjacent chords.
244
245   `Forced' stem directions are ignored.  If you don't want auto-knees,
246   don't set, or unset auto-knee-gap. */
247 void
248 Beam::consider_auto_knees (Grob *me, Direction d)
249 {
250   SCM scm = me->get_grob_property ("auto-knee-gap");
251
252   if (gh_number_p (scm))
253     {
254       bool knee_b = false;
255       Real knee_y = 0;
256       Real staff_space = Staff_symbol_referencer::staff_space (me);
257       Real gap = gh_scm2double (scm) / staff_space;
258
259
260       Link_array<Item> stems=
261         Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
262       
263       Grob *common = me->common_refpoint (stems[0], Y_AXIS);
264       for (int i=1; i < stems.size (); i++)
265         if (!Stem::invisible_b (stems[i]))
266           common = common->common_refpoint (stems[i], Y_AXIS);
267
268       int l = 0;
269       for (int i=1; i < stems.size (); i++)
270         {
271           if (!Stem::invisible_b (stems[i-1]))
272             l = i - 1;
273           if (Stem::invisible_b (stems[l]))
274             continue;
275           if (Stem::invisible_b (stems[i]))
276             continue;
277           
278           Real left = Stem::extremal_heads (stems[l])[d]
279             ->relative_coordinate (common, Y_AXIS);
280           Real right = Stem::extremal_heads (stems[i])[-d]
281             ->relative_coordinate (common, Y_AXIS);
282
283           Real dy = right - left;
284
285           if (abs (dy) >= gap)
286             {
287               knee_y = (right + left) / 2;
288               knee_b = true;
289               break;
290             }
291         }
292       
293       if (knee_b)
294         {
295           for (int i=0; i < stems.size (); i++)
296             {
297               if (Stem::invisible_b (stems[i]))
298                 continue;
299               Item *s = stems[i];         
300               Real y = Stem::extremal_heads (stems[i])[d]
301                 ->relative_coordinate (common, Y_AXIS);
302
303               Directional_element_interface::set (s, y < knee_y ? UP : DOWN);
304               s->set_grob_property ("dir-forced", SCM_BOOL_T);
305             }
306         }
307     }
308 }
309
310 /* Set stem's shorten property if unset.
311
312  TODO:
313    take some y-position (chord/beam/nearest?) into account
314    scmify forced-fraction
315
316    TODO:
317    
318    why is shorten stored in beam, and not directly in stem?
319
320 */
321 void
322 Beam::set_stem_shorten (Grob *m)
323 {
324   Spanner*me = dynamic_cast<Spanner*> (m);
325
326   Real forced_fraction = forced_stem_count (me) / visible_stem_count (me);
327
328   int multiplicity = get_multiplicity (me);
329
330   SCM shorten = me->get_grob_property ("beamed-stem-shorten");
331   if (shorten == SCM_EOL)
332     return;
333
334   int sz = scm_ilength (shorten);
335   
336   Real staff_space = Staff_symbol_referencer::staff_space (me);
337   SCM shorten_elt = scm_list_ref (shorten,
338                                   gh_int2scm (multiplicity <? (sz - 1)));
339   Real shorten_f = gh_scm2double (shorten_elt) * staff_space;
340
341   /* your similar cute comment here */
342   shorten_f *= forced_fraction;
343
344   if (shorten_f)
345     me->set_grob_property ("shorten", gh_double2scm (shorten_f));
346 }
347
348 /*  Call list of y-dy-callbacks, that handle setting of
349     grob-properties y, dy.
350     
351     User may set grob-properties: y-position-hs and height-hs
352  (to be fixed) that override the calculated y and dy.
353     
354     Because y and dy cannot be calculated and quanted separately, we
355     always calculate both, then check for user override. */
356 MAKE_SCHEME_CALLBACK (Beam, after_line_breaking, 1);
357 SCM
358 Beam::after_line_breaking (SCM smob)
359 {
360   Grob *me = unsmob_grob (smob);
361   
362   /* Copy to mutable list. */
363   SCM s = ly_deep_copy (me->get_grob_property ("positions"));
364   me->set_grob_property ("positions", s);
365
366   if (ly_car (s) != SCM_BOOL_F)
367     return SCM_UNSPECIFIED;
368
369   // one wonders if such genericity is necessary  --hwn.
370   SCM callbacks = me->get_grob_property ("position-callbacks");
371   for (SCM i = callbacks; gh_pair_p (i); i = ly_cdr (i))
372     gh_call1 (ly_car (i), smob);
373
374   set_stem_lengths (me);  
375   return SCM_UNSPECIFIED;
376 }
377
378 struct Quant_score
379 {
380   Real yl;
381   Real yr;
382   Real demerits;
383 };
384
385
386 /*
387   TODO:
388   
389    - Make all demerits customisable
390
391    - One sensible check per demerit (what's this --hwn)
392
393    - Add demerits for quants per se, as to forbid a specific quant
394      entirely
395
396 */
397 MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
398 SCM
399 Beam::quanting (SCM smob)
400 {
401   Grob *me = unsmob_grob (smob);
402
403   SCM s = me->get_grob_property ("positions");
404   Real yl = gh_scm2double (gh_car (s));
405   Real yr = gh_scm2double (gh_cdr (s));
406
407   Real ss = Staff_symbol_referencer::staff_space (me);
408   Real thickness = gh_scm2double (me->get_grob_property ("thickness")) / ss;
409   Real slt = me->paper_l ()->get_var ("linethickness") / ss;
410
411
412   SCM sdy = me->get_grob_property ("least-squares-dy");
413   Real dy_mus = gh_number_p (sdy) ? gh_scm2double (sdy) : 0.0;
414   
415   Real straddle = 0.0;
416   Real sit = (thickness - slt) / 2;
417   Real inter = 0.5;
418   Real hang = 1.0 - (thickness - slt) / 2;
419   Real quants [] = {straddle, sit, inter, hang };
420   
421   int num_quants = int (sizeof (quants)/sizeof (Real));
422   Array<Real> quantsl;
423   Array<Real> quantsr;
424
425   /*
426     going to REGION_SIZE == 2, yields another 0.6 second with
427     wtk1-fugue2.
428
429
430     (result indexes between 70 and 575)  ? --hwn. 
431
432   */
433
434
435   
436   /*
437     Do stem computations.  These depend on YL and YR linearly, so we can
438     precompute for every stem 2 factors.
439    */
440   Link_array<Grob> stems=
441     Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
442   Array<Stem_info> stem_infos;
443   Array<Real> lbase_lengths;
444   Array<Real> rbase_lengths;  
445
446   Drul_array<bool> dirs_found(0,0);
447   for (int i= 0; i < stems.size(); i++)
448     {
449       Grob*s = stems[i];
450       stem_infos.push( Stem::calc_stem_info (s));
451
452       Real b = calc_stem_y (me, s, Interval (1,0));
453       lbase_lengths.push (b);
454
455       b = calc_stem_y (me, s, Interval (0,1));
456       rbase_lengths.push (b);
457
458       dirs_found [stem_infos.top().dir_] = true;
459     }
460
461   Direction ldir = Direction (stem_infos[0].dir_);
462   Direction rdir = Direction (stem_infos.top ().dir_);
463   bool knee_b = dirs_found[LEFT] && dirs_found[RIGHT];
464
465   
466   int REGION_SIZE = 2;
467
468   /*
469     Knees are harder, lets try some more possibilities for knees. 
470    */
471   if (knee_b)
472     REGION_SIZE += 2 ;
473   
474   for (int i = -REGION_SIZE ; i < REGION_SIZE; i++)
475     for (int j = 0; j < num_quants; j++)
476       {
477         quantsl.push (i + quants[j] + int (yl));
478         quantsr.push (i + quants[j] + int (yr));
479       }
480
481   Array<Quant_score> qscores;
482   
483   for (int l =0; l < quantsl.size (); l++)  
484     for (int r =0; r < quantsr.size (); r++)
485       {
486         Quant_score qs;
487         qs.yl = quantsl[l];
488         qs.yr = quantsr[r];
489         qs.demerits = 0.0;
490         
491         qscores.push (qs);
492       }
493
494
495   /*
496     This is a longish function, but we don't separate this out into
497     neat modular separate subfunctions, as the subfunctions would be
498     called for many values of YL, YR. By precomputing various
499     parameters outside of the loop, we can save a lot of time.
500
501   */
502   for (int i = qscores.size (); i--;)
503     if (qscores[i].demerits < 100)
504       {
505         qscores[i].demerits
506           += score_slopes_dy (me, qscores[i].yl, qscores[i].yr,
507                               dy_mus, yr- yl); 
508       }
509
510   Real rad = Staff_symbol_referencer::staff_radius (me);
511   int multiplicity = get_multiplicity (me);
512   Real interbeam = multiplicity < 4
513     ? (2*ss + slt - thickness) / 2.0
514      : (3*ss + slt - thickness) / 3.0;
515
516   for (int i = qscores.size (); i--;)
517     if (qscores[i].demerits < 100)
518       {
519         qscores[i].demerits
520           += score_forbidden_quants (me, qscores[i].yl, qscores[i].yr,
521                                      rad, slt, thickness, interbeam,
522                                      multiplicity, ldir, rdir); 
523       }
524
525
526   for (int i = qscores.size (); i--;)
527     if (qscores[i].demerits < 100)
528       {
529         qscores[i].demerits
530           += score_stem_lengths (stems, stem_infos,
531                                  lbase_lengths, rbase_lengths,
532                                  knee_b,
533                                  me, qscores[i].yl, qscores[i].yr);
534       }
535
536
537   Real best = 1e6;
538   int best_idx = -1;
539   for (int i = qscores.size (); i--;)
540     {
541       if (qscores[i].demerits < best)
542         {
543           best = qscores [i].demerits ;
544           best_idx = i;
545         }
546     }
547
548   
549   me->set_grob_property ("positions",
550                          gh_cons (gh_double2scm (qscores[best_idx].yl),
551                                   gh_double2scm (qscores[best_idx].yr))
552                          );
553
554 #if DEBUG_QUANTING
555
556   // debug quanting
557   me->set_grob_property ("quant-score",
558                          gh_double2scm (qscores[best_idx].demerits));
559   me->set_grob_property ("best-idx", gh_int2scm (best_idx));
560 #endif
561
562   return SCM_UNSPECIFIED;
563 }
564
565 Real
566 Beam::score_stem_lengths (Link_array<Grob>stems,
567                           Array<Stem_info> stem_infos,
568                           Array<Real> left_factor,
569                           Array<Real> right_factor,
570                           bool knee, 
571                           Grob*me,
572                           Real yl, Real yr)
573 {
574   Real demerit_score = 0.0 ;
575   Real pen = STEM_LENGTH_LIMIT_PENALTY;
576   if (knee)
577     pen = sqrt(pen);
578
579   for (int i=0; i < stems.size (); i++)
580     {
581       Grob* s = stems[i];
582       if (Stem::invisible_b (s))
583         continue;
584
585       Real current_y =
586         yl * left_factor[i] + right_factor[i]* yr;
587
588       Stem_info info = stem_infos[i];
589       Direction d = info.dir_;
590
591       demerit_score += pen * ( 0 >? (info.dir_ *(info.shortest_y_ - current_y)));
592       demerit_score += STEM_LENGTH_DEMERIT_FACTOR
593         * shrink_extra_weight (d * current_y  - info.dir_ * info.ideal_y_);
594     }
595
596   demerit_score *= 2.0 / stems.size (); 
597
598   return demerit_score;
599 }
600
601 Real
602 Beam::score_slopes_dy (Grob *me,
603                        Real yl, Real yr,
604                        Real dy_mus, Real dy_damp)
605 {
606   Real dy = yr - yl;
607
608   Real dem = 0.0;
609   if (sign (dy_damp) != sign (dy))
610     {
611       dem += DAMPING_DIRECTIION_PENALTY;
612     }
613
614    dem += MUSICAL_DIRECTION_FACTOR * (0 >? (fabs (dy) - fabs (dy_mus)));
615    dem += shrink_extra_weight (fabs (dy_damp) - fabs (dy))* IDEAL_SLOPE_FACTOR;
616
617    return dem;
618 }
619
620 static Real
621 my_modf (Real x)
622 {
623   return x - floor (x);
624 }
625
626 Real
627 Beam::score_forbidden_quants (Grob*me,
628                               Real yl, Real yr,
629                               Real rad,
630                               Real slt,
631                               Real thickness, Real interbeam,
632                               int multiplicity,
633                               Direction ldir, Direction rdir)
634 {
635   Real dy = yr - yl;
636
637   Real dem = 0.0;
638   if (fabs (yl) < rad && fabs ( my_modf (yl) - 0.5) < 1e-3)
639     dem += INTER_QUANT_PENALTY;
640   if (fabs (yr) < rad && fabs ( my_modf (yr) - 0.5) < 1e-3)
641     dem += INTER_QUANT_PENALTY;
642
643   // todo: use multiplicity of outer stems.
644   if (multiplicity >= 2)
645     {
646      
647       Real straddle = 0.0;
648       Real sit = (thickness - slt) / 2;
649       Real inter = 0.5;
650       Real hang = 1.0 - (thickness - slt) / 2;
651       
652
653       if (fabs (yl - ldir * interbeam) < rad
654           && fabs (my_modf (yl) - inter) < 1e-3)
655         dem += SECONDARY_BEAM_DEMERIT;
656       if (fabs (yr - rdir * interbeam) < rad
657           && fabs (my_modf (yr) - inter) < 1e-3)
658         dem += SECONDARY_BEAM_DEMERIT;
659
660       Real eps = 1e-3;
661
662       /*
663         Can't we simply compute the distance between the nearest
664         staffline and the secondary beam? That would get rid of the
665         silly case analysis here (which is probably not when we have
666         different beam-thicknesses.)
667
668         --hwn
669        */
670
671
672       // hmm, without Interval/Drul_array, you get ~ 4x same code...
673       if (fabs (yl - ldir * interbeam) < rad + inter)
674         {
675           if (ldir == UP && dy <= eps
676               && fabs (my_modf (yl) - sit) < eps)
677             dem += SECONDARY_BEAM_DEMERIT;
678           
679           if (ldir == DOWN && dy >= eps
680               && fabs (my_modf (yl) - hang) < eps)
681             dem += SECONDARY_BEAM_DEMERIT;
682         }
683
684       if (fabs (yr - rdir * interbeam) < rad + inter)
685         {
686           if (rdir == UP && dy >= eps
687               && fabs (my_modf (yr) - sit) < eps)
688             dem += SECONDARY_BEAM_DEMERIT;
689           
690           if (rdir == DOWN && dy <= eps
691               && fabs (my_modf (yr) - hang) < eps)
692             dem += SECONDARY_BEAM_DEMERIT;
693         }
694       
695       if (multiplicity >= 3)
696         {
697           if (fabs (yl - 2 * ldir * interbeam) < rad + inter)
698             {
699               if (ldir == UP && dy <= eps
700                   && fabs (my_modf (yl) - straddle) < eps)
701                 dem += SECONDARY_BEAM_DEMERIT;
702               
703               if (ldir == DOWN && dy >= eps
704                   && fabs (my_modf (yl) - straddle) < eps)
705                 dem += SECONDARY_BEAM_DEMERIT;
706         }
707           
708           if (fabs (yr - 2 * rdir * interbeam) < rad + inter)
709             {
710               if (rdir == UP && dy >= eps
711                   && fabs (my_modf (yr) - straddle) < eps)
712                 dem += SECONDARY_BEAM_DEMERIT;
713               
714               if (rdir == DOWN && dy <= eps
715                   && fabs (my_modf (yr) - straddle) < eps)
716                 dem += SECONDARY_BEAM_DEMERIT;
717             }
718         }
719     }
720   
721   return dem;
722 }
723
724   
725
726 MAKE_SCHEME_CALLBACK (Beam, least_squares, 1);
727 SCM
728 Beam::least_squares (SCM smob)
729 {
730   Grob *me = unsmob_grob (smob);
731
732   int count = visible_stem_count (me);
733   Interval pos (0, 0);
734   
735   if (count <= 1)
736     {
737       me->set_grob_property ("positions", ly_interval2scm (pos));
738       return SCM_UNSPECIFIED;
739     }
740
741   Interval ideal (Stem::calc_stem_info (first_visible_stem (me)).ideal_y_,
742                   Stem::calc_stem_info (last_visible_stem (me)).ideal_y_);
743   
744   if (!ideal.delta ())
745     {
746       Interval chord (Stem::chord_start_y (first_visible_stem (me)),
747                       Stem::chord_start_y (last_visible_stem (me)));
748
749
750       /*
751         TODO  : use scoring for this.
752
753         complicated, because we take stem-info.ideal for determining
754         beam slopes.
755         
756        */
757       /* Make simple beam on middle line have small tilt */
758       if (!ideal[LEFT] && chord.delta () && count == 2)
759         {
760
761           /*
762             FIXME. -> UP
763           */
764           Direction d = (Direction) (sign (chord.delta ()) * UP);
765           pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
766           //                * dir;
767           pos[-d] = - pos[d];
768         }
769       else
770         {
771           pos = ideal;
772         }
773     }
774   else
775     {
776       Array<Offset> ideals;
777
778       // ugh -> use commonx
779       Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
780       Link_array<Item> stems=
781         Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
782
783       for (int i=0; i < stems.size (); i++)
784         {
785           Item* s = stems[i];
786           if (Stem::invisible_b (s))
787             continue;
788           ideals.push (Offset (s->relative_coordinate (0, X_AXIS) - x0,
789                                Stem::calc_stem_info (s).ideal_y_));
790         }
791       Real y; 
792       Real dydx;
793       minimise_least_squares (&dydx, &y, ideals);
794
795       Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
796       Real dy = dydx * dx;
797       me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
798
799       pos = Interval (y, (y+dy));
800     }
801
802   me->set_grob_property ("positions", ly_interval2scm (pos));
803   return SCM_UNSPECIFIED;
804 }
805
806 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
807 SCM
808 Beam::check_concave (SCM smob)
809 {
810   Grob *me = unsmob_grob (smob);
811
812   Link_array<Item> stems = 
813     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
814
815   for (int i = 0; i < stems.size ();)
816     {
817       if (Stem::invisible_b (stems[i]))
818         stems.del (i);
819       else
820         i++;
821     }
822   
823   if (stems.size () < 3)
824     return SCM_UNSPECIFIED;
825
826
827   /* Concaveness #1: If distance of an inner notehead to line between
828      two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
829      beam is concave (Heinz Stolba).
830
831      In the case of knees, the line connecting outer heads is often
832      not related to the beam slope (it may even go in the other
833      direction). Skip the check when the outer stems point in
834      different directions. --hwn
835      
836   */
837   bool concaveness1 = false;
838   SCM gap = me->get_grob_property ("concaveness-gap");
839   if (gh_number_p (gap)
840       && Stem::get_direction(stems.top ())
841          == Stem::get_direction(stems[0]))
842     {
843       Real r1 = gh_scm2double (gap);
844       Real dy = Stem::chord_start_y (stems.top ())
845         - Stem::chord_start_y (stems[0]);
846
847       
848       Real slope = dy / (stems.size () - 1);
849       
850       Real y0 = Stem::chord_start_y (stems[0]);
851       for (int i = 1; i < stems.size () - 1; i++)
852         {
853           Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
854           if (c > r1)
855             {
856               concaveness1 = true;
857               break;
858             }
859         }
860     }
861
862     
863   /* Concaveness #2: Sum distances of inner noteheads that fall
864      outside the interval of the two outer noteheads.
865
866      We only do this for beams where first and last stem have the same
867      direction. --hwn.
868
869
870      Note that "convex" stems compensate for "concave" stems.
871      (is that intentional?) --hwn.
872   */
873   
874   Real concaveness2 = 0;
875   SCM thresh = me->get_grob_property ("concaveness-threshold");
876   Real r2 = infinity_f;
877   if (!concaveness1 && gh_number_p (thresh)
878       && Stem::get_direction(stems.top ())
879          == Stem::get_direction(stems[0]))
880     {
881       r2 = gh_scm2double (thresh);
882
883       Direction dir = Stem::get_direction(stems.top ());
884       Real concave = 0;
885       Interval iv (Stem::chord_start_y (stems[0]),
886                    Stem::chord_start_y (stems.top ()));
887       
888       if (iv[MAX] < iv[MIN])
889         iv.swap ();
890       
891       for (int i = 1; i < stems.size () - 1; i++)
892         {
893           Real f = Stem::chord_start_y (stems[i]);
894           concave += ((f - iv[MAX] ) >? 0) +
895             ((f - iv[MIN] ) <? 0);
896         }
897       concave *= dir;
898       concaveness2 = concave / (stems.size () - 2);
899       
900       /* ugh: this is the a kludge to get
901          input/regression/beam-concave.ly to behave as
902          baerenreiter. */
903
904       /*
905         huh? we're dividing twice (which is not scalable) meaning that
906         the longer the beam, the more unlikely it will be
907         concave. Maybe you would even expect the other way around??
908
909         --hwn.
910         
911        */
912       concaveness2 /= (stems.size () - 2);
913     }
914   
915   /* TODO: some sort of damping iso -> plain horizontal */
916   if (concaveness1 || concaveness2 > r2)
917     {
918       Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
919       Real r = pos.linear_combination (0);
920       me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
921       me->set_grob_property ("least-squares-dy", gh_double2scm (0));
922     }
923
924   return SCM_UNSPECIFIED;
925 }
926
927 /* This neat trick is by Werner Lemberg,
928    damped = tanh (slope)
929    corresponds with some tables in [Wanske] CHECKME */
930 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
931 SCM
932 Beam::slope_damping (SCM smob)
933 {
934   Grob *me = unsmob_grob (smob);
935
936   if (visible_stem_count (me) <= 1)
937     return SCM_UNSPECIFIED;
938
939   SCM s = me->get_grob_property ("damping"); 
940   int damping = gh_scm2int (s);
941
942   if (damping)
943     {
944       Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
945       Real dy = pos.delta ();
946       
947       // ugh -> use commonx
948       Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS)
949         - first_visible_stem (me)->relative_coordinate (0, X_AXIS);
950       Real dydx = dy && dx ? dy/dx : 0;
951       dydx = 0.6 * tanh (dydx) / damping;
952
953       Real damped_dy = dydx * dx;
954       pos[LEFT] += (dy - damped_dy) / 2;
955       pos[RIGHT] -= (dy - damped_dy) / 2;
956       
957       me->set_grob_property ("positions", ly_interval2scm (pos));
958     }
959   return SCM_UNSPECIFIED;
960 }
961
962 /*
963   Calculate the Y position of the stem-end, given the Y-left, Y-right
964   in POS, and for stem S.
965  */
966 Real
967 Beam::calc_stem_y (Grob *me, Grob* s, Interval pos)
968 {
969   int beam_multiplicity = get_multiplicity (me);
970   int stem_multiplicity = (Stem::duration_log (s) - 2) >? 0;
971
972   Real thick = gh_scm2double (me->get_grob_property ("thickness"));
973   Real interbeam = get_interbeam (me);
974
975   // ugh -> use commonx
976   Real x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
977   Real dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
978   Real dy = pos.delta ();
979   Real stem_y = (dy && dx
980                  ? (s->relative_coordinate (0, X_AXIS) - x0) / dx
981                  * dy
982                  : 0) + pos[LEFT];
983
984
985   Direction first_dir = Directional_element_interface::get (first_visible_stem (me));
986   Direction my_dir = Directional_element_interface::get (s);
987
988   if (my_dir != first_dir)
989     {
990       /*
991         WTF is happening here ?
992
993          It looks as if this is some kind of fixup for multiple kneed
994          beams to get a piece of stem at the #.
995          
996
997                 x
998                 |
999          =======|
1000          |======#
1001          |
1002          |
1003         x 
1004
1005         Rules for this kind of stuff are hairy. In any event, the
1006         current stem should look at the multiplicity of its
1007         predecessor.
1008
1009         --hwn.
1010         
1011        */
1012       stem_y += my_dir * (thick / 2 + (beam_multiplicity - 1) * interbeam);
1013
1014       // huh, why not for first visible?
1015
1016       /*
1017         What the heck is happening here?? 
1018        */
1019       Grob *last_visible = last_visible_stem (me);
1020       if (last_visible)
1021         {
1022           if ( Staff_symbol_referencer::staff_symbol_l (s)
1023                != Staff_symbol_referencer::staff_symbol_l (last_visible))
1024             stem_y += Directional_element_interface::get (me)
1025               * (beam_multiplicity - stem_multiplicity) * interbeam;
1026         }
1027       else
1028         programming_error ("No last visible stem");
1029     }
1030
1031   return stem_y;
1032 }
1033
1034 /*
1035   Hmm.  At this time, beam position and slope are determined.  Maybe,
1036   stem directions and length should set to relative to the chord's
1037   position of the beam.  */
1038 void
1039 Beam::set_stem_lengths (Grob *me)
1040 {
1041   Link_array<Item> stems=
1042     Pointer_group_interface__extract_grobs (me, (Item*)0, "stems");
1043
1044   if (stems.size () <= 1)
1045     return;
1046   
1047   Grob *common = me->common_refpoint (stems[0], Y_AXIS);
1048   for (int i=1; i < stems.size (); i++)
1049     if (!Stem::invisible_b (stems[i]))
1050       common = common->common_refpoint (stems[i], Y_AXIS);
1051
1052   Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1053   Real staff_space = Staff_symbol_referencer::staff_space (me);
1054
1055   /*
1056     DOCUMENT THIS.
1057    */
1058 #if 0
1059   Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1060   Direction dir = Directional_element_interface::get (me);
1061   bool ps_testing = to_boolean (ly_symbol2scm ("ps-testing"));
1062 #endif
1063   
1064   for (int i=0; i < stems.size (); i++)
1065     {
1066       Item* s = stems[i];
1067       if (Stem::invisible_b (s))
1068         continue;
1069
1070       Real stem_y = calc_stem_y (me, s, pos);
1071
1072 #if 0
1073       // doesn't play well with dvips
1074       if (ps_testing)
1075         if (Stem::get_direction (s) == dir)
1076           stem_y += Stem::get_direction (s) * thick / 2;
1077 #endif
1078       
1079       /* caution: stem measures in staff-positions */
1080       Real id = me->relative_coordinate (common, Y_AXIS)
1081         - stems[i]->relative_coordinate (common, Y_AXIS);
1082       Stem::set_stemend (s, (stem_y + id) / staff_space * 2);
1083     }
1084 }
1085
1086 void
1087 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1088 {
1089   Link_array<Grob> stems=
1090     Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1091   
1092   Direction d = LEFT;
1093   for (int i=0; i  < stems.size (); i++)
1094     {
1095       do
1096         {
1097           /* Don't overwrite user override (?) */
1098           if (Stem::beam_count (stems[i], d) == -1
1099               /* Don't set beaming for outside of outer stems */
1100               && ! (d == LEFT && i == 0)
1101               && ! (d == RIGHT && i == stems.size () -1))
1102             {
1103               int b = beaming->infos_.elem (i).beams_i_drul_[d];
1104               Stem::set_beaming (stems[i], b, d);
1105             }
1106         }
1107       while (flip (&d) != LEFT);
1108     }
1109 }
1110
1111
1112
1113 /*
1114   beams to go with one stem.
1115
1116   FIXME: clean me up:
1117
1118   The beam should be constructed by one function that knows where the
1119   X and Y points are, and only inspects the stems to obtain
1120   multiplicity and stem directions.
1121   
1122   */
1123 Molecule
1124 Beam::stem_beams (Grob *me, Item *here, Item *next, Item *prev, Real dydx)
1125 {
1126   // ugh -> use commonx
1127   if ((next
1128        && ! (next->relative_coordinate (0, X_AXIS)
1129             > here->relative_coordinate (0, X_AXIS)))
1130       || (prev
1131           && ! (prev->relative_coordinate (0, X_AXIS)
1132                < here->relative_coordinate (0, X_AXIS))))
1133     programming_error ("Beams are not left-to-right");
1134
1135   Real thick = gh_scm2double (me->get_grob_property ("thickness"));
1136   Real bdy = get_interbeam (me);
1137   
1138   Molecule leftbeams;
1139   Molecule rightbeams;
1140
1141   Real nw_f;
1142   if (!Stem::first_head (here))
1143     nw_f = 0;
1144   else
1145     {
1146       int t = Stem::duration_log (here); 
1147
1148       SCM proc = me->get_grob_property ("flag-width-function");
1149       SCM result = gh_call1 (proc, gh_int2scm (t));
1150       nw_f = gh_scm2double (result);
1151     }
1152
1153
1154   /* [Tremolo] beams on whole notes may not have direction set? */
1155   Direction dir = Directional_element_interface::get (here);
1156
1157   /* half beams extending to the left. */
1158   if (prev)
1159     {
1160       int lhalfs= lhalfs = Stem::beam_count (here, LEFT)
1161         - Stem::beam_count (prev, RIGHT);
1162       int lwholebeams= Stem::beam_count (here, LEFT)
1163         <? Stem::beam_count (prev, RIGHT);
1164       
1165       /* Half beam should be one note-width,
1166          but let's make sure two half-beams never touch */
1167
1168       // FIXME: TODO (check) stem width / sloped beams
1169       Real w = here->relative_coordinate (0, X_AXIS)
1170         - prev->relative_coordinate (0, X_AXIS);
1171       Real stem_w = gh_scm2double (prev->get_grob_property ("thickness"))
1172         // URG
1173         * me->paper_l ()->get_var ("linethickness");
1174
1175       w = w/2 <? nw_f;
1176       Molecule a;
1177       if (lhalfs)               // generates warnings if not
1178         a =  Lookup::beam (dydx, w + stem_w, thick);
1179       a.translate (Offset (-w, -w * dydx));
1180       a.translate_axis (-stem_w/2, X_AXIS);
1181       for (int j = 0; j  < lhalfs; j++)
1182         {
1183           Molecule b (a);
1184           b.translate_axis (-dir * bdy * (lwholebeams+j), Y_AXIS);
1185           leftbeams.add_molecule (b);
1186         }
1187     }
1188
1189   if (next)
1190     {
1191       int rhalfs  = Stem::beam_count (here, RIGHT)
1192         - Stem::beam_count (next, LEFT);
1193       int rwholebeams= Stem::beam_count (here, RIGHT)
1194         <? Stem::beam_count (next, LEFT);
1195
1196       Real w = next->relative_coordinate (0, X_AXIS)
1197         - here->relative_coordinate (0, X_AXIS);
1198
1199       Real stem_w = gh_scm2double (next->get_grob_property ("thickness"))
1200         // URG
1201         * me->paper_l ()->get_var ("linethickness");
1202
1203       Molecule a = Lookup::beam (dydx, w + stem_w, thick);
1204       a.translate_axis (- stem_w/2, X_AXIS);
1205       int j = 0;
1206       Real gap_f = 0;
1207       
1208       SCM gap = me->get_grob_property ("gap");
1209       if (gh_number_p (gap))
1210         {
1211           int gap_i = gh_scm2int ((gap));
1212           int nogap = rwholebeams - gap_i;
1213           
1214           for (; j  < nogap; j++)
1215             {
1216               Molecule b (a);
1217               b.translate_axis (-dir  * bdy * j, Y_AXIS);
1218               rightbeams.add_molecule (b);
1219             }
1220           if (Stem::invisible_b (here))
1221             gap_f = nw_f;
1222           else
1223             gap_f = nw_f / 2;
1224           w -= 2 * gap_f;
1225           a = Lookup::beam (dydx, w + stem_w, thick);
1226         }
1227
1228       for (; j  < rwholebeams; j++)
1229         {
1230           Molecule b (a);
1231           Real tx = 0;
1232           if (Stem::invisible_b (here))
1233             // ugh, see chord-tremolo.ly
1234             tx = (-dir + 1) / 2 * nw_f * 1.5 + gap_f/4;
1235           else
1236             tx = gap_f;
1237           b.translate (Offset (tx, -dir * bdy * j));
1238           rightbeams.add_molecule (b);
1239         }
1240
1241       w = w/2 <? nw_f;
1242       if (rhalfs)
1243         a = Lookup::beam (dydx, w, thick);
1244
1245       for (; j  < rwholebeams + rhalfs; j++)
1246         {
1247           Molecule b (a);
1248           b.translate_axis (- dir * bdy * j, Y_AXIS);
1249           rightbeams.add_molecule (b);
1250         }
1251
1252     }
1253   leftbeams.add_molecule (rightbeams);
1254
1255   return leftbeams;
1256 }
1257
1258
1259 MAKE_SCHEME_CALLBACK (Beam, brew_molecule, 1);
1260 SCM
1261 Beam::brew_molecule (SCM smob)
1262 {
1263   Grob *me =unsmob_grob (smob);
1264
1265   Molecule mol;
1266   if (!gh_pair_p (me->get_grob_property ("stems")))
1267     return SCM_EOL;
1268   Real x0, dx;
1269   Link_array<Item>stems = 
1270     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");  
1271   if (visible_stem_count (me))
1272     {
1273       // ugh -> use commonx
1274       x0 = first_visible_stem (me)->relative_coordinate (0, X_AXIS);
1275       dx = last_visible_stem (me)->relative_coordinate (0, X_AXIS) - x0;
1276     }
1277   else
1278     {
1279       x0 = stems[0]->relative_coordinate (0, X_AXIS);
1280       dx = stems.top ()->relative_coordinate (0, X_AXIS) - x0;
1281     }
1282
1283   SCM posns = me->get_grob_property ("positions");
1284   Interval pos;
1285   if (!ly_number_pair_p (posns))
1286     {
1287       programming_error ("No beam posns");
1288       pos = Interval (0,0);
1289     }
1290   else
1291     pos= ly_scm2interval (posns);
1292   Real dy = pos.delta ();
1293   Real dydx = dy && dx ? dy/dx : 0;
1294
1295
1296   Direction firstdir = Directional_element_interface::get ( Beam::first_visible_stem (me) );
1297   
1298   for (int i=0; i < stems.size (); i++)
1299     {
1300       Item *item = stems[i];
1301       Item *prev = (i > 0)? stems[i-1] : 0;
1302       Item *next = (i < stems.size ()-1) ? stems[i+1] :0;
1303
1304
1305       
1306       Molecule sb = stem_beams (me, item, next, prev, dydx);
1307       Real x = item->relative_coordinate (0, X_AXIS) - x0;
1308       sb.translate (Offset (x, x * dydx + pos[LEFT]));
1309
1310       Direction sd = Stem::get_direction (item);      
1311       mol.add_molecule (sb);
1312     }
1313   
1314   mol.translate_axis (x0 
1315                       - dynamic_cast<Spanner*> (me)
1316                       ->get_bound (LEFT)->relative_coordinate (0, X_AXIS),
1317                       X_AXIS);
1318
1319 #if (DEBUG_QUANTING)
1320     {
1321       /*
1322         This code prints the demerits for each beam. Perhaps this
1323         should be switchable for those who want to twiddle with the
1324         parameters.
1325       */
1326       String str;
1327       if (1)
1328         {
1329           str += to_str (gh_scm2int (me->get_grob_property ("best-idx")));
1330           str += ":";
1331         }
1332       str += to_str (gh_scm2double (me->get_grob_property ("quant-score")),
1333                      "%.2f");
1334
1335       SCM properties = Font_interface::font_alist_chain (me);
1336
1337       
1338       Molecule tm = Text_item::text2molecule (me, ly_str02scm (str.ch_C ()), properties);
1339       mol.add_at_edge (Y_AXIS, UP, tm, 5.0);
1340     }
1341 #endif
1342     
1343   return mol.smobbed_copy ();
1344 }
1345
1346 int
1347 Beam::forced_stem_count (Grob *me) 
1348 {
1349   Link_array<Item>stems = 
1350     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1351   int f = 0;
1352   for (int i=0; i < stems.size (); i++)
1353     {
1354       Item *s = stems[i];
1355
1356       if (Stem::invisible_b (s))
1357         continue;
1358
1359       if (((int)Stem::chord_start_y (s)) 
1360         && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1361         f++;
1362     }
1363   return f;
1364 }
1365
1366
1367
1368
1369 int
1370 Beam::visible_stem_count (Grob *me) 
1371 {
1372   Link_array<Item>stems = 
1373     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1374   int c = 0;
1375   for (int i = stems.size (); i--;)
1376     {
1377       if (!Stem::invisible_b (stems[i]))
1378         c++;
1379     }
1380   return c;
1381 }
1382
1383 Item*
1384 Beam::first_visible_stem (Grob *me) 
1385 {
1386   Link_array<Item>stems = 
1387     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1388   
1389   for (int i = 0; i < stems.size (); i++)
1390     {
1391       if (!Stem::invisible_b (stems[i]))
1392         return stems[i];
1393     }
1394   return 0;
1395 }
1396
1397 Item*
1398 Beam::last_visible_stem (Grob *me) 
1399 {
1400   Link_array<Item>stems = 
1401     Pointer_group_interface__extract_grobs (me, (Item*) 0, "stems");
1402   for (int i = stems.size (); i--;)
1403     {
1404       if (!Stem::invisible_b (stems[i]))
1405         return stems[i];
1406     }
1407   return 0;
1408 }
1409
1410
1411 /*
1412   [TODO]
1413   
1414   handle rest under beam (do_post: beams are calculated now)
1415   what about combination of collisions and rest under beam.
1416
1417   Should lookup
1418     
1419     rest -> stem -> beam -> interpolate_y_position ()
1420 */
1421 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1422 SCM
1423 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1424 {
1425   Grob *rest = unsmob_grob (element_smob);
1426   Axis a = (Axis) gh_scm2int (axis);
1427   
1428   assert (a == Y_AXIS);
1429
1430   Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1431   Grob *stem = st;
1432   if (!stem)
1433     return gh_double2scm (0.0);
1434   Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1435   if (!beam
1436       || !Beam::has_interface (beam)
1437       || !Beam::visible_stem_count (beam))
1438     return gh_double2scm (0.0);
1439
1440   // make callback for rest from this.
1441   // todo: make sure this calced already.
1442
1443   //  Interval pos = ly_scm2interval (beam->get_grob_property ("positions"));
1444   Interval pos (0, 0);
1445   SCM s = beam->get_grob_property ("positions");
1446   if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1447     pos = ly_scm2interval (s);
1448
1449   Real dy = pos.delta ();
1450   // ugh -> use commonx
1451   Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1452   Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1453   Real dydx = dy && dx ? dy/dx : 0;
1454   
1455   Direction d = Stem::get_direction (stem);
1456   Real beamy = (stem->relative_coordinate (0, X_AXIS) - x0) * dydx + pos[LEFT];
1457
1458   Real staff_space = Staff_symbol_referencer::staff_space (rest);
1459
1460   
1461   Real rest_dim = rest->extent (rest, Y_AXIS)[d]*2.0 / staff_space; // refp??
1462
1463   Real minimum_dist
1464     = gh_scm2double (rest->get_grob_property ("minimum-beam-collision-distance"));
1465   Real dist =
1466     minimum_dist +  -d  * (beamy - rest_dim) >? 0;
1467
1468   int stafflines = Staff_symbol_referencer::line_count (rest);
1469
1470   // move discretely by half spaces.
1471   int discrete_dist = int (ceil (dist));
1472
1473   // move by whole spaces inside the staff.
1474   if (discrete_dist < stafflines+1)
1475     discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
1476
1477   return gh_double2scm (-d *  discrete_dist);
1478 }
1479
1480
1481
1482
1483 ADD_INTERFACE (Beam, "beam-interface",
1484   "A beam.
1485
1486 #'thickness= weight of beams, in staffspace
1487
1488
1489 We take the least squares line through the ideal-length stems, and
1490 then damp that using
1491
1492         damped = tanh (slope)
1493
1494 this gives an unquantized left and right position for the beam end.
1495 Then we take all combinations of quantings near these left and right
1496 positions, and give them a score (according to how close they are to
1497 the ideal slope, how close the result is to the ideal stems, etc.). We
1498 take the best scoring combination.
1499
1500 ",
1501   "position-callbacks concaveness-gap concaveness-threshold dir-function quant-score auto-knee-gap gap chord-tremolo beamed-stem-shorten shorten least-squares-dy damping flag-width-function neutral-direction positions space-function thickness");
1502
1503