]> git.donarmstrong.com Git - lilypond.git/blob - lily/third-try.cc
release: 1.5.36
[lilypond.git] / lily / third-try.cc
1 /*   
2   spacing-spanner.cc -- implement Spacing_spanner
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 1999--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   
8  */
9 #include "line-of-score.hh"
10 #include "paper-score.hh"
11 #include "paper-column.hh"
12 #include "item.hh"
13 #include "moment.hh"
14 #include "note-spacing.hh"
15 #include "misc.hh"
16 #include "warn.hh"
17
18 /*
19   paper-column:
20
21     right-neighbors = List of spacing-wish grobs that are close to the
22     current column.
23
24
25     left-neighbors = idem
26
27     (TODO: property-doc these!)
28
29   Don't be confused by right-items: each spacing wish can also contain
30   a number of items, with which a spacing constraint may be kept. It's
31   a little baroque, but it might come in handy later on?
32     
33  */
34
35 class Third_spacing_spanner
36 {
37 public:
38   static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment)  ;
39   static Real note_spacing (Grob*,Grob*,Grob*,Moment)  ;
40   static Real get_duration_space (Grob*,Moment dur, Moment shortest) ;
41   
42   static void breakable_column_spacing (Item* l, Item *r);
43   static void find_loose_columns () {}
44   static void prune_loose_colunms (Link_array<Grob> *cols);
45   static void find_loose_columns (Link_array<Grob> cols);
46   static void set_explicit_neighbor_columns (Link_array<Grob> cols);
47   static void set_implicit_neighbor_columns (Link_array<Grob> cols);
48   static void do_measure (Grob*me,Link_array<Grob> *cols);
49   DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
50 };
51
52
53
54 /*
55   Return whether COL is fixed to its neighbors by some kind of spacing
56   constraint.
57 */
58 static bool
59 loose_column (Grob *l, Grob *c, Grob *r) 
60 {
61   SCM rns = c->get_grob_property ("right-neighbors");
62   SCM lns = c->get_grob_property ("left-neighbors");
63
64  /*
65     If this column doesn't have a proper neighbor, we should really
66     make it loose, but spacing it correctly is more than we can
67     currently can handle.
68
69     (this happens in the following situation:
70
71        |
72        |    clef G 
73       *
74
75        |               |      ||
76        |               |      || 
77       O               O       ||
78
79
80     the column containing the clef is really loose, and should be
81     attached right to the first column, but that is a lot of work for
82     such a borderline case.
83
84     )
85     
86   */  
87   if (!gh_pair_p (lns) || !gh_pair_p (rns))
88     return false;
89
90   Item * l_neighbor = dynamic_cast<Item*>  (unsmob_grob (gh_car (lns)));
91   Item * r_neighbor = dynamic_cast<Item*>  (unsmob_grob (gh_car (rns)));
92
93   if (!l_neighbor || !r_neighbor)
94     return false;
95
96   l_neighbor = l_neighbor->column_l();
97   r_neighbor = dynamic_cast<Item*> (Note_spacing::right_column  (r_neighbor));
98
99   if (l == l_neighbor && r == r_neighbor)
100     return false;
101
102   if (!l_neighbor || !r_neighbor)
103     return false;
104
105   /*
106     Only declare loose if the bounds make a little sense.  This means
107     some cases (two isolated, consecutive clef changes) won't be
108     nicely folded, but hey, then don't do that.
109   */
110   if( (Paper_column::musical_b (l_neighbor) || Item::breakable_b (l_neighbor))
111       && (Paper_column::musical_b (r_neighbor) || Item::breakable_b (r_neighbor)))
112     {
113       return true;
114     }
115
116
117   /*
118     If in doubt: we're not loose; the spacing engine should space for
119     it, risking suboptimal spacing.
120
121     (Otherwise, we might risk core dumps, and other weird stuff.)
122
123
124   */
125   return false;
126 }
127
128 /*
129   Remove columns that are not tightly fitting from COLS. In the
130   removed columns, set 'between-cols to the columns where it is in
131   between.
132 */
133 void
134 Third_spacing_spanner::prune_loose_colunms (Link_array<Grob> *cols)
135 {
136   Link_array<Grob> newcols;
137   
138   for (int i=0; i < cols->size ();  i++)
139     {
140       if (Item::breakable_b (cols->elem(i)) || Paper_column::musical_b (cols->elem (i)))
141         {
142           newcols.push (cols->elem(i));
143           continue;
144         }
145
146       Grob *c = cols->elem(i);
147       if (loose_column (cols->elem (i-1), c, cols->elem (i+1)))
148         {
149           SCM lns = c->get_grob_property ("left-neighbors");
150           lns = gh_pair_p (lns) ? gh_car (lns) : SCM_BOOL_F;
151
152           SCM rns = c->get_grob_property ("right-neighbors");
153           rns = gh_pair_p (rns) ? gh_car (rns) : SCM_BOOL_F;
154
155           /*
156             Either object can be non existent, if the score ends
157             prematurely.
158            */
159           rns = gh_car (unsmob_grob (rns)->get_grob_property ("right-items"));
160           c->set_grob_property ("between-cols", gh_cons (lns,
161                                                          rns));
162
163           /*
164             Set distance constraints for loose columns
165           */
166           Drul_array<Grob*> next_door;
167           next_door[LEFT] =cols->elem (i - 1);
168           next_door[RIGHT] =cols->elem (i + 1);   
169           Direction d = LEFT;
170           Drul_array<Real> dists(0,0);
171
172           do
173             {
174               dists[d] = 0.0;
175               Grob *lc = d == LEFT  ? lc : c;
176               Grob *rc = d == LEFT  ? c : rc;         
177               
178               for (SCM s = lc->get_grob_property ("spacing-wishes");
179                    gh_pair_p (s); s = gh_cdr (s))
180                 {
181                   Grob *sp = unsmob_grob (gh_car (s));
182                   if (Note_spacing::left_column (sp) != lc
183                       || Note_spacing::right_column (sp) != rc)
184                     continue;
185
186                   dists[d] = dists[d] >? Note_spacing::get_spacing (sp);
187                 }
188             }
189           while (flip (&d) != LEFT);
190
191           Rod r;
192           r.distance_f_ = dists[LEFT] + dists[RIGHT];
193           r.item_l_drul_[LEFT] = dynamic_cast<Item*> (cols->elem(i-1));
194           r.item_l_drul_[RIGHT] = dynamic_cast<Item*> (cols->elem (i+1));
195
196           r.add_to_cols ();
197         }
198       else
199         {
200           newcols.push (c);
201         }
202     }
203
204   *cols = newcols;
205 }
206
207 /*
208   Set neighboring columns determined by the spacing-wishes grob property.  
209 */
210 void
211 Third_spacing_spanner::set_explicit_neighbor_columns (Link_array<Grob> cols)
212 {
213   for (int i=0; i < cols.size(); i++)
214     {
215       SCM right_neighbors = SCM_EOL;
216       int min_rank = 100000;    // inf.
217
218
219       SCM wishes=  cols[i]->get_grob_property ("spacing-wishes");
220       for (SCM s =wishes; gh_pair_p (s); s = gh_cdr (s))
221         {
222           Item * wish = dynamic_cast<Item*> (unsmob_grob (gh_car (s)));
223
224           Item * lc = wish->column_l ();
225           Grob * right = Note_spacing::right_column (wish);
226
227           if (!right)
228             continue;
229
230           Item * rc = dynamic_cast<Item*> (right);
231
232           int right_rank = Paper_column::rank_i (rc);
233           int left_rank = Paper_column::rank_i (lc);      
234
235           /*
236             update the left column.
237            */
238           if (right_rank <= min_rank)
239             {
240               if (right_rank < min_rank)
241                 right_neighbors  =SCM_EOL;
242               
243               min_rank = right_rank;
244               right_neighbors = gh_cons (wish->self_scm (), right_neighbors);
245             }
246
247           /*
248             update the right column of the wish.
249            */
250           int maxrank = 0;
251           SCM left_neighs = rc->get_grob_property ("left-neighbors");
252           if (gh_pair_p (left_neighs)
253               && unsmob_grob (gh_car (left_neighs)))
254             {
255               Item * it = dynamic_cast<Item*> (unsmob_grob (gh_car (left_neighs)));
256               maxrank = Paper_column::rank_i (it->column_l());
257             }
258
259           if (left_rank >= maxrank)
260             {
261               if (left_rank > maxrank)
262                 left_neighs = SCM_EOL;
263
264               left_neighs = gh_cons (wish->self_scm (), left_neighs);
265               rc->set_grob_property ("left-neighbors", right_neighbors);
266             }
267         }
268
269       if (gh_pair_p (right_neighbors))
270         {
271           cols[i]->set_grob_property ("right-neighbors", right_neighbors);
272         }
273     }
274 }
275
276 /*
277   Set neighboring columns that have no left/right-neighbor set
278   yet. Only do breakable non-musical columns, and musical columns. 
279 */
280 void
281 Third_spacing_spanner::set_implicit_neighbor_columns (Link_array<Grob> cols)
282 {
283   for (int i = 0; i < cols.size (); i++)
284     {
285       Item * it = dynamic_cast<Item*>(cols[i]);
286       if (!Item::breakable_b (it) && !Paper_column::musical_b (it))
287         continue;
288
289       // it->breakable || it->musical
290
291       /*
292         sloppy with typnig left/right-neighbors should take list, but paper-column found instead.
293        */
294       SCM ln = cols[i] ->get_grob_property ("left-neighbors");
295       if (!gh_pair_p (ln) && i ) 
296         {
297           cols[i]->set_grob_property ("left-neighbors", gh_cons (cols[i-1]->self_scm(), SCM_EOL));
298         }
299
300       SCM rn = cols[i] ->get_grob_property ("right-neighbors");
301       if (!gh_pair_p (rn) && i < cols.size () - 1) 
302         {
303           cols[i]->set_grob_property ("right-neighbors", gh_cons (cols[i + 1]->self_scm(), SCM_EOL));
304         }
305     }
306 }
307
308
309 MAKE_SCHEME_CALLBACK (Third_spacing_spanner, set_springs,1);
310 SCM
311 Third_spacing_spanner::set_springs (SCM smob)
312 {
313   Grob *me = unsmob_grob (smob);
314
315   Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
316
317   set_explicit_neighbor_columns (all);
318   prune_loose_colunms (&all);
319   set_implicit_neighbor_columns (all);
320   
321   int j = 0;
322   for (int i = 1; i < all.size (); i++)
323     {
324       Grob *sc = all[i];
325       if (Item::breakable_b (sc))
326         {
327           Link_array<Grob> measure (all.slice (j, i+1));          
328           do_measure (me, &measure);
329           j = i;
330         }
331     }
332
333   return SCM_UNSPECIFIED;
334 }
335
336
337 void
338 Third_spacing_spanner::do_measure (Grob*me, Link_array<Grob> *cols) 
339 {
340   Moment shortest_in_measure;
341
342   /*
343     space as if this duration  is present. 
344   */
345   Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
346   shortest_in_measure.set_infinite (1);
347
348   for (int i =0 ; i < cols->size (); i++)  
349     {
350       if (Paper_column::musical_b (cols->elem (i)))
351         {
352           Moment *when = unsmob_moment (cols->elem (i)->get_grob_property  ("when"));
353
354           /*
355             ignore grace notes for shortest notes.
356           */
357           if (when && when->grace_part_)
358             continue;
359           
360           SCM  st = cols->elem (i)->get_grob_property ("shortest-starter-duration");
361           Moment this_shortest = *unsmob_moment (st);
362           shortest_in_measure = shortest_in_measure <? this_shortest;
363         }
364     }
365   
366   Array<Spring> springs;
367
368   for (int i= 0; i < cols->size () - 1; i++)
369     {
370       Item * l = dynamic_cast<Item*> (cols->elem (i));
371       Item * r =  dynamic_cast<Item*> (cols->elem (i+1));
372
373       Paper_column * lc = dynamic_cast<Paper_column*> (l);
374       Paper_column * rc = dynamic_cast<Paper_column*> (r);
375
376       if (!Paper_column::musical_b (l))
377         {
378           breakable_column_spacing (l, r);
379
380           /*
381             
382             The case that the right part is broken as well is rather
383             rare, but it is possible, eg. with a single empty measure,
384             or if one staff finishes a tad earlier than the rest.
385             
386            */
387           Item *lb = l->find_prebroken_piece (RIGHT);
388           Item *rb = r->find_prebroken_piece (LEFT);
389           
390           if (lb)
391             breakable_column_spacing (lb,r);
392
393           if (rb)
394             breakable_column_spacing (l, rb);
395           if (lb && rb)
396             breakable_column_spacing (lb, rb);
397           
398           continue ; 
399         }
400       
401       Real note_space = note_spacing (me,lc, rc, shortest_in_measure <? base_shortest_duration);
402       Real hinterfleisch = note_space;
403       Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
404
405       SCM seq  = lc->get_grob_property ("right-neighbors");
406
407       /*
408         hinterfleisch = hind-meat = amount of space following a note.
409
410         
411         We adjust the space following a note only if the next note
412         happens after the current note (this is set in the grob
413         property SPACING-SEQUENCE.  */
414
415       Real stretch_distance = note_space;
416
417       hinterfleisch = -1.0;
418       Real max_factor = 0.0;
419       for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
420         {
421           Grob * wish = unsmob_grob (gh_car (s));
422
423           if (Note_spacing::left_column (wish) != lc
424               || Note_spacing::right_column (wish) != rc)
425             continue;
426
427           /*
428             This is probably a waste of time in the case of polyphonic
429             music.  */
430           if (Note_spacing::has_interface (wish))
431             {
432               hinterfleisch = hinterfleisch >?
433                 ( - headwid +
434
435                   (note_space + Note_spacing::get_spacing (wish))
436                   *gh_scm2double (wish->get_grob_property ("space-factor"))
437
438                   + Note_spacing::stem_dir_correction (wish));
439             }
440         }
441
442       if (hinterfleisch < 0)
443         {
444           // maybe should issue a programming error.
445           hinterfleisch = note_space;
446         }
447       else
448         stretch_distance -= headwid; // why?
449
450       if (max_factor == 0.0)
451         max_factor = 1.0; 
452       
453       Spring s;
454       s.distance_f_ = max_factor *  hinterfleisch;
455       s.strength_f_ = 1 / stretch_distance;
456
457       s.item_l_drul_[LEFT] = l;
458       s.item_l_drul_[RIGHT] = r;
459
460       s.add_to_cols();
461       if (r->find_prebroken_piece (LEFT))
462         {
463           s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
464           s.add_to_cols();
465         }
466     }
467
468 }
469
470
471 /*
472   Read hints from L (todo: R) and generate springs.
473  */
474 void
475 Third_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
476 {
477   Spring s;
478
479   Real break_dist = 0.0;
480   SCM espace = l->get_grob_property ("extra-space");
481   if (gh_pair_p (espace))
482     break_dist += gh_scm2double (ly_cdr (espace));
483
484   if (!break_dist)
485     break_dist = 1.0;
486
487   Real break_stretch = 0.0;
488
489   // todo: naming of "distance"
490   espace = l->get_grob_property ("stretch-distance");
491   if (gh_pair_p (espace))
492     break_stretch += gh_scm2double (ly_cdr (espace));
493
494   if (!break_stretch)
495     break_stretch = 1.0;
496   
497   s.distance_f_ = break_dist;
498   s.strength_f_ = 1/break_stretch;
499   s.item_l_drul_[LEFT] = l;
500   s.item_l_drul_[RIGHT] = r;
501
502   s.add_to_cols ();
503 }
504
505
506 /**
507   Get the measure wide ant for arithmetic spacing.
508
509   @see
510   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
511   OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
512   The Ohio State University, 1987.
513
514   */
515 Real
516 Third_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest) 
517 {
518   Real log =  log_2 (shortest.main_part_);
519   Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
520     - log;
521
522   Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
523   
524   return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
525 }
526
527
528 Real
529 Third_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
530                                    Moment shortest) 
531 {
532   Moment shortest_playing_len = 0;
533   SCM s = lc->get_grob_property ("shortest-playing-duration");
534
535
536   if (unsmob_moment (s))
537     shortest_playing_len = *unsmob_moment (s);
538   
539   if (! shortest_playing_len.to_bool ())
540     {
541       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
542       shortest_playing_len = 1;
543     }
544   
545   if (! shortest.to_bool ())
546     {
547       programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
548       shortest = 1;
549     }
550   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
551   Real dist = 0.0;
552
553   if (delta_t.main_part_)
554     {
555       dist = get_duration_space (me, shortest_playing_len, shortest);
556       dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
557     }
558   else if (delta_t.grace_part_)
559     {
560       dist = get_duration_space (me, shortest, shortest);
561
562       Real grace_fact = 1.0;
563       SCM gf = me->get_grob_property ("grace-space-factor");
564       if (gh_number_p (gf))
565         grace_fact = gh_scm2double (gf);
566
567       dist *= grace_fact; 
568     }
569
570 #if 0
571   /*
572     TODO: figure out how to space grace notes.
573    */
574
575   dist *= 
576     +  grace_fact * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
577
578
579   Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
580   Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
581
582   if (lm && rm)
583     {
584       if (lm->grace_part_ && rm->grace_part_)
585         dist *= 0.5;
586       else if (!rm->grace_part_ && lm->grace_part_)
587         dist *= 0.7;
588     }
589 #endif
590   
591   return dist;
592 }
593