source file of the GNU LilyPond music typesetter
- (c) 1999--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
+ (c) 1999--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
*/
+#include "spacing-spanner.hh"
+
#include <math.h>
#include <cstdio>
+using namespace std;
-#include "spacing-spanner.hh"
-#include "paper-column.hh"
-#include "output-def.hh"
-#include "paper-score.hh"
-#include "system.hh"
+#include "international.hh"
+#include "main.hh"
#include "moment.hh"
#include "note-spacing.hh"
-#include "main.hh"
-#include "warn.hh"
+#include "output-def.hh"
+#include "paper-column.hh"
+#include "paper-score.hh"
#include "pointer-group-interface.hh"
#include "spaceable-grob.hh"
-#include "staff-spacing.hh"
#include "spacing-interface.hh"
+#include "staff-spacing.hh"
+#include "system.hh"
+#include "warn.hh"
+/*
+ TODO:
-void
-Spacing_options::init (Grob*me)
-{
- packed_ = to_boolean (me->get_layout ()->c_variable ("packed"));
- uniform_ = to_boolean (me->get_property ("uniform-stretching"));
-}
-
+ use callback instead?
+*/
Rational
-Spacing_spanner::effective_shortest_duration (Grob *me, Link_array<Grob> const &all)
+Spacing_spanner::effective_shortest_duration (Grob *me,
+ vector<Grob*> const &all)
{
SCM preset_shortest = me->get_property ("common-shortest-duration");
Rational global_shortest;
if (unsmob_moment (preset_shortest))
- {
- global_shortest = unsmob_moment (preset_shortest)->main_part_;
- }
+ global_shortest = unsmob_moment (preset_shortest)->main_part_;
else
{
global_shortest = Spacing_spanner::find_shortest (me, all);
SCM
Spacing_spanner::set_springs (SCM smob)
{
- Grob *me = unsmob_grob (smob);
+ Spanner *me = unsmob_spanner (smob);
/*
can't use get_system() ? --hwn.
*/
- Link_array<Grob> all (me->pscore_->root_system ()->columns ());
-
+ vector<Grob*> all (get_root_system (me)->columns ());
+ vsize start = binary_search (all, (Grob*)me->get_bound (LEFT),
+ &Paper_column::compare);
+ vsize end = binary_search (all, (Grob*) me->get_bound (RIGHT),
+ &Paper_column::compare);
+
+ all = vector<Grob*>::vector<Grob*> (all.begin () + start,
+ all.begin () + end + 1);
+
set_explicit_neighbor_columns (all);
Spacing_options options;
- options.init (me);
+ options.init_from_grob (me);
options.global_shortest_ = effective_shortest_duration (me, all);
-
+
prune_loose_columns (me, &all, &options);
set_implicit_neighbor_columns (all);
-
- int j = 0;
- for (int i = 1; i < all.size (); i++)
- {
- Grob *sc = all[i];
- if (Item::is_breakable (sc))
- {
- Link_array<Grob> measure (all.slice (j, i + 1));
- do_measure (me, &measure, &options);
- j = i;
- }
- }
+ generate_springs (me, all, &options);
return SCM_UNSPECIFIED;
}
stuff, then.
*/
Rational
-Spacing_spanner::find_shortest (Grob *me, Link_array<Grob> const &cols)
+Spacing_spanner::find_shortest (Grob *me, vector<Grob*> const &cols)
{
/*
ascending in duration
*/
- Array<Rational> durations;
- Array<int> counts;
+ vector<Rational> durations;
+ vector<int> counts;
Rational shortest_in_measure;
shortest_in_measure.set_infinite (1);
- for (int i = 0; i < cols.size (); i++)
+ for (vsize i = 0; i < cols.size (); i++)
{
if (Paper_column::is_musical (cols[i]))
{
shortest_in_measure = min (shortest_in_measure, this_shortest.main_part_);
}
else if (!shortest_in_measure.is_infinity ()
- && Item::is_breakable (cols[i]))
+ && Paper_column::is_breakable (cols[i]))
{
- int j = 0;
+ vsize j = 0;
for (; j < durations.size (); j++)
{
if (durations[j] > shortest_in_measure)
{
- counts.insert (1, j);
- durations.insert (shortest_in_measure, j);
+ counts.insert (counts.begin () + j, 1);
+ durations.insert (durations.begin () + j, shortest_in_measure);
break;
}
else if (durations[j] == shortest_in_measure)
if (durations.size () == j)
{
- durations.push (shortest_in_measure);
- counts.push (1);
+ durations.push_back (shortest_in_measure);
+ counts.push_back (1);
}
shortest_in_measure.set_infinite (1);
int max_idx = -1;
int max_count = 0;
- for (int i = durations.size (); i--;)
+ for (vsize i = durations.size (); i--;)
{
if (counts[i] >= max_count)
{
max_count = counts[i];
}
- // printf ("duration %d/%d, count %d\n", durations[i].num (), durations[i].den (), counts[i]);
+ // printf ("duration %d/%d, count %d\n",
+ // durations[i].num (), durations[i].den (), counts[i]);
}
SCM bsd = me->get_property ("base-shortest-duration");
return d;
}
-/*
- Generate spacing for a single measure. We used to have code that did
- per-measure spacing. Now we have piecewise spacing. We should fix
- this to support "spacing-regions": some regions have different notes
- (different time sigs) than others, and should be spaced differently.
-*/
void
-Spacing_spanner::do_measure (Grob *me,
- Link_array<Grob> *cols,
- Spacing_options const *options
- )
+Spacing_spanner::generate_pair_spacing (Grob *me,
+ Paper_column *left_col, Paper_column *right_col,
+ Paper_column *after_right_col,
+ Spacing_options const *options)
{
- Real headwid = robust_scm2double (me->get_property ("spacing-increment"), 1);
- for (int i = 0; i < cols->size () - 1; i++)
+ if (Paper_column::is_musical (left_col))
{
- Item *l = dynamic_cast<Item *> (cols->elem (i));
- Item *r = dynamic_cast<Item *> (cols->elem (i + 1));
+ bool skip_unbroken_right = false;
- Paper_column *lc = dynamic_cast<Paper_column *> (l);
- Paper_column *rc = dynamic_cast<Paper_column *> (r);
+ if (!Paper_column::is_musical (right_col)
+ && options->float_nonmusical_columns_
+ && after_right_col
+ && Paper_column::is_musical (after_right_col))
+ skip_unbroken_right = true;
- if (Paper_column::is_musical (l))
- {
- musical_column_spacing (me, lc, rc, headwid, options);
- if (Item *rb = r->find_prebroken_piece (LEFT))
- musical_column_spacing (me, lc, rb, headwid, options);
- }
- else
+ if (skip_unbroken_right)
{
/*
- The case that the right part is broken as well is rather
- rare, but it is possible, eg. with a single empty measure,
- or if one staff finishes a tad earlier than the rest.
+ TODO: should generate rods to prevent collisions.
*/
- Item *lb = l->find_prebroken_piece (RIGHT);
- Item *rb = r->find_prebroken_piece (LEFT);
+ musical_column_spacing (me, left_col, after_right_col, options);
+ right_col->set_object ("between-cols", scm_cons (left_col->self_scm (),
+ after_right_col->self_scm ()));
+ }
+ else
+ musical_column_spacing (me, left_col, right_col, options);
+
+ if (Item *rb = right_col->find_prebroken_piece (LEFT))
+ musical_column_spacing (me, left_col, rb, options);
+ }
+ else
+ {
+ /*
+ The case that the right part is broken as well is rather
+ rare, but it is possible, eg. with a single empty measure,
+ or if one staff finishes a tad earlier than the rest.
+ */
+ Item *lb = left_col->find_prebroken_piece (RIGHT);
+ Item *rb = right_col->find_prebroken_piece (LEFT);
- if (i == 0 && Paper_column::get_rank (l) == 0)
- l = 0;
+ if (left_col && right_col)
+ breakable_column_spacing (me, left_col, right_col, options);
- if (l && r)
- breakable_column_spacing (me, l, r, options);
-
- if (lb && r)
- breakable_column_spacing (me, lb, r, options);
+ if (lb && right_col)
+ breakable_column_spacing (me, lb, right_col, options);
- if (l && rb)
- breakable_column_spacing (me, l, rb, options);
+ if (left_col && rb)
+ breakable_column_spacing (me, left_col, rb, options);
- if (lb && rb)
- breakable_column_spacing (me, lb, rb, options);
- }
+ if (lb && rb)
+ breakable_column_spacing (me, lb, rb, options);
+ }
+}
+
+void
+Spacing_spanner::generate_springs (Grob *me,
+ vector<Grob*> const &cols,
+ Spacing_options const *options)
+{
+ Paper_column *prev = 0;
+ for (vsize i = 0; i < cols.size (); i++)
+ {
+ Paper_column *col = dynamic_cast<Paper_column *> (cols[i]);
+ Paper_column *next = (i < cols.size()-1) ? dynamic_cast<Paper_column *> (cols[i+1]) : 0;
+
+ if (i > 0)
+ generate_pair_spacing (me, prev, col, next, options);
+
+ prev = col;
}
}
/*
- Generate the space between two musical columns LC and RC, given
+ Generate the space between two musical columns LEFT_COL and RIGHT_COL, given
spacing parameters INCR and SHORTEST.
*/
void
-Spacing_spanner::musical_column_spacing (Grob *me, Item *lc, Item *rc,
- Real increment,
+Spacing_spanner::musical_column_spacing (Grob *me,
+ Item *left_col,
+ Item *right_col,
Spacing_options const *options)
{
bool expand_only = false;
- Real base_note_space = note_spacing (me, lc, rc, options, &expand_only);
-
+ Real base_note_space = note_spacing (me, left_col, right_col, options, &expand_only);
+
+ Real max_fixed = 0;
+ Real max_space = 0;
Real compound_note_space = 0.0;
Real compound_fixed_note_space = 0.0;
- if (options->uniform_)
+ if (options->stretch_uniformly_)
{
compound_note_space = base_note_space;
+
+ if (!Paper_column::is_musical (right_col))
+ {
+ /*
+ Crude fix for notes that lead up to barlines and time sigs.
+ */
+ Interval lext = right_col->extent (right_col, X_AXIS);
+ if (!lext.is_empty ())
+ compound_note_space += -lext[LEFT];
+ }
+
}
else
{
int wish_count = 0;
- extract_grob_set (lc, "right-neighbors", neighbors);
+ extract_grob_set (left_col, "right-neighbors", neighbors);
/*
We adjust the space following a note only if the next note
happens after the current note (this is set in the grob
property SPACING-SEQUENCE.
*/
- for (int i = 0; i < neighbors.size (); i++)
+ for (vsize i = 0; i < neighbors.size (); i++)
{
Grob *wish = neighbors[i];
Item *wish_rcol = Note_spacing::right_column (wish);
- if (Note_spacing::left_column (wish) != lc
- || (wish_rcol != rc && wish_rcol != rc->original_))
+ if (Note_spacing::left_column (wish) != left_col
+ || (wish_rcol != right_col && wish_rcol != right_col->original ()))
continue;
/*
Real space = 0.0;
Real fixed = 0.0;
- Note_spacing::get_spacing (wish, rc, base_note_space, increment, &space, &fixed);
+ Note_spacing::get_spacing (wish, right_col, base_note_space, options->increment_, &space, &fixed);
+
- compound_note_space = compound_note_space + space;
- compound_fixed_note_space = compound_fixed_note_space + fixed;
+ max_space = max (max_space, space);
+ max_fixed = max (max_fixed, fixed);
+
+ compound_note_space += space;
+ compound_fixed_note_space += fixed;
wish_count++;
}
}
- if (Paper_column::when_mom (rc).grace_part_
- && !Paper_column::when_mom (lc).grace_part_)
+ if (Paper_column::when_mom (right_col).grace_part_
+ && !Paper_column::when_mom (left_col).grace_part_)
{
/*
Ugh. 0.8 is arbitrary.
if (compound_note_space < 0 || wish_count == 0)
{
+ /*
+ Fixed should be 0.0. If there are no spacing wishes, we're
+ likely dealing with polyphonic spacing of hemiolas.
+
+ We used to have compound_fixed_note_space = options->increment_
+
+ but this can lead to numeric instability problems when we
+ do
+
+ inverse_strength = (compound_note_space - compound_fixed_note_space)
+
+ */
+
compound_note_space = base_note_space;
- compound_fixed_note_space = increment;
+ compound_fixed_note_space = 0.0;
}
- else
+ else if (to_boolean (me->get_property ("average-spacing-wishes")))
{
compound_note_space /= wish_count;
compound_fixed_note_space /= wish_count;
}
+ else
+ {
+ compound_fixed_note_space = max_fixed;
+ compound_note_space = max_space;
+ }
/*
Whatever we do, the fixed space is smaller than the real
compound_fixed_note_space = min (compound_fixed_note_space,
compound_note_space);
}
-
+
Real inverse_strength = 1.0;
Real distance = 1.0;
distance = compound_note_space;
}
- Spaceable_grob::add_spring (lc, rc, distance, inverse_strength);
+ Spaceable_grob::add_spring (left_col, right_col, distance, inverse_strength);
}
/*
{
Real compound_fixed = 0.0;
Real compound_space = 0.0;
+ Real max_fixed = 0.0;
+ Real max_space = 0.0;
+
int wish_count = 0;
Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
{
extract_grob_set (l, "spacing-wishes", wishes);
- for (int i = 0; i < wishes.size (); i++)
+ for (vsize i = 0; i < wishes.size (); i++)
{
Item *spacing_grob = dynamic_cast<Item *> (wishes[i]);
space *= 0.8;
}
+ max_space = max (max_space, space);
+ max_fixed = max (max_fixed, fixed_space);
+
compound_space += space;
compound_fixed += fixed_space;
wish_count++;
}
else
{
- compound_space /= wish_count;
- compound_fixed /= wish_count;
+ if (to_boolean (me->get_property ("average-spacing-wishes")))
+ {
+ compound_space /= wish_count;
+ compound_fixed /= wish_count;
+ }
+ else
+ {
+ compound_fixed = max_fixed;
+ compound_space = max_space;
+ }
+
}
- if (options->uniform_ && l->break_status_dir () != RIGHT)
- {
- compound_fixed = 0.0;
- }
-
+ if (options->stretch_uniformly_ && l->break_status_dir () != RIGHT)
+ compound_fixed = 0.0;
+
assert (!isinf (compound_space));
compound_space = max (compound_space, compound_fixed);
"gets 2 note heads width (i.e. the space following a note is 1 note\n"
"head width) A 16th note is followed by 0.5 note head width. The\n"
"quarter note is followed by 3 NHW, the half by 4 NHW, etc.\n",
-
- "grace-space-factor spacing-increment base-shortest-duration "
- "shortest-duration-space common-shortest-duration uniform-stretching"
+
+ "average-spacing-wishes "
+ "base-shortest-duration "
+ "common-shortest-duration "
+ "grace-space-factor "
+ "packed-spacing "
+ "shortest-duration-space "
+ "spacing-increment "
+ "strict-note-spacing "
+ "uniform-stretching "
+
);
ADD_INTERFACE (Spacing_interface, "spacing-interface",