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