]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-spanner.cc
1bff342744f9b8205f725f61857074a99d161fe9
[lilypond.git] / lily / 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 "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
19 void
20 Spacing_spanner::set_interface (Grob*me)
21 {
22   me->set_extent_callback (SCM_EOL, X_AXIS);
23   me->set_extent_callback (SCM_EOL, Y_AXIS) ; 
24 }
25
26 #if 0  
27 struct Note_run
28 {
29   Array<int> idxes;
30   int start, end;
31   Moment duration;
32   int count;
33 };
34
35 int
36 column_compare (Grob  *const &t1, Grob *const &t2)
37 {
38   return Moment::compare (Paper_column::when_mom (t1),
39                           Paper_column::when_mom (t2));
40 }
41
42
43 Note_run
44 run_length (Moment dt, int i, Array<Moment> const &moms,
45             Link_array<Note_run> runs)
46 {
47   int k = 0;
48   Array<int> idxes;
49
50   idxes.push (i);
51   while (1)
52     {
53       Moment next = moms[i] + dt;
54       while (i < moms.size () && moms[i] < next)
55         i++;
56       if (i == moms.size () || moms[i] != next)
57         break;
58
59       idxes.push (i);
60       k++;
61     }
62
63   Moment dur = idxes.size ()
64 }
65
66 void
67 find_runs (Grob*me, Link_array<Grob> cols) 
68 {
69   Link_array<Grob> filter_cols;
70   Array<Moment> col_moments;
71   for (int i = 0; i < cols.size (); i++)
72     {
73       Moment w =  Paper_column::when_mom (cols[i]);
74       
75       if (!w.grace_mom_ && Paper_column::musical_b (cols[i]))
76         {
77           filter_cols.push (cols[i]);
78           col_moments.push (w);
79         }
80     }
81
82   Moment end_mom = col_moments.top ();
83   for (int i = 0; i < col_moments.size () ; i++)
84     {
85       for (int j = i+1; j < col_moments.size (); j++)
86         {
87           Moment dt = Paper_column::col_momentsfilter_cols 
88         }
89     }
90 }
91 #endif  
92
93 /*
94
95   The algorithm is partly taken from :
96
97   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
98   OSU-CISRC-10/87-TR35, Department of Computer and Information
99   Science, The Ohio State University, 1987.
100
101   TOO HAIRY.
102
103   TODO: write comments 
104   
105  */
106 void
107 Spacing_spanner::do_measure (Grob*me, Link_array<Grob> cols) 
108 {
109   Moment shortest;
110   Moment mean_shortest;
111
112   /*
113     space as if this duration  is present. 
114    */
115   Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
116   shortest.set_infinite (1);
117
118   int n = 0;
119   for (int i =0 ; i < cols.size (); i++)  
120     {
121       if (Paper_column::musical_b (cols[i]))
122         {
123           Moment *when = unsmob_moment (cols[i]->get_grob_property  ("when"));
124
125           /*
126             ignore grace notes for shortest notes.
127            */
128           if (when && when->grace_mom_)
129             continue;
130           
131           SCM  st = cols[i]->get_grob_property ("shortest-starter-duration");
132           Moment this_shortest = *unsmob_moment (st);
133           shortest = shortest <? this_shortest;
134           if (!mean_shortest.main_part_.infty_b ())
135             {
136               n++;
137               mean_shortest += this_shortest;
138             }
139         }
140     }
141   mean_shortest /= n;
142
143   for (int i= 0; i < cols.size () - 1; i++)
144     {
145       Item * l = dynamic_cast<Item*> (cols[i]);
146       Item * r =  dynamic_cast<Item*> (cols[i+1]);
147       Item * lb = dynamic_cast<Item*> (l->find_prebroken_piece (RIGHT));
148       Item * rb = dynamic_cast<Item*> (r->find_prebroken_piece (LEFT));
149
150       Item* combinations[4][2]={{l,r}, {lb,r}, {l,rb},{lb,rb}};
151
152
153       /*
154         left refers to the space that is associated with items of the left column, so you have
155
156           LC  <- left_space -><- right_space -> RC
157               <-    total space              ->
158               
159
160         typically, right_space is non-zero when there are
161         accidentals in RC
162           
163        */
164       for (int j=0; j < 4; j++)
165         {
166           Paper_column * lc = dynamic_cast<Paper_column*> (combinations[j][0]);
167           Paper_column *rc = dynamic_cast<Paper_column*> (combinations[j][1]);
168           if (!lc || !rc)
169             continue;
170
171           Spring s;
172           s.item_l_drul_[LEFT] = lc;
173           s.item_l_drul_[RIGHT] = rc;
174           
175           SCM hint = lc->get_grob_property ("extra-space");
176           SCM next_hint = rc->get_grob_property ("extra-space");
177           SCM stretch_hint = lc->get_grob_property ("stretch-distance");
178           SCM next_stretch_hint = rc->get_grob_property ("stretch-distance");     
179
180           Real left_distance = 0;
181           if (gh_pair_p (hint))
182             {
183               left_distance = gh_scm2double (gh_cdr (hint)); 
184             }
185            // 2nd condition should be (i+1 < col_count ()), ie. not the last column in score.  FIXME
186           else if (!Paper_column::musical_b (lc) && i+1 < cols.size ()) 
187             {
188               left_distance= default_bar_spacing (me,lc,rc,shortest <? base_shortest_duration);
189             }
190           else if (Paper_column::musical_b ( lc))
191             {
192               left_distance  = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
193             }
194           else
195               programming_error ("uninitialised left_distance");
196           
197           s.distance_f_ = left_distance;
198
199           /*
200             Only do tight spaces *after* barlines (breakable columns),
201             not before.
202
203             We want the space before barline to be like the note
204             spacing in the measure.
205           */
206           SCM sfac =lc->get_grob_property ("space-factor");
207           if (gh_number_p (lc->get_grob_property ("column-space-strength"))
208               && (Item::breakable_b (lc) || lc->original_l_))
209             {
210               s.strength_f_ =
211                 gh_scm2double (lc->get_grob_property ("column-space-strength"));
212             }
213           else if (gh_number_p (sfac))
214             left_distance *= gh_scm2double (sfac);
215
216           
217           Real right_dist = 0.0;
218           if (gh_pair_p (next_hint))
219             {
220               right_dist += - gh_scm2double (gh_car (next_hint));
221             }
222           else
223             {
224               Interval ext (rc->extent (rc, X_AXIS));
225               right_dist =  ext.empty_b () ? 0.0 : - ext [LEFT];
226             }
227
228           /*
229             don't want to create too much extra space for accidentals
230           */
231           if (Paper_column::musical_b (rc))
232            {
233               if (to_boolean (rc->get_grob_property ("contains-grace")))
234                 right_dist *= gh_scm2double (rc->get_grob_property ("before-grace-spacing-factor")); // fixme.
235               else
236                 right_dist *= gh_scm2double (lc->get_grob_property ("before-musical-spacing-factor"));
237            }
238
239           s.distance_f_ = left_distance + right_dist;
240             
241           Real stretch_dist = 0.;
242           if (gh_number_p (stretch_hint))
243             stretch_dist += gh_scm2double (stretch_hint);
244           else
245             stretch_dist += left_distance;
246           
247           if (gh_pair_p (next_stretch_hint))
248             // see regtest spacing-tight
249             stretch_dist += - gh_scm2double (gh_car (next_stretch_hint));
250           else
251             stretch_dist += right_dist;
252
253           if (s.distance_f_ <0)
254             {
255               programming_error ("Negative dist, setting to 1.0 PT");
256               s.distance_f_ = 1.0;
257             }
258           if (stretch_dist == 0.0)
259             {
260               /*
261                 \bar "".  We give it 0 space, with high strength. 
262                */
263               s.strength_f_ = 20.0; 
264             }
265           else
266             s.strength_f_ /= stretch_dist;
267           
268           s.add_to_cols ();
269         }
270     }
271   
272 }
273
274 /**
275    Do something if breakable column has no spacing hints set.
276  */
277 Real
278 Spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
279                                       Moment shortest) 
280 {
281   Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
282   Real durational_distance = 0;
283   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
284
285   /*
286                 ugh should use shortest_playing distance
287   */
288   if (delta_t)
289     {
290       durational_distance =  get_duration_space (me, delta_t, shortest);
291     }
292
293   return  symbol_distance >? durational_distance;
294 }
295
296
297 /**
298   Get the measure wide ant for arithmetic spacing.
299
300   @see
301   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
302   OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
303   The Ohio State University, 1987.
304
305   */
306 Real
307 Spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest) 
308 {
309   Real log =  log_2 (shortest);
310   Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
311     - log;
312   
313   return (log_2 (d) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
314 }
315
316
317 Real
318 Spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
319                                Moment shortest) 
320 {
321   Moment shortest_playing_len = 0;
322   SCM s = lc->get_grob_property ("shortest-playing-duration");
323
324   //  SCM s = lc->get_grob_property ("mean-playing-duration");  
325   if (unsmob_moment (s))
326     shortest_playing_len = *unsmob_moment (s);
327   
328   if (! shortest_playing_len)
329     {
330       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
331       shortest_playing_len = 1;
332     }
333   
334   if (! shortest)
335     {
336       programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
337       shortest = 1;
338     }
339   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
340   Real dist = get_duration_space (me, shortest_playing_len, shortest);
341
342
343   /*
344     ugh: 0.1 is an arbitrary distance.
345    */
346   dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
347     + 0.1 * (double) (delta_t.grace_mom_ / shortest_playing_len.main_part_);
348
349
350
351   /*
352     UGH: KLUDGE!
353   */
354   
355   if (delta_t > Moment (1,32))
356     dist += stem_dir_correction (me, lc,rc);
357
358
359   Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
360   Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
361
362   if (lm && rm)
363     {
364       if (lm->grace_mom_ && rm->grace_mom_)
365         dist *= 0.5;
366       else if (!rm->grace_mom_ && lm->grace_mom_)
367         dist *= 0.7;
368     }
369
370   
371   return dist;
372 }
373
374
375 /**
376    Correct for optical illusions. See [Wanske] p. 138. The combination
377    up-stem + down-stem should get extra space, the combination
378    down-stem + up-stem less.
379
380    This should be more advanced, since relative heights of the note
381    heads also influence required correction.
382
383    Also might not work correctly in case of multi voices or staff
384    changing voices
385
386    TODO: lookup correction distances?  More advanced correction?
387    Possibly turn this off?
388
389    TODO: have to check wether the stems are in the same staff.
390
391    This routine reads the DIR-LIST property of both its L and R arguments.  */
392 Real
393 Spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r) 
394 {
395   SCM dl = l->get_grob_property ("dir-list");
396   SCM dr = r->get_grob_property ("dir-list");
397   
398   if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
399     return 0.;
400
401   dl = gh_car (dl);
402   dr = gh_car (dr);
403
404   assert (gh_number_p (dl) && gh_number_p (dr));
405   int d1 = gh_scm2int (dl);
406   int d2 = gh_scm2int (dr);
407
408   if (d1 == d2)
409     return 0.0;
410
411
412   Real correction = 0.0;
413   Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
414
415   if (d1 && d2 && d1 * d2 == -1)
416     {
417       correction = d1 * ssc;
418     }
419   else
420     programming_error ("Stem directions not set correctly for optical correction");
421   return correction;
422 }
423   
424
425 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs,1);
426 SCM
427 Spacing_spanner::set_springs (SCM smob)
428 {
429   Grob *me = unsmob_grob (smob);
430   Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
431
432   int j = 0;
433
434   for (int i = 1; i < all.size (); i++)
435     {
436       Grob *sc = all[i];
437       if (Item::breakable_b (sc))
438         {
439           Link_array<Grob> measure (all.slice (j, i+1));          
440           do_measure (me, measure);
441           j = i;
442         }
443     }
444
445   /*
446     farewell, cruel world
447    */
448   me->suicide ();
449   return SCM_UNSPECIFIED;
450 }
451
452
453
454 /*
455   maximum-duration-for-spacing
456 From: bf250@freenet.carleton.ca (John Sankey)
457 To: gnu-music-discuss@gnu.org
458 Subject: note spacing suggestion
459 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
460
461 Currently, Lily spaces notes by starting with a basic distance,
462 arithmetic_multiplier, which it applies to the minimum duration note
463 of the bar. Then she adds a logarithmic increment, scaled from
464 arithmetic_basicspace, for longer notes. (Then, columns are aligned
465 and justified.) Fundamentally, this matches visual spacing to musical
466 weight and works well.
467
468 A lot of the time in music, I see a section basically in melodic
469 notes that occasionally has a rapid ornamental run (scale). So, there
470 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
471 a return to long notes. Currently, Lily gives the same horizontal
472 space to the 1/32nd notes in their bar (even if set in small size as
473 is commonly done for cadenzii) as she gives to 1/4 notes in bars
474 where 1/4 note is the minimum duration. The resulting visual weight
475 does not match the musical weight over the page.
476
477 Looking at the music I am typesetting, I feel that Lily's spacing
478 could be significantly improved if, with no change in the basic
479 method used, arithmetic_multiplier could be applied referred to the
480 same duration throughout a piece. Of course, the current method
481 should be retained for those who have already set music in it, so I
482 suggest a property called something like arithmetic_base=16 to fix
483 1/16 duration as the reference for arithmetic_multiplier; the default
484 would be a dynamic base is it is now.
485
486 Does anyone else feel that this would be a useful improvement for
487 their music? (Of course, if arithmetic_multiplier became a regular
488 property, this could be used to achieve a similar result by
489 tweaking.)
490   
491  */