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