]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-spanner.cc
* flower/include/std-string.hh:
[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--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "spacing-spanner.hh"
10
11 #include <math.h>
12 #include <cstdio>
13 using namespace std;
14
15 #include "international.hh"
16 #include "main.hh"
17 #include "moment.hh"
18 #include "note-spacing.hh"
19 #include "output-def.hh"
20 #include "paper-column.hh"
21 #include "paper-score.hh"
22 #include "pointer-group-interface.hh"
23 #include "spaceable-grob.hh"
24 #include "spacing-interface.hh"
25 #include "staff-spacing.hh"
26 #include "system.hh"
27 #include "warn.hh"
28
29
30 /*
31   TODO:
32
33   use callback instead?
34
35 */
36 Rational
37 Spacing_spanner::effective_shortest_duration (Grob *me,
38                                               vector<Grob*> const &all)
39 {
40   SCM preset_shortest = me->get_property ("common-shortest-duration");
41   Rational global_shortest;
42   if (unsmob_moment (preset_shortest))
43     global_shortest = unsmob_moment (preset_shortest)->main_part_;
44   else
45     {
46       global_shortest = Spacing_spanner::find_shortest (me, all);
47       if (be_verbose_global)
48         message (_f ("Global shortest duration is %s", global_shortest.to_string ()) + "\n");
49     }
50
51   return global_shortest;
52 }
53
54
55 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs, 1);
56 SCM
57 Spacing_spanner::set_springs (SCM smob)
58 {
59   Grob *me = unsmob_grob (smob);
60
61   /*
62     can't use get_system() ? --hwn.
63   */
64   vector<Grob*> all (get_root_system (me)->columns ());
65
66   set_explicit_neighbor_columns (all);
67
68   Spacing_options options;
69   options.init_from_grob (me);
70   options.global_shortest_ = effective_shortest_duration (me, all);
71
72   prune_loose_columns (me, &all, &options);
73   set_implicit_neighbor_columns (all);
74   generate_springs (me, all, &options);
75
76   return SCM_UNSPECIFIED;
77 }
78
79 /*
80   We want the shortest note that is also "common" in the piece, so we
81   find the shortest in each measure, and take the most frequently
82   found duration.
83
84   This probably gives weird effects with modern music, where every
85   note has a different duration, but hey, don't write that kind of
86   stuff, then.
87 */
88 Rational
89 Spacing_spanner::find_shortest (Grob *me, vector<Grob*> const &cols)
90 {
91   /*
92     ascending in duration
93   */
94   vector<Rational> durations;
95   vector<int> counts;
96
97   Rational shortest_in_measure;
98   shortest_in_measure.set_infinite (1);
99
100   for (vsize i = 0; i < cols.size (); i++)
101     {
102       if (Paper_column::is_musical (cols[i]))
103         {
104           Moment *when = unsmob_moment (cols[i]->get_property ("when"));
105
106           /*
107             ignore grace notes for shortest notes.
108           */
109           if (when && when->grace_part_)
110             continue;
111
112           SCM st = cols[i]->get_property ("shortest-starter-duration");
113           Moment this_shortest = *unsmob_moment (st);
114           assert (this_shortest.to_bool ());
115           shortest_in_measure = min (shortest_in_measure, this_shortest.main_part_);
116         }
117       else if (!shortest_in_measure.is_infinity ()
118                && Item::is_breakable (cols[i]))
119         {
120           vsize j = 0;
121           for (; j < durations.size (); j++)
122             {
123               if (durations[j] > shortest_in_measure)
124                 {
125                   counts.insert (counts.begin () + j, 1);
126                   durations.insert (durations.begin () + j, shortest_in_measure);
127                   break;
128                 }
129               else if (durations[j] == shortest_in_measure)
130                 {
131                   counts[j]++;
132                   break;
133                 }
134             }
135
136           if (durations.size () == j)
137             {
138               durations.push_back (shortest_in_measure);
139               counts.push_back (1);
140             }
141
142           shortest_in_measure.set_infinite (1);
143         }
144     }
145
146   int max_idx = -1;
147   int max_count = 0;
148   for (vsize i = durations.size (); i--;)
149     {
150       if (counts[i] >= max_count)
151         {
152           max_idx = i;
153           max_count = counts[i];
154         }
155
156       // printf ("duration %d/%d, count %d\n",
157       // durations[i].num (), durations[i].den (), counts[i]);
158     }
159
160   SCM bsd = me->get_property ("base-shortest-duration");
161   Rational d = Rational (1, 8);
162   if (Moment *m = unsmob_moment (bsd))
163     d = m->main_part_;
164
165   if (max_idx >= 0)
166     d = min (d, durations[max_idx]);
167
168   return d;
169 }
170
171 void
172 Spacing_spanner::generate_pair_spacing (Grob *me,
173                                         Paper_column *left_col, Paper_column *right_col,
174                                         Paper_column *after_right_col,
175                                         Spacing_options const *options)
176 {
177   if (Paper_column::is_musical (left_col))
178     {
179       bool skip_unbroken_right = false;
180
181       if (!Paper_column::is_musical (right_col)
182           && options->float_nonmusical_columns_
183           && after_right_col
184           && Paper_column::is_musical (after_right_col))
185         skip_unbroken_right = true;
186
187       if (skip_unbroken_right)
188         {
189           /*
190             TODO: should generate rods to prevent collisions.
191           */
192           musical_column_spacing (me, left_col, after_right_col, options);
193           right_col->set_object ("between-cols", scm_cons (left_col->self_scm (),
194                                                            after_right_col->self_scm ()));
195         }
196       else
197         musical_column_spacing (me, left_col, right_col, options);
198
199       if (Item *rb = right_col->find_prebroken_piece (LEFT))
200         musical_column_spacing (me, left_col, rb, options);
201     }
202   else
203     {
204       /*
205         The case that the right part is broken as well is rather
206         rare, but it is possible, eg. with a single empty measure,
207         or if one staff finishes a tad earlier than the rest.
208       */
209       Item *lb = left_col->find_prebroken_piece (RIGHT);
210       Item *rb = right_col->find_prebroken_piece (LEFT);
211
212       if (left_col && right_col)
213         breakable_column_spacing (me, left_col, right_col, options);
214
215       if (lb && right_col)
216         breakable_column_spacing (me, lb, right_col, options);
217
218       if (left_col && rb)
219         breakable_column_spacing (me, left_col, rb, options);
220
221       if (lb && rb)
222         breakable_column_spacing (me, lb, rb, options);
223     }
224 }
225
226 void
227 Spacing_spanner::generate_springs (Grob *me,
228                                    vector<Grob*> const &cols,
229                                    Spacing_options const *options)
230 {
231   Paper_column *next = 0;
232   Paper_column *next_next = 0;
233   for (vsize i = cols.size (); i--;)
234     {
235       Paper_column *col = dynamic_cast<Paper_column *> (cols[i]);
236       if (next)
237         generate_pair_spacing (me, col, next, next_next, options);
238
239       next_next = next;
240       next = col;
241     }
242 }
243
244 /*
245   Generate the space between two musical columns LEFT_COL and RIGHT_COL, given
246   spacing parameters INCR and SHORTEST.
247 */
248 void
249 Spacing_spanner::musical_column_spacing (Grob *me,
250                                          Item *left_col,
251                                          Item *right_col,
252                                          Spacing_options const *options)
253 {
254   bool expand_only = false;
255   Real base_note_space = note_spacing (me, left_col, right_col, options, &expand_only);
256
257   Real max_fixed = 0;
258   Real max_space = 0;
259   Real compound_note_space = 0.0;
260   Real compound_fixed_note_space = 0.0;
261
262   if (options->stretch_uniformly_)
263     compound_note_space = base_note_space;
264   else
265     {
266       int wish_count = 0;
267
268       extract_grob_set (left_col, "right-neighbors", neighbors);
269
270       /*
271         We adjust the space following a note only if the next note
272         happens after the current note (this is set in the grob
273         property SPACING-SEQUENCE.
274       */
275       for (vsize i = 0; i < neighbors.size (); i++)
276         {
277           Grob *wish = neighbors[i];
278
279           Item *wish_rcol = Note_spacing::right_column (wish);
280           if (Note_spacing::left_column (wish) != left_col
281               || (wish_rcol != right_col && wish_rcol != right_col->original ()))
282             continue;
283
284           /*
285             This is probably a waste of time in the case of polyphonic
286             music.  */
287           if (Note_spacing::has_interface (wish))
288             {
289               Real space = 0.0;
290               Real fixed = 0.0;
291
292               Note_spacing::get_spacing (wish, right_col, base_note_space, options->increment_, &space, &fixed);
293
294
295               max_space = max (max_space, space);
296               max_fixed = max (max_fixed, fixed);
297               
298               compound_note_space += space;
299               compound_fixed_note_space += fixed;
300               wish_count++;
301             }
302         }
303
304       if (Paper_column::when_mom (right_col).grace_part_
305           && !Paper_column::when_mom (left_col).grace_part_)
306         {
307           /*
308             Ugh. 0.8 is arbitrary.
309           */
310           compound_note_space *= 0.8;
311         }
312
313       if (compound_note_space < 0 || wish_count == 0)
314         {
315           compound_note_space = base_note_space;
316           compound_fixed_note_space = options->increment_;
317         }
318       else if (to_boolean (me->get_property ("average-spacing-wishes")))
319         {
320           compound_note_space /= wish_count;
321           compound_fixed_note_space /= wish_count;
322         }
323       else
324         {
325           compound_fixed_note_space = max_fixed;
326           compound_note_space = max_space;
327         }
328
329       /*
330         Whatever we do, the fixed space is smaller than the real
331         space.
332
333         TODO: this criterion is discontinuous in the derivative.
334         Maybe it should be continuous?
335       */
336       compound_fixed_note_space = min (compound_fixed_note_space,
337                                        compound_note_space);
338     }
339
340   Real inverse_strength = 1.0;
341   Real distance = 1.0;
342
343   /*
344     TODO: make sure that the space doesn't exceed the right margin.
345   */
346   if (options->packed_)
347     {
348       /*
349         In packed mode, pack notes as tight as possible.  This makes
350         sense mostly in combination with raggedright mode: the notes
351         are then printed at minimum distance.  This is mostly useful
352         for ancient notation, but may also be useful for some flavours
353         of contemporary music.  If not in raggedright mode, lily will
354         pack as much bars of music as possible into a line, but the
355         line will then be stretched to fill the whole linewidth.
356       */
357       inverse_strength = 1.0;
358       distance = compound_fixed_note_space;
359     }
360   else
361     {
362       inverse_strength = (compound_note_space - compound_fixed_note_space);
363       distance = compound_note_space;
364     }
365
366   Spaceable_grob::add_spring (left_col, right_col, distance, inverse_strength);
367 }
368
369 /*
370   Read hints from L and generate springs.
371 */
372 void
373 Spacing_spanner::breakable_column_spacing (Grob *me, Item *l, Item *r,
374                                            Spacing_options const *options)
375 {
376   Real compound_fixed = 0.0;
377   Real compound_space = 0.0;
378   Real max_fixed = 0.0;
379   Real max_space = 0.0;
380   
381   int wish_count = 0;
382
383   Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
384
385   if (dt == Moment (0, 0))
386     {
387       extract_grob_set (l, "spacing-wishes", wishes);
388
389       for (vsize i = 0; i < wishes.size (); i++)
390         {
391           Item *spacing_grob = dynamic_cast<Item *> (wishes[i]);
392
393           if (!spacing_grob || !Staff_spacing::has_interface (spacing_grob))
394             continue;
395
396           Real space;
397           Real fixed_space;
398
399           /*
400             column for the left one settings should be ok due automatic
401             pointer munging.
402
403           */
404           assert (spacing_grob->get_column () == l);
405
406           Staff_spacing::get_spacing_params (spacing_grob,
407                                              &space, &fixed_space);
408
409           if (Paper_column::when_mom (r).grace_part_)
410             {
411               /*
412                 Correct for grace notes.
413
414                 Ugh. The 0.8 is arbitrary.
415               */
416               space *= 0.8;
417             }
418
419           max_space = max (max_space, space);
420           max_fixed = max (max_fixed, fixed_space);
421           
422           compound_space += space;
423           compound_fixed += fixed_space;
424           wish_count++;
425         }
426     }
427
428   if (compound_space <= 0.0 || !wish_count)
429     {
430       standard_breakable_column_spacing (me, l, r, &compound_fixed, &compound_space,
431                                          options);
432       wish_count = 1;
433     }
434   else
435     {
436       if (to_boolean (me->get_property ("average-spacing-wishes")))
437         {
438           compound_space /= wish_count;
439           compound_fixed /= wish_count;
440         }
441       else
442         {
443           compound_fixed = max_fixed;
444           compound_space = max_space;
445         }
446       
447     }
448
449   if (options->stretch_uniformly_ && l->break_status_dir () != RIGHT)
450     compound_fixed = 0.0;
451
452   assert (!isinf (compound_space));
453   compound_space = max (compound_space, compound_fixed);
454
455   /*
456     There used to be code that changed spacing depending on
457     raggedright setting.  Ugh.
458
459     Do it more cleanly, or rename the property.
460
461   */
462   Real inverse_strength = (compound_space - compound_fixed);
463   Real distance = compound_space;
464   Spaceable_grob::add_spring (l, r, distance, inverse_strength);
465 }
466
467 ADD_INTERFACE (Spacing_spanner, "spacing-spanner-interface",
468                "The space taken by a note is dependent on its duration. Doubling a\n"
469                "duration adds spacing-increment to the space. The most common shortest\n"
470                "note gets @code{shortest-duration-space}. Notes that are even shorter are\n"
471                "spaced proportonial to their duration.\n"
472                "\n"
473                "Typically, the increment is the width of a black note head.  In a\n"
474                "piece with lots of 8th notes, and some 16th notes, the eighth note\n"
475                "gets 2 note heads width (i.e. the space following a note is 1 note\n"
476                "head width) A 16th note is followed by 0.5 note head width. The\n"
477                "quarter note is followed by  3 NHW, the half by 4 NHW, etc.\n",
478
479                "average-spacing-wishes "
480                "grace-space-factor "
481                "spacing-increment "
482                "base-shortest-duration "
483                "strict-note-spacing "
484                "shortest-duration-space "
485                "common-shortest-duration "
486                "uniform-stretching "
487                "packed-spacing "
488                );
489
490 ADD_INTERFACE (Spacing_interface, "spacing-interface",
491                "Something to do with line breaking and spacing. "
492                "Kill this one after determining line breaks.",
493                "");
494