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