]> git.donarmstrong.com Git - lilypond.git/blob - lily/spring-spacer.cc
release: 0.1.24
[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 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
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())
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
156       if (cols[i].fixed()&&
157           abs (cols[i].fixed_position() - v (i)) > COLFUDGE)
158         return false;
159
160       if (!i)
161         continue;
162
163       Real mindist=cols[i-1].width_[RIGHT]
164         -cols[i].width_[LEFT];
165
166       // ugh... compares
167       Real dif =v (i) - v (i-1)- mindist;
168       bool b = (dif > - COLFUDGE);
169
170
171       if (!b)
172         return false;
173
174     }
175   return true;
176 }
177
178 bool
179 Spring_spacer::check_feasible() const
180 {
181   Vector sol (try_initial_solution());
182   return check_constraints (sol);
183 }
184
185 /// generate a solution which obeys the min distances and fixed positions
186 Vector
187 Spring_spacer::try_initial_solution() const
188 {
189   int dim=cols.size();
190   Vector initsol (dim);
191   for (int i=0; i < dim; i++)
192     {
193       if (cols[i].fixed())
194         {
195           initsol (i)=cols[i].fixed_position();
196
197           if (i > 0)
198             {
199               Real r =initsol (i-1)  + cols[i-1].width_[RIGHT];
200               if (initsol (i) < r)
201                 {
202                   warning (_("overriding fixed position"));
203                   initsol (i) =r;
204                 }
205             }
206
207         }
208       else
209         {
210           Real mindist=cols[i-1].width_[RIGHT]
211             - cols[i].width_[LEFT];
212           if (mindist < 0.0)
213             warning (_("Excentric column"));
214           initsol (i)=initsol (i-1)+mindist;
215         }
216     }
217
218   return initsol;
219 }
220
221
222
223 Vector
224 Spring_spacer::find_initial_solution() const
225 {
226   Vector v (try_initial_solution());
227   assert (check_constraints (v));
228   return v;
229 }
230
231 // generate the matrices
232 void
233 Spring_spacer::make_matrices (Matrix &quad, Vector &lin, Real &c) const
234 {
235   quad.fill (0);
236   lin.fill (0);
237   c = 0;
238
239   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
240     {
241       int l = i->left_i_;
242       int r = i->right_i_;
243
244       quad (r,r) += i->hooke_f_;
245       quad (r,l) -= i->hooke_f_;
246       quad (l,r) -= i->hooke_f_;
247       quad (l,l) += i->hooke_f_;
248
249       lin (r) -= i->space_f_*i->hooke_f_;
250       lin (l) += i->space_f_*i->hooke_f_;
251
252       c += sqr (i->space_f_);
253     }
254 }
255
256 void
257 Spring_spacer::set_fixed_cols (Mixed_qp &qp) const
258 {
259   for (int j=0; j < cols.size(); j++)
260     if (cols[j].fixed())
261       qp.add_fixed_var (j,cols[j].fixed_position());
262
263
264 }
265
266 // put the constraints into the LP problem
267 void
268 Spring_spacer::make_constraints (Mixed_qp& lp) const
269 {
270   int dim=cols.size();
271   for (int j=0; j < dim; j++)
272     {
273       Colinfo c=cols[j];
274       if (j > 0)
275         {
276           Vector c1(dim);
277
278           c1(j)=1.0 ;
279           c1(j-1)=-1.0 ;
280           lp.add_inequality_cons (c1,
281                                   cols[j-1].width_[RIGHT] - cols[j].width_[LEFT]);
282         }
283     }
284 }
285
286
287 Real
288 Spring_spacer::calculate_energy_f (Vector solution) const
289 {
290   Real e = 0.0;
291   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok(); i++)
292     {
293       e += i->energy_f(solution(i->right_i_) - solution(i->left_i_));
294     }
295
296   return e;
297 }
298 void
299 Spring_spacer::lower_bound_solution (Col_hpositions*positions) const
300 {
301   Mixed_qp lp (cols.size());
302   make_matrices (lp.quad,lp.lin, lp.const_term);
303   set_fixed_cols (lp);
304
305   Vector start (cols.size());
306   start.fill (0.0);
307   Vector solution_vec (lp.solve (start));
308
309   positions->energy_f_ = calculate_energy_f (solution_vec);
310   positions->config = solution_vec;
311   positions->satisfies_constraints_b_ = check_constraints (solution_vec);
312 }
313
314 void
315 Spring_spacer::solve (Col_hpositions*positions) const
316 {
317   assert (check_feasible());
318
319   Mixed_qp lp (cols.size());
320   make_matrices (lp.quad,lp.lin, lp.const_term);
321   make_constraints (lp);
322   set_fixed_cols (lp);
323   Vector start=find_initial_solution();
324   Vector solution_vec (lp.solve (start));
325
326
327   positions->satisfies_constraints_b_ = check_constraints (solution_vec);
328   if (!positions->satisfies_constraints_b_)
329     {
330       WARN << _("solution doesn't satisfy constraints.\n") ;
331     }
332   position_loose_cols (solution_vec);
333   positions->energy_f_ = calculate_energy_f (solution_vec);
334   positions->config = solution_vec;
335   positions->error_col_l_arr_ = error_pcol_l_arr();
336
337 }
338
339 /**
340   add one column to the problem.
341 */
342 void
343 Spring_spacer::add_column (Paper_column  *col, bool fixed, Real fixpos)
344 {
345   Colinfo c (col,(fixed)? &fixpos :  0);
346   if (cols.size())
347     c.rank_i_ = cols.top().rank_i_+1;
348   else
349     c.rank_i_ = 0;
350   cols.push (c);
351 }
352
353 Line_of_cols
354 Spring_spacer::error_pcol_l_arr() const
355 {
356   Array<Paper_column*> retval;
357   for (int i=0; i< cols.size(); i++)
358     if (cols[i].ugh_b_)
359       retval.push (cols[i].pcol_l_);
360   for (int i=0;  i < loose_col_arr_.size(); i++)
361     {
362       retval.push (loose_col_arr_[i].pcol_l_);
363     }
364   return retval;
365 }
366
367 void
368 Spring_spacer::loosen_column (int i)
369 {
370   Colinfo c=cols.get (i);
371   for (PCursor<Idealspacing*> j (ideal_p_list_.top()); j.ok (); j++)
372     {
373       if (j->left_i_ == i|| j->right_i_ == i)
374         j.del();
375       else
376         j++;
377     }
378   c.ugh_b_ = true;
379
380   int j=0;
381   for (; j < loose_col_arr_.size(); j++)
382     {
383       if (loose_col_arr_[j].rank_i_ > c.rank_i_)
384         break;
385     }
386   loose_col_arr_.insert (c,j);
387 }
388
389
390 void
391 Spring_spacer::print() const
392 {
393 #ifndef NPRINT
394   for (int i=0; i < cols.size(); i++)
395     {
396       DOUT << "col " << i<<' ';
397       cols[i].print();
398     }
399   for (PCursor<Idealspacing*> i (ideal_p_list_.top()); i.ok (); i++)
400     {
401       i->print();
402     }
403 #endif
404 }
405
406
407 void
408 Spring_spacer::connect (int i, int j, Real d, Real h)
409 {
410   assert(d >= 0 && d <= 100 CM);
411   assert(h >=0);
412
413   Idealspacing * s = new Idealspacing;
414   s->left_i_ = i;
415   s->right_i_ = j;
416   s->space_f_ = d;
417   s->hooke_f_ = h;
418
419   ideal_p_list_.bottom().add (s);
420 }
421
422
423
424
425 void
426 Spring_spacer::prepare()
427 {
428   calc_idealspacing();
429   handle_loose_cols();
430   print();
431 }
432
433 Line_spacer*
434 Spring_spacer::constructor()
435 {
436   return new Spring_spacer;
437 }
438
439
440
441 /**
442   get the shortest_playing running note at a time. */
443 void
444 Spring_spacer::get_ruling_durations(Array<Moment> &shortest_playing_arr,
445                                     Array<Moment> &context_shortest_arr)
446 {
447   for (int i=0; i < cols.size(); i++)
448     scol_l (i)->preprocess();
449
450   int start_context_i=0;
451   Moment context_shortest = infinity_mom;
452   context_shortest_arr.set_size(cols.size());
453
454   for (int i=0; i < cols.size(); i++)
455     {
456       Moment now = scol_l (i)->when();
457       Moment shortest_playing = infinity_mom;
458
459       if (scol_l (i)->breakable_b_)
460         {
461           for (int ji=i; ji >= start_context_i; ji--)
462             context_shortest_arr[ji] = context_shortest;
463           start_context_i = i;
464           context_shortest = infinity_mom;
465         }
466       if (scol_l (i)->durations.size())
467         {
468           context_shortest = context_shortest <? scol_l(i)->durations[0];
469         }
470       // ji was j, but triggered ICE
471       for (int ji=i+1; ji --;)
472         {
473           if (scol_l(ji)->durations.size() &&
474               now - scol_l(ji)->when() >= shortest_playing)
475             break;
476
477           for (int k =  scol_l (ji)->durations.size();
478                k-- && scol_l(ji)->durations[k] + scol_l(ji)->when() > now;
479                )
480             {
481               shortest_playing = shortest_playing <? scol_l(ji)->durations[k];
482             }
483         }
484       shortest_playing_arr.push(shortest_playing);
485     }
486
487 #ifndef NPRINT
488   DOUT << "shortest_playing/:[ ";
489   for (int i=0; i < shortest_playing_arr.size(); i++)
490     {
491       DOUT << shortest_playing_arr[i] << " ";
492       DOUT << context_shortest_arr[i] << ", ";
493     }
494   DOUT << "]\n";
495 #endif
496 }
497
498 /**
499   generate springs between columns.
500
501   UNDER DESTRUCTION
502
503   TODO: This needs rethinking.  Spacing should take optical
504   effects into account, and should be local (measure wide)
505
506   The algorithm is taken from :
507
508   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
509   OSU-CISRC-10/87-TR35, Department of Computer and Information
510   Science, The Ohio State University, 1987.
511
512   */
513 void
514 Spring_spacer::calc_idealspacing()
515 {
516   Array<Moment> shortest_playing_arr;
517   Array<Moment> context_shortest_arr;
518   get_ruling_durations(shortest_playing_arr, context_shortest_arr);
519
520
521   Array<Real> ideal_arr_;
522   Array<Real> hooke_arr_;
523   for (int i=0; i < cols.size(); i++){
524     ideal_arr_.push (-1.0);
525     hooke_arr_.push (1.0);
526   }
527
528   for (int i=0; i < cols.size(); i++)
529     {
530       if (!scol_l (i)->musical_b())
531         {
532           Real symbol_distance =cols[i].width_[RIGHT] + 2 PT;
533           Real durational_distance = 0;
534
535           if (i+1 < cols.size())
536             {
537               Moment delta_t =  scol_l (i+1)->when() - scol_l (i)->when () ;
538
539               Real k=  paper_l()->arithmetic_constant(context_shortest_arr[i]);
540               /*
541                 ugh should use shortest_playing distance
542                 */
543               if (delta_t)
544                 durational_distance =  paper_l()->duration_to_dist (delta_t,k);
545               symbol_distance += -cols[i+1].width_[LEFT];
546             }
547
548           ideal_arr_[i] = symbol_distance >? durational_distance;
549           hooke_arr_[i] = 2.0;
550         }
551     }
552   for (int i=0; i < cols.size(); i++)
553     {
554       if (scol_l (i)->musical_b())
555         {
556           Moment shortest_playing_len = shortest_playing_arr[i];
557           Moment context_shortest = context_shortest_arr[i];
558           if (! shortest_playing_len)
559             {
560               warning (_("Can't find a ruling note at ")
561                        +String (scol_l (i)->when()));
562               shortest_playing_len = 1;
563             }
564           if (! context_shortest)
565             {
566               warning(_("No minimum in measure at ")
567                       + String (scol_l (i)->when()));
568               context_shortest = 1;
569             }
570           Moment delta_t = scol_l (i+1)->when() - scol_l (i)->when ();
571           Real k=  paper_l()->arithmetic_constant(context_shortest);
572           Real dist = paper_l()->duration_to_dist (shortest_playing_len, k);
573           dist *= delta_t / shortest_playing_len;
574
575           /* all sorts of ugliness to avoid running into bars/clefs, but not taking
576              extra space if this is not needed */
577           if (!scol_l (i+1)->musical_b())
578             {
579               Real minimum_dist =  - cols[i+1].width_[LEFT] + 2 PT + cols[i].width_[RIGHT];
580               if (ideal_arr_[i+1] + minimum_dist < dist)
581                 {
582                   ideal_arr_[i] = dist - ideal_arr_[i+1];
583                   // hooke_arr_[i+1] =1.0;
584                 } else {
585                   ideal_arr_[i] = minimum_dist;
586                 }
587
588             } else
589               ideal_arr_[i] = dist;
590         }
591     }
592
593   for (int i=0; i < ideal_arr_.size()-1; i++)
594     {
595       assert (ideal_arr_[i] >=0 && hooke_arr_[i] >=0);
596       connect (i, i+1, ideal_arr_[i], hooke_arr_[i]);
597     }
598 }