(add_configuration): new function.
* lily/tie.cc (set_control_points): new function
* lily/tie-column.cc (new_directions): new function.
* ly/music-functions-init.ly: set 'pitch property
iso. trill-pitch. This makes \relative work with \pitchedTrill
* lily/tie.cc (get_configuration): new function. Don't generate
control points, rather, generate configuration.
remove head-pair property.
* lily/include/tie.hh (struct Tie_configuration): new struct.
+2005-08-22 Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+ * lily/tie-column.cc (set_directions): set directions only once.
+ (add_configuration): new function.
+
+ * lily/tie.cc (set_control_points): new function
+
+ * lily/tie-column.cc (new_directions): new function.
+
+ * ly/music-functions-init.ly: set 'pitch property
+ iso. trill-pitch. This makes \relative work with \pitchedTrill
+
+ * lily/tie.cc (get_configuration): new function. Don't generate
+ control points, rather, generate configuration.
+ remove head-pair property.
+
+ * lily/include/tie.hh (struct Tie_configuration): new struct.
+
2005-08-22 Heikki Junes <hjunes@cc.hut.fi>
* Documentation/topdocs/NEWS.tely: clarify.
The first argument is the main note. The absolute pitch of the second
is printed as a stemless note head in parentheses.
-@refbugs
-
-Relative octave mode ignores the octave of the second argument of
-@code{\pitchedTrill}.
-
@node Feathered beams
@subsection Feathered beams
}
\relative {
- \pitchedTrill c4.\startTrillSpan fis f\stopTrillSpan
+ \pitchedTrill c4.\startTrillSpan es f\stopTrillSpan
}
for (int i = 0; i < notes_.size (); i++)
{
Grob *p = make_spanner ("Tie", SCM_EOL);
- Tie::set_interface (p); // cannot remove yet!
-
Tie::set_head (p, LEFT, prev_notes_[i]);
Tie::set_head (p, RIGHT, notes_[i]);
/*
- break-algorithm.hh -- declare Break_algorithm
+ break-algorithm.hh -- declare Break_algorithm
source file of the GNU LilyPond music typesetter
void solve_line (Column_x_positions *) const;
bool feasible (Link_array<Grob> const &) const;
- Simple_spacer_wrapper *generate_spacing_problem (Link_array<Grob> const &, Interval) const;
-
+ Simple_spacer_wrapper *generate_spacing_problem (Link_array<Grob> const &,
+ Interval) const;
virtual Array<Column_x_positions> do_solve () const = 0;
+
public:
virtual ~Break_algorithm ();
Simple_spacer *(*get_line_spacer) ();
-
/*
tie-column.hh -- declare Tie_column
DECLARE_SCHEME_CALLBACK (before_line_breaking, (SCM));
static void set_directions (Grob *me);
static void werner_directions (Grob *me);
+ static void new_directions (Grob *me);
};
#endif /* TIE_COLUMN_HH */
int position_;
Direction dir_;
Interval attachment_x_;
- Real edge_y_;
+ Real delta_y_;
- Tie_configuration ()
- {
- dir_ = CENTER;
- position_ = 0;
- }
+ Tie_configuration ();
static int compare (Tie_configuration const &a,
Tie_configuration const &b);
};
+INSTANTIATE_COMPARE (Tie_configuration, Tie_configuration::compare);
class Tie
{
public:
static void set_head (Grob *, Direction, Grob *head);
- static void set_interface (Grob *);
static bool has_interface (Grob *);
static void set_direction (Grob *);
static Grob *head (Grob *, Direction);
static int get_column_rank (Grob *, Direction);
static Real get_position (Grob *);
static Direction get_default_dir (Grob *);
- static SCM get_control_points (SCM);
- static SCM get_configuration (SCM);
+ static void get_configuration (Grob *, Grob **, Tie_configuration *);
+ static void set_control_points (Grob *, Grob **,Tie_configuration const&);
+ static void set_default_control_points (Grob *);
DECLARE_SCHEME_CALLBACK (print, (SCM));
DECLARE_SCHEME_CALLBACK (set_spacing_rods, (SCM));
+ static int compare (Grob *const &s1,
+ Grob *const &s2);
+
};
DECLARE_ACKNOWLEDGER (dots);
DECLARE_ACKNOWLEDGER (text_spanner);
void process_music ();
- virtual bool try_music (Music *);
void stop_translation_timestep ();
private:
if (mus
&& mus->is_mus_type ("trill-span-event")
&& to_dir (mus->get_property ("span-direction")) == START
- && unsmob_pitch (mus->get_property ("trill-pitch")))
+ && unsmob_pitch (mus->get_property ("pitch")))
make_trill (mus);
}
void
Pitched_trill_engraver::make_trill (Music *mus)
{
- SCM scm_pitch = mus->get_property ("trill-pitch");
+ SCM scm_pitch = mus->get_property ("pitch");
Pitch *p = unsmob_pitch (scm_pitch);
SCM keysig = get_property ("localKeySignature");
{
}
-bool
-Pitched_trill_engraver::try_music (Music *)
-{
- return false;
-}
#include "translator.icc"
+
ADD_ACKNOWLEDGER (Pitched_trill_engraver, note_head);
ADD_ACKNOWLEDGER (Pitched_trill_engraver, dots);
ADD_ACKNOWLEDGER (Pitched_trill_engraver, text_spanner);
+
ADD_TRANSLATOR (Pitched_trill_engraver,
/* doc */ "Print the bracketed notehead after a notehead with trill.",
- /* create */ "TrillPitchHead TrillPitchAccidental TrillPitchGroup",
+ /* create */
+ "TrillPitchHead "
+ "TrillPitchAccidental "
+ "TrillPitchGroup",
/* accept */ "",
/* read */ "",
/* write */ "");
(c) 2000--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
*/
+
+#include <math.h>
+#include <map>
+
+#include "staff-symbol-referencer.hh"
+#include "warn.hh"
#include "tie-column.hh"
#include "paper-column.hh"
#include "spanner.hh"
#include "directional-element-interface.hh"
#include "rhythmic-head.hh"
-/*
- tie dir depends on what Tie_column does.
-*/
-/*
- TODO: this doesn't follow standard pattern. Regularize.
-*/
void
Tie_column::add_tie (Grob *me, Grob *tie)
{
void
Tie_column::set_directions (Grob *me)
{
- werner_directions (me);
+ if (!to_boolean (me->get_property ("positioning-done")))
+ {
+ me->set_property ("positioning-done", SCM_BOOL_T);
+ new_directions (me);
+ }
}
int
-tie_compare (Grob *const &s1,
- Grob *const &s2)
+Tie::compare (Grob *const &s1,
+ Grob *const &s2)
{
return sign (Tie::get_position (s1) - Tie::get_position (s2));
}
-#if 0
/*
Werner:
if (!ties.size ())
return;
- ties.sort (tie_compare);
+ ties.sort (&Tie::compare);
Direction d = get_grob_direction (me);
if (d)
return;
}
-#endif
MAKE_SCHEME_CALLBACK (Tie_column, after_line_breaking, 1);
SCM
Tie_column::after_line_breaking (SCM smob)
{
- werner_directions (unsmob_grob (smob));
+ set_directions (unsmob_grob (smob));
return SCM_UNSPECIFIED;
}
ADD_INTERFACE (Tie_column, "tie-column-interface",
"Object that sets directions of multiple ties in a tied chord",
- "direction");
+ "direction "
+ "positioning-done "
+ );
+
+
+
+
+bool
+config_allowed (map<Tie_configuration, bool> const &allowed,
+ Tie_configuration conf)
+{
+ return allowed.find (conf) == allowed.end ();
+}
+
+void
+add_configuration (map<Tie_configuration, bool> *allowed,
+ Grob *tie_column,
+ Tie_configuration new_conf)
+{
+ bool on_line = Staff_symbol_referencer::on_staffline (tie_column, new_conf.position_);
+
+ if (allowed->find (new_conf) != allowed->end ()
+ && !(*allowed)[new_conf])
+ {
+ programming_error ("Tie configuration not allowed");
+ }
+
+
+ if (on_line)
+ {
+ Tie_configuration forbidden;
+
+ forbidden.dir_ = -new_conf.dir_ ;
+ forbidden.position_ = new_conf.position_;
+ (*allowed)[forbidden] = false;
+
+ forbidden.position_ += new_conf.dir_;
+ (*allowed)[forbidden] = false;
+ forbidden.position_ += new_conf.dir_;
+ (*allowed)[forbidden] = false;
+
+ forbidden.dir_ = new_conf.dir_;
+ forbidden.position_ = new_conf.position_ + new_conf.dir_;
+ (*allowed)[forbidden] = false;
+ }
+ else
+ {
+ Tie_configuration forbidden;
+ forbidden.dir_ = - new_conf.dir_;
+ forbidden.position_ = new_conf.position_;
+
+
+ (*allowed)[forbidden] = false;
+ forbidden.position_ -= new_conf.dir_;
+ forbidden.dir_ = new_conf.dir_;
+ (*allowed)[forbidden] = false;
+
+ forbidden.position_ += 2* new_conf.dir_;
+ (*allowed)[forbidden] = false;
+ }
+}
+
+
+void
+Tie_column::new_directions (Grob *me)
+{
+ extract_grob_set (me, "ties", ro_ties);
+ Link_array<Grob> ties (ro_ties);
+ if (!ties.size ())
+ return;
+
+ if (ties.size() == 1)
+ {
+ Tie::set_default_control_points (ties[0]);
+ return ;
+ }
+
+ ties.sort (&Tie::compare);
+ Array<Tie_configuration> tie_configs;
+ for (int i = 0; i < ties.size (); i++)
+ {
+ Tie_configuration conf;
+ conf.dir_ = get_grob_direction (ties[i]);
+ conf.position_ = (int) rint (Tie::get_position (ties[i]));
+ tie_configs.push (conf);
+ }
+
+
+ if (!tie_configs[0].dir_)
+ tie_configs[0].dir_ = DOWN;
+ if (!tie_configs.top().dir_)
+ tie_configs.top().dir_ = UP;
+
+
+ /*
+ Seconds
+ */
+ for (int i = 1; i < tie_configs.size(); i++)
+ {
+ if (fabs (tie_configs[i-1].position_ - tie_configs[i].position_) <= 1)
+ {
+ if (!tie_configs[i-1].dir_)
+ tie_configs[i-1].dir_ = DOWN;
+ if (!tie_configs[i].dir_)
+ tie_configs[i].dir_ = UP;
+ }
+ }
+
+ for (int i = 1; i < tie_configs.size() - 1; i++)
+ {
+ if (tie_configs[i].dir_)
+ continue;
+
+ tie_configs[i].dir_ = (Direction) sign (tie_configs[i].position_);
+ }
+
+ Grob *common[NO_AXES] = {
+ me, me
+ };
+ for (int i = 0; i < ties.size (); i++)
+ for (int a = X_AXIS; a < NO_AXES; a++)
+ {
+ Axis ax ((Axis) a);
+
+ common[ax] = dynamic_cast<Spanner*> (ties[i])->get_bound (LEFT)->common_refpoint (common[a], ax);
+ common[ax] = dynamic_cast<Spanner*> (ties[i])->get_bound (RIGHT)->common_refpoint (common[a], ax);
+ }
+
+ map<Tie_configuration, bool> allowed;
+
+ Tie::get_configuration (ties[0], common, &tie_configs.elem_ref (0));
+ Tie::get_configuration (ties.top (), common,
+ &tie_configs.elem_ref (tie_configs.size()-1));
+
+ add_configuration (&allowed, me, tie_configs[0]);
+ add_configuration (&allowed, me, tie_configs.top());
+
+ for (int i = 1; i < ties.size(); i++)
+ {
+ Tie_configuration conf = tie_configs[i];
+ Tie::get_configuration (ties[i], common, &conf);
+ if (!config_allowed (allowed, conf))
+ {
+ conf = tie_configs[i];
+
+ Direction d = LEFT;
+ do
+ {
+ conf.attachment_x_[d] = d * 1e6; // infty
+ for (int j = i - 1; j < i + 2; j++)
+ {
+ if (j >= 0 && j < ties.size())
+ {
+ Spanner *t = dynamic_cast<Spanner*> (ties[j]);
+ Interval ext
+ = robust_relative_extent (t->get_bound (d),
+ common[X_AXIS], X_AXIS);
+ conf.attachment_x_[d]
+ = d * min (d * conf.attachment_x_[d], d * ext[-d]);
+ }
+ }
+ }
+ while (flip (&d) != LEFT);
+ tie_configs[i] = conf;
+ }
+ else
+ tie_configs[i] = conf;
+
+ add_configuration (&allowed, me, conf);
+ }
+
+ for (int i = 0; i < ties.size(); i++)
+ {
+ Tie::set_control_points (ties[i], common, tie_configs[i]);
+ set_grob_direction (ties[i], tie_configs[i].dir_);
+ }
+}
Grob *p = new Spanner (heads_to_tie_[i].tie_definition_,
context ()->get_grob_key ("Tie"));
announce_grob (p, heads_to_tie_[i].event_->self_scm ());
- Tie::set_interface (p); // cannot remove yet!
-
Tie::set_head (p, LEFT, th);
Tie::set_head (p, RIGHT, h);
}
while (flip (&d) != LEFT);
- index_set_cell (her->get_property ("head-pair"), LEFT, new_head_drul[LEFT]->self_scm ());
- index_set_cell (her->get_property ("head-pair"), RIGHT, new_head_drul[RIGHT]->self_scm ());
+ Spanner *sp = dynamic_cast<Spanner*> (her);
+ sp->set_bound (LEFT, new_head_drul[LEFT]);
+ sp->set_bound (RIGHT, new_head_drul[RIGHT]);
}
#include "translator.icc"
+
ADD_ACKNOWLEDGER (Tie_engraver, note_head);
ADD_TRANSLATOR (Tie_engraver,
/* doc */ "Generate ties between noteheads of equal pitch.",
- /* create */ "Tie TieColumn",
+ /* create */
+ "Tie "
+ "TieColumn",
+
/* accept */ "tie-event",
/* read */ "tieWaitForNote",
/* write */ "tieMelismaBusy");
void
Tie::set_head (Grob *me, Direction d, Grob *h)
{
- assert (!head (me, d));
- index_set_cell (me->get_property ("head-pair"), d, h->self_scm ());
-
dynamic_cast<Spanner *> (me)->set_bound (d, h);
me->add_dependency (h);
}
-void
-Tie::set_interface (Grob *me)
-{
- me->set_property ("head-pair", scm_cons (SCM_EOL, SCM_EOL));
-}
-
Grob *
Tie::head (Grob *me, Direction d)
{
- SCM c = me->get_property ("head-pair");
-
- if (scm_is_pair (c))
- return unsmob_grob (index_get_cell (c, d));
+ Item *it = dynamic_cast<Spanner*> (me)->get_bound (d);
+ if (Note_head::has_interface (it))
+ return it;
else
return 0;
}
}
-SCM
-Tie::get_configuration (Grob *me_grob,
- Grob **common,
+void
+Tie::get_configuration (Grob *me_grob, Grob **common,
Tie_configuration *conf)
{
Spanner *me = dynamic_cast<Spanner*> (me_grob);
-
if (!head (me, LEFT) && !head (me, RIGHT))
{
programming_error ("tie without heads");
me->suicide ();
- return SCM_EOL;
+ return ;
}
- set_direction (me);
- int tie_position = (int) Tie::get_position (me);
+ Direction dir = CENTER;
- Direction dir = get_grob_direction (me);
-
- Real staff_space = Staff_symbol_referencer::staff_space (me);
- Real staff_position = tie_position;
+ int tie_position = (int) Tie::get_position (me);
+ int staff_position = conf->position_;
- Direction d = LEFT;
- Real gap = robust_scm2double (me->get_property ("x-gap"), 0.2);
- do
+ if (conf->dir_)
+ {
+ dir = conf->dir_;
+ }
+ else
{
- attachments[d]
- = robust_relative_extent (me->get_bound (d),
- common[X_AXIS],
- X_AXIS)[-d]
- - gap * d;
+ dir = get_grob_direction (me);
+ if (!dir)
+ dir = get_default_dir (me);
}
- while (flip (&d) != LEFT);
+
+ Real staff_space = Staff_symbol_referencer::staff_space (me);
bool in_between = true;
- if (attachments.length () < 0.6 * staff_space)
+ Interval attachments = conf->attachment_x_;
+ if (attachments.is_empty())
{
- /*
- Let short ties start over note heads, instead of between.
- */
- Drul_array<bool> allow (true, true);
-
Direction d = LEFT;
- do {
- if (Note_head::has_interface (me->get_bound (d)))
- {
- Grob *stem = unsmob_grob (me->get_bound (d)->get_object ("stem"));
- if (get_grob_direction (stem) == dir
- && -d == dir)
- allow[d] = false;
- }
- } while (flip (&d) != LEFT);
-
- if (allow[LEFT] && allow[RIGHT])
+ Real gap = robust_scm2double (me->get_property ("x-gap"), 0.2);
+ do
{
- staff_position += dir;
- do
+ attachments[d]
+ = robust_relative_extent (me->get_bound (d),
+ common[X_AXIS],
+ X_AXIS)[-d]
+ - gap * d;
+ }
+ while (flip (&d) != LEFT);
+
+ if (attachments.length () < 0.6 * staff_space)
+ {
+ /*
+ Let short ties start over note heads, instead of between.
+ */
+ Drul_array<bool> allow (true, true);
+
+ Direction d = LEFT;
+ do {
+ if (Note_head::has_interface (me->get_bound (d)))
+ {
+ Grob *stem = unsmob_grob (me->get_bound (d)->get_object ("stem"));
+ if (get_grob_direction (stem) == dir
+ && -d == dir)
+ allow[d] = false;
+ }
+ } while (flip (&d) != LEFT);
+
+ if (allow[LEFT] && allow[RIGHT])
{
- if (Note_head::has_interface (me->get_bound (d)))
+ staff_position += dir;
+ do
{
- Interval extent
- = robust_relative_extent (me->get_bound (d),
- common[X_AXIS], X_AXIS);
-
- attachments[d] = extent.linear_combination (- 0.5 * d);
- in_between = false;
+ if (Note_head::has_interface (me->get_bound (d)))
+ {
+ Interval extent
+ = robust_relative_extent (me->get_bound (d),
+ common[X_AXIS], X_AXIS);
+
+ attachments[d] = extent.linear_combination (- 0.5 * d);
+ in_between = false;
+ }
}
+ while (flip (&d) != LEFT);
}
- while (flip (&d) != LEFT);
}
}
-
SCM details = me->get_property ("details");
SCM limit
Offset middle = b.curve_point (0.5);
Offset edge = b.curve_point (0.0);
- staff_position = rint (staff_position);
+ staff_position = int (rint (staff_position));
Real dy = fabs (middle[Y_AXIS] - edge[Y_AXIS]);
bool in_space = !(Staff_symbol_referencer::on_staffline (me, (int) staff_position));
}
}
- /*
- Putting larger in-space ties next to the notes forces
- the edges to be opposite (Y-wise) to the tie direction.
- */
- if (staff_position == tie_position
- && in_space
- && dy > 0.3 * staff_space)
- {
- staff_position += 2 * dir;
- }
-
if (in_space != fits_in_space)
{
if (in_space)
}
}
+
+ /*
+ Putting larger in-space ties next to the notes forces
+ the edges to be opposite (Y-wise) to the tie direction.
+ */
+ if (staff_position == tie_position
+ && in_space
+ && dy > 0.3 * staff_space)
+ {
+ staff_position += 2 * dir;
+ }
+
if (!in_between
&& in_space
&& fabs (staff_position - tie_position) <= 1)
Real center = (edge[Y_AXIS] + middle[Y_AXIS])/2.0;
- conf->edge_y_ = staff_position * staff_space * 0.5
- - center;
+ conf->delta_y_ = - center;
}
else
{
- conf->edge_y_ =
- (staff_position - dir) * staff_space * 0.5
- + dir * 0.2 * staff_space;
+ conf->delta_y_ =
+ dir * staff_space * (- 0.3);
}
}
else
Real where = 0.5 * dir;
Real rounding_dy = (where - middle[Y_AXIS]);
- conf->edge_y_ = 0.5 * staff_position * staff_space + rounding_dy;
+ conf->delta_y_ = rounding_dy;
if (dir * b.curve_point (0.0)[Y_AXIS] <
dir * tie_position * 0.5 * staff_space)
- conf->edge_y_ += staff_space * dir;
+ conf->delta_y_ += staff_space * dir;
}
- conf->position_ = staff_position;
conf->dir_ = dir;
+ conf->position_ = staff_position;
conf->attachment_x_ = attachments;
}
-SCM
-Tie::get_control_points (SCM smob)
+void
+Tie::set_default_control_points (Grob *me_grob)
{
- Spanner *me = unsmob_spanner (smob);
-
+ Spanner *me = dynamic_cast<Spanner*> (me_grob);
Grob *common[NO_AXES] = {
0, 0
};
}
Tie_configuration conf;
- get_configuration (me, common, &conf);
+ if (!get_grob_direction (me))
+ set_grob_direction (me, get_default_dir (me));
+
+ int tie_position = (int) Tie::get_position (me);
+ conf.position_ = tie_position;
+
+ get_configuration (me, common, &conf);
+ set_control_points (me, common, conf);
+}
+
+void
+Tie::set_control_points (Grob *me,
+ Grob **common,
+ Tie_configuration const &conf)
+{
SCM details = me->get_property ("details");
SCM limit
= scm_assq (ly_symbol2scm ("height-limit"), details);
+ Real staff_space = Staff_symbol_referencer::staff_space (me);
Real h_inf = robust_scm2double (scm_cdr (limit), 0.75) * staff_space;
Real r_0 = robust_scm2double (scm_cdr (scm_assq (ly_symbol2scm ("ratio"),
details)),
.333);
- Bezier b = slur_shape (conf->attachment_x_.length(),
+ Bezier b = slur_shape (conf.attachment_x_.length(),
h_inf, r_0);
- b.scale (1, conf->dir_);
-
- Bezier b;
-
- b.translate (Offset (conf->attachment_x_[LEFT]
- - me->relative_coordinate (common[X_AXIS], X_AXIS), 0));
+ b.scale (1, conf.dir_);
+ b.translate (Offset (conf.attachment_x_[LEFT]
+ - me->relative_coordinate (common[X_AXIS], X_AXIS),
+ 0.5 * conf.position_ * staff_space
+ + conf.delta_y_
+ ));
SCM controls = SCM_EOL;
for (int i = 4; i--;)
controls = scm_cons (ly_offset2scm (b.control_[i]), controls);
- return controls;
+ me->set_property ("control-points", controls);
}
{
Grob *me = unsmob_grob (smob);
+ if (CENTER == get_grob_direction (me))
+ set_direction (me);
+
SCM cp = me->get_property ("control-points");
- if (!scm_is_pair (cp)) // list is more accurate
+ if (!scm_is_pair (cp))
{
- cp = get_control_points (smob);
- me->set_property ("control-points", cp);
+ set_default_control_points (me);
+ cp = me->get_property ("control-points");
}
if (!scm_is_pair (cp))
ADD_INTERFACE (Tie,
"tie-interface",
+
"A tie connecting two noteheads.\n",
"control-points "
- "dash-fraction"
+ "dash-fraction "
"dash-period "
"details "
"direction "
- "head-pair "
"thickness "
"x-gap ");
+int
+Tie_configuration::compare (Tie_configuration const &a,
+ Tie_configuration const &b)
+{
+ if (a.position_ - b.position_)
+ return sign (a.position_ - b.position_);
+ return sign (a.dir_ - b.dir_);
+}
+
-
+Tie_configuration::Tie_configuration ()
+{
+ dir_ = CENTER;
+ position_ = 0;
+ delta_y_ = 0.0;
+}
)))
(if (ly:pitch? trill-pitch)
- (for-each (lambda (m) (ly:music-set-property! m 'trill-pitch trill-pitch))
+ (for-each (lambda (m) (ly:music-set-property! m 'pitch trill-pitch))
trill-events)
(begin
(ly:warning (_ "Second argument of \\pitchedTrill should be single note: "))
;; TODO: use interface for this!
(chord-tremolo ,boolean? "if set, this beam is a tremolo. ")
(begin-of-line-visible ,boolean? "Used for marking ChordNames that should only show changes.")
- (head-pair ,pair? "Pair of grob pointers, pointing to the two heads of the tie.")
+
(quant-score ,string? "Beam quanting score -- can be stored for
debugging")
+
(least-squares-dy ,number?
"ideal beam slope, without damping.")
(ligature-primitive-callback ,procedure? "callback that brews ligature head.")
(print-function . ,Tie::print)
(details . ((ratio . 0.333) (height-limit . 1.0)))
(thickness . 1.0)
- (y-offset . 0.6)
- (minimum-length . 2.5)
(meta . ((class . Spanner)
(interfaces . (tie-interface))))))