Streaming and events
Use streams for tails, filter history, stateful dynamics, and seeded random continuity.
Reproduction requires the exact input block list and every event’s content, frame, and
order, including setParam and reset positions. Scheduled frame events are stream
operations. Close streams deterministically. Python exposes the aggregate
latency_samples; JavaScript exposes the same value as latencySamples on a
ChainStream. The AudioWorklet wrapper has no latency getter.
Python and JavaScript event parameters are partial updates merged with the effect’s
current semantic values. Frames are zero-based within that process() input. Events
must be in non-decreasing frame order; events sharing a frame are merged in supplied
order before that sample:
output = stream.process(audio, events=[
{"frame": 0, "effectId": "voice", "parameters": {"threshold": -24}},
{"frame": 0, "effectId": "voice", "parameters": {"ratio": 6}},
])
A scheduled event replaces the semantic parameter value at its frame; the event
mechanism itself generates no ramp between the old and new value. For value-only
effects such as Volume, schedule a series of small events across successive
frames when an audible-click-free ramp is required.
An open stream or AudioWorklet accepts only values the native parameter commit can
apply immediately. Open a new stream after changing IRReverb.channelMode,
latency, or convolutionRate; FIRCrossover.bandCount, latencyMode, or
filterDelaySamples; or latencyMode / filterDelaySamples on
FiveBandFIRPEQ, GroupDelayEQ, or RoomEQ. Live updates to those
asset-configuration parameters raise ValidationError before processing or posting
a Worklet command.
Processing a file in blocks
A frame-direction slice of a multi-channel planar array, such as
audio[:, start:stop], is a non-contiguous view, so copy each block before passing it
to the stream. Mono arrays stay contiguous under the same slice, and the copy is a
no-op for them, so the pattern below is correct for any channel count:
with chain.stream(sample_rate, channels=audio.shape[0]) as stream:
blocks = [
stream.process(np.ascontiguousarray(audio[:, start:start + 4096]))
for start in range(0, audio.shape[1], 4096)
]
output = np.concatenate(blocks, axis=1)
Passing a non-contiguous view directly raises ValidationError with the same guidance.