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