2 spring-spacer.cc -- implement Spring_spacer
4 source file of the GNU LilyPond music typesetter
6 (c) 1996, 1997, 1998 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"
23 #include "main.hh" // experimental_fietsers
26 Spring_spacer::default_solution() const
28 return try_initial_solution() ;
32 Spring_spacer::scol_l (int i)
34 return (Score_column*)cols[i].pcol_l_;
37 const Real COLFUDGE=1e-3;
38 template class P<Real>; // ugh.
41 Spring_spacer::contains (Paper_column const *w)
43 for (int i=0; i< cols.size(); i++)
44 if (cols[i].pcol_l_ == w)
51 Spring_spacer::OK() const
54 for (int i = 1; i < cols.size(); i++)
55 assert (cols[i].rank_i_ > cols[i-1].rank_i_);
56 for (int i = 1; i < loose_col_arr_.size(); i++)
57 assert (loose_col_arr_[i].rank_i_ > loose_col_arr_[i-1].rank_i_);
62 Make sure no unconnected columns happen.
65 Spring_spacer::handle_loose_cols()
67 Union_find connected (cols.size());
69 for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
71 connected.connect (i->left_i_,i->right_i_);
73 for (int i = 0; i < cols.size(); i++)
74 if (cols[i].fixed_b())
76 for (int i=1; i < fixed.size(); i++)
77 connected.connect (fixed[i-1], fixed[i]);
79 for (int i = cols.size(); i--;)
81 if (! connected.equiv (fixed[0], i))
83 warning (_("unconnected column: ") + String (i));
92 Guess a stupid position for loose columns. Put loose columns at
93 regular distances from enclosing calced columns
96 Spring_spacer::position_loose_cols (Vector &sol_vec) const
98 if (!loose_col_arr_.size())
100 assert (sol_vec.dim());
101 Array<bool> fix_b_arr;
102 fix_b_arr.set_size (cols.size() + loose_col_arr_.size ());
103 Real utter_right_f=-infinity_f;
104 Real utter_left_f =infinity_f;
105 for (int i=0; i < loose_col_arr_.size(); i++)
107 fix_b_arr[loose_col_arr_[i].rank_i_] = false;
109 for (int i=0; i < cols.size(); i++)
111 int r= cols[i].rank_i_;
113 utter_right_f = utter_right_f >? sol_vec (i);
114 utter_left_f = utter_left_f <? sol_vec (i);
116 Vector v (fix_b_arr.size());
119 for (int i=0; i < v.dim(); i++)
123 assert (cols[j].rank_i_ == i);
124 v (i) = sol_vec (j++);
129 (j>0) ?sol_vec (j-1) : utter_left_f;
131 (j < sol_vec.dim()) ? sol_vec (j) : utter_right_f;
132 int left_rank = (j>0) ? cols[j-1].rank_i_ : 0;
133 int right_rank = (j<sol_vec.dim()) ? cols[j].rank_i_ : sol_vec.dim ();
135 int d_r = right_rank - left_rank;
136 Colinfo loose=loose_col_arr_[k++];
137 int r = loose.rank_i_ ;
138 assert (r > left_rank && r < right_rank);
140 v (i) = (r - left_rank)*left_pos_f/ d_r +
141 (right_rank - r) *right_pos_f /d_r;
148 Spring_spacer::check_constraints (Vector v) const
151 assert (dim == cols.size());
152 DOUT << "checking " << v;
153 for (int i=0; i < dim; i++)
155 if (cols[i].fixed_b() &&
156 abs (cols[i].fixed_position() - v (i)) > COLFUDGE)
158 DOUT << "Fixpos broken\n";
161 Array<Column_rod> &rods (cols[i].pcol_l_->minimal_dists_arr_drul_[RIGHT]);
162 for (int j =0; j < rods.size (); j++)
164 int delta_idx= rods[j].other_l_->rank_i () - cols[i].rank_i ();
165 if (i + delta_idx >= dim )
167 if (rods[j].other_l_ != cols[i + delta_idx].pcol_l_)
169 if (v (i + delta_idx) - v (i) < rods[j].distance_f_)
171 DOUT << "v (i + delta_idx) - v (i) too small: i, delta_idx: "
172 << i << " " << delta_idx;
181 /** try to generate a solution which obeys the min distances and fixed positions
184 Spring_spacer::try_initial_solution() const
187 if (!try_initial_solution_and_tell (v))
189 warning ("I'm too fat; call Oprah");
190 DOUT << "tried solution: " << v;
197 Spring_spacer::try_initial_solution_and_tell (Vector &v) const
200 bool succeeded = true;
201 Vector initsol (dim);
202 for (int i=0; i < dim; i++)
204 int first_rank = cols[0].rank_i ();
205 int last_rank = cols.top ().rank_i ();
207 Real min_x = i ? initsol (i-1) : 0.0;
208 for (int j=0; j < cols[i].pcol_l_->minimal_dists_arr_drul_[LEFT].size (); j++)
210 Column_rod cr (cols[i].pcol_l_->minimal_dists_arr_drul_[LEFT] [j]);
211 if (cr.other_l_->rank_i () < first_rank)
214 int idx = cr.other_l_->rank_i () - first_rank;
215 assert (i > idx && idx >= 0);
216 if (cr.other_l_->break_status_i_ != cols[idx].pcol_l_->break_status_i_ )
219 min_x = min_x >? (initsol (idx) + cr.distance_f_);
223 if (cols[i].fixed_b())
225 initsol (i)=cols[i].fixed_position();
226 if (initsol (i) < min_x )
228 DOUT << "failing: init, min : " << initsol (i) << " " << min_x << "\n";
241 // generate the matrices
243 Spring_spacer::make_matrices (Matrix &quad, Vector &lin, Real &c) const
249 for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
254 quad (r,r) += i->hooke_f_;
255 quad (r,l) -= i->hooke_f_;
256 quad (l,r) -= i->hooke_f_;
257 quad (l,l) += i->hooke_f_;
259 lin (r) -= i->space_f_*i->hooke_f_;
260 lin (l) += i->space_f_*i->hooke_f_;
262 c += sqr (i->space_f_);
267 Spring_spacer::set_fixed_cols (Mixed_qp &qp) const
269 for (int j=0; j < cols.size(); j++)
270 if (cols[j].fixed_b())
271 qp.add_fixed_var (j,cols[j].fixed_position());
274 // put the constraints into the LP problem
276 Spring_spacer::make_constraints (Mixed_qp& lp) const
279 int last_rank = cols.top ().pcol_l_->rank_i ();
281 for (int j=0; j < dim -1; j++)
283 Paper_column* lc = cols[j].pcol_l_;
284 int my_rank = lc->rank_i();
285 for (int i = 0; i < lc->minimal_dists_arr_drul_[RIGHT].size (); i++)
288 Column_rod & cr = lc->minimal_dists_arr_drul_[RIGHT][i];
289 int right_rank = cr.other_l_->rank_i ();
292 if (right_rank > last_rank)
295 int right_idx = right_rank - my_rank + j;
299 lp.add_inequality_cons (c1, cr.distance_f_);
306 Spring_spacer::calculate_energy_f (Vector solution) const
309 for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok(); i++)
311 e += i->energy_f(solution(i->right_i_) - solution(i->left_i_));
317 Spring_spacer::lower_bound_solution (Col_hpositions*positions) const
319 Mixed_qp lp (cols.size());
320 make_matrices (lp.quad,lp.lin, lp.const_term);
323 Vector start (cols.size());
325 Vector solution_vec (lp.solve (start));
327 DOUT << "Lower bound sol: " << solution_vec;
328 positions->energy_f_ = calculate_energy_f (solution_vec);
329 positions->config = solution_vec;
330 positions->satisfies_constraints_b_ = check_constraints (solution_vec);
334 Spring_spacer::solve (Col_hpositions*positions) const
336 Vector solution_try (try_initial_solution());
338 if (check_constraints (solution_try))
340 Mixed_qp lp (cols.size());
341 make_matrices (lp.quad,lp.lin, lp.const_term);
342 make_constraints (lp);
345 Vector solution_vec (lp.solve (solution_try));
348 positions->satisfies_constraints_b_ = check_constraints (solution_vec);
349 if (!positions->satisfies_constraints_b_)
351 WARN << _("solution doesn't satisfy constraints.\n") ;
353 position_loose_cols (solution_vec);
354 positions->energy_f_ = calculate_energy_f (solution_vec);
355 positions->config = solution_vec;
356 positions->error_col_l_arr_ = error_pcol_l_arr();
360 positions->set_stupid_solution (solution_try);
365 add one column to the problem.
368 Spring_spacer::add_column (Paper_column *col, bool fixed, Real fixpos)
370 Colinfo c (col,(fixed)? &fixpos : 0);
372 c.rank_i_ = cols.top().rank_i_+1;
381 Spring_spacer::error_pcol_l_arr() const
383 Array<Paper_column*> retval;
384 for (int i=0; i< cols.size(); i++)
386 retval.push (cols[i].pcol_l_);
387 for (int i=0; i < loose_col_arr_.size(); i++)
389 retval.push (loose_col_arr_[i].pcol_l_);
395 Spring_spacer::loosen_column (int i)
397 Colinfo c=cols.get (i);
398 for (PCursor<Idealspacing*> j (ideal_p_list_.top()); j.ok (); j++)
400 if (j->left_i_ == i|| j->right_i_ == i)
408 for (; j < loose_col_arr_.size(); j++)
410 if (loose_col_arr_[j].rank_i_ > c.rank_i_)
413 loose_col_arr_.insert (c,j);
418 Spring_spacer::print() const
421 for (int i=0; i < cols.size(); i++)
423 DOUT << "col " << i<<' ';
426 for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
435 Spring_spacer::connect (int i, int j, Real d, Real h)
437 assert(d >= 0 && d <= 100 CM);
440 Idealspacing * s = new Idealspacing;
447 ideal_p_list_.bottom().add (s);
454 Spring_spacer::prepare()
462 Spring_spacer::constructor()
464 return new Spring_spacer;
470 get the shortest_playing running note at a time. */
472 Spring_spacer::get_ruling_durations(Array<Moment> &shortest_playing_arr,
473 Array<Moment> &context_shortest_arr)
475 for (int i=0; i < cols.size(); i++)
477 scol_l (i)->preprocess();
478 scol_l (i)->print ();
480 int start_context_i=0;
481 Moment context_shortest;
482 context_shortest.set_infinite (1);
483 context_shortest_arr.set_size(cols.size());
485 for (int i=0; i < cols.size(); i++)
487 Moment now = scol_l (i)->when();
488 Moment shortest_playing;
489 shortest_playing.set_infinite (1);
491 if (scol_l (i)->breakable_b_)
493 for (int ji=i; ji >= start_context_i; ji--)
494 context_shortest_arr[ji] = context_shortest;
496 context_shortest.set_infinite (1);
498 if (scol_l (i)->durations.size())
500 context_shortest = context_shortest <? scol_l(i)->durations[0];
503 // ji was j, but triggered ICE
504 for (int ji=i+1; ji --;)
506 if (scol_l(ji)->durations.size() &&
507 now - scol_l(ji)->when() >= shortest_playing)
510 for (int k = scol_l (ji)->durations.size();
511 k-- && scol_l(ji)->durations[k] + scol_l(ji)->when() > now;
514 shortest_playing = shortest_playing <? scol_l(ji)->durations[k];
517 shortest_playing_arr.push(shortest_playing);
521 DOUT << "shortest_playing/:[ ";
522 for (int i=0; i < shortest_playing_arr.size(); i++)
524 DOUT << shortest_playing_arr[i] << " ";
525 DOUT << context_shortest_arr[i] << ", ";
532 TODO: take out the refs to width
535 generate springs between columns.
537 TODO: This needs rethinking. Spacing should take optical
540 The algorithm is taken from :
542 John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
543 OSU-CISRC-10/87-TR35, Department of Computer and Information
544 Science, The Ohio State University, 1987.
548 Spring_spacer::calc_idealspacing()
550 Array<Moment> shortest_playing_arr;
551 Array<Moment> context_shortest_arr;
552 get_ruling_durations(shortest_playing_arr, context_shortest_arr);
554 Real interline_f = paper_l ()->interline_f ();
555 Real nw_f = paper_l ()->note_width ();
557 Array<Real> ideal_arr_;
558 Array<Real> hooke_arr_;
559 for (int i=0; i < cols.size() - 1; i++){
560 ideal_arr_.push (-1.0);
561 hooke_arr_.push (1.0);
565 First do all non-musical columns
567 for (int i=0; i < cols.size(); i++)
569 if (!scol_l (i)->musical_b() && i+1 < cols.size())
571 Real symbol_distance =cols[i].width_[RIGHT] + 2 PT;
572 Real durational_distance = 0;
575 Moment delta_t = scol_l (i+1)->when() - scol_l (i)->when () ;
577 Real k= paper_l()->arithmetic_constant (context_shortest_arr[i]);
579 ugh should use shortest_playing distance
582 durational_distance = paper_l()->duration_to_dist (delta_t,k);
583 symbol_distance += -cols[i+1].width_[LEFT];
586 ideal_arr_[i] = symbol_distance >? durational_distance;
587 hooke_arr_[i] = 1; //2.0;
594 for (int i=0; i < cols.size(); i++)
596 if (scol_l (i)->musical_b())
598 Moment shortest_playing_len = shortest_playing_arr[i];
599 Moment context_shortest = context_shortest_arr[i];
600 if (! shortest_playing_len)
602 warning (_("Can't find a ruling note at ")
603 +scol_l (i)->when().str ());
604 shortest_playing_len = 1;
606 if (! context_shortest)
608 warning(_("No minimum in measure at ")
609 + scol_l (i)->when().str ());
610 context_shortest = 1;
612 Moment delta_t = scol_l (i+1)->when() - scol_l (i)->when ();
613 Real k= paper_l()->arithmetic_constant(context_shortest);
614 Real dist = paper_l()->duration_to_dist (shortest_playing_len, k);
615 dist *= (double)(delta_t / shortest_playing_len);
618 According to [Ross] and [Wanske], and from what i've seen:
620 * whitespace at the begin of the bar should be fixed at
621 (about) one interline.
623 when spacing gets real tight, a smaller fixed value may be
624 used, so that there are two discrete amounts of whitespace
625 possible at the begin of a bar; but this is not implemented
628 * whitespace at the end of the bar is the normal amount of
629 "hinterfleish" that would have been used, had there been
630 yet another note in the bar.
632 some editors argue that the bar line should not take any
633 space, not to hinder the flow of music spaced around a bar
635 [Ross] and [Wanske] do not suggest this, however. Further,
636 it introduces some spacing problems and think that it is ugly
642 first musical column of bar
644 if (i && scol_l (i - 1)->breakable_b_)
646 // fixed: probably should set minimum (rod/spring)?
647 cols[i-1].width_[RIGHT] += interline_f;
648 // should adjust dist too?
649 ideal_arr_[i-1] = ideal_arr_[i-1] >? interline_f;
653 last musical column of bar
655 if (i + 1 < cols.size () && scol_l(i+1)->breakable_b_)
658 dist = dist >? interline_f;
661 uhuh, this code looks fine, already?
662 someone was junking this last "hinterfleisch" whitespace?!
664 but this seems to be fixed now :-)
667 cols[i].width_[RIGHT] += interline_f;
670 // ugh, do we need this?
671 if (i < cols.size () - 1 && !scol_l (i + 1)->musical_b ())
673 Real minimum = -cols[i + 1].width_[LEFT] + cols[i].width_[RIGHT]
675 dist = dist >? minimum;
678 // ugh: never let columns touch... try to set over here...
679 // ugh: use j iso i triggers ice in gcc-2.7.2.3
680 cols[i].width_[LEFT] -= nw_f / 4;
681 ideal_arr_[i] = dist;
685 for (int i=0; i < ideal_arr_.size(); i++)
687 assert (ideal_arr_[i] >=0 && hooke_arr_[i] >=0);
688 connect (i, i+1, ideal_arr_[i], hooke_arr_[i]);