2 spacing-spanner.cc -- implement Spacing_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
14 #include "spacing-spanner.hh"
15 #include "paper-column.hh"
16 #include "output-def.hh"
17 #include "paper-score.hh"
20 #include "note-spacing.hh"
23 #include "pointer-group-interface.hh"
24 #include "spaceable-grob.hh"
25 #include "staff-spacing.hh"
26 #include "spacing-interface.hh"
29 Spacing_spanner::effective_shortest_duration (Grob *me,
30 Link_array<Grob> const &all)
32 SCM preset_shortest = me->get_property ("common-shortest-duration");
33 Rational global_shortest;
34 if (unsmob_moment (preset_shortest))
35 global_shortest = unsmob_moment (preset_shortest)->main_part_;
38 global_shortest = Spacing_spanner::find_shortest (me, all);
39 if (be_verbose_global)
40 message (_f ("Global shortest duration is %s", global_shortest.to_string ()) + "\n");
43 return global_shortest;
47 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs, 1);
49 Spacing_spanner::set_springs (SCM smob)
51 Grob *me = unsmob_grob (smob);
54 can't use get_system() ? --hwn.
56 Link_array<Grob> all (get_root_system (me)->columns ());
58 set_explicit_neighbor_columns (all);
60 Spacing_options options;
61 options.init_from_grob (me);
62 options.global_shortest_ = effective_shortest_duration (me, all);
64 prune_loose_columns (me, &all, &options);
65 set_implicit_neighbor_columns (all);
66 generate_springs (me, all, &options);
68 return SCM_UNSPECIFIED;
72 We want the shortest note that is also "common" in the piece, so we
73 find the shortest in each measure, and take the most frequently
76 This probably gives weird effects with modern music, where every
77 note has a different duration, but hey, don't write that kind of
81 Spacing_spanner::find_shortest (Grob *me, Link_array<Grob> const &cols)
86 Array<Rational> durations;
89 Rational shortest_in_measure;
90 shortest_in_measure.set_infinite (1);
92 for (int i = 0; i < cols.size (); i++)
94 if (Paper_column::is_musical (cols[i]))
96 Moment *when = unsmob_moment (cols[i]->get_property ("when"));
99 ignore grace notes for shortest notes.
101 if (when && when->grace_part_)
104 SCM st = cols[i]->get_property ("shortest-starter-duration");
105 Moment this_shortest = *unsmob_moment (st);
106 assert (this_shortest.to_bool ());
107 shortest_in_measure = min (shortest_in_measure, this_shortest.main_part_);
109 else if (!shortest_in_measure.is_infinity ()
110 && Item::is_breakable (cols[i]))
113 for (; j < durations.size (); j++)
115 if (durations[j] > shortest_in_measure)
117 counts.insert (1, j);
118 durations.insert (shortest_in_measure, j);
121 else if (durations[j] == shortest_in_measure)
128 if (durations.size () == j)
130 durations.push (shortest_in_measure);
134 shortest_in_measure.set_infinite (1);
140 for (int i = durations.size (); i--;)
142 if (counts[i] >= max_count)
145 max_count = counts[i];
148 // printf ("duration %d/%d, count %d\n",
149 // durations[i].num (), durations[i].den (), counts[i]);
152 SCM bsd = me->get_property ("base-shortest-duration");
153 Rational d = Rational (1, 8);
154 if (Moment *m = unsmob_moment (bsd))
158 d = min (d, durations[max_idx]);
164 Spacing_spanner::generate_pair_spacing (Grob *me,
165 Paper_column *left_col, Paper_column *right_col,
166 Paper_column *after_right_col,
167 Spacing_options const *options)
169 if (Paper_column::is_musical (left_col))
171 bool skip_unbroken_right = false;
173 if (!Paper_column::is_musical (right_col)
174 && options->float_nonmusical_columns_
176 && Paper_column::is_musical (after_right_col))
177 skip_unbroken_right = true;
179 if (skip_unbroken_right)
182 TODO: should generate rods to prevent collisions.
184 musical_column_spacing (me, left_col, after_right_col, options);
185 right_col->set_object ("between-cols", scm_cons (left_col->self_scm (),
186 after_right_col->self_scm ()));
189 musical_column_spacing (me, left_col, right_col, options);
191 if (Item *rb = right_col->find_prebroken_piece (LEFT))
192 musical_column_spacing (me, left_col, rb, options);
197 The case that the right part is broken as well is rather
198 rare, but it is possible, eg. with a single empty measure,
199 or if one staff finishes a tad earlier than the rest.
201 Item *lb = left_col->find_prebroken_piece (RIGHT);
202 Item *rb = right_col->find_prebroken_piece (LEFT);
204 if (left_col && right_col)
205 breakable_column_spacing (me, left_col, right_col, options);
208 breakable_column_spacing (me, lb, right_col, options);
211 breakable_column_spacing (me, left_col, rb, options);
214 breakable_column_spacing (me, lb, rb, options);
219 Spacing_spanner::generate_springs (Grob *me,
220 Link_array<Grob> const &cols,
221 Spacing_options const *options)
223 Paper_column *next = 0;
224 Paper_column *next_next = 0;
225 for (int i = cols.size (); i--;)
227 Paper_column *col = dynamic_cast<Paper_column *> (cols[i]);
229 generate_pair_spacing (me, col, next, next_next, options);
237 Generate the space between two musical columns LEFT_COL and RIGHT_COL, given
238 spacing parameters INCR and SHORTEST.
241 Spacing_spanner::musical_column_spacing (Grob *me,
244 Spacing_options const *options)
246 bool expand_only = false;
247 Real base_note_space = note_spacing (me, left_col, right_col, options, &expand_only);
249 Real compound_note_space = 0.0;
250 Real compound_fixed_note_space = 0.0;
252 if (options->stretch_uniformly_)
253 compound_note_space = base_note_space;
258 extract_grob_set (left_col, "right-neighbors", neighbors);
261 We adjust the space following a note only if the next note
262 happens after the current note (this is set in the grob
263 property SPACING-SEQUENCE.
265 for (int i = 0; i < neighbors.size (); i++)
267 Grob *wish = neighbors[i];
269 Item *wish_rcol = Note_spacing::right_column (wish);
270 if (Note_spacing::left_column (wish) != left_col
271 || (wish_rcol != right_col && wish_rcol != right_col->original ()))
275 This is probably a waste of time in the case of polyphonic
277 if (Note_spacing::has_interface (wish))
282 Note_spacing::get_spacing (wish, right_col, base_note_space, options->increment_, &space, &fixed);
284 compound_note_space = compound_note_space + space;
285 compound_fixed_note_space = compound_fixed_note_space + fixed;
290 if (Paper_column::when_mom (right_col).grace_part_
291 && !Paper_column::when_mom (left_col).grace_part_)
294 Ugh. 0.8 is arbitrary.
296 compound_note_space *= 0.8;
299 if (compound_note_space < 0 || wish_count == 0)
301 compound_note_space = base_note_space;
302 compound_fixed_note_space = options->increment_;
306 compound_note_space /= wish_count;
307 compound_fixed_note_space /= wish_count;
311 Whatever we do, the fixed space is smaller than the real
314 TODO: this criterion is discontinuous in the derivative.
315 Maybe it should be continuous?
317 compound_fixed_note_space = min (compound_fixed_note_space,
318 compound_note_space);
321 Real inverse_strength = 1.0;
325 TODO: make sure that the space doesn't exceed the right margin.
327 if (options->packed_)
330 In packed mode, pack notes as tight as possible. This makes
331 sense mostly in combination with raggedright mode: the notes
332 are then printed at minimum distance. This is mostly useful
333 for ancient notation, but may also be useful for some flavours
334 of contemporary music. If not in raggedright mode, lily will
335 pack as much bars of music as possible into a line, but the
336 line will then be stretched to fill the whole linewidth.
338 inverse_strength = 1.0;
339 distance = compound_fixed_note_space;
343 inverse_strength = (compound_note_space - compound_fixed_note_space);
344 distance = compound_note_space;
347 Spaceable_grob::add_spring (left_col, right_col, distance, inverse_strength);
351 Read hints from L and generate springs.
354 Spacing_spanner::breakable_column_spacing (Grob *me, Item *l, Item *r,
355 Spacing_options const *options)
357 Real compound_fixed = 0.0;
358 Real compound_space = 0.0;
361 Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
363 if (dt == Moment (0, 0))
365 extract_grob_set (l, "spacing-wishes", wishes);
367 for (int i = 0; i < wishes.size (); i++)
369 Item *spacing_grob = dynamic_cast<Item *> (wishes[i]);
371 if (!spacing_grob || !Staff_spacing::has_interface (spacing_grob))
378 column for the left one settings should be ok due automatic
382 assert (spacing_grob->get_column () == l);
384 Staff_spacing::get_spacing_params (spacing_grob,
385 &space, &fixed_space);
387 if (Paper_column::when_mom (r).grace_part_)
390 Correct for grace notes.
392 Ugh. The 0.8 is arbitrary.
397 compound_space += space;
398 compound_fixed += fixed_space;
403 if (compound_space <= 0.0 || !wish_count)
405 standard_breakable_column_spacing (me, l, r, &compound_fixed, &compound_space,
411 compound_space /= wish_count;
412 compound_fixed /= wish_count;
415 if (options->stretch_uniformly_ && l->break_status_dir () != RIGHT)
416 compound_fixed = 0.0;
418 assert (!isinf (compound_space));
419 compound_space = max (compound_space, compound_fixed);
422 There used to be code that changed spacing depending on
423 raggedright setting. Ugh.
425 Do it more cleanly, or rename the property.
428 Real inverse_strength = (compound_space - compound_fixed);
429 Real distance = compound_space;
430 Spaceable_grob::add_spring (l, r, distance, inverse_strength);
433 ADD_INTERFACE (Spacing_spanner, "spacing-spanner-interface",
434 "The space taken by a note is dependent on its duration. Doubling a\n"
435 "duration adds spacing-increment to the space. The most common shortest\n"
436 "note gets @code{shortest-duration-space}. Notes that are even shorter are\n"
437 "spaced proportonial to their duration.\n"
439 "Typically, the increment is the width of a black note head. In a\n"
440 "piece with lots of 8th notes, and some 16th notes, the eighth note\n"
441 "gets 2 note heads width (i.e. the space following a note is 1 note\n"
442 "head width) A 16th note is followed by 0.5 note head width. The\n"
443 "quarter note is followed by 3 NHW, the half by 4 NHW, etc.\n",
445 "grace-space-factor "
447 "base-shortest-duration "
448 "strict-note-spacing "
449 "shortest-duration-space "
450 "common-shortest-duration "
451 "uniform-stretching "
455 ADD_INTERFACE (Spacing_interface, "spacing-interface",
456 "Something to do with line breaking and spacing. "
457 "Kill this one after determining line breaks.",