Skip to content
anecho.ai

How do you use Anecho speaker isolation as a Pipecat audio filter?

Write a small Pipecat BaseAudioFilter that re-chunks the transport's inbound audio into 240-sample hops at 16 kHz and runs the Anecho SDK on each hop. It is code you write, not a published plugin; the full filter and its numbers are below.

Anecho Engineering06 Oct 20263 min read
pipecatintegrationsdkvoice-agents

You use Anecho speaker isolation in Pipecat by writing a small BaseAudioFilter that re-chunks the transport's inbound audio into 240-sample hops at 16 kHz and runs the Anecho SDK on each hop. We do not ship a Pipecat package. Pipecat's audio-filter slot already carries Krisp VIVA, ai-coustics, RNNoise, Koala and noisereduce (docs.pipecat.ai, retrieved 2026-10-15); ours is the code below, which you own and adapt.

Where it sits

A Pipecat transport accepts audio_in_filter, which sees raw inbound bytes before VAD and STT. That is the right place for a model that must run on a continuous stream. The SDK contract: Processor.process() takes a float32 block and returns the same number of samples 15 ms later; the first 240 samples are silence (the algorithmic delay). The UsageReporter sends durations, never audio.

The filter

Pipecat hands the filter arbitrary-sized int16 byte chunks at the transport's rate. The model wants 240-sample hops at 16 kHz and the filter must return as many bytes as it received, so it buffers on both sides.

import os
import numpy as np
from pipecat.audio.filters.base_audio_filter import BaseAudioFilter
from pipecat.frames.frames import FilterControlFrame
from anecho import Model, Processor, UsageReporter

RATE, HOP = 16_000, 240          # model rate; 240 samples = 15 ms


class AnechoFilter(BaseAudioFilter):
    """Speaker isolation on the transport's inbound audio. Our code, not a package."""

    def __init__(self, model_path: str):
        self._model = Model.from_file(model_path)
        self._reporter = UsageReporter(os.environ["ANECHO_API_KEY"])
        self._proc = None
        self._pending = np.zeros(0, dtype=np.float32)    # input not yet a full hop
        self._out = np.zeros(HOP, dtype=np.float32)      # one-hop prefill keeps output ahead of input

    async def start(self, sample_rate: int):
        if sample_rate != RATE:                          # resample at the transport, not here
            raise ValueError(f"AnechoFilter needs 16 kHz input, got {sample_rate}")
        self._proc = Processor(self._model, self._reporter.ensure_license())
        self._reporter.attach(self._proc)
        self._reporter.start()

    async def stop(self):
        self._proc = None

    async def process_frame(self, frame: FilterControlFrame):
        pass                                             # no runtime controls

    async def filter(self, audio: bytes) -> bytes:
        x = np.frombuffer(audio, dtype=np.int16).astype(np.float32) / 32768.0
        self._pending = np.concatenate([self._pending, x])
        while len(self._pending) >= HOP:
            block, self._pending = self._pending[:HOP], self._pending[HOP:]
            self._out = np.concatenate([self._out, self._proc.process(block)])
        y, self._out = self._out[: len(x)], self._out[len(x):]   # same byte count out as in
        return (np.clip(y, -1.0, 1.0) * 32767).astype(np.int16).tobytes()
transport = SmallWebRTCTransport(
    webrtc_connection=conn,
    params=TransportParams(
        audio_in_enabled=True,
        audio_in_filter=AnechoFilter("anecho.ai_focus_model_16khz_v8_2.anecho"),
    ),
)

This is a sketch against the BaseAudioFilter interface (start, stop, process_frame, filter). We have not benchmarked a live Pipecat pipeline, so check the interface against your Pipecat version and test on your own audio. Because output is buffered to the input length, the filter adds the model's 15 ms plus up to one hop of buffering.

Notes

  • The filter requires 16 kHz input and raises otherwise, so set the transport's audio_in_sample_rate or resample before it. Upsampling 8 kHz telephony cannot restore bandwidth, and in our run the isolation model made telephony conditions slightly worse; see 8 kHz is where voice AI breaks.
  • Create one filter per call, since the processor holds state.
  • Pipecat also has turn-detection models trained for noise; isolation and turn detection fix different failure modes (why voice agents fail in noisy calls).

What we measured

ItemValueSource and date
Algorithmic delay15 msNull Test, 2026-08-14
Real-time factor, one CPU thread0.067Null Test, 2026-08-14 (Apple M1 Max)
Worst block time, p993.96 msNull Test, 2026-08-14
Two voices at 0 dB, WER raw vs ours84.74% vs 74.21%Null Test, 2026-08-14
Pooled over 19 conditions, WER raw vs ours14.32% vs 16.59%Null Test, 2026-08-14

The pooled row is a loss, which is why this is a filter you opt into, per deployment, after measuring: how to evaluate speaker isolation.

FAQ

Is there an Anecho package for Pipecat?

No. There is no published package. The filter above is code you write against the Pipecat BaseAudioFilter interface.

Which Pipecat extension point do I use?

The transport's audio_in_filter, which sees inbound audio before VAD and STT.

Why does the filter buffer?

The model works on 240-sample hops at 16 kHz while Pipecat delivers arbitrary-sized chunks at the transport rate, and the filter must return the same number of bytes it receives.

Next