]> git.donarmstrong.com Git - lilypond.git/blob - lily/spring-spacer.cc
fffd8318df6df91f5e13d2fad1ff9eec2b6709eb
[lilypond.git] / lily / spring-spacer.cc
1 /*
2   spring-spacer.cc -- implement Spring_spacer
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996, 1997, 1998 Han-Wen Nienhuys <hanwen@stack.nl>
7 */
8
9
10 #include <math.h>
11 #include <limits.h>
12 #include "spring-spacer.hh"
13 #include "p-col.hh"
14 #include "debug.hh"
15 #include "qlp.hh"
16 #include "unionfind.hh"
17 #include "idealspacing.hh"
18 #include "pointer.tcc"
19 #include "score-column.hh"
20 #include "paper-def.hh"
21 #include "dimen.hh"
22 #include "colhpos.hh"
23 #include "main.hh"              // experimental_fietsers
24
25 Vector
26 Spring_spacer::default_solution() const
27 {
28   return try_initial_solution() ;
29 }
30
31 Score_column*
32 Spring_spacer::scol_l (int i)
33 {
34   return (Score_column*)cols[i].pcol_l_;
35 }
36
37 const Real COLFUDGE=1e-3;
38 template class P<Real>;         // ugh.
39
40 bool
41 Spring_spacer::contains (Paper_column const *w)
42 {
43   for (int i=0; i< cols.size(); i++)
44     if (cols[i].pcol_l_ == w)
45       return true;
46   return false;
47 }
48
49
50 void
51 Spring_spacer::OK() const
52 {
53 #ifndef NDEBUG
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_);
58 #endif
59 }
60
61 /**
62   Make sure no unconnected columns happen.
63  */
64 void
65 Spring_spacer::handle_loose_cols()
66 {
67   Union_find connected (cols.size());
68   Array<int> fixed;
69   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
70     {
71       connected.connect (i->left_i_,i->right_i_);
72     }
73   for (int i = 0; i < cols.size(); i++)
74     if (cols[i].fixed_b())
75       fixed.push (i);
76   for (int i=1; i < fixed.size(); i++)
77     connected.connect (fixed[i-1], fixed[i]);
78
79   for (int i = cols.size(); i--;)
80     {
81       if (! connected.equiv (fixed[0], i))
82         {
83           warning (_("unconnected column: ") + String (i));
84           loosen_column (i);
85         }
86     }
87   OK();
88 }
89
90
91 /**
92   Guess a stupid position for loose columns.  Put loose columns at
93   regular distances from enclosing calced columns
94   */
95 void
96 Spring_spacer::position_loose_cols (Vector &sol_vec) const
97 {
98   if (!loose_col_arr_.size())
99     return ;
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++)
106     {
107       fix_b_arr[loose_col_arr_[i].rank_i_] = false;
108     }
109   for (int i=0; i < cols.size(); i++)
110     {
111       int r= cols[i].rank_i_;
112       fix_b_arr[r] = true;
113       utter_right_f = utter_right_f >? sol_vec (i);
114       utter_left_f = utter_left_f <? sol_vec (i);
115     }
116   Vector v (fix_b_arr.size());
117   int j =0;
118   int k =0;
119   for (int i=0; i < v.dim(); i++)
120     {
121       if (fix_b_arr[i])
122         {
123           assert (cols[j].rank_i_ == i);
124           v (i) = sol_vec (j++);
125         }
126       else
127         {
128           Real left_pos_f =
129             (j>0) ?sol_vec (j-1) : utter_left_f;
130           Real right_pos_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 ();
134
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);
139
140           v (i) =  (r - left_rank)*left_pos_f/ d_r +
141             (right_rank - r) *right_pos_f /d_r;
142         }
143     }
144   sol_vec = v;
145 }
146
147 bool
148 Spring_spacer::check_constraints (Vector v) const
149 {
150   int dim=v.dim();
151   assert (dim == cols.size());
152
153   for (int i=0; i < dim; i++)
154     {
155       if (cols[i].fixed_b() &&
156           abs (cols[i].fixed_position() - v (i)) > COLFUDGE)
157         return false;
158
159       Array<Column_rod> &rods (cols[i].pcol_l_->minimal_dists_arr_drul_[RIGHT]);
160       for (int j =0; j < rods.size (); j++)
161         {
162           int delta_idx=  rods[j].other_l_->rank_i () - cols[i].rank_i ();
163           if (i + delta_idx >= dim )
164             break;
165           if (rods[j].other_l_ != cols[i + delta_idx].pcol_l_)
166             continue;
167           if (v (i + delta_idx) - v (i) < rods[j].distance_f_)
168             return false;
169         }
170
171     }
172   return true;
173 }
174
175 /** try to generate a solution which obeys the min distances and fixed positions
176  */
177 Vector
178 Spring_spacer::try_initial_solution() const
179 {
180   Vector v;
181   if (try_initial_solution_and_tell (v))
182     warning ("I'm too fat; call Oprah");
183   return v;
184
185 }
186
187 bool
188 Spring_spacer::try_initial_solution_and_tell (Vector &v) const
189 {
190   int dim=cols.size();
191   bool succeeded = true;
192   Vector initsol (dim);
193   for (int i=0; i < dim; i++)
194     {
195       int first_rank = cols[0].rank_i ();
196       int last_rank = cols.top ().rank_i ();
197
198       Real min_x = i ?  initsol (i-1) : 0.0;
199       for (int j=0; j < cols[i].pcol_l_->minimal_dists_arr_drul_[LEFT].size (); j++)
200         {
201           Column_rod cr (cols[i].pcol_l_->minimal_dists_arr_drul_[LEFT] [j]);
202           if (cr.other_l_->rank_i () < first_rank)
203             break;
204
205           int idx = cr.other_l_->rank_i () - first_rank;
206           assert (i > idx && idx >= 0);
207           if (cr.other_l_->break_status_i_ !=  cols[idx].pcol_l_->break_status_i_ )
208             continue;
209           
210           min_x = min_x >? (initsol (idx) + cr.distance_f_);
211         }
212       
213       if (cols[i].fixed_b())
214         {
215           initsol (i)=cols[i].fixed_position();
216           if (initsol (i) < min_x )
217             {
218               initsol (i) = min_x;
219               succeeded = false;
220             }
221         }
222     }
223   v = initsol;
224   
225   return succeeded;
226 }
227
228
229
230 // generate the matrices
231 void
232 Spring_spacer::make_matrices (Matrix &quad, Vector &lin, Real &c) const
233 {
234   quad.fill (0);
235   lin.fill (0);
236   c = 0;
237
238   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
239     {
240       int l = i->left_i_;
241       int r = i->right_i_;
242
243       quad (r,r) += i->hooke_f_;
244       quad (r,l) -= i->hooke_f_;
245       quad (l,r) -= i->hooke_f_;
246       quad (l,l) += i->hooke_f_;
247
248       lin (r) -= i->space_f_*i->hooke_f_;
249       lin (l) += i->space_f_*i->hooke_f_;
250
251       c += sqr (i->space_f_);
252     }
253 }
254
255 void
256 Spring_spacer::set_fixed_cols (Mixed_qp &qp) const
257 {
258   for (int j=0; j < cols.size(); j++)
259     if (cols[j].fixed_b())
260       qp.add_fixed_var (j,cols[j].fixed_position());
261
262
263 // put the constraints into the LP problem
264 void
265 Spring_spacer::make_constraints (Mixed_qp& lp) const
266 {
267   int dim=cols.size();
268   int last_rank = cols.top ().pcol_l_->rank_i ();
269   
270   for (int j=0; j < dim -1; j++)
271     {
272       Paper_column* lc = cols[j].pcol_l_;
273       int my_rank = lc->rank_i();
274       for (int i = 0; i < lc->minimal_dists_arr_drul_[RIGHT].size (); i++)
275         {
276           Vector c1(dim);
277           Column_rod & cr = lc->minimal_dists_arr_drul_[RIGHT][i];
278           int right_rank = cr.other_l_->rank_i ();
279
280           cout << "lr, rr, last = " << my_rank << ", " <<right_rank << ", " << last_rank << endl;
281
282           if (right_rank > last_rank)
283             break;
284               
285           int right_idx = right_rank - my_rank + j;
286           cout << "li, ri = " << j << "," << right_idx;
287           cout << "lr, rr = " << my_rank << ", " <<right_rank << endl;
288           c1(right_idx)=1.0 ;
289           c1(j)=-1.0 ;
290
291           lp.add_inequality_cons (c1, cr.distance_f_);
292         }
293     }
294 }
295
296
297 Real
298 Spring_spacer::calculate_energy_f (Vector solution) const
299 {
300   Real e = 0.0;
301   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok(); i++)
302     {
303       e += i->energy_f(solution(i->right_i_) - solution(i->left_i_));
304     }
305
306   return e;
307 }
308 void
309 Spring_spacer::lower_bound_solution (Col_hpositions*positions) const
310 {
311   Mixed_qp lp (cols.size());
312   make_matrices (lp.quad,lp.lin, lp.const_term);
313   set_fixed_cols (lp);
314
315   Vector start (cols.size());
316   start.fill (0.0);
317   Vector solution_vec (lp.solve (start));
318
319   DOUT << "Lower bound sol: " << solution_vec;
320   positions->energy_f_ = calculate_energy_f (solution_vec);
321   positions->config = solution_vec;
322   positions->satisfies_constraints_b_ = check_constraints (solution_vec);
323 }
324
325 void
326 Spring_spacer::solve (Col_hpositions*positions) const
327 {
328   Vector solution_try (try_initial_solution());
329   
330   if  (check_constraints (solution_try))
331     {
332       Mixed_qp lp (cols.size());
333       make_matrices (lp.quad,lp.lin, lp.const_term);
334       make_constraints (lp);
335       set_fixed_cols (lp);
336
337       Vector solution_vec (lp.solve (solution_try));
338
339
340       positions->satisfies_constraints_b_ = check_constraints (solution_vec);
341       if (!positions->satisfies_constraints_b_)
342         {
343           WARN << _("solution doesn't satisfy constraints.\n") ;
344         }
345       position_loose_cols (solution_vec);
346       positions->energy_f_ = calculate_energy_f (solution_vec);
347       positions->config = solution_vec;
348       positions->error_col_l_arr_ = error_pcol_l_arr();
349     }
350   else
351     {
352       positions->set_stupid_solution (solution_try);
353     }
354 }
355
356 /**
357   add one column to the problem.
358 */
359 void
360 Spring_spacer::add_column (Paper_column  *col, bool fixed, Real fixpos)
361 {
362   Colinfo c (col,(fixed)? &fixpos :  0);
363   if (cols.size())
364     c.rank_i_ = cols.top().rank_i_+1;
365   else
366     c.rank_i_ = 0;
367   cols.push (c);
368
369   
370 }
371
372 Line_of_cols
373 Spring_spacer::error_pcol_l_arr() const
374 {
375   Array<Paper_column*> retval;
376   for (int i=0; i< cols.size(); i++)
377     if (cols[i].ugh_b_)
378       retval.push (cols[i].pcol_l_);
379   for (int i=0;  i < loose_col_arr_.size(); i++)
380     {
381       retval.push (loose_col_arr_[i].pcol_l_);
382     }
383   return retval;
384 }
385
386 void
387 Spring_spacer::loosen_column (int i)
388 {
389   Colinfo c=cols.get (i);
390   for (PCursor<Idealspacing*> j (ideal_p_list_.top()); j.ok (); j++)
391     {
392       if (j->left_i_ == i|| j->right_i_ == i)
393         j.del();
394       else
395         j++;
396     }
397   c.ugh_b_ = true;
398
399   int j=0;
400   for (; j < loose_col_arr_.size(); j++)
401     {
402       if (loose_col_arr_[j].rank_i_ > c.rank_i_)
403         break;
404     }
405   loose_col_arr_.insert (c,j);
406 }
407
408
409 void
410 Spring_spacer::print() const
411 {
412 #ifndef NPRINT
413   for (int i=0; i < cols.size(); i++)
414     {
415       DOUT << "col " << i<<' ';
416       cols[i].print();
417     }
418   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
419     {
420       i->print();
421     }
422 #endif
423 }
424
425
426 void
427 Spring_spacer::connect (int i, int j, Real d, Real h)
428 {
429   assert(d >= 0 && d <= 100 CM);
430   assert(h >=0);
431
432   Idealspacing * s = new Idealspacing;
433
434   s->left_i_ = i ;
435   s->right_i_ = j;
436   s->space_f_ = d;
437   s->hooke_f_ = h;
438
439   ideal_p_list_.bottom().add (s);
440 }
441
442
443
444
445 void
446 Spring_spacer::prepare()
447 {
448   calc_idealspacing();
449   handle_loose_cols();
450   print();
451 }
452
453 Line_spacer*
454 Spring_spacer::constructor()
455 {
456   return new Spring_spacer;
457 }
458
459
460
461 /**
462   get the shortest_playing running note at a time. */
463 void
464 Spring_spacer::get_ruling_durations(Array<Moment> &shortest_playing_arr,
465                                     Array<Moment> &context_shortest_arr)
466 {
467   for (int i=0; i < cols.size(); i++)
468     {
469       scol_l (i)->preprocess();
470       scol_l (i)->print ();
471     }
472   int start_context_i=0;
473   Moment context_shortest;
474   context_shortest.set_infinite (1);
475   context_shortest_arr.set_size(cols.size());
476
477   for (int i=0; i < cols.size(); i++)
478     {
479       Moment now = scol_l (i)->when();
480       Moment shortest_playing;
481       shortest_playing.set_infinite (1);
482
483       if (scol_l (i)->breakable_b_)
484         {
485           for (int ji=i; ji >= start_context_i; ji--)
486             context_shortest_arr[ji] = context_shortest;
487           start_context_i = i;
488           context_shortest.set_infinite (1);
489         }
490       if (scol_l (i)->durations.size())
491         {
492           context_shortest = context_shortest <? scol_l(i)->durations[0];
493         }
494       
495       // ji was j, but triggered ICE
496       for (int ji=i+1; ji --;)
497         {
498           if (scol_l(ji)->durations.size() &&
499               now - scol_l(ji)->when() >= shortest_playing)
500             break;
501
502           for (int k =  scol_l (ji)->durations.size();
503                k-- && scol_l(ji)->durations[k] + scol_l(ji)->when() > now;
504                )
505             {
506               shortest_playing = shortest_playing <? scol_l(ji)->durations[k];
507             }
508         }
509       shortest_playing_arr.push(shortest_playing);
510     }
511
512 #ifndef NPRINT
513   DOUT << "shortest_playing/:[ ";
514   for (int i=0; i < shortest_playing_arr.size(); i++)
515     {
516       DOUT << shortest_playing_arr[i] << " ";
517       DOUT << context_shortest_arr[i] << ", ";
518     }
519   DOUT << "]\n";
520 #endif
521 }
522
523 /**
524   generate springs between columns.
525
526   UNDER DESTRUCTION
527
528   TODO: This needs rethinking.  Spacing should take optical
529   effects into account, and should be local (measure wide)
530
531   The algorithm is taken from :
532
533   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
534   OSU-CISRC-10/87-TR35, Department of Computer and Information
535   Science, The Ohio State University, 1987.
536
537   */
538 void
539 Spring_spacer::calc_idealspacing()
540 {
541   Array<Moment> shortest_playing_arr;
542   Array<Moment> context_shortest_arr;
543   get_ruling_durations(shortest_playing_arr, context_shortest_arr);
544
545   Real interline_f = paper_l ()->interline_f ();
546   Real nw_f = paper_l ()->note_width ();
547
548   Array<Real> ideal_arr_;
549   Array<Real> hooke_arr_;
550   for (int i=0; i < cols.size() - 1; i++){
551     ideal_arr_.push (-1.0);
552     hooke_arr_.push (1.0);
553   }
554
555   /* 
556      First do all non-musical columns
557   */
558   for (int i=0; i < cols.size(); i++)
559     {
560       if (!scol_l (i)->musical_b() && i+1 < cols.size())
561         {
562           Real symbol_distance =cols[i].width_[RIGHT] + 2 PT;
563           Real durational_distance = 0;
564
565           
566           Moment delta_t =  scol_l (i+1)->when() - scol_l (i)->when () ;
567
568           Real k=  paper_l()->arithmetic_constant(context_shortest_arr[i]);
569           /*
570             ugh should use shortest_playing distance
571           */
572           if (delta_t)
573             durational_distance =  paper_l()->duration_to_dist (delta_t,k);
574           symbol_distance += -cols[i+1].width_[LEFT];
575  
576
577           ideal_arr_[i] = symbol_distance >? durational_distance;
578           hooke_arr_[i] = 1; //2.0;
579         }
580     }
581
582   /* 
583      Then musicals
584   */
585   for (int i=0; i < cols.size(); i++)
586     {
587       if (scol_l (i)->musical_b())
588         {
589           Moment shortest_playing_len = shortest_playing_arr[i];
590           Moment context_shortest = context_shortest_arr[i];
591           if (! shortest_playing_len)
592             {
593               warning (_("Can't find a ruling note at ")
594                        +scol_l (i)->when().str ());
595               shortest_playing_len = 1;
596             }
597           if (! context_shortest)
598             {
599               warning(_("No minimum in measure at ")
600                       + scol_l (i)->when().str ());
601               context_shortest = 1;
602             }
603           Moment delta_t = scol_l (i+1)->when() - scol_l (i)->when ();
604           Real k=  paper_l()->arithmetic_constant(context_shortest);
605           Real dist = paper_l()->duration_to_dist (shortest_playing_len, k);
606           dist *= delta_t / shortest_playing_len;
607
608           /*
609             According to [Ross] and [Wanske], and from what i've seen:
610              
611             * whitespace at the begin of the bar should be fixed at 
612             (about) one interline.
613             [Ross]:
614             when spacing gets real tight, a smaller fixed value may be 
615             used, so that there are two discrete amounts of whitespace 
616             possible at the begin of a bar; but this is not implemented 
617             right now.
618              
619             * whitespace at the end of the bar is the normal amount of 
620             "hinterfleish" that would have been used, had there been
621             yet another note in the bar.  
622             [Ross]:
623             some editors argue that the bar line should not take any 
624             space, not to hinder the flow of music spaced around a bar 
625             line.  
626             [Ross] and [Wanske] do not suggest this, however.  Further, 
627             it introduces some spacing problems and think that it is ugly 
628             too.
629             [jcn]
630           */
631
632           /* 
633              first musical column of bar
634           */
635           if (i && scol_l (i - 1)->breakable_b_)
636             {
637               // fixed: probably should set minimum (rod/spring)?
638               cols[i-1].width_[RIGHT] += interline_f;
639               // should adjust dist too?
640               ideal_arr_[i-1] = ideal_arr_[i-1] >? interline_f;
641             }
642
643           /* 
644              last musical column of bar
645           */
646           if (i + 1 < cols.size () && scol_l(i+1)->breakable_b_)
647             {
648               // hmm, how bout?
649               dist = dist >? interline_f;
650
651               /*
652                 uhuh, this code looks fine, already?
653                 someone was junking this last "hinterfleisch" whitespace?!
654
655                 but this seems to be fixed now :-)
656               */
657               // set minimum rod 
658               cols[i].width_[RIGHT] += interline_f;
659             }
660
661           // ugh, do we need this?
662           if (i < cols.size () - 1 && !scol_l (i + 1)->musical_b ())
663             {
664               Real minimum = -cols[i + 1].width_[LEFT] + cols[i].width_[RIGHT]
665                 + interline_f / 2;
666               dist = dist >? minimum;
667             }
668
669           // ugh: never let columns touch... try to set over here...
670           // ugh: use j iso i triggers ice in gcc-2.7.2.3 
671           cols[i].width_[LEFT] -= nw_f / 4;
672           ideal_arr_[i] = dist;
673         }
674     }
675
676   for (int i=0; i < ideal_arr_.size(); i++)
677     {
678       assert (ideal_arr_[i] >=0 && hooke_arr_[i] >=0);
679       connect (i, i+1, ideal_arr_[i], hooke_arr_[i]);
680     }
681 }