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