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 intersects
  • invert/1 — new coordinates become the domain
  • map_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.

t()

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

interval()

@type interval() :: {Tamale.Coord.t(), Tamale.Coord.t()}

A {from, to} interval.

piece()

{old_start, old_stop, new_start, new_stop} — linear, strictly monotone.

t()

@type t() :: %Tamale.Warp{pieces: :identity | [piece()]}

Functions

at(warp, x)

@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.

at!(warp, x)

@spec at!(t(), Tamale.Coord.input()) :: {:ok, Tamale.Coord.t()} | :undefined

Raising variant of at/2, for tests and known-good inputs.

compose(outer, inner)

@spec compose(t(), t()) :: t()

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.

from_segments(segments)

@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.

from_span(old_span, new_span)

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.

identity()

@spec identity() :: t()

The identity warp: every coordinate maps to itself.

invert(warp)

@spec invert(t()) :: t()

Inverts a warp: new coordinates become the domain. The inverse is partial in both directions — coordinates outside the original images become undefined.

map_interval(warp, from, to)

@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} where covered are the image intervals (new coordinates, ordered) and lost the 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.

map_interval!(warp, from, to)

@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.