signal_generator.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """
  2. Signal Generator — Generate test signals for audio calibration.
  3. Supports:
  4. - Logarithmic sine sweep (chirp)
  5. - Pink noise (1/f)
  6. - White noise
  7. - Sine tone
  8. """
  9. import numpy as np
  10. from scipy.signal import chirp
  11. def generate_log_sweep(
  12. start_freq: float = 20.0,
  13. end_freq: float = 20000.0,
  14. duration: float = 10.0,
  15. sample_rate: int = 44100,
  16. amplitude: float = 0.8
  17. ) -> np.ndarray:
  18. """
  19. Generate a logarithmic sine sweep (chirp).
  20. This is the preferred method for measuring frequency response
  21. as it provides a high signal-to-noise ratio across the spectrum.
  22. """
  23. t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
  24. # Generate the sweep
  25. sweep = chirp(t, f0=start_freq, f1=end_freq, t1=duration, method='logarithmic')
  26. # Apply a short fade-in and fade-out to prevent clicks
  27. fade_len = int(0.05 * sample_rate) # 50ms fade
  28. window = np.ones_like(sweep)
  29. if len(window) > 2 * fade_len:
  30. fade_in = np.linspace(0, 1, fade_len)
  31. fade_out = np.linspace(1, 0, fade_len)
  32. window[:fade_len] = fade_in
  33. window[-fade_len:] = fade_out
  34. return (sweep * window * amplitude).astype(np.float32)
  35. def generate_white_noise(
  36. duration: float = 10.0,
  37. sample_rate: int = 44100,
  38. amplitude: float = 0.8
  39. ) -> np.ndarray:
  40. """Generate white noise (equal energy per Hz)."""
  41. num_samples = int(sample_rate * duration)
  42. # np.random.randn generates Gaussian white noise (mean 0, variance 1)
  43. noise = np.random.randn(num_samples)
  44. # Normalize and scale
  45. noise = (noise / np.max(np.abs(noise))) * amplitude
  46. return noise.astype(np.float32)
  47. def generate_pink_noise(
  48. duration: float = 10.0,
  49. sample_rate: int = 44100,
  50. amplitude: float = 0.8
  51. ) -> np.ndarray:
  52. """
  53. Generate pink noise (equal energy per octave, 1/f spectrum).
  54. Uses a standard IIR filter approximation (Voss-McCartney algorithm or similar filter bank)
  55. applied to white noise.
  56. """
  57. import scipy.signal as signal
  58. white = np.random.randn(int(sample_rate * duration))
  59. # Filter coefficients for a rough pinking filter
  60. # (Paul Kellet's refined filter)
  61. b = [0.049922035, -0.095993537, 0.050612699, -0.004408786]
  62. a = [1.000000000, -2.494956002, 2.017265875, -0.522189400]
  63. pink = signal.lfilter(b, a, white)
  64. # Normalize and scale
  65. pink = (pink / np.max(np.abs(pink))) * amplitude
  66. return pink.astype(np.float32)
  67. def generate_sine_tone(
  68. frequency: float = 1000.0,
  69. duration: float = 5.0,
  70. sample_rate: int = 44100,
  71. amplitude: float = 0.8
  72. ) -> np.ndarray:
  73. """Generate a pure sine wave tone."""
  74. t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
  75. tone = np.sin(2 * np.pi * frequency * t)
  76. # Apply fade in/out
  77. fade_len = int(0.05 * sample_rate)
  78. window = np.ones_like(tone)
  79. if len(window) > 2 * fade_len:
  80. window[:fade_len] = np.linspace(0, 1, fade_len)
  81. window[-fade_len:] = np.linspace(1, 0, fade_len)
  82. return (tone * window * amplitude).astype(np.float32)