X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fnote-spacing.cc;h=9e34006967f78a9429317ade4ebd8ada16f0803f;hb=5b4b0d6e9a197e8f9eb085b7c2ad78b8be3e5cfc;hp=a002d50dc25ed0f8922768ab1beffbea12bffb10;hpb=c380a7abde14a60ebd6d8a6eab91ae4e13677a23;p=lilypond.git diff --git a/lily/note-spacing.cc b/lily/note-spacing.cc index a002d50dc2..2cc5c70ae2 100644 --- a/lily/note-spacing.cc +++ b/lily/note-spacing.cc @@ -1,360 +1,330 @@ -/* +/* note-spacing.cc -- implement Note_spacing source file of the GNU LilyPond music typesetter - (c) 2001--2002 Han-Wen Nienhuys + (c) 2001--2008 Han-Wen Nienhuys */ +#include "note-spacing.hh" +#include "bar-line.hh" +#include "directional-element-interface.hh" +#include "grob-array.hh" #include "paper-column.hh" -#include "item.hh" #include "moment.hh" -#include "note-spacing.hh" -#include "grob.hh" #include "note-column.hh" #include "warn.hh" #include "stem.hh" #include "separation-item.hh" +#include "spacing-interface.hh" #include "staff-spacing.hh" +#include "accidental-placement.hh" +#include "output-def.hh" +#include "pointer-group-interface.hh" -bool -Note_spacing::has_interface (Grob* g) +static bool +non_empty_barline (Grob *me) { - return g && g->has_interface (ly_symbol2scm ("note-spacing-interface")); + return Bar_line::has_interface (me) && !me->extent (me, X_AXIS).is_empty (); } -void -Note_spacing::get_spacing (Grob *me, Item* right_col, - Real base_space, Real increment, Real *space, Real *fixed) +/* + TODO: detect hshifts due to collisions, and account for them in + spacing? +*/ + +Spring +Note_spacing::get_spacing (Grob *me, Item *right_col, + Real base_space, Real increment) { + vector note_columns = Spacing_interface::left_note_columns (me); + Real left_head_end = 0; - Drul_array props(me->get_grob_property ("left-items"), - me->get_grob_property ("right-items")); - Direction d = LEFT; - Direction col_dir = right_col->break_status_dir (); - Drul_array extents; - do + for (vsize i = 0; i < note_columns.size (); i++) { - for (SCM s = props[d]; gh_pair_p (s); s = gh_cdr (s)) - { - Item * it= dynamic_cast (unsmob_grob (gh_car(s))); - - if (d == RIGHT && it->break_status_dir () != col_dir) - { - it = it -> find_prebroken_piece (col_dir); - } - - /* - some kind of mismatch, eg. a note column, that is behind a - linebreak. - */ - if (!it) - continue; + SCM r = note_columns[i]->get_object ("rest"); + Grob *g = unsmob_grob (r); + Grob *col = note_columns[i]->get_column (); - if (d == RIGHT && right_col != it->column_l ()) - continue; - - if (Separation_item::has_interface (it)) - { - extents[d].unite (Separation_item::my_width (it)); - continue; - } - - extents[d].unite (it->extent (it->column_l (), X_AXIS)); - if (d == RIGHT) - { - Grob * accs = Note_column::accidentals (it); - if (!accs) - accs = Note_column::accidentals (it->get_parent (X_AXIS)); - - if (accs) - extents[d].unite (accs->extent (it->column_l (), X_AXIS)); - } - } + if (!g) + g = Note_column::first_head (note_columns[i]); - if (extents[d].empty_b ()) - extents[d] = Interval (0,0); + /* + Ugh. If Stem is switched off, we don't know what the + first note head will be. + */ + if (g) + { + if (g->common_refpoint (col, X_AXIS) != col) + programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect"); + else + left_head_end = g->extent (col, X_AXIS)[RIGHT]; + } } - while (flip (&d) != LEFT); - *fixed = (extents[LEFT][RIGHT] >? increment); - *space = (base_space - increment) + *fixed ; + /* + The main factor that determines the amount of space is the width of the + note head (or the rest). For example, a quarter rest gets almost 0.5 ss + less horizontal space than a note. - if (*space - *fixed < 2 * ((- extents[RIGHT][LEFT]) >? 0)) + The other parts of a note column (eg. flags, accidentals, etc.) don't get + the full amount of space. We give them half the amount of space, but then + adjust things so there are no collisions. + */ + Drul_array skys = Spacing_interface::skylines (me, right_col); + Real distance = skys[LEFT].distance (skys[RIGHT]); + Real min_dist = max (0.0, distance); + Real min_desired_space = left_head_end + (min_dist - left_head_end + base_space - increment) / 2; + Real ideal = base_space - increment + left_head_end; + + /* If we have a NonMusical column on the right, we measure the ideal distance + to the bar-line (if present), not the start of the column. */ + if (!Paper_column::is_musical (right_col) + && !skys[RIGHT].is_empty () + && to_boolean (me->get_property ("space-to-barline"))) { - /* - - What's sticking out at the left of the right side has less - influence. We only take it into account if there is not enough - space. + Grob *bar = Pointer_group_interface::find_grob (right_col, + ly_symbol2scm ("elements"), + non_empty_barline); - this sucks: this criterion is discontinuous; FIXME. - */ - *space += 0.5 * (( -extents[RIGHT][LEFT]) >? 0); + if (bar) + { + Real shift = bar->extent (right_col, X_AXIS)[LEFT]; + ideal -= shift; + min_desired_space -= max (shift, 0.0); + } + else + ideal -= right_col->extent (right_col, X_AXIS)[RIGHT]; } - *space += stem_dir_correction (me, right_col, increment); -} + ideal = max (ideal, min_desired_space); + stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space); -Item * -Note_spacing::left_column (Grob *me) -{ - if (me->immutable_property_alist_ == SCM_EOL) - return 0; - - return dynamic_cast (me)->column_l (); + /* TODO: grace notes look bad when things are stretched. Should we increase + their stretch strength? */ + Spring ret (max (0.0, ideal), min_dist); + ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space)); + ret.set_inverse_stretch_strength (max (0.1, base_space - increment)); + return ret; } -/* - Compute the column of the right-items. This is a big function, -since RIGHT-ITEMS may span more columns (eg. if a clef if inserted, -this will add a new columns to RIGHT-ITEMS. Here we look at the -columns, and return the left-most. If there are multiple columns, we -prune RIGHT-ITEMS. - - */ -Item * -Note_spacing::right_column (Grob*me) +static Real +knee_correction (Grob *note_spacing, Grob *right_stem, Real increment) { - /* - ugh. should have generic is_live() method? - */ - if (me->immutable_property_alist_ == SCM_EOL) - return 0; - - SCM right = me->get_grob_property ("right-items"); - Item *mincol = 0; - int min_rank = INT_MAX; - bool prune = false; - for (SCM s = right ; gh_pair_p (s) ; s = gh_cdr (s)) - { - Item * ri = unsmob_item (gh_car (s)); + Real note_head_width = increment; + Grob *head = right_stem ? Stem::support_head (right_stem) : 0; + Grob *rcolumn = dynamic_cast (head)->get_column (); - Item * col = ri->column_l (); - int rank = Paper_column::rank_i (col); + Interval head_extent; + if (head) + { + head_extent = head->extent (rcolumn, X_AXIS); - if (rank < min_rank) - { - min_rank = rank; - if (mincol) - prune = true; + if (!head_extent.is_empty ()) + note_head_width = head_extent[RIGHT]; - mincol = col; - } + note_head_width -= Stem::thickness (right_stem); } - - if (prune) - { - // I'm a lazy bum. We could do this in-place. - SCM newright = SCM_EOL; - for (SCM s = right ; gh_pair_p (s) ; s =gh_cdr (s)) - { - if (unsmob_item (gh_car (s))->column_l () == mincol) - newright = gh_cons (gh_car (s), newright); - } - me->set_grob_property ("right-items", newright); - } - - if (!mincol) + return -note_head_width * get_grob_direction (right_stem) + * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0); +} + +static Real +different_directions_correction (Grob *note_spacing, + Drul_array stem_posns, + Direction left_stem_dir) +{ + Real ret = 0.0; + Interval intersect = stem_posns[LEFT]; + intersect.intersect (stem_posns[RIGHT]); + + if (!intersect.is_empty ()) { + ret = abs (intersect.length ()); + /* - int r = Paper_column::rank_i (dynamic_cast(me)->column_l ()); - programming_error (_f("Spacing wish column %d has no right item.", r)); + Ugh. 7 is hardcoded. */ - - return 0; + ret = min (ret / 7, 1.0) + * left_stem_dir + * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0); } + return ret; +} - return mincol; +static Real +same_direction_correction (Grob *note_spacing, Drul_array head_posns) +{ + /* + Correct for the following situation: + + X X + | | + | | + | X | + | | | + ======== + + ^ move the center one to the left. + + + this effect seems to be much more subtle than the + stem-direction stuff (why?), and also does not scale with the + difference in stem length. + + */ + + Interval hp = head_posns[LEFT]; + hp.intersect (head_posns[RIGHT]); + if (!hp.is_empty ()) + return 0; + + Direction lowest + = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT; + + Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP]; + Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0); + + return (delta > 1) ? -lowest * corr : 0; } + /** Correct for optical illusions. See [Wanske] p. 138. The combination up-stem + down-stem should get extra space, the combination down-stem + up-stem less. - TODO: have to check wether the stems are in the same staff. - + TODO: have to check whether the stems are in the same staff. */ -Real -Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn, - Real increment) +void +Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn, + Real increment, + Real *space, Real *fixed) { - Drul_array stem_dirs(CENTER,CENTER); + Drul_array stem_dirs (CENTER, CENTER); Drul_array stem_posns; - Drul_array head_posns; - Drul_array props(me->get_grob_property ("left-items"), - me->get_grob_property ("right-items")); + Drul_array head_posns; + Drul_array props (me->get_object ("left-items"), + me->get_object ("right-items")); + + Drul_array beams_drul (0, 0); + Drul_array stems_drul (0, 0); - Drul_array beams_drul(0,0); - Real correction = 0.0; - stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER; Interval intersect; Interval bar_xextent; - Interval bar_yextent; - - bool correct = true; + Interval bar_yextent; + Direction d = LEFT; - bool acc_right = false; - + + Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT, + rcolumn->break_status_dir (), + &bar_xextent); + if (bar && dynamic_cast (bar)->get_column () == rcolumn) + bar_yextent = Staff_spacing::bar_y_positions (bar); + do { - for (SCM s = props[d]; gh_pair_p (s); s = gh_cdr (s)) + vector const &items (ly_scm2link_array (props [d])); + for (vsize i = 0; i < items.size (); i++) { - Item * it= dynamic_cast (unsmob_grob (gh_car(s))); + Item *it = dynamic_cast (items[i]); + if (!Note_column::has_interface (it)) + continue; - if (d == RIGHT) - acc_right = acc_right || Note_column::accidentals (it); - - Grob *stem = Note_column::stem_l (it); + /* + don't correct if accidentals are sticking out of the right side. + */ + if (d == RIGHT && Note_column::accidentals (it)) + return; - if (!stem) - { - if (d == RIGHT && Separation_item::has_interface (it)) - { - if (it->column_l () != rcolumn) - { - it = it->find_prebroken_piece (rcolumn->break_status_dir ()); - } - - Grob *last = Staff_spacing::extremal_break_aligned_grob (it, LEFT, &bar_xextent); - - if (last) - bar_yextent = Staff_spacing::bar_y_positions (last); - - break; - } - - goto exit_func; - } - - if(Stem::invisible_b (stem)) - { - correct = false; - goto exit_func ; - } + Grob *stem = Note_column::get_stem (it); + + if (!stem || !stem->is_live () || Stem::is_invisible (stem)) + return; + + stems_drul[d] = stem; + beams_drul[d] = Stem::get_beam (stem); + + Direction stem_dir = get_grob_direction (stem); + if (stem_dirs[d] && stem_dirs[d] != stem_dir) + return; - beams_drul[d] = Stem::beam_l (stem); - - - Direction sd = Stem::get_direction (stem); - if (stem_dirs[d] && stem_dirs[d] != sd) + stem_dirs[d] = stem_dir; + + /* + Correction doesn't seem appropriate when there is a large flag + hanging from the note. + */ + if (d == LEFT + && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem)) + return; + + Interval hp = Stem::head_positions (stem); + if (!hp.is_empty ()) { - correct = false; - goto exit_func; + Real chord_start = hp[stem_dir]; + + /* + can't look at stem-end-position, since that triggers + beam slope computations. + */ + Real stem_end = hp[stem_dir] + + stem_dir * robust_scm2double (stem->get_property ("length"), 7); + + stem_posns[d] = Interval (min (chord_start, stem_end), + max (chord_start, stem_end)); + head_posns[d].unite (hp); } - stem_dirs[d] = sd; - - Interval hp = Stem::head_positions (stem); - Real chord_start = hp[sd]; - Real stem_end = Stem::stem_end_position (stem); - - stem_posns[d] = Interval(chord_start? stem_end); - head_posns[d].unite (hp); } } while (flip (&d) != LEFT); - - - /* - don't correct if accidentals are sticking out of the right side. - */ - if (acc_right) - return 0.0; + Real correction = 0.0; - if (!bar_yextent.empty_b()) + if (!bar_yextent.is_empty ()) { - stem_dirs[RIGHT] = - stem_dirs[LEFT]; + stem_dirs[RIGHT] = -stem_dirs[LEFT]; stem_posns[RIGHT] = bar_yextent; + stem_posns[RIGHT] *= 2; } - - if (correct &&stem_dirs[LEFT] *stem_dirs[RIGHT] == -1) + + if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1) { - if (beams_drul[LEFT] == beams_drul[RIGHT]) + if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT]) { - /* - this is a knee: maximal correction. - */ - - correction = increment* stem_dirs[LEFT]; + correction = knee_correction (me, stems_drul[RIGHT], increment); + *fixed += correction; } else { - intersect = stem_posns[LEFT]; - intersect.intersect(stem_posns[RIGHT]); - correct = correct && !intersect.empty_b (); - - if (!correct) - return 0.0; - - correction = abs (intersect.length ()); + correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]); - - /* - Ugh. 7 is hardcoded. - */ - correction = (correction/7) get_grob_property ("stem-spacing-correction")); - - if (!bar_yextent.empty_b()) - { - correction *= 0.5; - } + if (!bar_yextent.is_empty ()) + correction *= 0.5; } } - else if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP) - { - /* - Correct for the following situation: - - X X - | | - | | - | X | - | | | - ======== - - ^ move the center one to the left. - - - this effect seems to be much more subtle than the - stem-direction stuff (why?), and also does not scale with the - difference in stem length. - - */ + else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1) + correction = same_direction_correction (me, head_posns); - - Interval hp = head_posns[LEFT]; - hp.intersect (head_posns[RIGHT]); - if (!hp.empty_b()) - return 0.0; + *space += correction; - Direction lowest = - (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT; - - Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ; - Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction")); - corr = (delta <= 1) ? 0.0 : 0.25; - - correction= -lowest * corr ; - } - - if (!bar_xextent.empty_b()) - correction += - bar_xextent[LEFT]; - - exit_func: - return correction; + /* there used to be a correction for bar_xextent () here, but + it's unclear what that was good for ? + */ } - - - -ADD_INTERFACE (Note_spacing,"note-spacing-interface", - "", - "left-items right-items stem-spacing-correction"); +ADD_INTERFACE (Note_spacing, + "This object calculates spacing wishes for individual voices.", + + /* properties */ + "knee-spacing-correction " + "left-items " + "right-items " + "same-direction-correction " + "stem-spacing-correction " + "space-to-barline " + );