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