]> git.donarmstrong.com Git - lilypond.git/blob - lily/beam.cc
*** empty log message ***
[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_var ("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_var ("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       
485       Molecule tm = Text_item::text2molecule (me, scm_makfrom0str (str.to_str0 ()), properties);
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   Real y =0;  
836   Real dydx = 0;
837   Real dy = 0;
838   
839   if (!ideal.delta ())
840     {
841       Interval chord (Stem::chord_start_y (first_visible_stem (me)),
842                       Stem::chord_start_y (last_visible_stem (me)));
843
844       /* Simple beams (2 stems) on middle line should be allowed to be
845          slightly sloped.
846          
847          However, if both stems reach middle line,
848          ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0.
849
850          For that case, we apply artificial slope */
851       if (!ideal[LEFT] && chord.delta () && count == 2)
852         {
853           /* FIXME. -> UP */
854           Direction d = (Direction) (sign (chord.delta ()) * UP);
855           pos[d] = gh_scm2double (me->get_grob_property ("thickness")) / 2;
856           pos[-d] = - pos[d];
857         }
858       else
859         {
860           pos = ideal;
861         }
862
863       y = pos[LEFT];
864       dy = pos[RIGHT]- y;
865       dydx = dy/dx;
866     }
867   else
868     {
869       Array<Offset> ideals;
870       for (int i=0; i < stems.size (); i++)
871         {
872           Grob* s = stems[i];
873           if (Stem::invisible_b (s))
874             continue;
875           ideals.push (Offset (x_posns[i],
876                                Stem::get_stem_info (s).ideal_y_
877                                + s->relative_coordinate (commony, Y_AXIS)
878                                - my_y));
879         }
880       minimise_least_squares (&dydx, &y, ideals);
881
882       dy = dydx * dx;
883       me->set_grob_property ("least-squares-dy", gh_double2scm (dy));
884       pos = Interval (y, (y+dy));
885     }
886
887   me->set_grob_property ("positions", ly_interval2scm (pos));
888  
889   return SCM_UNSPECIFIED;
890 }
891
892
893 /*
894   We can't combine with previous function, since check concave and
895   slope damping comes first.
896
897 TODO: we should use the concaveness to control the amount of damping
898 applied.
899   
900  */
901 MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 1);
902 SCM
903 Beam::shift_region_to_valid (SCM grob)
904 {
905   Grob *me = unsmob_grob (grob);
906   /*
907     Code dup.
908    */
909   Array<Real> x_posns ;
910   Link_array<Grob> stems=
911     Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
912   Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
913   Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);  
914
915   Grob *fvs = first_visible_stem (me);
916
917   if (!fvs)
918     return SCM_UNSPECIFIED;
919     
920   Real x0 =fvs->relative_coordinate (commonx, X_AXIS);
921   for (int i=0; i < stems.size (); i++)
922     {
923       Grob* s = stems[i];
924
925       Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
926       x_posns.push (x);
927     }
928
929   Grob *lvs = last_visible_stem (me);
930   if (!lvs)
931     return SCM_UNSPECIFIED;
932   
933   Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
934
935   Interval pos = ly_scm2interval ( me->get_grob_property ("positions"));
936   Real dy = pos.delta();
937   Real y = pos[LEFT];
938   Real dydx =dy/dx;
939
940   
941   /*
942     Shift the positions so that we have a chance of finding good
943     quants (i.e. no short stem failures.)
944    */
945   Interval feasible_left_point;
946   feasible_left_point.set_full ();
947   for (int i=0; i < stems.size (); i++)
948     {
949       Grob* s = stems[i];
950       if (Stem::invisible_b (s))
951         continue;
952
953       Direction d = Stem::get_direction (s);
954
955       Real left_y =
956         Stem::get_stem_info (s).shortest_y_
957         - dydx * x_posns [i];
958
959       /*
960         left_y is now relative to the stem S. We want relative to
961         ourselves, so translate:
962        */
963       left_y += 
964         + s->relative_coordinate (commony, Y_AXIS)
965         - me->relative_coordinate (commony, Y_AXIS);
966
967       Interval flp ;
968       flp.set_full ();
969       flp[-d] = left_y;
970
971       feasible_left_point.intersect (flp);
972     }
973       
974   if (feasible_left_point.empty_b())
975     {
976       warning (_("Not sure that we can find a nice beam slope (no viable initial configuration found)."));
977     }
978   else if (!feasible_left_point.elem_b(y))
979     {
980       if (isinf (feasible_left_point[DOWN]))
981         y = feasible_left_point[UP] - REGION_SIZE;
982       else if (isinf (feasible_left_point[UP]))
983         y = feasible_left_point[DOWN]+ REGION_SIZE;
984       else
985         y = feasible_left_point.center ();
986     }
987   pos = Interval (y, (y+dy));
988   me->set_grob_property ("positions", ly_interval2scm (pos));
989   return SCM_UNSPECIFIED;
990 }
991
992
993 MAKE_SCHEME_CALLBACK (Beam, check_concave, 1);
994 SCM
995 Beam::check_concave (SCM smob)
996 {
997   Grob *me = unsmob_grob (smob);
998
999   Link_array<Grob> stems = 
1000     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1001
1002   for (int i = 0; i < stems.size ();)
1003     {
1004       if (Stem::invisible_b (stems[i]))
1005         stems.del (i);
1006       else
1007         i++;
1008     }
1009   
1010   if (stems.size () < 3)
1011     return SCM_UNSPECIFIED;
1012
1013
1014   /* Concaveness #1: If distance of an inner notehead to line between
1015      two outer noteheads is bigger than CONCAVENESS-GAP (2.0ss),
1016      beam is concave (Heinz Stolba).
1017
1018      In the case of knees, the line connecting outer heads is often
1019      not related to the beam slope (it may even go in the other
1020      direction). Skip the check when the outer stems point in
1021      different directions. --hwn
1022      
1023   */
1024   bool concaveness1 = false;
1025   SCM gap = me->get_grob_property ("concaveness-gap");
1026   if (gh_number_p (gap)
1027       && Stem::get_direction(stems.top ())
1028          == Stem::get_direction(stems[0]))
1029     {
1030       Real r1 = gh_scm2double (gap);
1031       Real dy = Stem::chord_start_y (stems.top ())
1032         - Stem::chord_start_y (stems[0]);
1033
1034       
1035       Real slope = dy / (stems.size () - 1);
1036       
1037       Real y0 = Stem::chord_start_y (stems[0]);
1038       for (int i = 1; i < stems.size () - 1; i++)
1039         {
1040           Real c = (Stem::chord_start_y (stems[i]) - y0) - i * slope;
1041           if (c > r1)
1042             {
1043               concaveness1 = true;
1044               break;
1045             }
1046         }
1047     }
1048
1049     
1050   /* Concaveness #2: Sum distances of inner noteheads that fall
1051      outside the interval of the two outer noteheads.
1052
1053      We only do this for beams where first and last stem have the same
1054      direction. --hwn.
1055
1056
1057      Note that "convex" stems compensate for "concave" stems.
1058      (is that intentional?) --hwn.
1059   */
1060   
1061   Real concaveness2 = 0;
1062   SCM thresh = me->get_grob_property ("concaveness-threshold");
1063   Real r2 = infinity_f;
1064   if (!concaveness1 && gh_number_p (thresh)
1065       && Stem::get_direction(stems.top ())
1066          == Stem::get_direction(stems[0]))
1067     {
1068       r2 = gh_scm2double (thresh);
1069
1070       Direction dir = Stem::get_direction(stems.top ());
1071       Real concave = 0;
1072       Interval iv (Stem::chord_start_y (stems[0]),
1073                    Stem::chord_start_y (stems.top ()));
1074       
1075       if (iv[MAX] < iv[MIN])
1076         iv.swap ();
1077       
1078       for (int i = 1; i < stems.size () - 1; i++)
1079         {
1080           Real f = Stem::chord_start_y (stems[i]);
1081           concave += ((f - iv[MAX] ) >? 0) +
1082             ((f - iv[MIN] ) <? 0);
1083         }
1084       concave *= dir;
1085       concaveness2 = concave / (stems.size () - 2);
1086       
1087       /*
1088
1089       ugh: this is the a kludge to get
1090       input/regression/beam-concave.ly to behave as
1091       baerenreiter.
1092
1093       */
1094
1095       /*
1096         huh? we're dividing twice (which is not scalable) meaning that
1097         the longer the beam, the more unlikely it will be
1098         concave. Maybe you would even expect the other way around??
1099
1100         --hwn.
1101         
1102        */
1103       concaveness2 /= (stems.size () - 2);
1104     }
1105   
1106   /* TODO: some sort of damping iso -> plain horizontal */
1107   if (concaveness1 || concaveness2 > r2)
1108     {
1109       Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1110       Real r = pos.linear_combination (0);
1111       me->set_grob_property ("positions", ly_interval2scm (Interval (r, r)));
1112       me->set_grob_property ("least-squares-dy", gh_double2scm (0));
1113     }
1114
1115   return SCM_UNSPECIFIED;
1116 }
1117
1118 /* This neat trick is by Werner Lemberg,
1119    damped = tanh (slope)
1120    corresponds with some tables in [Wanske] CHECKME */
1121 MAKE_SCHEME_CALLBACK (Beam, slope_damping, 1);
1122 SCM
1123 Beam::slope_damping (SCM smob)
1124 {
1125   Grob *me = unsmob_grob (smob);
1126
1127   if (visible_stem_count (me) <= 1)
1128     return SCM_UNSPECIFIED;
1129
1130   SCM s = me->get_grob_property ("damping"); 
1131   int damping = gh_scm2int (s);
1132
1133   if (damping)
1134     {
1135       Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1136       Real dy = pos.delta ();
1137
1138       Grob *fvs  = first_visible_stem (me);
1139       Grob *lvs  = last_visible_stem (me);
1140
1141       Grob *commonx = fvs->common_refpoint (lvs, X_AXIS);
1142
1143
1144       Real dx = last_visible_stem (me)->relative_coordinate (commonx, X_AXIS)
1145         - first_visible_stem (me)->relative_coordinate (commonx, X_AXIS);
1146       Real dydx = dy && dx ? dy/dx : 0;
1147       dydx = 0.6 * tanh (dydx) / damping;
1148
1149       Real damped_dy = dydx * dx;
1150       pos[LEFT] += (dy - damped_dy) / 2;
1151       pos[RIGHT] -= (dy - damped_dy) / 2;
1152       
1153       me->set_grob_property ("positions", ly_interval2scm (pos));
1154     }
1155   return SCM_UNSPECIFIED;
1156 }
1157
1158 /*
1159   Report slice containing the numbers that are both in (car BEAMING)
1160   and (cdr BEAMING)
1161  */
1162 Slice
1163 where_are_the_whole_beams(SCM beaming)
1164 {
1165   Slice l; 
1166   
1167   for( SCM s = gh_car (beaming); gh_pair_p (s) ; s = gh_cdr (s))
1168     {
1169       if (scm_memq (gh_car (s), gh_cdr (beaming)) != SCM_BOOL_F)
1170         
1171         l.add_point (gh_scm2int (gh_car (s)));
1172     }
1173
1174   return l;
1175 }
1176
1177 /* Return the Y position of the stem-end, given the Y-left, Y-right
1178    in POS for stem S.  This Y position is relative to S. */
1179 Real
1180 Beam::calc_stem_y (Grob *me, Grob* s, Grob ** common,
1181                    Real xl, Real xr,
1182                    Interval pos, bool french) 
1183 {
1184   Real beam_translation = get_beam_translation (me);
1185
1186     
1187   Real r = s->relative_coordinate (common[X_AXIS], X_AXIS) - xl;
1188   Real dy = pos.delta ();
1189   Real dx = xr - xl;
1190   Real stem_y_beam0 = (dy && dx
1191                        ? r / dx
1192                        * dy
1193                        : 0) + pos[LEFT];
1194   
1195   Direction my_dir = Directional_element_interface::get (s);
1196   SCM beaming = s->get_grob_property ("beaming");
1197  
1198   Real stem_y = stem_y_beam0;
1199   if (french)
1200     {
1201       Slice bm = where_are_the_whole_beams (beaming);
1202       if (!bm.empty_b())
1203         stem_y += beam_translation * bm[-my_dir];
1204     }
1205   else
1206     {
1207       Slice bm = Stem::beam_multiplicity(s);
1208       if (!bm.empty_b())
1209         stem_y +=bm[my_dir] * beam_translation;
1210     }
1211   
1212   Real id = me->relative_coordinate (common[Y_AXIS], Y_AXIS)
1213     - s->relative_coordinate (common[Y_AXIS], Y_AXIS);
1214   
1215   return stem_y + id;
1216 }
1217
1218 /*
1219   Hmm.  At this time, beam position and slope are determined.  Maybe,
1220   stem directions and length should set to relative to the chord's
1221   position of the beam.  */
1222 void
1223 Beam::set_stem_lengths (Grob *me)
1224 {
1225   Link_array<Grob> stems=
1226     Pointer_group_interface__extract_grobs (me, (Grob*)0, "stems");
1227
1228   if (stems.size () <= 1)
1229     return;
1230   
1231   Grob *common[2];
1232   for (int a = 2; a--;)
1233     common[a] = common_refpoint_of_array (stems, me, Axis(a));
1234   
1235   Interval pos = ly_scm2interval (me->get_grob_property ("positions"));
1236   Real staff_space = Staff_symbol_referencer::staff_space (me);
1237
1238   bool gap = false;
1239   Real thick =0.0;
1240   if (gh_number_p (me->get_grob_property ("gap"))
1241       &&gh_scm2double (me->get_grob_property ("gap")))
1242   {
1243     gap = true;
1244     thick = get_thickness(me);
1245   }
1246       
1247   // ugh -> use commonx
1248   Grob * fvs = first_visible_stem (me);
1249   Grob *lvs = last_visible_stem (me);
1250     
1251   Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1252   Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
1253   
1254   for (int i=0; i < stems.size (); i++)
1255     {
1256       Grob* s = stems[i];
1257       if (Stem::invisible_b (s))
1258         continue;
1259
1260       
1261       bool french = to_boolean (s->get_grob_property ("french-beaming"));
1262       Real stem_y = calc_stem_y (me, s, common,
1263                                  xl, xr,
1264                                  pos, french && i > 0&& (i < stems.size  () -1));
1265
1266       /*
1267         Make the stems go up to the end of the beam. This doesn't matter
1268         for normal beams, but for tremolo beams it looks silly otherwise.
1269        */
1270       if (gap)
1271         stem_y += thick * 0.5 * Directional_element_interface::get(s);
1272       
1273       Stem::set_stemend (s, 2* stem_y / staff_space);
1274     }
1275 }
1276
1277 void
1278 Beam::set_beaming (Grob *me, Beaming_info_list *beaming)
1279 {
1280   Link_array<Grob> stems=
1281     Pointer_group_interface__extract_grobs (me, (Grob *)0, "stems");
1282   
1283   Direction d = LEFT;
1284   for (int i=0; i  < stems.size (); i++)
1285     {
1286       /*
1287         Don't overwrite user settings.
1288        */
1289       
1290       do
1291         {
1292           /* Don't set beaming for outside of outer stems */      
1293           if ((d == LEFT && i == 0)
1294               ||(d == RIGHT && i == stems.size () -1))
1295             continue;
1296
1297           Grob *st =  stems[i];
1298           SCM beaming_prop = st->get_grob_property ("beaming");
1299           if (beaming_prop == SCM_EOL ||
1300               index_get_cell (beaming_prop, d) == SCM_EOL)
1301             {
1302               int b = beaming->infos_.elem (i).beams_i_drul_[d];
1303               if (i>0
1304                   && i < stems.size() -1
1305                   && Stem::invisible_b (st))
1306                 b = b <? beaming->infos_.elem(i).beams_i_drul_[-d];
1307               
1308               Stem::set_beaming (st, b, d);
1309             }
1310         }
1311       while (flip (&d) != LEFT);
1312     }
1313 }
1314
1315 int
1316 Beam::forced_stem_count (Grob *me) 
1317 {
1318   Link_array<Grob>stems = 
1319     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1320   int f = 0;
1321   for (int i=0; i < stems.size (); i++)
1322     {
1323       Grob *s = stems[i];
1324
1325       if (Stem::invisible_b (s))
1326         continue;
1327
1328       /* I can imagine counting those boundaries as a half forced stem,
1329          but let's count them full for now. */
1330       if (abs (Stem::chord_start_y (s)) > 0.1
1331         && (Stem::get_direction (s) != Stem::get_default_dir (s)))
1332         f++;
1333     }
1334   return f;
1335 }
1336
1337
1338
1339
1340 int
1341 Beam::visible_stem_count (Grob *me) 
1342 {
1343   Link_array<Grob>stems = 
1344     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1345   int c = 0;
1346   for (int i = stems.size (); i--;)
1347     {
1348       if (!Stem::invisible_b (stems[i]))
1349         c++;
1350     }
1351   return c;
1352 }
1353
1354 Grob*
1355 Beam::first_visible_stem (Grob *me) 
1356 {
1357   Link_array<Grob>stems = 
1358     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1359   
1360   for (int i = 0; i < stems.size (); i++)
1361     {
1362       if (!Stem::invisible_b (stems[i]))
1363         return stems[i];
1364     }
1365   return 0;
1366 }
1367
1368 Grob*
1369 Beam::last_visible_stem (Grob *me) 
1370 {
1371   Link_array<Grob>stems = 
1372     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1373   for (int i = stems.size (); i--;)
1374     {
1375       if (!Stem::invisible_b (stems[i]))
1376         return stems[i];
1377     }
1378   return 0;
1379 }
1380
1381
1382 /*
1383   [TODO]
1384   
1385   handle rest under beam (do_post: beams are calculated now)
1386   what about combination of collisions and rest under beam.
1387
1388   Should lookup
1389     
1390     rest -> stem -> beam -> interpolate_y_position ()
1391 */
1392 MAKE_SCHEME_CALLBACK (Beam, rest_collision_callback, 2);
1393 SCM
1394 Beam::rest_collision_callback (SCM element_smob, SCM axis)
1395 {
1396   Grob *rest = unsmob_grob (element_smob);
1397   Axis a = (Axis) gh_scm2int (axis);
1398   
1399   assert (a == Y_AXIS);
1400
1401   Grob *st = unsmob_grob (rest->get_grob_property ("stem"));
1402   Grob *stem = st;
1403   if (!stem)
1404     return gh_double2scm (0.0);
1405   Grob *beam = unsmob_grob (stem->get_grob_property ("beam"));
1406   if (!beam
1407       || !Beam::has_interface (beam)
1408       || !Beam::visible_stem_count (beam))
1409     return gh_double2scm (0.0);
1410
1411   Interval pos (0, 0);
1412   SCM s = beam->get_grob_property ("positions");
1413   if (gh_pair_p (s) && gh_number_p (ly_car (s)))
1414     pos = ly_scm2interval (s);
1415
1416   Real dy = pos.delta ();
1417   // ugh -> use commonx
1418   Real x0 = first_visible_stem (beam)->relative_coordinate (0, X_AXIS);
1419   Real dx = last_visible_stem (beam)->relative_coordinate (0, X_AXIS) - x0;
1420   Real dydx = dy && dx ? dy/dx : 0;
1421   
1422   Direction d = Stem::get_direction (stem);
1423   Real stem_y = (pos[LEFT]
1424                  + (stem->relative_coordinate (0, X_AXIS) - x0) * dydx)
1425     * d;
1426   
1427   Real beam_translation = get_beam_translation (beam);
1428   Real beam_thickness = gh_scm2double (beam->get_grob_property ("thickness"));
1429   int beam_count = get_direction_beam_count (beam, d);
1430   Real height_of_my_beams = beam_thickness
1431     + (beam_count - 1) * beam_translation;
1432   Real beam_y = stem_y - height_of_my_beams + beam_thickness / 2.0;
1433
1434   Real staff_space = Staff_symbol_referencer::staff_space (rest);
1435
1436   /* Better calculate relative-distance directly, rather than using
1437      rest_dim? */
1438   Grob *common_x = rest->common_refpoint (beam, Y_AXIS);
1439   Real rest_dim = rest->extent (common_x, Y_AXIS)[d] / staff_space * d;
1440
1441   Real minimum_distance = gh_scm2double
1442     (rest->get_grob_property ("minimum-beam-collision-distance"));
1443
1444   Real distance = beam_y - rest_dim;
1445   Real shift = 0;
1446   if (distance < 0)
1447     shift = minimum_distance - distance;
1448   else if (minimum_distance > distance)
1449     shift = minimum_distance - distance;
1450       
1451   int stafflines = Staff_symbol_referencer::line_count (rest);
1452
1453   /* Always move discretely by half spaces */
1454   Real discrete_shift = ceil (shift * 2.0) / 2.0;
1455
1456   /* Inside staff, move by whole spaces*/
1457   if ((rest->extent (common_x, Y_AXIS)[d] + discrete_shift) * d
1458       < stafflines / 2.0
1459       ||(rest->extent (common_x, Y_AXIS)[-d] + discrete_shift) * -d
1460       < stafflines / 2.0)
1461     discrete_shift = ceil (discrete_shift);
1462
1463   return gh_double2scm (-d * discrete_shift);
1464 }
1465
1466 bool
1467 Beam::knee_b (Grob* me)
1468 {
1469   SCM k = me->get_grob_property ("knee");
1470   if (gh_boolean_p (k))
1471     return gh_scm2bool (k);
1472
1473   bool knee = false;
1474   int d = 0;
1475   for (SCM s = me->get_grob_property ("stems"); gh_pair_p (s); s = ly_cdr (s))
1476     {
1477       Direction dir = Directional_element_interface::get
1478         (unsmob_grob (ly_car (s)));
1479       if (d && d != dir)
1480         {
1481           knee = true;
1482           break;
1483         }
1484       d = dir;
1485     }
1486   
1487   me->set_grob_property ("knee", gh_bool2scm (knee));
1488
1489   return knee;
1490 }
1491
1492 int
1493 Beam::get_direction_beam_count (Grob *me, Direction d )
1494 {
1495   Link_array<Grob>stems = 
1496     Pointer_group_interface__extract_grobs (me, (Grob*) 0, "stems");
1497   int bc = 0;
1498   
1499   for (int i = stems.size (); i--;)
1500     {
1501       /*
1502         Should we take invisible stems into account?
1503        */
1504       if (Stem::get_direction (stems[i]) == d)
1505         bc = bc >? (Stem::beam_multiplicity (stems[i]).length () + 1);
1506     }
1507
1508   return bc;
1509 }
1510
1511
1512 ADD_INTERFACE (Beam, "beam-interface",
1513   "A beam. \n\n"
1514 " "
1515 "#'thickness= weight of beams, in staffspace "
1516 " "
1517 " "
1518 "We take the least squares line through the ideal-length stems, and "
1519 "then damp that using "
1520 " \n"
1521 "       damped = tanh (slope) \n"
1522 " \n"
1523 "this gives an unquantized left and right position for the beam end. "
1524 "Then we take all combinations of quantings near these left and right "
1525 "positions, and give them a score (according to how close they are to "
1526 "the ideal slope, how close the result is to the ideal stems, etc.). We "
1527 "take the best scoring combination. "
1528 ,
1529   "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");
1530
1531