Skip to content

Offsets and Time

Time Constants

The Time enum provides nanosecond-based constants for consistent unit handling:

from sgnts.base import Time

print(Time.SECONDS)       # 1_000_000_000
print(Time.MILLISECONDS)  # 1_000_000
print(Time.MICROSECONDS)  # 1_000
print(Time.NANOSECONDS)   # 1

# Convert 2.5 seconds to nanoseconds
ns = int(2.5 * Time.SECONDS)

The Offset System

Offsets are integer sample counts at the maximum sample rate (16384 Hz by default). They provide a precise, rounding-error-free alternative to floating-point timestamps.

from sgnts.base import Offset

# Convert between seconds and offsets
offset = Offset.fromsec(1.0)      # 16384
seconds = Offset.tosec(16384)     # 1.0

# Convert between nanoseconds and offsets
offset = Offset.fromns(1_000_000_000)  # 16384
ns = Offset.tons(16384)                # 1_000_000_000

# Convert between samples at a given rate and offsets
offset = Offset.fromsamples(2048, 2048)  # 16384
samples = Offset.tosamples(16384, 2048)  # 2048

One second of data always spans 16384 offset units, regardless of sample rate. This is what makes cross-rate alignment possible. For a deeper discussion of why offsets exist and the problems they solve, see The Offset System.

Allowed Sample Rates

Only power-of-2 rates are allowed (1, 2, 4, ... up to the max rate). This ensures offset conversions are always exact integers:

from sgnts.base import Offset

print(Offset.ALLOWED_RATES)  # {1, 2, 4, 8, 16, ..., 16384}
print(Offset.MAX_RATE)       # 16384

Changing the Maximum Rate

If your application needs rates above 16384 Hz, increase the maximum globally before creating any buffers:

from sgnts.base import Offset

Offset.set_max_rate(262144)
print(Offset.MAX_RATE)        # 262144
print(Offset.ALLOWED_RATES)   # {1, 2, 4, ..., 262144}

This must be called once at application startup, before any buffers or elements are created.

Converting Between Sample Rates

Offsets act as a common currency between sample rates. Convert samples at one rate to offsets, then from offsets to samples at another rate:

from sgnts.base import Offset

# 1000 samples at 8192 Hz → how many samples at 4096 Hz?
offset = Offset.fromsamples(1000, 8192)
samples_4k = Offset.tosamples(offset, 4096)  # 500

This also means two buffers at different rates that cover the same time span share identical offset and offset_end values:

import numpy as np
from sgnts.base import Offset, SeriesBuffer

start = Offset.fromsec(10.0)

buf_8k = SeriesBuffer(offset=start, sample_rate=8192, data=np.zeros(8192))
buf_4k = SeriesBuffer(offset=start, sample_rate=4096, data=np.zeros(4096))

assert buf_8k.offset == buf_4k.offset          # same start
assert buf_8k.offset_end == buf_4k.offset_end  # same end — both 1 second

Valid Conversions

tosamples requires the offset to correspond to a whole number of samples at the target rate. In practice, an offset must be a multiple of MAX_RATE / sample_rate:

from sgnts.base import Offset

# 16384 offsets = exactly 4096 samples at 4096 Hz ✓
Offset.tosamples(16384, 4096)  # 4096

# 10000 offsets is not a whole number of samples at 4096 Hz ✗
# Offset.tosamples(10000, 4096)  → raises AssertionError

If you're constructing offsets from external timestamps, use fromsamples or the rounding support in fromsec/fromns (below) to stay on valid boundaries.

Rounding to Sample Boundaries

When converting from seconds or nanoseconds, pass a sample_rate to round the result to the nearest sample boundary at that rate. This is useful when working with external timestamps that don't fall exactly on sample points:

from sgnts.base import Offset

# Without sample_rate — exact conversion, may not align with any rate
offset = Offset.fromns(1_000_000_123)

# With sample_rate — rounds to nearest sample at 2048 Hz
offset = Offset.fromns(1_000_000_123, sample_rate=2048)

# Now guaranteed to convert cleanly
samples = Offset.tosamples(offset, 2048)  # integer result

This is particularly useful when defining segment boundaries from external data (GPS times, file timestamps) that need to map to exact sample points.

Calculating Time Differences

Since offsets are plain integers, arithmetic works naturally:

from sgnts.base import Offset

event1 = Offset.fromsec(10.25)
event2 = Offset.fromsec(12.75)

diff = event2 - event1
print(Offset.tosec(diff))                 # 2.5 seconds
print(Offset.tosamples(diff, 8192))  # 20480 samples