Tamale. Warp
(tamale v0.1.0)
Copy Markdown
A monotone partial map between coordinate systems — the transport medium
for Tamale.Anchor.Metric.
A warp answers one question: where did coordinate x go? — {:ok, x'}
or :undefined when x lies in a deleted region. Tempo changes, note
drags, duration edits and ripple deletes all reduce to the same thing: a
warp.
Representation
Either :identity or a sorted list of linear pieces
{old_start, old_stop, new_start, new_stop}. Every endpoint is an exact
rational coordinate (Tamale.Coord), so interpolation and composition
never accumulate float dust — a 1/3 tempo produces thirds, and
conformance vectors can pin them. Pieces are closed intervals; where
abutting pieces share an endpoint, at/2 resolves the (measure-zero)
ambiguity to the first piece. Old domains and new images are each
non-overlapping and monotone — a warp is piecewise monotone by
definition; non-monotone reordering is Tamale.Op.Move, not a warp,
and from_segments/1 rejects it.
Algebra
from_segments/1— piecewise assembly from{old_span, new_span}segment pairs (the only raw material is coordinate data; turning tempo maps or element span tables into segments is the adapter layer's job)compose/2— partial composition, domain intersectsinvert/1— new coordinates become the domainmap_interval/2— interval transport with first-class partial coverage ({:clip, covered, lost})
Summary
Types
A {from, to} interval.
{old_start, old_stop, new_start, new_stop} — linear, strictly monotone.
Functions
Where did coordinate x go? :undefined outside all pieces.
Raising variant of at/2, for tests and known-good inputs.
Composes two warps: compose(outer, inner) maps x through inner
first, then outer — the partial composition, defined exactly where
inner(x) is defined and lands in outer's domain. Intersections of
measure zero (a single shared point) are dropped: the composite may be
undefined at an isolated tangent point, matching the measure-zero
conventions of at/2 and map_interval/2.
Assembles a piecewise warp from {old_span, new_span} segments.
A single-piece warp linearly mapping old_span onto new_span.
The identity warp: every coordinate maps to itself.
Inverts a warp: new coordinates become the domain. The inverse is partial in both directions — coordinates outside the original images become undefined.
Maps the interval [from, to] through the warp.
Raising variant of map_interval/3, for tests and known-good inputs.
Types
@type interval() :: {Tamale.Coord.t(), Tamale.Coord.t()}
A {from, to} interval.
@type piece() :: {Tamale.Coord.t(), Tamale.Coord.t(), Tamale.Coord.t(), Tamale.Coord.t()}
{old_start, old_stop, new_start, new_stop} — linear, strictly monotone.
@type t() :: %Tamale.Warp{pieces: :identity | [piece()]}
Functions
@spec at(t(), Tamale.Coord.input()) :: {:ok, Tamale.Coord.t()} | :undefined | {:error, {:invalid_coordinate, term()}}
Where did coordinate x go? :undefined outside all pieces.
x is cast to a rational coordinate: floats are
{:error, {:invalid_coordinate, value}}. The result is always a
normalized rational. at!/2 is the raising variant.
@spec at!(t(), Tamale.Coord.input()) :: {:ok, Tamale.Coord.t()} | :undefined
Raising variant of at/2, for tests and known-good inputs.
Composes two warps: compose(outer, inner) maps x through inner
first, then outer — the partial composition, defined exactly where
inner(x) is defined and lands in outer's domain. Intersections of
measure zero (a single shared point) are dropped: the composite may be
undefined at an isolated tangent point, matching the measure-zero
conventions of at/2 and map_interval/2.
@spec from_segments([ {{Tamale.Coord.input(), Tamale.Coord.input()}, {Tamale.Coord.input(), Tamale.Coord.input()}} ]) :: {:ok, t()} | {:error, term()}
Assembles a piecewise warp from {old_span, new_span} segments.
Segments are cast to rational coordinates and normalized (sorted by old
start). Both the old domains and the new images must be non-overlapping
and monotone; shared endpoints are allowed. Errors: :invalid_segment
(malformed, non-increasing, or non-coordinate spans — floats included),
:segments_overlap, :non_monotone.
@spec from_span( {Tamale.Coord.input(), Tamale.Coord.input()}, {Tamale.Coord.input(), Tamale.Coord.input()} ) :: t()
A single-piece warp linearly mapping old_span onto new_span.
This is the literal form for hand-written warps: endpoints are cast with
Coord.cast!/1 and a non-increasing span raises ArgumentError. For
data-driven assembly use from_segments/1, which returns errors.
@spec identity() :: t()
The identity warp: every coordinate maps to itself.
Inverts a warp: new coordinates become the domain. The inverse is partial in both directions — coordinates outside the original images become undefined.
@spec map_interval(t(), Tamale.Coord.input(), Tamale.Coord.input()) :: {:ok, interval()} | {:clip, [interval()], [interval()]} | :undefined | {:error, {:invalid_coordinate, term()} | :invalid_interval}
Maps the interval [from, to] through the warp.
- fully covered →
{:ok, {from', to'}}— including intervals stretched over an insertion (a jump in the warp): the anchor grows to enclose the new material; what happens to the payload is the adapter's call (Tamale.ChannelAdapter.warp_payload/2) - partly covered →
{:clip, covered, lost}wherecoveredare the image intervals (new coordinates, ordered) andlostthe sub-intervals of[from, to](old coordinates) with no image - no coverage →
:undefined
For a non-point interval, coverage of measure zero (a shared boundary endpoint) does not count as coverage.
Endpoint images are evaluated on the pieces that contribute coverage: an
interval starting exactly at a jump boundary maps its start to the
arriving piece's value, not the departing one's. A point interval keeps
at/2's convention and resolves to the first piece.
Both endpoints are cast to rational coordinates: floats are
{:error, {:invalid_coordinate, value}} and from > to is
{:error, :invalid_interval}. map_interval!/3 is the raising
variant.
@spec map_interval!(t(), Tamale.Coord.input(), Tamale.Coord.input()) :: {:ok, interval()} | {:clip, [interval()], [interval()]} | :undefined
Raising variant of map_interval/3, for tests and known-good inputs.