Skip to content

warangchinmay/bode-live

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bode Live

🎵 Try it live at bode-live.duckdns.org 🎵

A real-time web app for designing and aurally testing IIR (Infinite Impulse Response) digital filters. Place poles and zeros on an interactive Z-plane, watch the full Bode plot (magnitude + phase) update instantly, and hear the filter applied to your microphone in real time. Includes a preset library of standard filter designs. Could be utilized as a teaching tool for DSP students and a sandbox for anyone curious about how poles and zeros shape sound. Have fun!! ദ്ദി( • ᴗ < )

Bode Live Screenshot


What it does

  • Place poles and zeros on an interactive Z plane canvas by double clicking
  • Hear the filter instantly - your microphone input is processed server-side and played back through your headphones in real time
  • Full Bode plot - both the magnitude response (dB) and the unwrapped phase response (°) update live as you drag poles and zeros
  • Preset library - instantly load a Butterworth, Chebyshev I/II, or Elliptic design by choosing order, cutoff, and band type; poles and zeros populate automatically
  • A/B compare - toggle between the filtered output and the raw microphone signal with a single button
  • Complex conjugate pairs are automatically mirrored so filters always have a real-valued impulse response

Architecture

Browser Mic Input → WebRTC → FastAPI backend → pipecat pipeline
                                                      ↓
                                               IIRFilterProcessor
                                                      ↓
                                                     WebRTC → Browser headphones
Layer Technology
Backend Python · FastAPI · pipecat · aiortc
Audio pipeline pipecat SmallWebRTCTransport + custom FrameProcessor
Filter math scipy.signal.zpk2sos / sosfilt (server-side)
Preset designs scipy.signal.butter / cheby1 / cheby2 / ellip
Frontend React 18 · TypeScript · Vite
Z-plane UI SVG (no external dependency)
Bode plot Recharts LineChart (magnitude + phase)

Audio runs at 16 kHz mono inside pipecat. The Bode plot is computed directly in the browser from the current poles/zeros so it updates with no extra round-trip.


Prerequisites

Tool Version Notes
Python ≥ 3.10 3.11 recommended
uv any recent Python package manager
Node.js ≥ 18 For the frontend
npm ≥ 9 Bundled with Node

Installing uv

uv is a fast Python package manager. Install it with the official one-liner:

macOS / Linux

curl -LsSf https://astral.sh/uv/install.sh | sh

Windows

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

After installation, restart your terminal (or run source ~/.bashrc / source ~/.zshrc) so the uv command is on your PATH. Verify with uv --version.

Installing Node.js (includes npm)

The easiest cross-platform option is nvm (Node Version Manager):

macOS / Linux

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# restart terminal then:
nvm install --lts
nvm use --lts

Windows - use nvm-windows: download and run the installer, then:

nvm install lts
nvm use lts

Alternative - direct installer: download the LTS package from nodejs.org and run it. npm is bundled automatically. Verify with node --version and npm --version.

macOS only: aiortc (the WebRTC library) depends on libav. Install it once with Homebrew:

brew install ffmpeg

On Linux, install libavcodec-dev libavformat-dev libavdevice-dev via your package manager.


Quick start

1. Clone

git clone https://github.com/warangchinmay/bode-live.git
cd bode-live

2. Backend

# Install Python dependencies
uv sync

# Start the FastAPI server (auto-reloads on file changes)
uv run uvicorn main:app --reload --port 8000

If uv run fails due to a conflicting active virtualenv, use the direct path instead:

.venv/bin/uvicorn main:app --reload --port 8000

The backend is now running at http://localhost:8000.

3. Frontend

Open a second terminal:

cd frontend
npm install
npm run dev

The dev server starts at http://localhost:5173 and automatically proxies /offer, /filter, and /preset requests to the backend.

4. Open the app

Go to http://localhost:5173 in your browser. Allow microphone access when prompted. Wear headphones before clicking Start Audio.


Usage

Z-Plane controls

Action Result
Double-click on canvas Add a pole (×) at that location
⌘ + double-click (Mac) / Ctrl + double-click (Win/Linux) Add a zero (○) at that location
Drag a pole or zero Move it; its conjugate mirror moves automatically
Right-click a pole or zero Delete it (and its conjugate)

Poles placed outside the unit circle turn the Z-plane border red, this means the filter is unstable. Keep poles inside the unit circle.

Preset library

The Preset Designs panel lets you load a standard filter design in one click:

  1. Select a filter type: Butterworth, Chebyshev I, Chebyshev II, or Elliptic
  2. Select a band type: Low-pass, High-pass, Band-pass, or Band-stop
  3. Set the order (1–8) and cutoff frequency in Hz (second cutoff shown for band filters)
  4. For Chebyshev I / Elliptic: set passband ripple (dB)
  5. For Chebyshev II / Elliptic: set stopband attenuation (dB)
  6. Click Apply Preset → - poles and zeros are computed by scipy on the server and loaded onto the Z-plane immediately. The Bode plot updates instantly.
  7. Click Clear to remove all poles and zeros and return to passthrough mode.

Applying a new preset always replaces the current poles/zeros! You can then drag individual points to tweak the design by hand.

Bode plot

Two stacked charts update in real time whenever poles/zeros change:

  • Magnitude (cyan) - |H(e^jω)| in dB from DC to Nyquist (8 kHz). Range: −80 to +20 dB.
  • Phase (purple) - unwrapped phase in degrees. No ±180° discontinuities.

Audio controls

  1. Click Start Audio - grants mic access and establishes a WebRTC connection to the server
  2. Speak or play audio through your microphone
  3. Hear the filtered result in your headphones
  4. Click Bypass: OFF (filtered) to switch to the raw input for comparison; click again to switch back
  5. Adjust the Gain (K) slider to scale the filter's overall level

Example filters to try

Manual placement

Filter type Recipe
Lowpass Two conjugate poles near (0.9, ±0.3), two zeros at (−1, 0)
Highpass Two conjugate poles near (0.9, ±0.3), two zeros at (1, 0)
Notch at 1 kHz Conjugate zeros on the unit circle at θ = ±2π·1000/16000; conjugate poles just inside at the same angle
Resonator Conjugate poles close to the unit circle at the desired frequency; no zeros
All-pass Pole at (a, 0), zero at (1/a, 0), magnitude flat, phase changes

Via presets

Goal Settings
Remove low-frequency rumble Butterworth · High-pass · order 4 · cutoff 200 Hz
Telephone-band effect Butterworth · Band-pass · order 4 · 300 Hz – 3400 Hz
Sharp lowpass with ripple Chebyshev I · Low-pass · order 6 · cutoff 2000 Hz · rp 1 dB
Maximally steep lowpass Elliptic · Low-pass · order 4 · cutoff 1000 Hz · rp 1 dB · rs 60 dB

Project structure

bode-live/
├── main.py                         # uvicorn entry point
├── pyproject.toml                  # Python project & dependencies
│
├── backend/
│   ├── app.py                      # FastAPI routes: /offer, /filter, /preset
│   ├── filter_state.py             # asyncio-safe shared FilterConfig
│   ├── iir_processor.py            # pipecat FrameProcessor (the actual filter)
│   ├── presets.py                  # scipy filter design: butter/cheby1/cheby2/ellip
│   └── webrtc_pipeline.py          # pipecat Pipeline + PipelineTask per session
│
└── frontend/
    ├── vite.config.ts              # dev proxy: /offer, /filter, /preset → :8000
    └── src/
        ├── App.tsx
        ├── dsp.ts                  # H(z) evaluation → dB magnitude + unwrapped phase
        ├── webrtc.ts               # RTCPeerConnection offer/answer/ICE
        ├── types.ts
        ├── hooks/
        │   └── useFilterState.ts   # pole/zero state + debounced PUT /filter
        └── components/
            ├── ZPlane.tsx          # interactive SVG Z-plane
            ├── FrequencyResponse.tsx  # Bode plot: magnitude + phase charts
            ├── PresetPanel.tsx     # preset design UI
            └── AudioControls.tsx   # start/stop + bypass toggle

How the filter is applied

Each ~20 ms audio chunk arriving from the browser is processed by IIRFilterProcessor:

  1. Convert the int16 PCM bytes to a float64 numpy array
  2. Call scipy.signal.sosfilt(sos, pcm, zi=self._zi) where sos is the second-order-sections matrix computed from the current poles and zeros via scipy.signal.zpk2sos
  3. Store the updated filter state zi so there are no discontinuities at chunk boundaries
  4. Clip back to int16 range and return

When you drag a pole/zero the frontend sends a PUT /filter request within 50 ms. The processor picks up the new configuration on the next chunk and recomputes the SOS matrix. Preset application uses a GET /preset call to have scipy design the filter server-side, then immediately sends the resulting poles/zeros to the audio pipeline.


Troubleshooting

"No audio" / WebRTC connection fails

  • Make sure the backend is running on port 8000 before starting the frontend
  • Check the browser console (F12 → Console) for ICE connection errors
  • On strict corporate networks, WebRTC may be blocked; try on a home network
  • Chrome DevTools: Open chrome://webrtc-internals/ to see detailed ICE candidate negotiation logs

High latency / audio artifacts

  • Use wired headphones; Bluetooth introduces extra latency
  • Close other browser tabs that use the microphone
  • The round-trip is: mic → WebRTC → server filter → WebRTC → headphones. On localhost this is typically < 50 ms

Filter sounds distorted / clipping

  • A pole outside the unit circle makes the filter unstable! (The Z-plane will warn you in red)
  • Very high gain combined with resonant poles can clip even with stable poles; reduce the Gain slider
  • High-order elliptic/Chebyshev filters have very small gain values by design; increase Gain (K) if the output is too quiet

License

MIT. Do whatever you want with it, make sure you understand the theory alongside the tool's functionality. Attribution appreciated but not required.

⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠛⣡⣿⣟⠛⠛⠛⠻⢿⣿⣿⣿⣿⣿⣿⣯⡟⣁⣨⣁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠉⠉⠉⠉⠛⠛⠀⠀⣠⠟⠛⠛⠿⠿⠶⠶⠶⣤⡍⠙⠋⠹⠿⠛⢫⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⣠⣴⣤⣴⣾⠃⠀⣴⣧⣤⣄⡤⠖⠋⠉⠉⠉⠛⠛⠀⠀⠀⠀⢀⣤⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⢠⠎⠉⠋⠁⢾⣷⣀⣀⣈⣡⠟⣻⠟⠻⣦⣴⣶⣶⣶⡄⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣌⢿⣿⠀⠀⣿⡦⠀⣠⣶⣿⡿⠟⢻⣏⣀⣼⣥⣶⢠⣿⣿⣿⣿⣿⣿⣷⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⣷⣾⣧⡞⠉⠟⠉⠀⠀⠘⢛⣿⡿⢁⡟⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣆⠃⠹⡟⠀⣠⠴⠚⠛⠳⣔⢿⣿⢁⡼⣵⠛⠛⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠁⠀⠀⠀⠀⢀⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠈⠳⣄⢀⡼⠁⠀⣀⣀⠀⡿⠘⣿⣿⣾⠁⢀⡴⠶⢦⡀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⣀⣤⣴⣶⣶⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣦⡈⠛⢀⣴⣿⣿⣿⢾⣧⠀⣸⠁⢿⠀⠀⢳⣀⠀⠉⠀⣸⡟⢿⣿⣿⣿⣿⣿⣿⡿⠁⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢿⣻⠿⠷⠎⠀⠉⠁⢠⣿⡿⠞⠁⢀⣈⣷⣄⡀⠈⠛⠓⣚⡿⠃⠀⠉⠻⢿⣿⣿⣿⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⠀⠀⠹⣆⣀⣠⡀⣀⣴⣿⢿⡀⠀⠀⣿⢉⠉⢻⡙⣿⣿⣿⣿⣦⣤⣀⠀⠀⠀⠉⠛⠞⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠟⠁⠀⢀⣤⣶⣿⣿⣿⣿⣿⣿⣿⣶⣽⡄⠀⢻⡼⣇⣈⡿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠙⠶⠿⠶⠋⠛⠋⠉⣉⣿⣿⣿⣿⡿⢉⣷⡀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⡟⢋⡿⠁⠀⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣭⣄⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣿⣿⣿⣿⣋⣠⣾⣿⣿⣆⠈⣧⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⠟⠀⢸⠃⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠟⠋⠁⠐⠛⢿⣆⡴⣄⠀⣤⣄⣾⠋⣻⠋⠀⠀⠉⠻⣿⣿⣿⣿⣿⣷⣸⡇⠀⣠⣄⡉⢻⣿⣿⣿⣿⣿⣿ ⣿⣿⡵⡄⠀⣿⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⠿⠿⡅⠀⠀⠀⠀⠀⠀⠀⠈⠙⢆⠈⠻⡃⠀⠉⠀⢻⡀⢠⡞⠛⢦⡘⣿⣿⣿⣿⣿⣿⠀⢸⡇⠀⠙⣆⣿⣿⣿⣿⣿⣿ ⣿⡿⠀⡿⠀⣿⠀⣼⣿⣿⣿⣿⣿⣿⣿⣡⣀⠀⢹⡄⠀⠀⣀⣤⣴⣶⣶⣾⣿⣿⣶⣾⣦⣄⡀⠈⣷⣸⣧⠀⢈⣿⡿⣿⣿⣿⣿⣿⣦⡈⠳⣄⣀⣹⡿⣝⠻⣿⣿⣿ ⣿⣧⣼⣣⣼⡥⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣌⣳⣼⠗⠛⠋⠉⠈⠛⠿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⠛⠛⠛⠋⠰⡜⣿⣿⣿⣿⡿⢿⣿⡟⢻⡍⠳⣌⠙⢾⣿⣿ ⣿⣿⣿⠁⠸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⠀⢀⡤⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣌⠳⡀⠀⢳⡘⢿⣿⣯⣤⣾⣿⣿⡄⠹⣦⣈⠳⢄⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠶⠾⣥⣀⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠙⠆⠀⠁⠈⢻⣿⣿⣿⣿⣿⣿⣆⣹⣿⣷⣾⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣼⣯⣤⣤⣶⣏⣉⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠹⠀⠀⠀⠀⠙⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣥⣤⣤⣀⣠⣀⣀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣉⡏⠁⣾⠁⠀⣭⠀⢘⡏⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

About

Interactive IIR filter designer with real-time audio processing, Z-plane editing, and live Bode plots.

Topics

Resources

License

Stars

Watchers

Forks

Contributors