12. Sliding-Window Silence Detection in a Streamed Audio Signal
turn.start / turn.eager_end / turn.end events (cartesia.ai/blog/ink-2). This problem implements the classic energy-based silence/VAD primitive such a pipeline would still use as one low-level signal feeding into (but not replacing) that semantic layer; Cartesia has not published the internals of that layer, so treat this as the general building block, not a description of Ink-2's actual implementation.Problem
Before any semantic turn-taking logic runs, a streaming voice pipeline needs a cheap low-level signal for "is anyone talking right now." A classic building block for that is windowed RMS energy: split the raw audio stream into fixed-size frames and flag any frame whose energy falls below a threshold as silence.
Given a flat list of audio samples, split them into consecutive, non-overlapping frames of window_size samples each (the final frame may be shorter if the signal doesn't divide evenly), and return one boolean per frame: True if that frame's RMS energy is below energy_threshold.
Source: src/12_silence_window_detector.py
def detect_silence_windows(samples: list[float], window_size: int, energy_threshold: float) -> list[bool]:
>>> detect_silence_windows([0, 0, 0, 0, 0.9, -0.9, 0.8, -0.8, 0.01, -0.01, 0.02, -0.02], window_size=4, energy_threshold=0.1)
[True, False, True]
>>> detect_silence_windows([1.0, 1.0], window_size=4, energy_threshold=0.1)
[False]
Step-by-Step Approach
- Iterate over the samples in steps of
window_sizeusingrange(0, len(samples), window_size), which naturally produces the start index of each consecutive, non-overlapping frame. - Slice out each frame with
samples[start:start + window_size]— Python slicing handles the shorter final frame automatically without any bounds-checking code. - Compute RMS (root-mean-square) energy for the frame: square every sample, average the squares, then take the square root —
math.sqrt(sum(x*x for x in frame) / len(frame)). - RMS is used instead of a simple average because audio samples oscillate around zero; squaring first makes negative and positive swings both contribute to energy instead of canceling out.
- Append
rms < energy_thresholdto the result list for each frame, and return the full list once every frame has been processed.
The key insight is that windowed RMS energy is a purely local, O(window_size) computation per frame with no dependency between frames, so the whole pass is O(n) with O(1) extra memory beyond the output — there's no need for a running/incremental energy trick unless the windows overlap, which they don't here.
Reference solution
import math
def detect_silence_windows(samples: list[float], window_size: int, energy_threshold: float) -> list[bool]:
# non-overlapping frames: O(n) total work, one RMS computation per frame
result = []
for start in range(0, len(samples), window_size):
frame = samples[start:start + window_size]
# RMS = sqrt(mean(x^2)); classic energy measure, robust to sign
rms = math.sqrt(sum(x * x for x in frame) / len(frame))
result.append(rms < energy_threshold)
return result
Key Functions & Tricks
range(0, len(samples), window_size)— yields the start index of each consecutive frame in one line.samples[start:start + window_size]— slices out a frame; Python silently truncates at the end, handling the short final frame for free.math.sqrt(sum(x*x ...) / len(frame))— the RMS formula — squares samples so positive/negative swings both add to energy.list comprehension inside sum()— computes sum of squares in one expression without a manual accumulator loop.
How to Recognize This Pattern
Reach for windowed/framed processing whenever a problem describes a continuous signal that needs to be evaluated in fixed-size chunks — audio, sensor readings, or any time series where "per-frame" or "per-window" summary statistics are requested. The tell here is "non-overlapping" windows, which makes this simpler than sliding-window problems that ask for a rolling statistic per position (those need incremental updates to stay O(n); see the sliding-window-maximum family of problems). A common variation asks for overlapping windows with a stride smaller than the window size, which does require an incremental running-sum trick to stay efficient. A common pitfall is using mean absolute value or peak amplitude instead of RMS — RMS is the standard energy measure because it weights louder transients more heavily (via the square) than a plain average would.