import numpy as np
import soundfile as sf
import os
from pathlib import Path
OUTPUT_DIR = Path(__file__).parent.parent / "tests" / "fixtures"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
SAMPLE_RATE = 44100
def generate_kick_drum(bpm: float, duration: float, sample_rate: int = SAMPLE_RATE) -> np.ndarray:
beat_interval = 60.0 / bpm
samples = np.zeros(int(duration * sample_rate), dtype=np.float32)
for beat_time in np.arange(0, duration, beat_interval):
beat_sample = int(beat_time * sample_rate)
kick_duration = int(0.1 * sample_rate) t = np.arange(kick_duration) / sample_rate
kick = (np.sin(2 * np.pi * 60 * t) * 0.6 +
np.sin(2 * np.pi * 120 * t) * 0.3 +
np.sin(2 * np.pi * 180 * t) * 0.1)
envelope = np.exp(-t * 10)
kick *= envelope
end = min(beat_sample + kick_duration, len(samples))
samples[beat_sample:end] += kick[:end - beat_sample]
max_val = np.max(np.abs(samples))
if max_val > 0:
samples = samples / max_val
return samples
def generate_c_major_scale(duration_per_note: float = 0.5, sample_rate: int = SAMPLE_RATE) -> np.ndarray:
frequencies = [
261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, ]
samples_list = []
for freq in frequencies:
note_samples = int(duration_per_note * sample_rate)
t = np.arange(note_samples) / sample_rate
note = np.sin(2 * np.pi * freq * t)
fade_samples = int(0.05 * sample_rate) envelope = np.ones(note_samples)
envelope[:fade_samples] = np.linspace(0, 1, fade_samples)
envelope[-fade_samples:] = np.linspace(1, 0, fade_samples)
note *= envelope
samples_list.append(note)
samples = np.concatenate(samples_list).astype(np.float32)
max_val = np.max(np.abs(samples))
if max_val > 0:
samples = samples / max_val
return samples
def generate_mixed_silence(silence_duration: float = 5.0,
audio_duration: float = 5.0,
sample_rate: int = SAMPLE_RATE) -> np.ndarray:
silence_samples = int(silence_duration * sample_rate)
audio_samples = int(audio_duration * sample_rate)
leading_silence = np.zeros(silence_samples, dtype=np.float32)
trailing_silence = np.zeros(silence_samples, dtype=np.float32)
t = np.arange(audio_samples) / sample_rate
audio_content = np.sin(2 * np.pi * 440.0 * t).astype(np.float32) * 0.5
samples = np.concatenate([leading_silence, audio_content, trailing_silence])
return samples
def main():
print("Generating test fixtures for Stratum DSP...")
print(f"Output directory: {OUTPUT_DIR}")
print()
print("Generating 120bpm_4bar.wav...")
kick_120 = generate_kick_drum(120.0, 8.0)
sf.write(OUTPUT_DIR / "120bpm_4bar.wav", kick_120, SAMPLE_RATE)
print(f" [OK] Created: {len(kick_120) / SAMPLE_RATE:.2f} seconds, {len(kick_120)} samples")
print("Generating 128bpm_4bar.wav...")
kick_128 = generate_kick_drum(128.0, 7.5)
sf.write(OUTPUT_DIR / "128bpm_4bar.wav", kick_128, SAMPLE_RATE)
print(f" [OK] Created: {len(kick_128) / SAMPLE_RATE:.2f} seconds, {len(kick_128)} samples")
print("Generating cmajor_scale.wav...")
scale = generate_c_major_scale(duration_per_note=0.5)
sf.write(OUTPUT_DIR / "cmajor_scale.wav", scale, SAMPLE_RATE)
print(f" [OK] Created: {len(scale) / SAMPLE_RATE:.2f} seconds, {len(scale)} samples")
print("Generating mixed_silence.wav...")
mixed = generate_mixed_silence(silence_duration=5.0, audio_duration=5.0)
sf.write(OUTPUT_DIR / "mixed_silence.wav", mixed, SAMPLE_RATE)
print(f" [OK] Created: {len(mixed) / SAMPLE_RATE:.2f} seconds, {len(mixed)} samples")
print()
print("[OK] All test fixtures generated successfully!")
print(f" Location: {OUTPUT_DIR}")
print()
print("Expected results:")
print(" - 120bpm_4bar.wav: ~16 onsets (4 beats/bar × 4 bars)")
print(" - 128bpm_4bar.wav: ~16 onsets (4 beats/bar × 4 bars)")
print(" - cmajor_scale.wav: Key = C Major, confidence > 0.8")
print(" - mixed_silence.wav: Trimmed duration = ~5 seconds")
if __name__ == "__main__":
main()