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>
11 #include "spring-spacer.hh"
15 #include "unionfind.hh"
16 #include "idealspacing.hh"
17 #include "pointer.tcc"
18 #include "score-column.hh"
19 #include "paper-def.hh"
21 #include "minterval.hh"
24 Spring_spacer::default_solution()const
26 return try_initial_solution() ;
30 Spring_spacer::scol_l(int i)
32 return (Score_column*)cols[i].pcol_l_;
35 const Real COLFUDGE=1e-3;
36 template class P<Real>; // ugh.
39 Spring_spacer::contains(PCol const *w)
41 for (int i=0; i< cols.size(); i++)
42 if (cols[i].pcol_l_ == w)
49 Spring_spacer::OK() const
52 for (int i = 1; i < cols.size(); i++)
53 assert(cols[i].rank_i_ > cols[i-1].rank_i_);
54 for (int i = 1; i < loose_col_arr_.size(); i++)
55 assert(loose_col_arr_[i].rank_i_ > loose_col_arr_[i-1].rank_i_);
60 Make sure no unconnected columns happen.
63 Spring_spacer::handle_loose_cols()
65 Union_find connected(cols.size());
67 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++){
68 connected.connect(i->left_i_,i->right_i_);
70 for (int i = 0; i < cols.size(); i++)
73 for (int i=1; i < fixed.size(); i++)
74 connected.connect(fixed[i-1], fixed[i]);
76 for (int i = cols.size(); i--; ) {
77 if (! connected.equiv(fixed[0], i)) {
78 warning("unconnected column: " + String(i));
87 Guess a stupid position for loose columns. Put loose columns at
88 regular distances from enclosing calced columns
91 Spring_spacer::position_loose_cols(Vector &sol_vec)const
93 if (!loose_col_arr_.size())
95 assert(sol_vec.dim());
96 Array<bool> fix_b_arr;
97 fix_b_arr.set_size(cols.size() + loose_col_arr_.size());
98 Real utter_right_f=-INFTY;
99 Real utter_left_f =INFTY;
100 for (int i=0; i < loose_col_arr_.size(); i++) {
101 fix_b_arr[loose_col_arr_[i].rank_i_] = false;
103 for (int i=0; i < cols.size(); i++) {
104 int r= cols[i].rank_i_;
106 utter_right_f = utter_right_f >? sol_vec(i);
107 utter_left_f = utter_left_f <? sol_vec(i);
109 Vector v(fix_b_arr.size());
112 for (int i=0; i < v.dim(); i++) {
114 assert(cols[j].rank_i_ == i);
118 (j>0) ?sol_vec(j-1) : utter_left_f;
120 (j < sol_vec.dim()) ? sol_vec(j) : utter_right_f;
121 int left_rank = (j>0) ? cols[j-1].rank_i_ : 0;
122 int right_rank = (j<sol_vec.dim()) ? cols[j].rank_i_ : sol_vec.dim();
124 int d_r = right_rank - left_rank;
125 Colinfo loose=loose_col_arr_[k++];
126 int r = loose.rank_i_ ;
127 assert(r > left_rank && r < right_rank);
129 v(i) = (r - left_rank)*left_pos_f/ d_r +
130 (right_rank - r) *right_pos_f /d_r;
137 Spring_spacer::check_constraints(Vector v) const
140 assert(dim == cols.size());
142 for (int i=0; i < dim; i++) {
144 if (cols[i].fixed()&&
145 abs(cols[i].fixed_position() - v(i)) > COLFUDGE)
151 Real mindist=cols[i-1].minright()
155 Real dif =v(i) - v(i-1)- mindist;
156 bool b = (dif > - COLFUDGE);
167 Spring_spacer::check_feasible() const
169 Vector sol(try_initial_solution());
170 return check_constraints(sol);
173 /// generate a solution which obeys the min distances and fixed positions
175 Spring_spacer::try_initial_solution() const
179 for (int i=0; i < dim; i++) {
180 if (cols[i].fixed()) {
181 initsol(i)=cols[i].fixed_position();
184 Real r =initsol(i-1) + cols[i-1].minright();
185 if (initsol(i) < r ) {
186 warning("overriding fixed position");
192 Real mindist=cols[i-1].minright()
195 warning("Excentric column");
196 initsol(i)=initsol(i-1)+mindist;
206 Spring_spacer::find_initial_solution() const
208 Vector v(try_initial_solution());
209 assert(check_constraints(v));
213 // generate the matrices
215 Spring_spacer::make_matrices(Matrix &quad, Vector &lin, Real &c) const
221 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++) {
225 quad(r,r) += i->hooke_f_;
226 quad(r,l) -= i->hooke_f_;
227 quad(l,r) -= i->hooke_f_;
228 quad(l,l) += i->hooke_f_;
230 lin(r) -= i->space_f_*i->hooke_f_;
231 lin(l) += i->space_f_*i->hooke_f_;
233 c += sqr(i->space_f_);
237 // put the constraints into the LP problem
239 Spring_spacer::make_constraints(Mixed_qp& lp) const
242 for (int j=0; j < dim; j++) {
245 lp.add_fixed_var(j,c.fixed_position());
252 lp.add_inequality_cons(c1, cols[j-1].minright() +
259 Spring_spacer::solve() const
261 assert(check_feasible());
263 Mixed_qp lp(cols.size());
264 make_matrices(lp.quad,lp.lin, lp.const_term);
265 make_constraints(lp);
266 Vector start=find_initial_solution();
267 Vector sol(lp.solve(start));
268 if (!check_constraints(sol)) {
269 WARN << "solution doesn't satisfy constraints.\n" ;
271 Real energy_f =lp.eval(sol);
272 position_loose_cols(sol);
274 Array<Real> posns(sol);
276 posns.push(energy_f);
281 add one column to the problem.
284 Spring_spacer::add_column(PCol *col, bool fixed, Real fixpos)
286 Colinfo c(col,(fixed)? &fixpos : 0);
288 c.rank_i_ = cols.top().rank_i_+1;
295 Spring_spacer::error_pcol_l_arr()const
298 for (int i=0; i< cols.size(); i++)
300 retval.push(cols[i].pcol_l_);
301 for (int i=0; i < loose_col_arr_.size(); i++) {
302 retval.push(loose_col_arr_[i].pcol_l_);
308 Spring_spacer::loosen_column(int i)
310 Colinfo c=cols.get(i);
311 for (PCursor<Idealspacing*> j(ideal_p_list_.top()); j.ok(); j++){
312 if (j->left_i_ == i|| j->right_i_ == i)
320 for (; j < loose_col_arr_.size(); j++) {
321 if (loose_col_arr_[j].rank_i_ > c.rank_i_)
324 loose_col_arr_.insert(c,j);
329 Spring_spacer::print() const
332 for (int i=0; i < cols.size(); i++) {
333 mtor << "col " << i<<' ';
336 for (PCursor<Idealspacing*> i(ideal_p_list_.top()); i.ok(); i++){
345 Spring_spacer::connect(int i, int j, Real d, Real h)
347 Idealspacing * s = new Idealspacing;
353 ideal_p_list_.bottom().add(s);
357 walk through all durations in all Score_columns
359 struct Durations_iter
361 Spring_spacer * sp_l_;
365 Durations_iter(Spring_spacer*);
367 Moment duration()const;
374 Durations_iter::Durations_iter(Spring_spacer * s)
380 if (! sp_l_->scol_l(col_i_)->durations.size() )
385 Durations_iter::duration() const
387 return sp_l_->scol_l(col_i_)->durations[d_i_];
391 Durations_iter::ok()const{
392 return col_i_ < sp_l_->cols.size();
396 Durations_iter::when()const{
397 return sp_l_->scol_l(col_i_)->when();
401 Durations_iter::next()
404 while ( col_i_ < sp_l_->cols.size()
405 && d_i_ >= sp_l_->scol_l(col_i_)->durations.size()){
413 generate springs between columns.
417 TODO: This needs rethinking. Spacing should take optical
418 effects into account, and should be local (measure wide)
420 The algorithm is taken from :
422 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
423 OSU-CISRC-10/87-TR35, Department of Computer and Information
424 Science, The Ohio State University, 1987.
428 Spring_spacer::calc_idealspacing()
431 for (int i=0; i < cols.size(); i++)
432 scol_l(i)->preprocess();
434 /* get the shortest running note at a time. */
435 Array<Moment> shortest_arr_;
437 Durations_iter d_iter(this);
438 for (int i=0; i < cols.size(); i++) {
439 Moment now = scol_l(i)->when();
440 while ( d_iter.ok() && now >= d_iter.when() ) {
441 if ( now < d_iter.when() + d_iter.duration())
445 if ( d_iter.ok() && now >= d_iter.when()) {
446 Durations_iter d2 = d_iter;
447 Moment shortest = INFTY;
448 while (d2.ok() && d2.when() <= now) {
449 shortest = shortest <? d2.duration();
452 shortest_arr_.push( shortest );
454 shortest_arr_.push(0);
459 mtor << "shortest:[ ";
460 for (int i=0; i < shortest_arr_.size(); i++)
461 mtor << shortest_arr_[i] << " ";
465 Array<Real> ideal_arr_;
466 Array<Real> hooke_arr_;
467 for (int i=0; i < cols.size(); i++){
468 ideal_arr_.push( -1.0);
469 hooke_arr_.push(1.0);
472 for (int i=0; i < cols.size(); i++) {
473 if ( !scol_l(i)->musical_b()) {
474 ideal_arr_[i] = cols[i].minright() + 2 PT;
476 if (i+1 < cols.size()) {
477 Moment delta_t = scol_l(i+1)->when() - scol_l(i)->when() ;
478 Real dist = delta_t ? paper_l()->duration_to_dist(delta_t) : 0;
479 if (delta_t && dist > ideal_arr_[i])
480 ideal_arr_[i] = dist;
484 for (int i=0; i < cols.size(); i++) {
485 if (scol_l(i)->musical_b()) {
486 Moment shortest_len = shortest_arr_[i];
487 if ( ! shortest_len ) {
488 warning( "Can't find a ruling note at "
489 +String( scol_l(i)->when()));
492 Moment delta_t = scol_l(i+1)->when() - scol_l(i)->when();
493 Real dist = paper_l()->duration_to_dist(shortest_len);
494 dist *= delta_t / shortest_len;
495 if (!scol_l(i+1)->musical_b() ) {
497 if (ideal_arr_[i+1] + cols[i+1].minleft() < dist) {
498 ideal_arr_[i+1] = dist/2 + cols[i+1].minleft();
499 hooke_arr_[i+1] =1.0;
501 ideal_arr_[i] = dist/2;
503 ideal_arr_[i] = dist;
507 for (int i=0; i < ideal_arr_.size()-1; i++) {
508 assert (ideal_arr_[i] >=0 && hooke_arr_[i] >=0);
509 connect(i, i+1, ideal_arr_[i], hooke_arr_[i]);
517 Spring_spacer::prepare()
525 Spring_spacer::constructor()
527 return new Spring_spacer;
533 for (int i=0; i < cols.size(); i++) {
534 if (!scol_l(i)->used_b())
540 if (scol_l(i)->musical_b()) {
541 assert ( j < cols.size());
543 for (int n=0; n < scol_l(i)->durations.size(); n++) {
544 Moment d = scol_l(i)->durations[n];
545 Real dist = paper_l()->duration_to_dist(d);
546 Real strength = scol_l(i)->durations[0]/scol_l(i)->durations[n];
547 assert(strength <= 1.0);
549 while (j < cols.size()) {
550 if (scol_l(j)->used_b()
551 && scol_l(j)->when() >= d + scol_l(i)->when() )
555 if ( j < cols.size() ){
556 Moment delta_desired = scol_l(j)->when() - (d+scol_l(i)->when());
557 dist += paper_l()->duration_to_dist(delta_desired);
558 if (scol_l(j)->musical_b()) {
559 dist += cols[j].minleft() + 2 PT;
561 connect(i, j, dist, strength);
564 } else if (j < cols.size()) {
565 while (!scol_l(j)->used_b())
568 /* attach i to the next column in use. This exists, since
569 the last col is breakable, and therefore in use
572 Moment d = scol_l(j)->when() - scol_l(i)->when();
573 Real minimal_f = cols[i].minright() +cols[j].minleft() + 2 PT;
574 Real durdist_f = (d) ? paper_l()->duration_to_dist(d) : 0; // todo
576 connect(i, j, minimal_f <? durdist_f, (d) ? 1.0:1.0);
578 // !j.ok() might hold if we're at the last col.