2 spacing-spanner.cc -- implement Spacing_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "spacing-spanner.hh"
15 #include "international.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"
37 Spacing_spanner::effective_shortest_duration (Grob *me,
38 vector<Grob*> const &all)
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_;
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");
51 return global_shortest;
55 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs, 1);
57 Spacing_spanner::set_springs (SCM smob)
59 Spanner *me = unsmob_spanner (smob);
62 can't use get_system() ? --hwn.
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);
70 all = vector<Grob*>::vector<Grob*> (all.begin () + start,
71 all.begin () + end + 1);
73 set_explicit_neighbor_columns (all);
75 Spacing_options options;
76 options.init_from_grob (me);
77 options.global_shortest_ = effective_shortest_duration (me, all);
79 prune_loose_columns (me, &all, &options);
80 set_implicit_neighbor_columns (all);
81 generate_springs (me, all, &options);
83 return SCM_UNSPECIFIED;
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
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
96 Spacing_spanner::find_shortest (Grob *me, vector<Grob*> const &cols)
101 vector<Rational> durations;
104 Rational shortest_in_measure;
105 shortest_in_measure.set_infinite (1);
107 for (vsize i = 0; i < cols.size (); i++)
109 if (Paper_column::is_musical (cols[i]))
111 Moment *when = unsmob_moment (cols[i]->get_property ("when"));
114 ignore grace notes for shortest notes.
116 if (when && when->grace_part_)
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_);
124 else if (!shortest_in_measure.is_infinity ()
125 && Paper_column::is_breakable (cols[i]))
128 for (; j < durations.size (); j++)
130 if (durations[j] > shortest_in_measure)
132 counts.insert (counts.begin () + j, 1);
133 durations.insert (durations.begin () + j, shortest_in_measure);
136 else if (durations[j] == shortest_in_measure)
143 if (durations.size () == j)
145 durations.push_back (shortest_in_measure);
146 counts.push_back (1);
149 shortest_in_measure.set_infinite (1);
155 for (vsize i = durations.size (); i--;)
157 if (counts[i] >= max_count)
160 max_count = counts[i];
163 // printf ("duration %d/%d, count %d\n",
164 // durations[i].num (), durations[i].den (), counts[i]);
167 SCM bsd = me->get_property ("base-shortest-duration");
168 Rational d = Rational (1, 8);
169 if (Moment *m = unsmob_moment (bsd))
173 d = min (d, durations[max_idx]);
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)
184 if (Paper_column::is_musical (left_col))
186 bool skip_unbroken_right = false;
188 if (!Paper_column::is_musical (right_col)
189 && options->float_nonmusical_columns_
191 && Paper_column::is_musical (after_right_col))
192 skip_unbroken_right = true;
194 if (skip_unbroken_right)
197 TODO: should generate rods to prevent collisions.
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 ()));
204 musical_column_spacing (me, left_col, right_col, options);
206 if (Item *rb = right_col->find_prebroken_piece (LEFT))
207 musical_column_spacing (me, left_col, rb, options);
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.
216 Item *lb = left_col->find_prebroken_piece (RIGHT);
217 Item *rb = right_col->find_prebroken_piece (LEFT);
219 if (left_col && right_col)
220 breakable_column_spacing (me, left_col, right_col, options);
223 breakable_column_spacing (me, lb, right_col, options);
226 breakable_column_spacing (me, left_col, rb, options);
229 breakable_column_spacing (me, lb, rb, options);
234 Spacing_spanner::generate_springs (Grob *me,
235 vector<Grob*> const &cols,
236 Spacing_options const *options)
238 Paper_column *prev = 0;
239 for (vsize i = 0; i < cols.size (); i++)
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;
245 generate_pair_spacing (me, prev, col, next, options);
252 Generate the space between two musical columns LEFT_COL and RIGHT_COL, given
253 spacing parameters INCR and SHORTEST.
256 Spacing_spanner::musical_column_spacing (Grob *me,
259 Spacing_options const *options)
261 bool expand_only = false;
262 Real base_note_space = note_spacing (me, left_col, right_col, options, &expand_only);
266 Real compound_note_space = 0.0;
267 Real compound_fixed_note_space = 0.0;
269 if (options->stretch_uniformly_)
271 compound_note_space = base_note_space;
273 if (!Paper_column::is_musical (right_col))
276 Crude fix for notes that lead up to barlines and time sigs.
278 Interval lext = right_col->extent (right_col, X_AXIS);
279 if (!lext.is_empty ())
280 compound_note_space += -lext[LEFT];
288 extract_grob_set (left_col, "right-neighbors", neighbors);
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.
295 for (vsize i = 0; i < neighbors.size (); i++)
297 Grob *wish = neighbors[i];
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 ()))
305 This is probably a waste of time in the case of polyphonic
307 if (Note_spacing::has_interface (wish))
312 Note_spacing::get_spacing (wish, right_col, base_note_space, options->increment_, &space, &fixed);
315 max_space = max (max_space, space);
316 max_fixed = max (max_fixed, fixed);
318 compound_note_space += space;
319 compound_fixed_note_space += fixed;
324 if (Paper_column::when_mom (right_col).grace_part_
325 && !Paper_column::when_mom (left_col).grace_part_)
328 Ugh. 0.8 is arbitrary.
330 compound_note_space *= 0.8;
333 if (compound_note_space < 0 || wish_count == 0)
335 compound_note_space = base_note_space;
336 compound_fixed_note_space = options->increment_;
338 else if (to_boolean (me->get_property ("average-spacing-wishes")))
340 compound_note_space /= wish_count;
341 compound_fixed_note_space /= wish_count;
345 compound_fixed_note_space = max_fixed;
346 compound_note_space = max_space;
350 Whatever we do, the fixed space is smaller than the real
353 TODO: this criterion is discontinuous in the derivative.
354 Maybe it should be continuous?
356 compound_fixed_note_space = min (compound_fixed_note_space,
357 compound_note_space);
360 Real inverse_strength = 1.0;
364 TODO: make sure that the space doesn't exceed the right margin.
366 if (options->packed_)
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.
377 inverse_strength = 1.0;
378 distance = compound_fixed_note_space;
382 inverse_strength = (compound_note_space - compound_fixed_note_space);
383 distance = compound_note_space;
386 Spaceable_grob::add_spring (left_col, right_col, distance, inverse_strength);
390 Read hints from L and generate springs.
393 Spacing_spanner::breakable_column_spacing (Grob *me, Item *l, Item *r,
394 Spacing_options const *options)
396 Real compound_fixed = 0.0;
397 Real compound_space = 0.0;
398 Real max_fixed = 0.0;
399 Real max_space = 0.0;
403 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
405 if (dt == Moment (0, 0))
407 extract_grob_set (l, "spacing-wishes", wishes);
409 for (vsize i = 0; i < wishes.size (); i++)
411 Item *spacing_grob = dynamic_cast<Item *> (wishes[i]);
413 if (!spacing_grob || !Staff_spacing::has_interface (spacing_grob))
420 column for the left one settings should be ok due automatic
424 assert (spacing_grob->get_column () == l);
426 Staff_spacing::get_spacing_params (spacing_grob,
427 &space, &fixed_space);
429 if (Paper_column::when_mom (r).grace_part_)
432 Correct for grace notes.
434 Ugh. The 0.8 is arbitrary.
439 max_space = max (max_space, space);
440 max_fixed = max (max_fixed, fixed_space);
442 compound_space += space;
443 compound_fixed += fixed_space;
448 if (compound_space <= 0.0 || !wish_count)
450 standard_breakable_column_spacing (me, l, r, &compound_fixed, &compound_space,
456 if (to_boolean (me->get_property ("average-spacing-wishes")))
458 compound_space /= wish_count;
459 compound_fixed /= wish_count;
463 compound_fixed = max_fixed;
464 compound_space = max_space;
469 if (options->stretch_uniformly_ && l->break_status_dir () != RIGHT)
470 compound_fixed = 0.0;
472 assert (!isinf (compound_space));
473 compound_space = max (compound_space, compound_fixed);
476 There used to be code that changed spacing depending on
477 raggedright setting. Ugh.
479 Do it more cleanly, or rename the property.
482 Real inverse_strength = (compound_space - compound_fixed);
483 Real distance = compound_space;
484 Spaceable_grob::add_spring (l, r, distance, inverse_strength);
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"
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",
500 "average-spacing-wishes "
501 "base-shortest-duration "
502 "common-shortest-duration "
503 "grace-space-factor "
505 "shortest-duration-space "
507 "strict-note-spacing "
508 "uniform-stretching "
512 ADD_INTERFACE (Spacing_interface, "spacing-interface",
513 "Something to do with line breaking and spacing. "
514 "Kill this one after determining line breaks.",