Working with tensors
A consistent representation for samples, channels, and derived features.
AudioTensor: samples × channels
AudioTensor stores float32 samples in a two-dimensional NumPy array shaped (num_samples, channels). A one-dimensional input becomes a single channel. The sample rate is positive and measured in Hz; duration is num_samples / sample_rate.
The data property is read-only. Use to_numpy() for a writable copy. Construction can share memory with an input NumPy array, so pass a copy when you need independent storage.
import numpy as np
from phonotensor import AudioTensor
samples = np.zeros((48000, 2), dtype=np.float32)
audio = AudioTensor(samples.copy(), sample_rate=48000)
assert audio.shape == (48000, 2)
assert audio.duration == 1.0
mono = audio.to_mono() # Average channels
left = audio.channel(0)
editable = audio.to_numpy()Transform without losing the sample rate
Arithmetic operators return AudioTensor objects. Tensor arithmetic checks matching sample rates and uses NumPy broadcasting for sample shapes. Explicitly align durations and channels when combining signals.
time_slice() takes seconds. to_stereo() duplicates a mono channel; it does not convert audio with more than two channels.
from phonotensor import ops
first = ops.sine(220, duration=1.0, amplitude=0.2)
second = ops.sine(440, duration=1.0, amplitude=0.1)
mixed = first + second
excerpt = mixed.time_slice(start=0.1, end=0.5)
resampled = ops.resample(excerpt, target_rate=16000)
assert resampled.sample_rate == 16000FeatureTensor: frames × features
Spectral operations return FeatureTensor objects with a feature_type and optional n_fft and hop_length metadata. Data may be complex, as in STFT output. to_numpy() returns a writable copy.
When hop_length is available, frame_rate is sample_rate / hop_length. Feature duration is derived from the frame count and hop, so it need not match the exact input duration. to_dict() contains metadata only; reconstruct with FeatureTensor.from_dict(metadata, data).
features = ops.stft(first, n_fft=1024, hop_length=256)
assert features.is_complex
print(features.shape) # (frames, frequency bins)
print(features.n_features) # 513
magnitudes = ops.magnitude(features)