A community integration for Pipecat
pipecat-effects
pipecat-effects runs an audio effects chain on the output of a pipecat bot. You build the chain from 8 effects, and it adds no latency.
Install
pip install pipecat-effects
Use
EffectsFilter is a pipecat audio filter. Give it to audio_out_filter, the output filter field
of pipecat 1.13 and later. Tested with pipecat 1.14.0.
from pipecat.transports.base_transport import TransportParams
from pipecat_effects import (
AGC,
Biquad,
Compressor,
DeEsser,
Effects,
EffectsFilter,
Limiter,
Saturation,
)
CHAIN: Effects = (
AGC(target_lufs=-20.0),
Biquad(kind="highpass", hz=90.0),
Biquad(kind="lowshelf", hz=200.0, gain_db=2.5),
Biquad(kind="peak", hz=3200.0, q=1.2, gain_db=-2.0),
DeEsser(hz=6500.0, threshold_db=-32.0, ratio=4.0),
Compressor(threshold_db=-20.0, ratio=3.0, attack_ms=8.0, release_ms=120.0, makeup_db=2.0),
Saturation(drive=1.2, mix=0.15),
Limiter(ceiling_db=-1.0, knee_db=3.0),
)
def transport_params() -> TransportParams:
return TransportParams(
audio_in_enabled=True,
audio_out_enabled=True,
audio_out_filter=EffectsFilter(CHAIN),
)
examples/bot.py runs one voice bot with that chain. It needs 4 pipecat extras and
OPENAI_API_KEY. The wheel has no examples/, so run it from a clone of this repository.
uv run --with "pipecat-ai[runner,webrtc,openai,silero]" examples/bot.py --chain telephone
--chain picks one of the 4 chains in Listen. --bypass runs the session with the
chain off. A chain with Reverb runs in FilterMixer, so its tail rings out. --meter runs any
chain in FilterMixer, and the log gives the loudness and the true peak of each bot turn.
Filter or mixer
FilterMixer runs the same filter as the output mixer. It adds the meter, and the chain
runs through silence.
from pipecat_effects import FilterMixer
TransportParams(
audio_in_enabled=True,
audio_out_enabled=True,
audio_out_channels=1,
audio_out_mixer=FilterMixer(EffectsFilter(CHAIN), channels=1),
)
audio_out_filter |
FilterMixer |
|
|---|---|---|
| audio the chain runs on | bot audio | each output chunk, silence included |
Reverb tail |
stops at the last bot chunk | rings out in silence |
audio_out_volume, VolumeFrame |
after the chain, past Limiter |
before the chain |
| meter | none | FilterMixer.read() |
| control frames | FilterEnableFrame, FilterUpdateSettingsFrame |
MixerEnableFrame, MixerUpdateSettingsFrame |
| idle cost | none | 25 silent chunks a second |
audio_out_destinations |
default destination only | one FilterMixer per destination |
| a flush that fails to drain | no mixer chunk keeps it alive | holds, see Pipecat |
Pick audio_out_filter unless you need the meter, a Reverb tail or a chain on another
destination.
Listen
One sentence of Cartesia sonic-3.5 speech, 24 kHz mono, run in 40 ms chunks, the stock output
chunk of pipecat. examples/chains.py holds each chain. examples/clips.py renders the clips
with ffmpeg. GitHub mutes each player at load. Unmute it to listen. On PyPI each player is a
link. Loudness is integrated, measured by ffmpeg ebur128.
Unprocessed
https://github.com/user-attachments/assets/9b4a4b80-8168-4380-a12d-b98f9fdcaaa7
No filter. -18.3 LUFS, -1.3 dBTP.
EQ and compression
https://github.com/user-attachments/assets/33bbbafc-3627-416c-84f0-baf398f4922f
The chain in Use: 2.5 dB low shelf at 200 Hz, 2 dB cut at 3.2 kHz, de-esser, 3:1 compressor. -23.2 LUFS, -5.5 dBTP.
Telephone
https://github.com/user-attachments/assets/6fff6b7a-ef53-44a1-b275-5e29ca4ab1bb
300 Hz to 3400 Hz band, 4:1 compressor, drive 3.0 at 0.3 mix. -22.0 LUFS, -5.1 dBTP.
Broadcast
https://github.com/user-attachments/assets/0e336600-fde6-4ddb-8210-bd74be7fe0b4
100 Hz high-pass, de-esser, 4:1 compressor with 0.5 ms attack and 18 dB makeup, +4 dB at 3.5 kHz. -16.3 LUFS, -1.4 dBTP. Against the unprocessed clip: +2.0 LU, and +3.7 dB in the share of power from 2 to 4 kHz.
Room
https://github.com/user-attachments/assets/6cbc794c-5a33-4cb4-a2be-ccfebffe1f79
Schroeder reverb, 400 ms decay, 0.3 mix. -21.2 LUFS, -3.5 dBTP.
Build a chain
- A chain is any sequence of effects. The filter runs them in order and adds no stage.
- Build one filter per session. Each effect holds its state across chunks.
- Put
AGCfirst. It reads the level before any stage changes it. - If a chain ends without
Limiter, the int16 cast hard clips each sample over full scale. End each chain withLimiter. - Keep
audio_out_channelsat 1, the default. GiveFilterMixerthat value aschannels. - Each effect validates its values at build time. A value outside the range raises
ValueErrorthat names the field and the range.
Effects
| effect | field | default | range |
|---|---|---|---|
Gain |
db |
0.0 | -60 to 24 |
Biquad |
kind |
required | one of lowpass, highpass, bandpass, peak, lowshelf, highshelf |
hz |
required | 10 to 20000 | |
q |
0.7071 | 0.1 to 20 | |
gain_db |
0.0 | -24 to 24 | |
Saturation |
drive |
2.0 | 0.1 to 20 |
mix |
1.0 | 0 to 1 | |
Compressor |
threshold_db |
-18.0 | -60 to 0 |
ratio |
3.0 | 1 to 20 | |
attack_ms |
5.0 | 0 to 200 | |
release_ms |
80.0 | 1 to 2000 | |
makeup_db |
0.0 | -24 to 24 | |
AGC |
target_lufs |
-20.0 | -40 to -10 |
max_db_per_second |
6.0 | 0.1 to 20 | |
Limiter |
ceiling_db |
-1.0 | -24 to 0 |
knee_db |
3.0 | 0 to 12 | |
Reverb |
decay_ms |
200.0 | 10 to 500 |
mix |
0.15 | 0 to 1 | |
DeEsser |
hz |
6500.0 | 1000 to 20000 |
q |
1.5 | 0.1 to 20 | |
threshold_db |
-30.0 | -60 to 0 | |
ratio |
4.0 | 1 to 20 | |
attack_ms |
1.0 | 0 to 200 | |
release_ms |
40.0 | 1 to 2000 |
mix sets the dry/wet ratio. 0 gives only the input, the dry signal. 1 gives only the processed
signal, the wet signal.
Method of each effect:
| effect | method |
|---|---|
Gain |
one multiply |
Biquad |
one second-order section, Audio EQ Cookbook by Robert Bristow-Johnson |
Saturation |
tanh waveshaper, tanh(drive * x) / tanh(drive), dry/wet mix |
Compressor |
one envelope follower |
AGC |
K-weighted loudness over 400 ms, gain ramped at max_db_per_second or less |
Limiter |
memoryless soft clipper, soft knee, hard ceiling on sample peaks |
Reverb |
Schroeder reverberator, 4 comb filters and 2 allpass filters |
DeEsser |
split-band, the envelope of one band-pass band sets the cut of that band |
Control
Change the chain at runtime with 2 stock frames.
from pipecat.frames.frames import FilterEnableFrame, FilterUpdateSettingsFrame
from pipecat_effects import Gain, Limiter
await worker.queue_frame(FilterEnableFrame(enable=False))
await worker.queue_frame(
FilterUpdateSettingsFrame(settings={"effects": (Gain(db=-3.0), Limiter())})
)
FilterEnableFrame(enable=False)bypasses the chain. The filter gives the input, and the chain keeps running on it, so envelopes and delay lines stay current.FilterUpdateSettingsFramewith aneffectskey builds a new chain and swaps it in.- Each change fades over one chunk, from the output last heard to the new output. If 2 updates arrive before one chunk, the chain last heard fades to the last chain. The chain between them is not heard.
- The output transport passes the 2 frames to
audio_out_filter, in order with the audio. - With
FilterMixer, sendMixerEnableFrameandMixerUpdateSettingsFramewith the same fields.FilterMixermaps them to the 2 filter frames. The transport gives filter frames toaudio_out_filteronly. - Call
EffectsFilter.process_framewith the 2 filter frames when you hold the filter directly. - An
effectsvalue outside a sequence of effects raisesTypeErrorthat names the field. An item whosestartgives no callable raisesTypeErrorthat names the field and the index. - A chain that fails to build raises
ValueErrorthat names theeffectsfield. The old chain keeps running. - An update before
startbuilds atstart. A failed build then raises atstartand names the field of the effect, noteffects.
Meter
reading = mixer.read() # Reading(lufs=-19.92, dbtp=-1.04), or None on silence
- The meter runs in
FilterMixer. Onaudio_out_filterthere is no reading. FilterMixermeters each chunk it gives to the transport, after the int16 cast.FilterMixer.read()gives aReadingwithlufsanddbtpsince the last read, then clears both. Silence givesNone.lufsis the K-weighted loudness of ITU-R BS.1770, the loudness standard. The published 48 kHz filter moves to the session rate by the bilinear transform. A 997 Hz sine at 0 dBFS reads -3.01 LUFS.dbtpis the true peak, on 4 times oversampling with 48 taps.Meterreads float chunks for any other caller. Itsreadgives the floor, -120.0 dB, in place ofNone.
Cost
One 40 ms chunk at 24 kHz, the stock output chunk of pipecat. The 8 effect chain above, mean of 1000 chunks, median of 4 runs. Python 3.13.9, macOS arm64.
| path | mean | 95th |
|---|---|---|
| filter | 0.245 ms | 0.265 ms |
| mixer on silence with the meter | 0.315 ms | 0.347 ms |
| true peak | 0.028 ms | 0.030 ms |
| loudness and true peak | 0.063 ms | 0.069 ms |
python examples/bench.py --out rows.jsonl --sha <commit> writes one row.
Limits
Chain
- The filter runs on mono.
FilterMixer.startraises on more than 1 channel. A filter gets no channel count, so onaudio_out_filterstereo output runs through the chain as one interleaved signal, without an error. - In
FilterMixerthe chain runs on each chunk, silence included, so attack and release stay continuous through silence. At the stockaudio_out_10ms_chunksof 4, one idle session costs 25 silent chunks of 40 ms a second, 7.9 ms of compute a second on the Cost machine. - On
audio_out_filterthe chain runs on bot audio only. Each effect holds its state over the gap between turns, and aReverbtail stops at the last bot chunk. - On
audio_out_filterpipecat appliesaudio_out_volumeandVolumeFrameafter the chain. A volume over 1 drives the output past theLimiterceiling. At a volume of 4 a chain that ends in a -1 dBLimiterwrites full scale. InFilterMixerthe volume comes before the chain. audio_out_filterruns on the default destination only. Audio for a destination inaudio_out_destinationsskips the chain.audio_out_mixertakes a mapping of destination to mixer, so each destination can get its ownFilterMixer.- A bypassed chain keeps running, so it costs the same as an active chain.
- Outside a bypass, the chunk after an update runs the old chain and the new chain.
- A chunk of 0 samples passes through. An odd byte count raises
ValueErrorthat namesaudio. - A
BiquadorDeEssercentre over 0.45 of the sample rate raises atstart, with the highest allowed centre and the rate. The error nameshz, not the effect. - At 8 kHz the highest centre is 3600 Hz. The chain in Use raises there, since its
DeEssersits at 6500 Hz. - Not included: lookahead, convolution, pitch shift, formant shift. The broadcast clip holds a peak-to-loudness ratio, true peak minus loudness, of 14.9 dB. The unprocessed clip holds 17.0 dB. A hall reverb needs convolution.
Effects
CompressorandDeEsserset the gain of each 1 ms block from the peak of that block.AGCsets one target per chunk from the loudness at the chunk end. Each gain reads ahead inside its chunk, by up to 1 ms or 1 chunk.AGCreads momentary loudness without the gate of BS.1770. Under -50 LUFS the gain holds.AGCsets the level at its place in the chain. Later stages move the output level. The EQ and compression clip holdsAGC(target_lufs=-20.0)and measures -23.2 LUFS.Limiteris a memoryless soft clipper. It has no attack or release, so a signal driven far over the ceiling distorts.- The -1 dB ceiling leaves the margin for inter-sample peaks at a codec resampler. The true peak meter only reports.
- One section falls 12 dB per octave. A lowpass at 6000 Hz drops an 8 kHz tone by 10.0 dB at a 24 kHz rate.
Reverbtakesdecay_msto 500.
Meter
- The true peak meter cuts at the input Nyquist. At a 24 kHz rate it reads a 10 kHz sine 0.5 dB under its peak.
- The K-weighting at 8 kHz differs from the standard by 0.43 dB at most from 100 Hz to 3 kHz. At 24 kHz it differs by 0.040 dB at most.
Pipecat
- With an output mixer, a flush that fails to drain does not time out. Each mixer chunk reaches
the pipeline sink and counts as progress. A flush that drains returns. The defect holds in
pipecat 1.14.0 for each output mixer.
audio_out_filterneeds no mixer.
Develop
- The package ships a
py.typedmarker. It exportsEffect,ApplyandSamplesfor a caller who writes an effect. make lintchecks the lock, the format, the lint rules, and the types with pyright.make testruns the tests.make test-lowestruns them on the lowest allowed pipecat-ai, numpy and scipy. CI runs both.make auditchecksuv.lockfor known vulnerabilities.make buildbuilds the wheel and the sdist intodist/. CI runs it on each push tomain.- A
vtag that matches the project version publishes the package to PyPI.
License
BSD 2-Clause. Softcery builds and maintains the package.
Metadata
Release files for pipecat-effects 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pipecat_effects-0.1.3.tar.gz | 17.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pipecat_effects-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.8 kB
Release files / pipecat_effects-0.1.3.tar.gz
| Download URL | pipecat_effects-0.1.3.tar.gz |
|---|---|
| Size | 17.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e7e238b4746ece8e5a40362d85a77ca74bf1e785ac344136d32811554e70f4fc
|
|
BLAKE2b-256 checksum How to use checksums |
6868409a3df45d02df3405313e2505dec65d1cb889e9acc005c946dd19026911
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / pipecat_effects-0.1.3-py3-none-any.whl
| Download URL | pipecat_effects-0.1.3-py3-none-any.whl |
|---|---|
| Size | 19.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ee5020e9ea79c048f6f0de8e4b6e54d5881d23b912fe54bf9381ea00b794593f
|
|
BLAKE2b-256 checksum How to use checksums |
ffe9345bfa5bbce725c3ff533b8692e4f1a0b3de38bc9b5a70b1148b08c10a4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for pipecat-effects 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pipecat_effects-0.1.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.