2 spring-spacer.cc -- implement Spring_spacer
4 source file of the GNU LilyPond music typesetter
6 (c) 1996,1997 Han-Wen Nienhuys <hanwen@stack.nl>
12 #include "spring-spacer.hh"
16 #include "unionfind.hh"
17 #include "idealspacing.hh"
18 #include "pointer.tcc"
19 #include "score-column.hh"
20 #include "paper-def.hh"
22 #include "minterval.hh"
25 Spring_spacer::default_solution()const
27 return try_initial_solution() ;
31 Spring_spacer::scol_l(int i)
33 return (Score_column*)cols[i].pcol_l_;
36 const Real COLFUDGE=1e-3;
37 template class P<Real>; // ugh.
40 Spring_spacer::contains(PCol const *w)
42 for (int i=0; i< cols.size(); i++)
43 if (cols[i].pcol_l_ == w)
50 Spring_spacer::OK() const
53 for (int i = 1; i < cols.size(); i++)
54 assert(cols[i].rank_i_ > cols[i-1].rank_i_);
55 for (int i = 1; i < loose_col_arr_.size(); i++)
56 assert(loose_col_arr_[i].rank_i_ > loose_col_arr_[i-1].rank_i_);
61 Make sure no unconnected columns happen.
64 Spring_spacer::handle_loose_cols()
66 Union_find connected(cols.size());
68 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++){
69 connected.connect(i->left_i_,i->right_i_);
71 for (int i = 0; i < cols.size(); i++)
74 for (int i=1; i < fixed.size(); i++)
75 connected.connect(fixed[i-1], fixed[i]);
77 for (int i = cols.size(); i--; ) {
78 if (! connected.equiv(fixed[0], i)) {
79 warning("unconnected column: " + String(i));
88 Guess a stupid position for loose columns. Put loose columns at
89 regular distances from enclosing calced columns
92 Spring_spacer::position_loose_cols(Vector &sol_vec)const
94 if (!loose_col_arr_.size())
96 assert(sol_vec.dim());
97 Array<bool> fix_b_arr;
98 fix_b_arr.set_size(cols.size() + loose_col_arr_.size());
99 Real utter_right_f=-INFTY_f;
100 Real utter_left_f =INFTY_f;
101 for (int i=0; i < loose_col_arr_.size(); i++) {
102 fix_b_arr[loose_col_arr_[i].rank_i_] = false;
104 for (int i=0; i < cols.size(); i++) {
105 int r= cols[i].rank_i_;
107 utter_right_f = utter_right_f >? sol_vec(i);
108 utter_left_f = utter_left_f <? sol_vec(i);
110 Vector v(fix_b_arr.size());
113 for (int i=0; i < v.dim(); i++) {
115 assert(cols[j].rank_i_ == i);
119 (j>0) ?sol_vec(j-1) : utter_left_f;
121 (j < sol_vec.dim()) ? sol_vec(j) : utter_right_f;
122 int left_rank = (j>0) ? cols[j-1].rank_i_ : 0;
123 int right_rank = (j<sol_vec.dim()) ? cols[j].rank_i_ : sol_vec.dim();
125 int d_r = right_rank - left_rank;
126 Colinfo loose=loose_col_arr_[k++];
127 int r = loose.rank_i_ ;
128 assert(r > left_rank && r < right_rank);
130 v(i) = (r - left_rank)*left_pos_f/ d_r +
131 (right_rank - r) *right_pos_f /d_r;
138 Spring_spacer::check_constraints(Vector v) const
141 assert(dim == cols.size());
143 for (int i=0; i < dim; i++) {
145 if (cols[i].fixed()&&
146 abs(cols[i].fixed_position() - v(i)) > COLFUDGE)
152 Real mindist=cols[i-1].minright()
156 Real dif =v(i) - v(i-1)- mindist;
157 bool b = (dif > - COLFUDGE);
168 Spring_spacer::check_feasible() const
170 Vector sol(try_initial_solution());
171 return check_constraints(sol);
174 /// generate a solution which obeys the min distances and fixed positions
176 Spring_spacer::try_initial_solution() const
180 for (int i=0; i < dim; i++) {
181 if (cols[i].fixed()) {
182 initsol(i)=cols[i].fixed_position();
185 Real r =initsol(i-1) + cols[i-1].minright();
186 if (initsol(i) < r ) {
187 warning("overriding fixed position");
193 Real mindist=cols[i-1].minright()
196 warning("Excentric column");
197 initsol(i)=initsol(i-1)+mindist;
207 Spring_spacer::find_initial_solution() const
209 Vector v(try_initial_solution());
210 assert(check_constraints(v));
214 // generate the matrices
216 Spring_spacer::make_matrices(Matrix &quad, Vector &lin, Real &c) const
222 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++) {
226 quad(r,r) += i->hooke_f_;
227 quad(r,l) -= i->hooke_f_;
228 quad(l,r) -= i->hooke_f_;
229 quad(l,l) += i->hooke_f_;
231 lin(r) -= i->space_f_*i->hooke_f_;
232 lin(l) += i->space_f_*i->hooke_f_;
234 c += sqr(i->space_f_);
238 // put the constraints into the LP problem
240 Spring_spacer::make_constraints(Mixed_qp& lp) const
243 for (int j=0; j < dim; j++) {
246 lp.add_fixed_var(j,c.fixed_position());
253 lp.add_inequality_cons(c1, cols[j-1].minright() +
260 Spring_spacer::solve() const
262 assert(check_feasible());
264 Mixed_qp lp(cols.size());
265 make_matrices(lp.quad,lp.lin, lp.const_term);
266 make_constraints(lp);
267 Vector start=find_initial_solution();
268 Vector sol(lp.solve(start));
269 if (!check_constraints(sol)) {
270 WARN << "solution doesn't satisfy constraints.\n" ;
272 Real energy_f =lp.eval(sol);
273 position_loose_cols(sol);
275 Array<Real> posns(sol);
277 posns.push(energy_f);
282 add one column to the problem.
285 Spring_spacer::add_column(PCol *col, bool fixed, Real fixpos)
287 Colinfo c(col,(fixed)? &fixpos : 0);
289 c.rank_i_ = cols.top().rank_i_+1;
296 Spring_spacer::error_pcol_l_arr()const
299 for (int i=0; i< cols.size(); i++)
301 retval.push(cols[i].pcol_l_);
302 for (int i=0; i < loose_col_arr_.size(); i++) {
303 retval.push(loose_col_arr_[i].pcol_l_);
309 Spring_spacer::loosen_column(int i)
311 Colinfo c=cols.get(i);
312 for (PCursor<Idealspacing*> j(ideal_p_list_.top()); j.ok(); j++){
313 if (j->left_i_ == i|| j->right_i_ == i)
321 for (; j < loose_col_arr_.size(); j++) {
322 if (loose_col_arr_[j].rank_i_ > c.rank_i_)
325 loose_col_arr_.insert(c,j);
330 Spring_spacer::print() const
333 for (int i=0; i < cols.size(); i++) {
334 mtor << "col " << i<<' ';
337 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++){
346 Spring_spacer::connect(int i, int j, Real d, Real h)
348 Idealspacing * s = new Idealspacing;
354 ideal_p_list_.bottom().add(s);
358 walk through all durations in all Score_columns
360 struct Durations_iter
362 Spring_spacer * sp_l_;
366 Durations_iter(Spring_spacer*);
368 Moment duration()const;
375 Durations_iter::Durations_iter(Spring_spacer * s)
381 if (! sp_l_->scol_l(col_i_)->durations.size() )
386 Durations_iter::duration() const
388 return sp_l_->scol_l(col_i_)->durations[d_i_];
392 Durations_iter::ok()const{
393 return col_i_ < sp_l_->cols.size();
397 Durations_iter::when()const{
398 return sp_l_->scol_l(col_i_)->when();
402 Durations_iter::next()
405 while ( col_i_ < sp_l_->cols.size()
406 && d_i_ >= sp_l_->scol_l(col_i_)->durations.size()){
414 generate springs between columns.
418 TODO: This needs rethinking. Spacing should take optical
419 effects into account, and should be local (measure wide)
421 The algorithm is taken from :
423 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
424 OSU-CISRC-10/87-TR35, Department of Computer and Information
425 Science, The Ohio State University, 1987.
429 Spring_spacer::calc_idealspacing()
432 for (int i=0; i < cols.size(); i++)
433 scol_l(i)->preprocess();
435 /* get the shortest running note at a time. */
436 Array<Moment> shortest_arr_;
438 Durations_iter d_iter(this);
439 for (int i=0; i < cols.size(); i++) {
440 Moment now = scol_l(i)->when();
441 while ( d_iter.ok() && now >= d_iter.when() ) {
442 if ( now < d_iter.when() + d_iter.duration())
446 if ( d_iter.ok() && now >= d_iter.when()) {
447 Durations_iter d2 = d_iter;
448 Moment shortest = (Real)INT_MAX; //ugh INFTY;
449 while (d2.ok() && d2.when() <= now) {
450 shortest = shortest <? d2.duration();
453 shortest_arr_.push( shortest );
455 shortest_arr_.push(0);
460 mtor << "shortest:[ ";
461 for (int i=0; i < shortest_arr_.size(); i++)
462 mtor << shortest_arr_[i] << " ";
466 Array<Real> ideal_arr_;
467 Array<Real> hooke_arr_;
468 for (int i=0; i < cols.size(); i++){
469 ideal_arr_.push( -1.0);
470 hooke_arr_.push(1.0);
473 for (int i=0; i < cols.size(); i++) {
474 if ( !scol_l(i)->musical_b()) {
475 ideal_arr_[i] = cols[i].minright() + 2 PT;
477 if (i+1 < cols.size()) {
478 Moment delta_t = scol_l(i+1)->when() - scol_l(i)->when() ;
479 Real dist = delta_t ? paper_l()->duration_to_dist(delta_t) : 0;
480 if (delta_t && dist > ideal_arr_[i])
481 ideal_arr_[i] = dist;
485 for (int i=0; i < cols.size(); i++) {
486 if (scol_l(i)->musical_b()) {
487 Moment shortest_len = shortest_arr_[i];
488 if ( ! shortest_len ) {
489 warning( "Can't find a ruling note at "
490 +String( scol_l(i)->when()));
493 Moment delta_t = scol_l(i+1)->when() - scol_l(i)->when();
494 Real dist = paper_l()->duration_to_dist(shortest_len);
495 dist *= delta_t / shortest_len;
496 if (!scol_l(i+1)->musical_b() ) {
498 if (ideal_arr_[i+1] + cols[i+1].minleft() < dist) {
499 ideal_arr_[i+1] = dist/2 + cols[i+1].minleft();
500 hooke_arr_[i+1] =1.0;
502 ideal_arr_[i] = dist/2;
504 ideal_arr_[i] = dist;
508 for (int i=0; i < ideal_arr_.size()-1; i++) {
509 assert (ideal_arr_[i] >=0 && hooke_arr_[i] >=0);
510 connect(i, i+1, ideal_arr_[i], hooke_arr_[i]);
518 Spring_spacer::prepare()
526 Spring_spacer::constructor()
528 return new Spring_spacer;
534 for (int i=0; i < cols.size(); i++) {
535 if (!scol_l(i)->used_b())
541 if (scol_l(i)->musical_b()) {
542 assert ( j < cols.size());
544 for (int n=0; n < scol_l(i)->durations.size(); n++) {
545 Moment d = scol_l(i)->durations[n];
546 Real dist = paper_l()->duration_to_dist(d);
547 Real strength = scol_l(i)->durations[0]/scol_l(i)->durations[n];
548 assert(strength <= 1.0);
550 while (j < cols.size()) {
551 if (scol_l(j)->used_b()
552 && scol_l(j)->when() >= d + scol_l(i)->when() )
556 if ( j < cols.size() ){
557 Moment delta_desired = scol_l(j)->when() - (d+scol_l(i)->when());
558 dist += paper_l()->duration_to_dist(delta_desired);
559 if (scol_l(j)->musical_b()) {
560 dist += cols[j].minleft() + 2 PT;
562 connect(i, j, dist, strength);
565 } else if (j < cols.size()) {
566 while (!scol_l(j)->used_b())
569 /* attach i to the next column in use. This exists, since
570 the last col is breakable, and therefore in use
573 Moment d = scol_l(j)->when() - scol_l(i)->when();
574 Real minimal_f = cols[i].minright() +cols[j].minleft() + 2 PT;
575 Real durdist_f = (d) ? paper_l()->duration_to_dist(d) : 0; // todo
577 connect(i, j, minimal_f <? durdist_f, (d) ? 1.0:1.0);
579 // !j.ok() might hold if we're at the last col.