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