]> git.donarmstrong.com Git - lilypond.git/blob - lily/new-spacing-spanner.cc
patch::: 1.5.4.jcn5
[lilypond.git] / lily / new-spacing-spanner.cc
1 /*   
2   spacing-spanner.cc --  implement Spacing_spanner
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 1999--2001 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   
8  */
9
10 #include "new-spacing-spanner.hh"
11 #include "paper-column.hh"
12 #include "dimensions.hh"
13 #include "paper-def.hh"
14 #include "warn.hh"
15 #include "paper-score.hh"
16 #include "line-of-score.hh"
17 #include "misc.hh"
18 #include "separation-item.hh"
19
20
21 void
22 New_spacing_spanner::set_interface (Grob*me)
23 {
24   me->set_extent_callback (SCM_EOL, X_AXIS);
25   me->set_extent_callback (SCM_EOL, Y_AXIS) ; 
26 }
27
28 /*
29
30   The algorithm is partly taken from :
31
32   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
33   OSU-CISRC-10/87-TR35, Department of Computer and Information
34   Science, The Ohio State University, 1987.
35
36   TOO HAIRY.
37
38   TODO: write comments 
39   
40  */
41 void
42 New_spacing_spanner::do_measure (Grob*me, Link_array<Item> const & cols) 
43 {
44   Moment shortest;
45   Moment mean_shortest;
46
47   /*
48     space as if this duration  is present. 
49    */
50   Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
51   shortest.set_infinite (1);
52
53   int n = 0;
54   for (int i =0 ; i < cols.size (); i++)  
55     {
56       if (Paper_column::musical_b (cols[i]))
57         {
58           Moment *when = unsmob_moment (cols[i]->get_grob_property  ("when"));
59
60           /*
61             ignore grace notes for shortest notes.
62            */
63           if (when && when->grace_part_)
64             continue;
65           
66           SCM  st = cols[i]->get_grob_property ("shortest-starter-duration");
67           Moment this_shortest = *unsmob_moment (st);
68           shortest = shortest <? this_shortest;
69           if (!mean_shortest.main_part_.infty_b ())
70             {
71               n++;
72               mean_shortest += this_shortest;
73             }
74         }
75     }
76   
77   Array<Spring> springs;
78
79   Item * first_col = 0;
80   for (int i= 0; i < cols.size () - 1; i++)
81     {
82       Item * l = dynamic_cast<Item*> (cols[i]);
83
84       if (!first_col && Paper_column::musical_b (l))
85         first_col = l;
86
87       SCM between = cols[i]->get_grob_property ("between-cols"); 
88       if (gh_pair_p (between)
89           && i > 0
90           && i < cols.size ()-1
91           && (gh_cdr (between) != cols[i+1]->self_scm ()
92            || gh_car (between) != cols[i-1]->self_scm ()) 
93           )
94         continue ;
95       
96       int j = i+1;
97       for (; j < cols.size () - 1; j++)
98         {
99           if  (Paper_column::musical_b (cols[j]))
100             break;
101
102           SCM between = cols[j]->get_grob_property ("between-cols");
103           if (!gh_pair_p (between))
104             continue;
105
106           if (gh_car (between) == cols[i]->self_scm () )
107             break ;
108         }
109
110       Item * r =  dynamic_cast<Item*> (cols[j]);
111       Paper_column * lc = dynamic_cast<Paper_column*> (l);
112       Paper_column *rc = dynamic_cast<Paper_column*> (r);
113
114
115       cout << "params for cols " << Paper_column::rank_i (l) << " " << Paper_column::rank_i (r) << endl;
116       cout << " musical: " << Paper_column::musical_b (l) << " " << Paper_column::musical_b (r) << endl;
117       if (!Paper_column::musical_b (l))
118         {
119           breakable_column_spacing (l, r);
120
121           l = l->find_prebroken_piece (RIGHT);
122           if (l)
123             breakable_column_spacing (l,r);
124
125           continue ; 
126         }
127       
128       Real note_space = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
129       Real hinterfleisch = note_space;
130       Real headwid = gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
131       
132       for (SCM s = lc->get_grob_property ("spacing-sequence"); gh_pair_p (s); s = gh_cdr (s))
133         {
134           Grob *lm = unsmob_grob (gh_caar (s));
135           Grob *rm = unsmob_grob (gh_cdar (s));
136
137           // TODO; configgable.
138           hinterfleisch += -headwid + Separation_item::my_width (lm)[RIGHT] -
139             0.5 * Separation_item::my_width (rm)[LEFT];
140
141
142           /*
143             UGH: KLUDGE!
144           */
145           
146           //      if (delta_t > Moment (1,32))
147           hinterfleisch += stem_dir_correction (me, l, r);
148         }
149
150       Real stretch_distance = note_space - headwid;
151       Spring s;
152       s.distance_f_ = hinterfleisch;
153       s.strength_f_ = 1 / stretch_distance;
154
155       s.item_l_drul_[LEFT] = l;
156       s.item_l_drul_[RIGHT] = r;
157
158       s.add_to_cols();
159       if (r->find_prebroken_piece (LEFT))
160         {
161           s.item_l_drul_[RIGHT] = r->find_prebroken_piece(LEFT);
162           s.add_to_cols();
163         }
164     }
165
166 }
167
168 /*
169   Read hints from L (todo: R) and generate springs.
170  */
171 void
172 New_spacing_spanner::breakable_column_spacing (Item* l, Item *r)
173 {
174   Spring s;
175
176   Real break_dist = 0.0;
177   SCM espace = l->get_grob_property ("extra-space");
178   if (gh_pair_p (espace))
179     break_dist += gh_scm2double (gh_cdr (espace));
180
181   if (!break_dist)
182     break_dist = 1.0;
183
184   Real break_stretch = 0.0;
185
186   // todo: naming of "distance"
187   espace = l->get_grob_property ("stretch-distance");
188   if (gh_pair_p (espace))
189     break_stretch += gh_scm2double (gh_cdr (espace));
190
191   if (!break_stretch)
192     break_stretch = 1.0;
193   
194   s.distance_f_ = break_dist;
195   s.strength_f_ = 1/break_stretch;
196   s.item_l_drul_[LEFT] = l;
197   s.item_l_drul_[RIGHT] = r;
198
199   s.add_to_cols ();
200 }
201
202 /*
203   Look at COLS, searching for columns that have 'regular-distance-to
204   set. A sequence of columns that have this property set should have
205   an equal distance (an equispaced run). Extract the projected
206   distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
207   widest space necessary.
208
209
210   TODO:
211   
212   -- inefficient code; maybe it is easier to twiddle with the springs
213   after they've become grob properties (ie. have their
214   minimum-distances set)
215
216   -- does not adjust strength field of the springs very well: result
217   awkward spacing at the start of a line. (?)
218
219   -- will be confused when there are multiple equispaced runs in a measure.
220
221   -- dealing with springs for line breaks is a little tricky; in any
222   case, we will only space per measure.
223
224   -- we scale to actual distances, not to optical effects. Eg. if the
225   equispaced run contains optical corrections, then the scaling will
226   cancel those.
227
228   -- Regular_spacing_engraver doesn't mark the first column of the
229   next bar, making the space before a barline too short, in this case
230
231
232        x<- 16ths--> x(8th)
233        x(8th)       x(8th)      <- equispaced run.      
234   
235 */
236
237 void
238 New_spacing_spanner::stretch_to_regularity (Grob *me,
239                                         Array<Spring> * springs,
240                                         Link_array<Item> const & cols)
241 {
242   /*
243     Find the starting column of the run. REGULAR-DISTANCE-TO points
244     back to a previous column, so we look ahead to find a column
245     pointing back to the first one.
246     
247    */
248   Grob    * first_regular_spaced_col = 0;
249   for (int i = 0 ;  i <  cols.size () && !first_regular_spaced_col; i++)
250     {
251       SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
252       if (cols.find_l (dynamic_cast<Item*> (unsmob_grob (rdt))))
253         first_regular_spaced_col = unsmob_grob (rdt);
254     }
255   for (int i = springs->size ();  i-- ;)
256     springs->elem (i).set_to_cols ();
257   
258   int i;
259   for (i = 0; i < springs->size ()
260          && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
261        i++)
262     ;
263
264
265   if (i==springs->size ())
266     return ;
267     
268   Real maxdist = 0.0;
269   Real dist  =0.0;
270   Grob *last_col = first_regular_spaced_col;
271   Grob *last_regular_spaced_col = first_regular_spaced_col;
272   
273
274   /*
275     find the max distance for this run. 
276    */
277   for (int j = i;  j < springs->size (); j++)
278     {
279       Spring *s = &(springs->elem_ref (j));
280       if (s->item_l_drul_[LEFT] != last_col)
281         continue;
282       
283       dist += s->distance_f_;
284
285       last_col = s->item_l_drul_[RIGHT];
286       SCM rdt = last_col->get_grob_property ("regular-distance-to");
287       if (unsmob_grob (rdt) == last_regular_spaced_col)
288         {
289           maxdist = maxdist >? dist;
290           dist = 0.0;
291           last_regular_spaced_col = last_col;
292         }
293
294     }
295
296   /*
297     Scale the springs
298    */
299   dist =0.0;
300   last_col =  first_regular_spaced_col;
301   last_regular_spaced_col = first_regular_spaced_col;
302   for (int j = i;   j < springs->size (); j++)
303     {
304       Spring *s = &springs->elem_ref (j);
305       if (s->item_l_drul_[LEFT] != last_col)
306         continue;
307       dist += s->distance_f_;
308
309       last_col = s->item_l_drul_[RIGHT];
310       SCM rdt = last_col->get_grob_property ("regular-distance-to");
311       if (unsmob_grob (rdt) == last_regular_spaced_col)
312         {
313           do {
314             springs->elem_ref (i).distance_f_ *= maxdist / dist;
315             springs->elem_ref (i).strength_f_ *= dist / maxdist;            
316           } while (i++ < j);
317           last_regular_spaced_col = last_col;
318           dist =0.0;
319         }
320     }
321 }
322
323 /**
324    Do something if breakable column has no spacing hints set.
325  */
326 Real
327 New_spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
328                                       Moment shortest) 
329 {
330   Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
331   Real durational_distance = 0;
332   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
333
334   /*
335                 ugh should use shortest_playing distance
336   */
337   if (delta_t.to_bool ())
338     {
339       durational_distance =  get_duration_space (me, delta_t, shortest);
340     }
341
342   return  symbol_distance >? durational_distance;
343 }
344
345
346 /**
347   Get the measure wide ant for arithmetic spacing.
348
349   @see
350   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
351   OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
352   The Ohio State University, 1987.
353
354   */
355 Real
356 New_spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest) 
357 {
358   Real log =  log_2 (shortest.main_part_);
359   Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
360     - log;
361
362   Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
363   
364   return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
365 }
366
367
368 Real
369 New_spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
370                                Moment shortest) 
371 {
372   Moment shortest_playing_len = 0;
373   SCM s = lc->get_grob_property ("shortest-playing-duration");
374
375   //  SCM s = lc->get_grob_property ("mean-playing-duration");  
376   if (unsmob_moment (s))
377     shortest_playing_len = *unsmob_moment (s);
378   
379   if (! shortest_playing_len.to_bool ())
380     {
381       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
382       shortest_playing_len = 1;
383     }
384   
385   if (! shortest.to_bool ())
386     {
387       programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
388       shortest = 1;
389     }
390   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
391   Real dist = get_duration_space (me, shortest_playing_len, shortest);
392
393
394   /*
395     ugh: 0.1 is an arbitrary distance.
396    */
397   dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
398     + 0.1 * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
399
400
401
402   Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
403   Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
404
405   if (lm && rm)
406     {
407       if (lm->grace_part_ && rm->grace_part_)
408         dist *= 0.5;
409       else if (!rm->grace_part_ && lm->grace_part_)
410         dist *= 0.7;
411     }
412
413   
414   return dist;
415 }
416
417
418 /**
419    Correct for optical illusions. See [Wanske] p. 138. The combination
420    up-stem + down-stem should get extra space, the combination
421    down-stem + up-stem less.
422
423    This should be more advanced, since relative heights of the note
424    heads also influence required correction.
425
426    Also might not work correctly in case of multi voices or staff
427    changing voices
428
429    TODO: lookup correction distances?  More advanced correction?
430    Possibly turn this off?
431
432    TODO: have to check wether the stems are in the same staff.
433
434    This routine reads the DIR-LIST property of both its L and R arguments.  */
435 Real
436 New_spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r) 
437 {
438   SCM dl = l->get_grob_property ("dir-list");
439   SCM dr = r->get_grob_property ("dir-list");
440   
441   if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
442     return 0.;
443
444   dl = gh_car (dl);
445   dr = gh_car (dr);
446
447   assert (gh_number_p (dl) && gh_number_p (dr));
448   int d1 = gh_scm2int (dl);
449   int d2 = gh_scm2int (dr);
450
451   if (d1 == d2)
452     return 0.0;
453
454
455   Real correction = 0.0;
456   Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
457
458   if (d1 && d2 && d1 * d2 == -1)
459     {
460       correction = d1 * ssc;
461     }
462   else
463     programming_error ("Stem directions not set correctly for optical correction");
464   return correction;
465 }
466   
467
468 MAKE_SCHEME_CALLBACK (New_spacing_spanner, set_springs,1);
469 SCM
470 New_spacing_spanner::set_springs (SCM smob)
471 {
472   Grob *me = unsmob_grob (smob);
473   Link_array<Item> all (me->pscore_l_->line_l_->column_l_arr ()) ;
474
475   int j = 0;
476
477   for (int i = 1; i < all.size (); i++)
478     {
479       Grob *sc = all[i];
480       if (Item::breakable_b (sc))
481         {
482           Link_array<Item> measure (all.slice (j, i+1));          
483           do_measure (me, measure);
484           j = i;
485         }
486     }
487
488   /*
489     farewell, cruel world
490    */
491   me->suicide ();
492   return SCM_UNSPECIFIED;
493 }
494
495
496
497 /*
498   maximum-duration-for-spacing
499 From: bf250@freenet.carleton.ca (John Sankey)
500 To: gnu-music-discuss@gnu.org
501 Subject: note spacing suggestion
502 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
503
504 Currently, Lily spaces notes by starting with a basic distance,
505 arithmetic_multiplier, which it applies to the minimum duration note
506 of the bar. Then she adds a logarithmic increment, scaled from
507 arithmetic_basicspace, for longer notes. (Then, columns are aligned
508 and justified.) Fundamentally, this matches visual spacing to musical
509 weight and works well.
510
511 A lot of the time in music, I see a section basically in melodic
512 notes that occasionally has a rapid ornamental run (scale). So, there
513 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
514 a return to long notes. Currently, Lily gives the same horizontal
515 space to the 1/32nd notes in their bar (even if set in small size as
516 is commonly done for cadenzii) as she gives to 1/4 notes in bars
517 where 1/4 note is the minimum duration. The resulting visual weight
518 does not match the musical weight over the page.
519
520 Looking at the music I am typesetting, I feel that Lily's spacing
521 could be significantly improved if, with no change in the basic
522 method used, arithmetic_multiplier could be applied referred to the
523 same duration throughout a piece. Of course, the current method
524 should be retained for those who have already set music in it, so I
525 suggest a property called something like arithmetic_base=16 to fix
526 1/16 duration as the reference for arithmetic_multiplier; the default
527 would be a dynamic base is it is now.
528
529 Does anyone else feel that this would be a useful improvement for
530 their music? (Of course, if arithmetic_multiplier became a regular
531 property, this could be used to achieve a similar result by
532 tweaking.)
533   
534  */