from fractions import Fraction
import pandas as pd
from timetoalign import Coordinate, Domain, NumberType, TimeUnitHow to Do Coordinate Math
Domains, TimeUnits, NumberType, Coordinate arithmetic
How to Do Coordinate Math
Domains, TimeUnits, NumberTypes, and type-safe Coordinate arithmetic.
The Three Domains
| Domain | Description | Examples |
|---|---|---|
| Physical | Real-world time | Seconds, samples |
| Logical | Symbolic/musical | Beats, quarters, ticks |
| Graphical | Visual/spatial | Pixels, centimetres |
list(Domain)["logical", "physical", "graphical"]
Domain.physical == Domain.ph == Domain("physical") == Domain("ph")True
TimeUnits
unit_data = [
{"unit": u.name, "domain": u.domain.name, "discrete": u.is_discrete}
for u in TimeUnit
]
pd.DataFrame(unit_data).sort_values(["domain", "discrete", "unit"])| unit | domain | discrete | |
|---|---|---|---|
| 12 | centimeters | graphical | False |
| 14 | inches | graphical | False |
| 11 | meters | graphical | False |
| 13 | millimeters | graphical | False |
| 15 | points | graphical | False |
| 10 | pixels | graphical | True |
| 1 | beats | logical | False |
| 2 | measures | logical | False |
| 0 | number | logical | False |
| 3 | quarters | logical | False |
| 4 | ticks | logical | True |
| 5 | milliseconds | physical | False |
| 7 | minutes | physical | False |
| 6 | seconds | physical | False |
| 9 | frames | physical | True |
| 8 | samples | physical | True |
# Convenient aliases
aliases = {
"TimeUnit.s": TimeUnit.seconds,
"TimeUnit.ms": TimeUnit.milliseconds,
"TimeUnit.q": TimeUnit.quarters,
"TimeUnit.b": TimeUnit.beats,
"TimeUnit.px": TimeUnit.pixels,
"TimeUnit.pulses": TimeUnit.ticks,
"TimeUnit.divs": TimeUnit.ticks,
}
pd.Series({k: v.name for k, v in aliases.items()}, name="resolves_to")TimeUnit.s seconds
TimeUnit.ms milliseconds
TimeUnit.q quarters
TimeUnit.b beats
TimeUnit.px pixels
TimeUnit.pulses ticks
TimeUnit.divs ticks
Name: resolves_to, dtype: object
NumberType
| Type | Python Type | Use Case |
|---|---|---|
int |
int |
Discrete units (samples, ticks) |
float |
float |
Physical time (seconds) |
fraction |
Fraction |
Exact rationals (beats, quarters) |
{
"from int": NumberType.from_number(42),
"from float": NumberType.from_number(3.14),
"from Fraction": NumberType.from_number(Fraction(3, 4)),
}{'from int': <NumberType.int: <class 'int'>>,
'from float': <NumberType.float: <class 'float'>>,
'from Fraction': <NumberType.fraction: <class 'fractions.Fraction'>>}
Why Fractions Matter
float_sum = sum(0.1 for _ in range(10))
fraction_sum = sum(Fraction(1, 10) for _ in range(10))
{
"10x float": float_sum,
"10x float == 1": float_sum == 1,
"10x fraction": fraction_sum,
"10x fraction == 1": fraction_sum == 1,
}{'10x float': 0.9999999999999999,
'10x float == 1': False,
'10x fraction': Fraction(1, 1),
'10x fraction == 1': True}
Coordinates
Immutable, hashable, type-safe value+unit pairs.
c1 = Coordinate(120, TimeUnit.ticks)
c2 = Coordinate(1.5, TimeUnit.seconds)
c3 = Coordinate(Fraction(3, 4), TimeUnit.quarters)
c1, c2, c3(Coordinate(120, ticks),
Coordinate(1.5, seconds),
Coordinate(Fraction(3, 4), quarters))
{
"value": c3.value,
"unit": c3.unit,
"number_type": c3.number_type,
"domain": c3.domain,
}{'value': Fraction(3, 4),
'unit': "quarters",
'number_type': <NumberType.fraction: <class 'fractions.Fraction'>>,
'domain': "logical"}
Arithmetic
x = Coordinate(10, TimeUnit.seconds)
y = Coordinate(5, TimeUnit.seconds)
{"x > y": x > y, "x == y": x == y, "x <= y": x <= y}{'x > y': True, 'x == y': False, 'x <= y': False}
# Unit mismatch raises TypeError
try:
Coordinate(480, TimeUnit.ticks) + Coordinate(1.0, TimeUnit.seconds)
except TypeError as e:
print(f"TypeError: {e}")TypeError: Cannot add coordinates with different units: ticks vs seconds
Type Conversions
c = Coordinate(Fraction(7, 4), TimeUnit.quarters)
{
"original": c,
"to_float()": c.to_float(),
"to_int()": c.to_int(),
"to_int('round')": c.to_int("round"),
"to_fraction()": c.to_fraction(),
}{'original': Coordinate(Fraction(7, 4), quarters),
'to_float()': 1.75,
'to_int()': 1,
"to_int('round')": 2,
'to_fraction()': Fraction(7, 4)}
original = Coordinate(100, TimeUnit.ticks)
{
"original": original,
"with_value(200)": original.with_value(200),
"with_unit(samples)": original.with_unit(TimeUnit.samples),
}{'original': Coordinate(100, ticks),
'with_value(200)': Coordinate(200, ticks),
'with_unit(samples)': Coordinate(100, samples)}