Windowed (Length-Aware) Datasets¶
create_windowed_audio_dataset() samples windows
instead of files. It solves a tension that appears when a corpus mixes short and
long files (e.g., 1-minute songs alongside hour-long DJ mixes):
create_audio_dataset()visits each file once per epoch and draws one random excerpt – so a long file is under-covered (its content is sampled with replacement, unevenly) yet counts the same as a short file.Naively enumerating every window of a file covers it evenly but emits those windows back-to-back, so the loader lingers on one file for many batches and batch diversity collapses.
The windowed dataset gets both even coverage and diverse batches by flattening every file’s windows into one index and letting Grain globally shuffle it.
Basic Usage¶
from audiotree.sources import create_windowed_audio_dataset
ds = create_windowed_audio_dataset(
sources=audio_dir, # one short (4s) + one long (40s) file
duration=1.0, # 1-second excerpts
hop=1.0, # non-overlapping windows (the default)
alpha=1.0, # sample frequency proportional to length
sample_rate=16000,
num_epochs=1, # one finite epoch (the default)
)
# One epoch contains every window exactly once: 4 + 40 = 44 slots.
print(len(ds))
print(ds[0].waveform.shape)
44
(1, 1, 16000)
The alpha Knob¶
alpha controls how a file’s sampling frequency scales with its length. Each
file contributes m_i = round(n_i ** alpha) slots, where n_i is its number
of natural hop windows:
alpha = 0– one slot per file (uniform per file, like the file-as-unit default).alpha = 1– one slot per window (proportional to length; even coverage per second of audio).0 < alpha < 1– in between (alpha = 0.5issqrt(length)).
for alpha in (0.0, 0.5, 1.0):
ds = create_windowed_audio_dataset(
sources=audio_dir,
duration=1.0,
hop=1.0,
alpha=alpha,
sample_rate=16000,
num_epochs=1,
)
print(alpha, len(ds))
0.0 2
0.5 8
1.0 44
At alpha = 1 the 40-second file’s 40 windows are scattered uniformly
across the epoch by Grain’s global shuffle, so batches stay diverse instead of
lingering on that file.
Jitter and Coverage¶
Each slot owns a contiguous stride of its file’s timeline. With
jitter=True (the default) every draw picks a random offset within its
stride, so the slots tile the whole file while excerpts never repeat their exact
boundaries across epochs – continuous variety with even coverage at any
alpha. Offsets are confined to [0, file_duration - duration], so windows
never run past end-of-file (clamping is built in).
Caching Durations¶
Building the slot index needs each file’s duration. These are read from headers
(no decode) and can be cached so retuning alpha/hop/duration never
re-reads the corpus:
from audiotree.sources import scan_durations, create_windowed_audio_dataset
durations = scan_durations(filepaths) # {path: seconds}, header-only
# persist `durations` (e.g., JSON) and pass it back on later runs:
ds = create_windowed_audio_dataset(filepaths=filepaths, durations=durations)
Loudness-Based Saliency (Optional)¶
For uncurated corpora you can drop quiet windows up front. Precompute a per-file
windowed-LUFS cache once (computed on the CPU, no JAX/GPU work), then point
the dataset at it – slots below lufs_cutoff are removed at build time,
so no loudness search happens during training:
from audiotree.sources import build_window_lufs_cache, create_windowed_audio_dataset
# One-time offline pass. Ragged per-file LUFS arrays are stored as bagz.
build_window_lufs_cache(
filepaths,
lufs_window_sec=1.0,
out_dir="lufs_cache/",
sample_rate=44100, # match the dataset so LUFS reflects the loaded audio
mono=True,
)
ds = create_windowed_audio_dataset(
sources="/data/audio",
duration=1.0,
alpha=0.5,
sample_rate=44100,
mono=True,
lufs_cache="lufs_cache/", # supplies durations + LUFS + window
lufs_cutoff=-40.0, # keep windows at or above -40 LUFS
)
The cache also stores durations (so it doubles as the duration cache) and the
sample_rate/mono it was measured with; the dataset raises if those don’t
match what it loads, since the cutoff would otherwise apply to a different signal.
Well-curated data needs none of this – omit lufs_cache and no filtering
is done.
Composing with Balanced Datasets¶
Pass a WindowConfig to
create_balanced_audio_dataset() to build every group
with windowed sampling. The group weights balance across categories while
alpha controls length bias within each category – the two compose
multiplicatively:
from audiotree.sources import create_balanced_audio_dataset, WindowConfig
ds = create_balanced_audio_dataset(
sources={"speech": ["/data/speech"], "music": ["/data/music"]},
weights={"speech": 0.7, "music": 0.3}, # across-group balance
sample_rate=44100,
mono=True,
window=WindowConfig(duration=1.0, alpha=0.5), # within-group coverage
)
Note
window cannot be combined with a non-raising on_read_error
(see Unreadable Files); the combination raises ValueError rather
than half-working. Windowed sampling reads every file’s duration up front to
build the window index, and a file that cannot be read contributes no
duration — dropping items one at a time is a per-item answer to a question
asked here once per corpus.