#!/usr/bin/env python3 """Generate a click-track WAV file at a known BPM. Example: python tests/make_bpm_test_audio.py --bpm 128 --seconds 60 --output tests/audio_128bpm.wav """ from __future__ import annotations import argparse import math import struct import wave def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate known-BPM click track") parser.add_argument("--bpm", type=float, required=True, help="Target BPM (e.g. 120)") parser.add_argument("--seconds", type=float, default=30.0, help="Audio duration in seconds") parser.add_argument("--sample-rate", type=int, default=44100, help="Sample rate") parser.add_argument("--click-ms", type=float, default=25.0, help="Click length in milliseconds") parser.add_argument( "--output", default="tests/bpm_test.wav", help="Output WAV path", ) return parser.parse_args() def main() -> int: args = parse_args() if args.bpm <= 0: raise SystemExit("--bpm must be > 0") if args.seconds <= 0: raise SystemExit("--seconds must be > 0") sample_rate = int(args.sample_rate) total_samples = int(args.seconds * sample_rate) beat_interval = 60.0 / float(args.bpm) click_samples = max(1, int((args.click_ms / 1000.0) * sample_rate)) data = [0.0] * total_samples beat_index = 0 t = 0.0 while t < args.seconds: start = int(t * sample_rate) # Slight accent every 4 beats to help human counting. freq = 1760.0 if beat_index % 4 == 0 else 1320.0 amp = 0.9 if beat_index % 4 == 0 else 0.6 for i in range(click_samples): idx = start + i if idx >= total_samples: break env = math.exp(-8.0 * (i / click_samples)) s = amp * env * math.sin((2.0 * math.pi * freq * i) / sample_rate) data[idx] += s t += beat_interval beat_index += 1 with wave.open(args.output, "wb") as wf: wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(sample_rate) frames = bytearray() for s in data: clipped = max(-1.0, min(1.0, s)) frames.extend(struct.pack("