streaming
Block-wise reading and writing for files that should not sit in memory: multi-hour recordings, live encoding, seam-free streaming resample. The reader keeps its decoder (and resampler state) across reads, so blocks concatenate exactly to the full-file decode.
StreamReader
with specux.audio.StreamReader("long.flac", sr=16000, mono=True) as r: r.samplerate, r.channels, r.length block = r.read(30 * r.samplerate) # 30 s: (frames,) here, short at EOF r.seek(600 * r.samplerate) # frame-accurate reposition to 10:00read_into
read_into(out) decodes the next block directly into a buffer you own
(no intermediate allocation) and returns the frames written, 0 once
drained. out is writable float32: (frames, channels) C-contiguous
interleaved, or 1-D for a mono stream. Allocate the buffer once as pinned
torch memory and the loop becomes the fastest disk-to-GPU path: decoder →
pinned RAM → async H2D, with no hop in between:
with specux.audio.StreamReader("long.flac", sr=16000) as r: buf = torch.empty((65536, r.channels), dtype=torch.float32).pin_memory() view = buf.numpy() # zero-copy view of the pinned RAM while (got := r.read_into(view)): gpu = buf[:got].to("cuda", non_blocking=True).T # (channels, got) ...blocks
The one-liner for the common loop. block_frames sets the window size,
hop how far the window advances per iteration (default: block_frames,
so windows sit back to back):
sr = 16000for y in specux.audio.blocks("long.flac", 30 * sr, sr=sr, mono=True): S = specux.melspectrogram(y, sr=sr) # 30 s at a timeWith hop < block_frames the windows overlap; the tail of one block
equals the head of the next:
for y in specux.audio.blocks("long.flac", sr, hop=sr // 2, mono=True): ... # 1 s windows, every 0.5 sSized so STFT frames tile exactly, this streams a spectrogram of
arbitrarily long audio in constant memory. Each window carries the
n_fft - hop_length samples of context the next one needs; hop a
multiple of hop_length keeps the framing aligned, and center=False
stops the transform from padding each block as if it were a whole signal:
n_fft, hop_length = 2048, 512step = hop_length * 1024 # ~33 s of new samples per windowfor y in specux.audio.blocks("long.flac", step + n_fft - hop_length, hop=step, sr=sr, mono=True): S = specux.melspectrogram(y, sr=sr, n_fft=n_fft, hop_length=hop_length, center=False) ... # concatenating S along time == the one-shot spectrogramStreamWriter
The writing mirror: open once, push blocks, encoder state persists. A constant-memory transcode is one loop over reader and writer:
with specux.audio.StreamReader("in.wav") as r, \ specux.audio.StreamWriter("out.flac", r.samplerate, r.channels) as w: block = r.read(65536) while block.shape[-1]: w.write(block) block = r.read(65536)input_sr= resamples on the way in, statefully, so block boundaries leave
no seams:
with specux.audio.StreamWriter("out.mp3", 44100, 2, input_sr=48000) as w: for block in specux.audio.blocks("in_48k.wav", 48000): w.write(block) # written at 44100