""" Signal Generator — Generate test signals for audio calibration. Supports: - Logarithmic sine sweep (chirp) - Pink noise (1/f) - White noise - Sine tone """ import numpy as np from scipy.signal import chirp def generate_log_sweep( start_freq: float = 20.0, end_freq: float = 20000.0, duration: float = 10.0, sample_rate: int = 44100, amplitude: float = 0.8 ) -> np.ndarray: """ Generate a logarithmic sine sweep (chirp). This is the preferred method for measuring frequency response as it provides a high signal-to-noise ratio across the spectrum. """ t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) # Generate the sweep sweep = chirp(t, f0=start_freq, f1=end_freq, t1=duration, method='logarithmic') # Apply a short fade-in and fade-out to prevent clicks fade_len = int(0.05 * sample_rate) # 50ms fade window = np.ones_like(sweep) if len(window) > 2 * fade_len: fade_in = np.linspace(0, 1, fade_len) fade_out = np.linspace(1, 0, fade_len) window[:fade_len] = fade_in window[-fade_len:] = fade_out return (sweep * window * amplitude).astype(np.float32) def generate_white_noise( duration: float = 10.0, sample_rate: int = 44100, amplitude: float = 0.8 ) -> np.ndarray: """Generate white noise (equal energy per Hz).""" num_samples = int(sample_rate * duration) # np.random.randn generates Gaussian white noise (mean 0, variance 1) noise = np.random.randn(num_samples) # Normalize and scale noise = (noise / np.max(np.abs(noise))) * amplitude return noise.astype(np.float32) def generate_pink_noise( duration: float = 10.0, sample_rate: int = 44100, amplitude: float = 0.8 ) -> np.ndarray: """ Generate pink noise (equal energy per octave, 1/f spectrum). Uses a standard IIR filter approximation (Voss-McCartney algorithm or similar filter bank) applied to white noise. """ import scipy.signal as signal white = np.random.randn(int(sample_rate * duration)) # Filter coefficients for a rough pinking filter # (Paul Kellet's refined filter) b = [0.049922035, -0.095993537, 0.050612699, -0.004408786] a = [1.000000000, -2.494956002, 2.017265875, -0.522189400] pink = signal.lfilter(b, a, white) # Normalize and scale pink = (pink / np.max(np.abs(pink))) * amplitude return pink.astype(np.float32) def generate_sine_tone( frequency: float = 1000.0, duration: float = 5.0, sample_rate: int = 44100, amplitude: float = 0.8 ) -> np.ndarray: """Generate a pure sine wave tone.""" t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) tone = np.sin(2 * np.pi * frequency * t) # Apply fade in/out fade_len = int(0.05 * sample_rate) window = np.ones_like(tone) if len(window) > 2 * fade_len: window[:fade_len] = np.linspace(0, 1, fade_len) window[-fade_len:] = np.linspace(1, 0, fade_len) return (tone * window * amplitude).astype(np.float32)