๐๏ธ Pedalboard Cheat Sheet #
Spotify’s Python library for studio-quality audio effects processing
๐ฏ What is Pedalboard? #
Pedalboard is a Python library built by Spotify’s Audio Intelligence Lab that enables using studio-quality audio effects from within Python and TensorFlow. It’s designed for real-time audio processing, data augmentation for ML models, and creative audio manipulation.
โจ Key Features #
๐ Performance
- Releases Python’s GIL for multi-core processing
- 300x faster than pySoX for single transforms
- 2-5x faster than SoxBindings
- 4x faster file reading than librosa.load
๐ต Audio I/O
- Built-in support for AIFF, FLAC, MP3, OGG, WAV
- Platform-specific support for AAC, AC3, WMA
- O(1) memory usage for on-the-fly resampling
- Live audio streaming via AudioStream
๐ Plugin Support
- VST3ยฎ instrument and effect plugins (macOS, Windows, Linux)
- Audio Units on macOS
- Built-in studio-quality effects
๐ง ML Integration
- TensorFlow compatibility
- tf.data pipeline support
- Thread-safe operations
๐ Pedalboard vs Other Audio Libraries #
Comprehensive Comparison Table #
| Feature | Pedalboard | Pydub-NG | AudioFlux | librosa | Notes |
|---|---|---|---|---|---|
| Primary Focus | Real-time effects | Audio manipulation | Analysis & features | Music analysis | Different specializations |
| Performance | โก Fastest | ๐ Fast | ๐ Fast | ๐ Analysis-focused | Pedalboard releases GIL |
| Real-time Processing | โ Excellent | โ Limited | โ No | โ No | Pedalboard’s key strength |
| VST Plugin Support | โ Full VST3/AU | โ No | โ No | โ No | Unique to Pedalboard |
| Built-in Effects | ๐๏ธ Studio-quality | ๐ง Basic | ๐ Analysis only | ๐ Analysis only | Professional audio effects |
| File Format Support | ๐ Extensive | ๐ Extensive | ๐ Good | ๐ Good | All handle common formats |
| ML Integration | ๐ค TensorFlow | ๐ง Manual | ๐ค Research-focused | ๐ค scikit-learn | TF compatibility varies |
| Learning Curve | ๐ Moderate | ๐ Easy | ๐ Steep | ๐ Steep | Complexity matches power |
When to Use Each Library #
Use Pedalboard when:
- Real-time audio processing is required
- You need professional studio-quality effects
- VST plugin integration is essential
- Performance is critical (GIL release)
- Building audio applications or live systems
Use Pydub-NG when:
- Simple audio manipulation tasks
- Format conversion is primary need
- Beginner-friendly API is preferred
- Basic audio editing operations
Use AudioFlux when:
- Audio analysis and feature extraction
- Music information retrieval (MIR)
- Research applications
- Time-frequency analysis
Use librosa when:
- Music analysis and research
- Feature extraction for ML
- Spectral analysis
- Academic/research contexts
Integration Strategies #
# Combine libraries for comprehensive audio processing
from pedalboard import Pedalboard, Compressor, Reverb
from pydub import AudioSegment
import librosa
import audioflux as af
def comprehensive_audio_pipeline(input_file):
"""Example combining multiple libraries"""
# 1. Load and basic manipulation with Pydub-NG
audio = AudioSegment.from_file(input_file)
audio = audio.normalize() # Basic preprocessing
# 2. Convert to numpy for analysis
samples = audio.get_array_of_samples()
audio_np = np.array(samples, dtype=np.float32)
# 3. Feature extraction with librosa
tempo, beats = librosa.beat.beat_track(y=audio_np, sr=audio.frame_rate)
# 4. Advanced analysis with AudioFlux
spectral_features = af.spectral_centroid(audio_np)
# 5. Real-time effects with Pedalboard
board = Pedalboard([Compressor(), Reverb()])
processed = board(audio_np.reshape(1, -1), audio.frame_rate)
return processed, tempo, spectral_features
๐ฆ Installation #
Basic Installation #
pip install pedalboard
With uv (Recommended) #
uv add pedalboard
Development Installation #
# For contributing or custom builds
git clone https://github.com/spotify/pedalboard.git
cd pedalboard
pip install -e .
System Requirements #
- Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
- Platform wheels for x86_64 and aarch64
- No additional dependencies required
- Optional: VST3 plugins for extended functionality
๐ธ Built-in Effects #
Guitar-Style Effects #
from pedalboard import Chorus, Distortion, Phaser, Clipping
# Guitar effects
chorus = Chorus()
distortion = Distortion()
phaser = Phaser()
clipping = Clipping()
Dynamics & Loudness #
from pedalboard import Compressor, Gain, Limiter
# Dynamics control
compressor = Compressor(threshold_db=-20, ratio=4)
gain = Gain(gain_db=6)
limiter = Limiter()
Filters & EQ #
from pedalboard import HighpassFilter, LadderFilter, LowpassFilter
# Filtering
highpass = HighpassFilter(cutoff_hz=100)
lowpass = LowpassFilter(cutoff_hz=8000)
ladder = LadderFilter(mode=LadderFilter.Mode.HPF12, cutoff_hz=900)
Spatial Effects #
from pedalboard import Convolution, Delay, Reverb
# Spatial processing
delay = Delay(delay_seconds=0.25, mix=0.5)
reverb = Reverb(room_size=0.25)
convolution = Convolution("./impulse_response.wav", 1.0)
Pitch & Time #
from pedalboard import PitchShift
# Pitch manipulation
pitch_shift = PitchShift(semitones=7)
Compression & Quality #
from pedalboard import GSMFullRateCompressor, MP3Compressor, Resample, Bitcrush
# Lossy compression
gsm_comp = GSMFullRateCompressor()
mp3_comp = MP3Compressor()
# Quality reduction
resample = Resample(target_sample_rate=22050)
bitcrush = Bitcrush(bit_depth=8)
๐ง Core Usage Patterns #
Basic Audio Processing #
from pedalboard import Pedalboard, Chorus, Reverb
from pedalboard.io import AudioFile
# Create pedalboard with effects
board = Pedalboard([Chorus(), Reverb(room_size=0.25)])
# Process audio file
with AudioFile('input.wav') as f:
with AudioFile('output.wav', 'w', f.samplerate, f.num_channels) as o:
while f.tell() < f.frames:
chunk = f.read(f.samplerate) # Read 1 second
effected = board(chunk, f.samplerate, reset=False)
o.write(effected)
Whole File Processing #
from pedalboard import *
from pedalboard.io import AudioFile
# Read entire file with resampling
samplerate = 44100.0
with AudioFile('input.wav').resampled_to(samplerate) as f:
audio = f.read(f.frames)
# Create complex pedalboard
board = Pedalboard([
Compressor(threshold_db=-50, ratio=25),
Gain(gain_db=30),
Chorus(),
LadderFilter(mode=LadderFilter.Mode.HPF12, cutoff_hz=900),
Phaser(),
Convolution("./guitar_amp.wav", 1.0),
Reverb(room_size=0.25),
])
# Process and save
effected = board(audio, samplerate)
with AudioFile('output.wav', 'w', samplerate, effected.shape[0]) as f:
f.write(effected)
Live Audio Streaming #
from pedalboard import Pedalboard, Chorus, Compressor, Delay, Gain, Reverb, Phaser
from pedalboard.io import AudioStream
# Create real-time effects chain
board = Pedalboard([
Compressor(threshold_db=-16, ratio=2.5),
Gain(gain_db=3),
Chorus(rate_hz=1.0, depth=0.25, mix=0.3),
Phaser(),
Delay(delay_seconds=0.125, mix=0.25),
Reverb(room_size=0.25, mix=0.2),
])
# Stream live audio
with AudioStream(
input_device_name="Built-in Microphone",
output_device_name="Built-in Output",
) as stream:
stream.plugins = board
input("Press Enter to stop streaming...")
๐ VST3 & Audio Unit Plugins #
Loading External Plugins #
from pedalboard import load_plugin, Pedalboard, Reverb
from mido import Message
# Load VST3 or Audio Unit
instrument = load_plugin("./VSTs/Magical8BitPlug2.vst3")
effect = load_plugin("./VSTs/RoughRider3.vst3")
# Inspect parameters
print(effect.parameters.keys())
# Set parameters
effect.ratio = 15
# Use with MIDI for instruments
sample_rate = 44100
audio = instrument(
[Message("note_on", note=60), Message("note_off", note=60, time=5)],
duration=5,
sample_rate=sample_rate,
)
# Chain with other effects
board = Pedalboard([effect, Reverb()])
effected = board(audio, sample_rate)
๐ Advanced Techniques #
Parallel Effects Chains #
from pedalboard import Pedalboard, Compressor, Delay, Gain, PitchShift, Reverb, Mix
# Create parallel processing chains
passthrough = Gain(gain_db=0)
delay_and_pitch_shift = Pedalboard([
Delay(delay_seconds=0.25, mix=1.0),
PitchShift(semitones=7),
Gain(gain_db=-3),
])
delay_longer_and_more_pitch_shift = Pedalboard([
Delay(delay_seconds=0.5, mix=1.0),
PitchShift(semitones=12),
Gain(gain_db=-6),
])
# Combine with Mix plugin
board = Pedalboard([
Compressor(),
Mix([
passthrough,
delay_and_pitch_shift,
delay_longer_and_more_pitch_shift,
]),
Reverb()
])
Dynamic Pedalboard Manipulation #
from pedalboard import Pedalboard, Compressor, Gain, Limiter
# Create pedalboard
board = Pedalboard([
Compressor(threshold_db=-50, ratio=25),
Gain(gain_db=30),
])
# Pedalboards behave like lists
board.append(Limiter())
board.insert(1, Gain(gain_db=10))
# Modify parameters dynamically
board[0].threshold_db = -40
board[1].gain_db = 15
# Remove effects
del board[2]
TensorFlow Integration #
import tensorflow as tf
from pedalboard import Pedalboard, Compressor, Reverb
def apply_effects(audio_batch):
board = Pedalboard([Compressor(), Reverb()])
return board(audio_batch, sample_rate=44100)
# Use in tf.data pipeline
dataset = tf.data.Dataset.from_tensor_slices(audio_data)
dataset = dataset.map(apply_effects)
๐ File I/O Operations #
Reading Audio Files #
from pedalboard.io import AudioFile
# Basic file reading
with AudioFile('input.wav') as f:
print(f"Sample rate: {f.samplerate}")
print(f"Channels: {f.num_channels}")
print(f"Frames: {f.frames}")
print(f"Duration: {f.duration}")
# Read all audio
audio = f.read(f.frames)
# Read chunk by chunk
chunk_size = f.samplerate # 1 second
while f.tell() < f.frames:
chunk = f.read(chunk_size)
# Process chunk...
Writing Audio Files #
from pedalboard.io import AudioFile
import numpy as np
# Generate test audio
sample_rate = 44100
duration = 5.0
audio = np.sin(2 * np.pi * 440 * np.linspace(0, duration, int(sample_rate * duration)))
# Write to file
with AudioFile('output.wav', 'w', sample_rate, 1) as f:
f.write(audio)
# Write with different formats
with AudioFile('output.flac', 'w', sample_rate, 1) as f:
f.write(audio)
Resampling #
from pedalboard.io import AudioFile
# Automatic resampling during read
target_rate = 22050
with AudioFile('input.wav').resampled_to(target_rate) as f:
audio = f.read(f.frames)
print(f"Resampled to: {f.samplerate} Hz")
๐๏ธ Parameter Control #
Effect Parameter Access #
from pedalboard import Compressor, Reverb
# Create effects with parameters
comp = Compressor(threshold_db=-20, ratio=4, attack_ms=10, release_ms=100)
reverb = Reverb(room_size=0.5, damping=0.3, wet_level=0.2, dry_level=0.8)
# Access and modify parameters
print(comp.threshold_db) # -20.0
comp.ratio = 6
reverb.room_size = 0.8
# Get all parameters
print(comp.parameters)
Automation and Modulation #
import numpy as np
from pedalboard import Pedalboard, LowpassFilter
board = Pedalboard([LowpassFilter(cutoff_hz=1000)])
sample_rate = 44100
audio = np.random.randn(sample_rate * 2) # 2 seconds of noise
# Process with parameter automation
chunk_size = 1024
output = []
for i in range(0, len(audio), chunk_size):
chunk = audio[i:i+chunk_size]
# Automate cutoff frequency
time = i / sample_rate
cutoff = 500 + 1500 * (0.5 + 0.5 * np.sin(2 * np.pi * 0.5 * time))
board[0].cutoff_hz = cutoff
processed = board(chunk, sample_rate, reset=False)
output.append(processed)
final_audio = np.concatenate(output)
๐ Best Practices #
Performance Optimization #
# โ
Good: Process in chunks for large files
with AudioFile('large_file.wav') as f:
chunk_size = f.samplerate # 1 second chunks
while f.tell() < f.frames:
chunk = f.read(chunk_size)
processed = board(chunk, f.samplerate, reset=False)
# โ Avoid: Loading entire large files into memory
with AudioFile('large_file.wav') as f:
audio = f.read(f.frames) # May cause memory issues
Reset Parameter Usage #
# For continuous processing (streaming/chunks)
board = Pedalboard([Delay(), Reverb()])
# Don't reset between chunks
for chunk in audio_chunks:
processed = board(chunk, sample_rate, reset=False)
# Reset when starting new audio stream
processed = board(new_audio, sample_rate, reset=True)
Memory Management #
# โ
Good: Use context managers
with AudioFile('input.wav') as f:
audio = f.read(f.frames)
# โ
Good: Process in place when possible
audio = board(audio, sample_rate) # Modifies audio in place
# โ
Good: Delete large arrays when done
del audio
Error Handling #
from pedalboard import load_plugin
from pedalboard.io import AudioFile
try:
plugin = load_plugin("./path/to/plugin.vst3")
except Exception as e:
print(f"Failed to load plugin: {e}")
try:
with AudioFile('input.wav') as f:
audio = f.read(f.frames)
except FileNotFoundError:
print("Audio file not found")
except Exception as e:
print(f"Error reading audio: {e}")
๐ Common Use Cases #
Audio Data Augmentation #
import random
from pedalboard import Pedalboard, Gain, PitchShift, Reverb, Compressor
def augment_audio(audio, sample_rate):
"""Apply random audio augmentation for ML training"""
effects = []
# Random gain adjustment
if random.random() > 0.5:
gain_db = random.uniform(-6, 6)
effects.append(Gain(gain_db=gain_db))
# Random pitch shift
if random.random() > 0.7:
semitones = random.uniform(-2, 2)
effects.append(PitchShift(semitones=semitones))
# Random reverb
if random.random() > 0.6:
room_size = random.uniform(0.1, 0.8)
effects.append(Reverb(room_size=room_size, mix=0.2))
# Random compression
if random.random() > 0.5:
threshold = random.uniform(-30, -10)
ratio = random.uniform(2, 8)
effects.append(Compressor(threshold_db=threshold, ratio=ratio))
if effects:
board = Pedalboard(effects)
return board(audio, sample_rate)
return audio
Real-time Guitar Processing #
from pedalboard import Pedalboard, Compressor, Distortion, Chorus, Delay, Reverb
from pedalboard.io import AudioStream
# Create guitar amp simulation
guitar_amp = Pedalboard([
Compressor(threshold_db=-18, ratio=3, attack_ms=5, release_ms=50),
Distortion(drive_db=15),
Chorus(rate_hz=0.5, depth=0.3, mix=0.4),
Delay(delay_seconds=0.2, feedback=0.3, mix=0.25),
Reverb(room_size=0.4, mix=0.15),
])
# Stream guitar input
with AudioStream(
input_device_name="Audio Interface",
output_device_name="Speakers",
sample_rate=44100,
buffer_size=128, # Low latency
) as stream:
stream.plugins = guitar_amp
print("Guitar amp ready! Play your guitar...")
input("Press Enter to stop...")
Batch Audio Processing #
import os
from pathlib import Path
from pedalboard import Pedalboard, Compressor, Limiter
from pedalboard.io import AudioFile
def process_audio_batch(input_dir, output_dir, effects_chain):
"""Process all audio files in a directory"""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
audio_extensions = {'.wav', '.mp3', '.flac', '.aiff', '.ogg'}
for file_path in input_path.iterdir():
if file_path.suffix.lower() in audio_extensions:
print(f"Processing: {file_path.name}")
try:
with AudioFile(str(file_path)) as f:
audio = f.read(f.frames)
processed = effects_chain(audio, f.samplerate)
output_file = output_path / f"{file_path.stem}_processed{file_path.suffix}"
with AudioFile(str(output_file), 'w', f.samplerate, f.num_channels) as out:
out.write(processed)
except Exception as e:
print(f"Error processing {file_path.name}: {e}")
# Usage
mastering_chain = Pedalboard([
Compressor(threshold_db=-12, ratio=3),
Limiter(threshold_db=-1),
])
process_audio_batch('./input_tracks', './mastered_tracks', mastering_chain)
๐งช Testing Audio Processing Code #
Unit Testing Framework #
import unittest
import numpy as np
from pedalboard import Pedalboard, Compressor, Gain, Reverb
from pedalboard.io import AudioFile
import tempfile
import os
class TestPedalboardProcessing(unittest.TestCase):
def setUp(self):
"""Create test audio and temporary files"""
self.sample_rate = 44100
self.duration = 1.0 # 1 second
self.num_samples = int(self.sample_rate * self.duration)
# Generate test sine wave
t = np.linspace(0, self.duration, self.num_samples)
self.test_audio = np.sin(2 * np.pi * 440 * t).astype(np.float32)
self.test_audio = self.test_audio.reshape(1, -1) # Mono
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
"""Clean up temporary files"""
import shutil
shutil.rmtree(self.temp_dir)
def test_gain_effect(self):
"""Test gain adjustment"""
gain_db = 6.0
board = Pedalboard([Gain(gain_db=gain_db)])
processed = board(self.test_audio, self.sample_rate)
# Check that gain was applied (approximately)
expected_gain_linear = 10 ** (gain_db / 20)
actual_gain = np.max(np.abs(processed)) / np.max(np.abs(self.test_audio))
self.assertAlmostEqual(actual_gain, expected_gain_linear, places=2)
def test_compressor_reduces_peaks(self):
"""Test that compressor reduces dynamic range"""
# Create audio with varying amplitude
loud_audio = self.test_audio * 0.9
quiet_audio = self.test_audio * 0.1
test_signal = np.concatenate([loud_audio, quiet_audio], axis=1)
board = Pedalboard([Compressor(threshold_db=-20, ratio=4)])
processed = board(test_signal, self.sample_rate)
# Check that dynamic range was reduced
original_range = np.max(test_signal) - np.min(test_signal)
processed_range = np.max(processed) - np.min(processed)
self.assertLess(processed_range, original_range)
def test_audio_file_roundtrip(self):
"""Test file I/O operations"""
temp_file = os.path.join(self.temp_dir, "test.wav")
# Write test audio
with AudioFile(temp_file, 'w', self.sample_rate, 1) as f:
f.write(self.test_audio)
# Read it back
with AudioFile(temp_file) as f:
loaded_audio = f.read(f.frames)
# Check that audio is preserved (within floating point precision)
np.testing.assert_array_almost_equal(
self.test_audio, loaded_audio, decimal=5
)
def test_pedalboard_chain_order(self):
"""Test that effect order matters"""
# Chain 1: Gain then Compressor
chain1 = Pedalboard([Gain(gain_db=12), Compressor(threshold_db=-10)])
result1 = chain1(self.test_audio, self.sample_rate)
# Chain 2: Compressor then Gain
chain2 = Pedalboard([Compressor(threshold_db=-10), Gain(gain_db=12)])
result2 = chain2(self.test_audio, self.sample_rate)
# Results should be different
self.assertFalse(np.allclose(result1, result2, rtol=1e-3))
def test_reset_parameter(self):
"""Test reset parameter for stateful effects"""
board = Pedalboard([Reverb(room_size=0.5)])
# Process with reset=True (default)
result1 = board(self.test_audio, self.sample_rate, reset=True)
result2 = board(self.test_audio, self.sample_rate, reset=True)
# Results should be identical
np.testing.assert_array_almost_equal(result1, result2, decimal=5)
# Process with reset=False (continuous)
board(self.test_audio, self.sample_rate, reset=True) # Initialize
result3 = board(self.test_audio, self.sample_rate, reset=False)
result4 = board(self.test_audio, self.sample_rate, reset=False)
# Results should be different (reverb tail continues)
self.assertFalse(np.allclose(result3, result4, rtol=1e-3))
if __name__ == '__main__':
unittest.main()
Property-Based Testing #
from hypothesis import given, strategies as st
import numpy as np
from pedalboard import Pedalboard, Gain
class TestAudioProperties:
@given(st.floats(min_value=-20, max_value=20))
def test_gain_linearity(self, gain_db):
"""Test that gain changes are linear"""
sample_rate = 44100
test_audio = np.random.randn(1, 1000).astype(np.float32) * 0.1
board = Pedalboard([Gain(gain_db=gain_db)])
processed = board(test_audio, sample_rate)
expected_gain = 10 ** (gain_db / 20)
actual_gain = np.sqrt(np.mean(processed**2)) / np.sqrt(np.mean(test_audio**2))
# Allow for some numerical precision
assert abs(actual_gain - expected_gain) < 0.01
@given(st.integers(min_value=1000, max_value=100000))
def test_audio_length_preservation(self, num_samples):
"""Test that audio length is preserved"""
sample_rate = 44100
test_audio = np.random.randn(1, num_samples).astype(np.float32) * 0.1
board = Pedalboard([Gain(gain_db=0)]) # No-op gain
processed = board(test_audio, sample_rate)
assert processed.shape == test_audio.shape
Performance Testing #
import time
import numpy as np
from pedalboard import Pedalboard, Compressor, Reverb, Delay
def benchmark_pedalboard_performance():
"""Benchmark Pedalboard processing speed"""
sample_rate = 44100
duration = 10.0 # 10 seconds
num_samples = int(sample_rate * duration)
# Generate test audio
test_audio = np.random.randn(2, num_samples).astype(np.float32) * 0.1
# Create complex pedalboard
board = Pedalboard([
Compressor(threshold_db=-20, ratio=4),
Delay(delay_seconds=0.1, mix=0.3),
Reverb(room_size=0.5, mix=0.2)
])
# Benchmark processing
start_time = time.time()
processed = board(test_audio, sample_rate)
end_time = time.time()
processing_time = end_time - start_time
real_time_factor = duration / processing_time
print(f"Processed {duration}s of audio in {processing_time:.3f}s")
print(f"Real-time factor: {real_time_factor:.1f}x")
# For real-time applications, factor should be > 1
assert real_time_factor > 1, "Processing too slow for real-time"
return real_time_factor
if __name__ == "__main__":
benchmark_pedalboard_performance()
Mock Testing for External Dependencies #
import unittest.mock as mock
from pedalboard import load_plugin
import pytest
def test_vst_plugin_loading_error_handling():
"""Test handling of VST plugin loading errors"""
# Test with non-existent plugin
with pytest.raises(Exception) as exc_info:
load_plugin("/non/existent/plugin.vst3")
assert "not found" in str(exc_info.value).lower() or "no such file" in str(exc_info.value).lower()
# Test with invalid plugin format
with mock.patch('os.path.exists') as mock_exists:
mock_exists.return_value = True
with pytest.raises(Exception) as exc_info:
load_plugin("/fake/invalid.txt")
assert "invalid" in str(exc_info.value).lower() or "format" in str(exc_info.value).lower()
๐ Advanced Integrations #
Web Framework Integration #
FastAPI Real-time Audio Processing #
from fastapi import FastAPI, UploadFile, File, WebSocket
from fastapi.responses import StreamingResponse
from pedalboard import Pedalboard, Compressor, Reverb, Gain
from pedalboard.io import AudioFile
import tempfile
import asyncio
import numpy as np
import io
app = FastAPI(title="Pedalboard Audio API")
# Global pedalboard for consistent processing
global_board = Pedalboard([
Compressor(threshold_db=-20, ratio=4),
Gain(gain_db=3),
Reverb(room_size=0.3, mix=0.2)
])
@app.post("/process-audio/")
async def process_audio_file(file: UploadFile = File(...)):
"""Process uploaded audio file with effects"""
with tempfile.NamedTemporaryFile(suffix=".wav") as temp_input:
# Save uploaded file
content = await file.read()
temp_input.write(content)
temp_input.flush()
# Process with Pedalboard
with AudioFile(temp_input.name) as f:
audio = f.read(f.frames)
processed = global_board(audio, f.samplerate)
# Return processed audio
with tempfile.NamedTemporaryFile(suffix=".wav") as temp_output:
with AudioFile(temp_output.name, 'w', f.samplerate, f.num_channels) as out:
out.write(processed)
temp_output.seek(0)
return StreamingResponse(
io.BytesIO(temp_output.read()),
media_type="audio/wav",
headers={"Content-Disposition": "attachment; filename=processed.wav"}
)
@app.websocket("/ws/realtime-effects")
async def websocket_audio_processing(websocket: WebSocket):
"""Real-time audio processing via WebSocket"""
await websocket.accept()
board = Pedalboard([Compressor(), Reverb()])
try:
while True:
# Receive audio data (base64 encoded)
data = await websocket.receive_json()
audio_data = np.frombuffer(
base64.b64decode(data['audio']),
dtype=np.float32
).reshape(1, -1)
# Process audio
processed = board(audio_data, data['sample_rate'], reset=False)
# Send back processed audio
await websocket.send_json({
'processed_audio': base64.b64encode(processed.tobytes()).decode(),
'sample_rate': data['sample_rate']
})
except Exception as e:
await websocket.close(code=1000)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Flask Audio Processing Service #
from flask import Flask, request, send_file, jsonify
from pedalboard import Pedalboard, Distortion, Chorus, Delay
from pedalboard.io import AudioFile
import tempfile
import os
app = Flask(__name__)
# Effect presets
PRESETS = {
'guitar': Pedalboard([Distortion(drive_db=10), Chorus(), Delay()]),
'vocal': Pedalboard([Compressor(), Reverb(room_size=0.4)]),
'master': Pedalboard([Compressor(), Limiter()])
}
@app.route('/effects/<preset>', methods=['POST'])
def apply_preset(preset):
"""Apply effect preset to uploaded audio"""
if preset not in PRESETS:
return jsonify({'error': 'Unknown preset'}), 400
if 'audio' not in request.files:
return jsonify({'error': 'No audio file provided'}), 400
audio_file = request.files['audio']
with tempfile.NamedTemporaryFile(suffix='.wav') as temp_input, \
tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_output:
# Save uploaded file
audio_file.save(temp_input.name)
# Process with preset
with AudioFile(temp_input.name) as f:
audio = f.read(f.frames)
processed = PRESETS[preset](audio, f.samplerate)
# Save processed audio
with AudioFile(temp_output.name, 'w', f.samplerate, f.num_channels) as out:
out.write(processed)
return send_file(temp_output.name, as_attachment=True,
download_name=f'{preset}_processed.wav')
if __name__ == '__main__':
app.run(debug=True)
Machine Learning Pipeline Integration #
TensorFlow Data Pipeline #
import tensorflow as tf
import numpy as np
from pedalboard import Pedalboard, Gain, PitchShift, Reverb
import random
class AudioAugmentation:
"""TensorFlow-compatible audio augmentation using Pedalboard"""
def __init__(self, sample_rate=44100):
self.sample_rate = sample_rate
self.augmentation_boards = [
Pedalboard([Gain(gain_db=random.uniform(-3, 3))]),
Pedalboard([PitchShift(semitones=random.uniform(-2, 2))]),
Pedalboard([Reverb(room_size=random.uniform(0.1, 0.5))]),
]
@tf.function
def augment_audio(self, audio_tensor):
"""Apply random augmentation to audio tensor"""
# Convert to numpy for Pedalboard processing
audio_np = audio_tensor.numpy()
# Randomly select augmentation
board_idx = random.randint(0, len(self.augmentation_boards) - 1)
board = self.augmentation_boards[board_idx]
# Apply augmentation
augmented = board(audio_np.reshape(1, -1), self.sample_rate)
return tf.convert_to_tensor(augmented.flatten(), dtype=tf.float32)
# Usage in tf.data pipeline
def create_augmented_dataset(audio_files, labels):
"""Create augmented audio dataset"""
augmenter = AudioAugmentation()
dataset = tf.data.Dataset.from_tensor_slices((audio_files, labels))
def load_and_augment(file_path, label):
# Load audio file
audio = tf.py_function(
lambda path: load_audio_file(path.numpy().decode()),
[file_path],
tf.float32
)
# Apply augmentation
augmented = tf.py_function(
augmenter.augment_audio,
[audio],
tf.float32
)
return augmented, label
return dataset.map(load_and_augment)
def load_audio_file(file_path):
"""Load audio file using Pedalboard"""
from pedalboard.io import AudioFile
with AudioFile(file_path) as f:
audio = f.read(f.frames)
return audio.flatten().astype(np.float32)
PyTorch Integration #
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from pedalboard import Pedalboard, Compressor, EQ
import numpy as np
class PedalboardAudioDataset(Dataset):
"""PyTorch Dataset with Pedalboard augmentation"""
def __init__(self, audio_files, labels, sample_rate=44100, augment=True):
self.audio_files = audio_files
self.labels = labels
self.sample_rate = sample_rate
self.augment = augment
if augment:
self.augmentation_board = Pedalboard([
Compressor(threshold_db=-20, ratio=2),
# Add more effects as needed
])
def __len__(self):
return len(self.audio_files)
def __getitem__(self, idx):
# Load audio
from pedalboard.io import AudioFile
with AudioFile(self.audio_files[idx]) as f:
audio = f.read(f.frames)
# Apply augmentation if enabled
if self.augment and np.random.random() > 0.5:
audio = self.augmentation_board(audio, self.sample_rate)
# Convert to PyTorch tensor
audio_tensor = torch.from_numpy(audio.flatten()).float()
label_tensor = torch.tensor(self.labels[idx], dtype=torch.long)
return audio_tensor, label_tensor
# Usage
train_dataset = PedalboardAudioDataset(train_files, train_labels, augment=True)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
Production Deployment #
Docker Configuration #
# Dockerfile for Pedalboard application
FROM python:3.11-slim
# Install system dependencies for audio processing
RUN apt-get update && apt-get install -y \
ffmpeg \
libsndfile1 \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Kubernetes Deployment #
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pedalboard-api
spec:
replicas: 3
selector:
matchLabels:
app: pedalboard-api
template:
metadata:
labels:
app: pedalboard-api
spec:
containers:
- name: pedalboard-api
image: your-registry/pedalboard-api:latest
ports:
- containerPort: 8000
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
env:
- name: WORKERS
value: "4"
---
apiVersion: v1
kind: Service
metadata:
name: pedalboard-service
spec:
selector:
app: pedalboard-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancer
Performance Monitoring #
import time
import psutil
import logging
from functools import wraps
from pedalboard import Pedalboard
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def monitor_performance(func):
"""Decorator to monitor audio processing performance"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
start_memory = psutil.Process().memory_info().rss / 1024 / 1024 # MB
try:
result = func(*args, **kwargs)
end_time = time.time()
end_memory = psutil.Process().memory_info().rss / 1024 / 1024 # MB
processing_time = end_time - start_time
memory_delta = end_memory - start_memory
logger.info(f"{func.__name__} - Time: {processing_time:.3f}s, Memory: {memory_delta:.1f}MB")
return result
except Exception as e:
logger.error(f"{func.__name__} failed: {str(e)}")
raise
return wrapper
@monitor_performance
def process_audio_with_monitoring(audio, sample_rate, effects_chain):
"""Process audio with performance monitoring"""
board = Pedalboard(effects_chain)
return board(audio, sample_rate)
๐ Performance Benchmarks & Real-World Use Cases #
Performance Comparison Table #
| Library | Processing Speed | Memory Usage | Real-time Factor | GIL Release | Notes |
|---|---|---|---|---|---|
| Pedalboard | โก 300x faster than pySoX | ๐ข Low | ๐ 50-100x | โ Yes | Best for real-time |
| Pydub-NG | ๐ Fast | ๐ก Medium | ๐ 10-20x | โ No | Good for batch |
| librosa | ๐ Analysis-focused | ๐ก Medium | ๐ 5-10x | โ No | Research/analysis |
| SoxBindings | ๐ 2-5x slower | ๐ High | ๐ 2-5x | โ No | Legacy option |
Detailed Benchmarks #
import time
import numpy as np
from pedalboard import Pedalboard, Compressor, Reverb, Delay
import psutil
def comprehensive_benchmark():
"""Comprehensive performance benchmark"""
sample_rates = [22050, 44100, 48000, 96000]
durations = [1, 5, 10, 30] # seconds
# Complex effect chain
board = Pedalboard([
Compressor(threshold_db=-20, ratio=4, attack_ms=5, release_ms=100),
Delay(delay_seconds=0.125, feedback=0.3, mix=0.25),
Reverb(room_size=0.5, damping=0.3, wet_level=0.3)
])
results = []
for sr in sample_rates:
for duration in durations:
num_samples = int(sr * duration)
test_audio = np.random.randn(2, num_samples).astype(np.float32) * 0.1
# Memory before
process = psutil.Process()
mem_before = process.memory_info().rss / 1024 / 1024 # MB
# Benchmark processing
start_time = time.time()
processed = board(test_audio, sr)
end_time = time.time()
# Memory after
mem_after = process.memory_info().rss / 1024 / 1024 # MB
processing_time = end_time - start_time
real_time_factor = duration / processing_time
memory_delta = mem_after - mem_before
results.append({
'sample_rate': sr,
'duration': duration,
'processing_time': processing_time,
'real_time_factor': real_time_factor,
'memory_delta': memory_delta
})
print(f"SR: {sr}Hz, Duration: {duration}s, "
f"Processing: {processing_time:.3f}s, "
f"RT Factor: {real_time_factor:.1f}x, "
f"Memory: +{memory_delta:.1f}MB")
return results
# Run benchmark
if __name__ == "__main__":
benchmark_results = comprehensive_benchmark()
Real-World Use Cases #
1. Podcast Production Pipeline #
from pedalboard import Pedalboard, Compressor, NoiseGate, EQ, Limiter
from pedalboard.io import AudioFile
import os
class PodcastProcessor:
"""Professional podcast processing chain"""
def __init__(self):
# Voice processing chain
self.voice_chain = Pedalboard([
NoiseGate(threshold_db=-40, ratio=10, release_ms=250),
Compressor(threshold_db=-18, ratio=3, attack_ms=3, release_ms=100),
# EQ for voice clarity
HighpassFilter(cutoff_hz=80), # Remove rumble
# Presence boost around 2-5kHz would go here
Limiter(threshold_db=-1, release_ms=50)
])
# Music/intro processing
self.music_chain = Pedalboard([
Compressor(threshold_db=-12, ratio=2),
Limiter(threshold_db=-0.1)
])
def process_episode(self, voice_file, music_file, output_file):
"""Process complete podcast episode"""
# Process voice
with AudioFile(voice_file) as f:
voice_audio = f.read(f.frames)
processed_voice = self.voice_chain(voice_audio, f.samplerate)
# Process music/intro
with AudioFile(music_file) as f:
music_audio = f.read(f.frames)
processed_music = self.music_chain(music_audio, f.samplerate)
# Mix and save (simplified - real mixing would be more complex)
# This is a basic example - professional mixing requires more steps
mixed_audio = processed_voice + (processed_music * 0.3) # Music at 30%
with AudioFile(output_file, 'w', f.samplerate, f.num_channels) as out:
out.write(mixed_audio)
print(f"Podcast episode processed: {output_file}")
# Usage
processor = PodcastProcessor()
processor.process_episode('voice.wav', 'intro_music.wav', 'final_episode.wav')
2. Live Performance Setup #
from pedalboard import Pedalboard, Compressor, Distortion, Chorus, Delay, Reverb
from pedalboard.io import AudioStream
import threading
import time
class LivePerformanceRig:
"""Live audio processing rig with preset switching"""
def __init__(self):
self.presets = {
'clean': Pedalboard([
Compressor(threshold_db=-20, ratio=2),
Chorus(rate_hz=0.5, depth=0.2, mix=0.3),
Reverb(room_size=0.3, mix=0.15)
]),
'crunch': Pedalboard([
Compressor(threshold_db=-15, ratio=3),
Distortion(drive_db=8),
Delay(delay_seconds=0.125, mix=0.2),
Reverb(room_size=0.4, mix=0.1)
]),
'lead': Pedalboard([
Compressor(threshold_db=-12, ratio=4),
Distortion(drive_db=15),
Delay(delay_seconds=0.25, feedback=0.4, mix=0.3),
Reverb(room_size=0.5, mix=0.25)
])
}
self.current_preset = 'clean'
self.stream = None
def start_performance(self, input_device="Audio Interface", output_device="Speakers"):
"""Start live audio processing"""
self.stream = AudioStream(
input_device_name=input_device,
output_device_name=output_device,
sample_rate=44100,
buffer_size=128 # Low latency
)
self.stream.plugins = self.presets[self.current_preset]
self.stream.__enter__()
print(f"Live rig started with '{self.current_preset}' preset")
print("Use switch_preset() to change sounds")
def switch_preset(self, preset_name):
"""Switch to different preset during performance"""
if preset_name in self.presets and self.stream:
self.current_preset = preset_name
self.stream.plugins = self.presets[preset_name]
print(f"Switched to '{preset_name}' preset")
def stop_performance(self):
"""Stop live processing"""
if self.stream:
self.stream.__exit__(None, None, None)
print("Live rig stopped")
# Usage example
rig = LivePerformanceRig()
rig.start_performance()
# Simulate preset changes during performance
time.sleep(5)
rig.switch_preset('crunch')
time.sleep(5)
rig.switch_preset('lead')
time.sleep(5)
rig.stop_performance()
3. Music Production Mastering Chain #
from pedalboard import Pedalboard, Compressor, EQ, Limiter, Reverb
from pedalboard.io import AudioFile
import numpy as np
class MasteringProcessor:
"""Professional mastering chain"""
def __init__(self):
# Multi-stage mastering chain
self.mastering_chain = Pedalboard([
# Stage 1: Gentle compression
Compressor(
threshold_db=-24,
ratio=1.5,
attack_ms=30,
release_ms=100
),
# Stage 2: EQ adjustments (simplified)
HighpassFilter(cutoff_hz=30), # Remove sub-bass
# Stage 3: Main compressor
Compressor(
threshold_db=-12,
ratio=2.5,
attack_ms=10,
release_ms=50
),
# Stage 4: Final limiting
Limiter(
threshold_db=-0.3,
release_ms=30
)
])
def master_track(self, input_file, output_file, target_lufs=-14):
"""Master audio track to broadcast standards"""
with AudioFile(input_file) as f:
audio = f.read(f.frames)
# Apply mastering chain
mastered = self.mastering_chain(audio, f.samplerate)
# Analyze loudness (simplified)
rms_level = np.sqrt(np.mean(mastered**2))
peak_level = np.max(np.abs(mastered))
print(f"Mastering complete:")
print(f" RMS Level: {20 * np.log10(rms_level):.1f} dB")
print(f" Peak Level: {20 * np.log10(peak_level):.1f} dB")
print(f" Peak to RMS: {20 * np.log10(peak_level/rms_level):.1f} dB")
# Save mastered audio
with AudioFile(output_file, 'w', f.samplerate, f.num_channels) as out:
out.write(mastered)
def batch_master(self, input_dir, output_dir):
"""Master entire album/EP"""
import os
from pathlib import Path
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
audio_files = list(input_path.glob('*.wav')) + list(input_path.glob('*.flac'))
for audio_file in audio_files:
output_file = output_path / f"{audio_file.stem}_mastered{audio_file.suffix}"
print(f"Mastering: {audio_file.name}")
self.master_track(str(audio_file), str(output_file))
# Usage
mastering = MasteringProcessor()
mastering.master_track('raw_mix.wav', 'mastered_track.wav')
mastering.batch_master('./raw_mixes', './mastered_tracks')
4. Audio Analysis & Feature Extraction Integration #
from pedalboard import Pedalboard, Compressor, EQ
from pedalboard.io import AudioFile
import librosa
import numpy as np
class AudioAnalysisProcessor:
"""Combine Pedalboard processing with audio analysis"""
def __init__(self):
self.processing_chain = Pedalboard([
Compressor(threshold_db=-20, ratio=3),
HighpassFilter(cutoff_hz=80)
])
def analyze_and_process(self, input_file):
"""Analyze audio characteristics and apply appropriate processing"""
# Load and analyze original audio
with AudioFile(input_file) as f:
audio = f.read(f.frames)
sr = f.samplerate
# Convert to mono for analysis
mono_audio = np.mean(audio, axis=0) if audio.shape[0] > 1 else audio[0]
# Extract features using librosa
tempo, beats = librosa.beat.beat_track(y=mono_audio, sr=sr)
spectral_centroid = librosa.feature.spectral_centroid(y=mono_audio, sr=sr)[0]
zero_crossing_rate = librosa.feature.zero_crossing_rate(mono_audio)[0]
# Analyze characteristics
avg_centroid = np.mean(spectral_centroid)
avg_zcr = np.mean(zero_crossing_rate)
print(f"Audio Analysis:")
print(f" Tempo: {tempo:.1f} BPM")
print(f" Spectral Centroid: {avg_centroid:.0f} Hz")
print(f" Zero Crossing Rate: {avg_zcr:.4f}")
# Adapt processing based on analysis
if avg_centroid > 3000: # Bright/harsh audio
print(" -> Applying gentle high-frequency reduction")
self.processing_chain.append(LowpassFilter(cutoff_hz=8000))
if avg_zcr > 0.1: # Very dynamic/noisy audio
print(" -> Applying stronger compression")
self.processing_chain[0].ratio = 4 # Increase compression ratio
# Apply processing
processed = self.processing_chain(audio, sr)
return processed, {
'tempo': tempo,
'spectral_centroid': avg_centroid,
'zero_crossing_rate': avg_zcr
}
# Usage
analyzer = AudioAnalysisProcessor()
processed_audio, features = analyzer.analyze_and_process('input.wav')
๐ง Troubleshooting #
Common Issues #
Plugin Loading Fails
# Check plugin path and format
try:
plugin = load_plugin("/full/path/to/plugin.vst3")
except Exception as e:
print(f"Plugin error: {e}")
# Try different path or plugin format
Audio File Format Issues
# Check supported formats
supported_formats = ['.wav', '.flac', '.mp3', '.ogg', '.aiff']
if file_path.suffix.lower() not in supported_formats:
print(f"Unsupported format: {file_path.suffix}")
Memory Issues with Large Files
# Process in chunks instead of loading entire file
chunk_size = sample_rate * 10 # 10 seconds
with AudioFile('large_file.wav') as f:
with AudioFile('output.wav', 'w', f.samplerate, f.num_channels) as out:
while f.tell() < f.frames:
chunk = f.read(min(chunk_size, f.frames - f.tell()))
processed = board(chunk, f.samplerate, reset=False)
out.write(processed)
Performance Tips #
๐ Maximize Performance
- Use appropriate chunk sizes (1-10 seconds)
- Leverage GIL release for CPU-intensive processing
- Avoid unnecessary resampling
- Reuse Pedalboard objects when possible
- Use
reset=Falsefor continuous processing
๐ฏ Optimize for Real-time
- Use small buffer sizes (64-256 samples)
- Minimize effect chain complexity
- Pre-load VST plugins
- Use dedicated audio interfaces
๐ Resources #
๐ Official Documentation
๐ฅ Learning Resources
๐ ๏ธ Related Tools
- librosa: Audio analysis
- soundfile: Audio I/O
- numpy: Numerical computing
- TensorFlow: Machine learning integration
Created with โค๏ธ for audio processing enthusiasts