#!/usr/bin/env python3 """Record 5s from the USB Speaker Phone with a live RMS meter, save out.wav. Pass criteria for the record stage: - Level meter visibly responds to speech and to silence. - out.wav is written and non-empty. """ import os os.environ.setdefault("PA_ALSA_PLUGHW", "1") import sys import time import numpy as np import sounddevice as sd import soundfile as sf SAMPLE_RATE = 16_000 DURATION_S = 5 CHANNELS = 1 DTYPE = "int16" OUT_WAV = "out.wav" def find_usb_device() -> int: for idx, dev in enumerate(sd.query_devices()): name = dev["name"].lower() if "usb" in name and dev["max_input_channels"] >= 1: return idx print("USB audio device not found. Devices:", file=sys.stderr) print(sd.query_devices(), file=sys.stderr) sys.exit(1) def rms(int16_buf: np.ndarray) -> float: if int16_buf.size == 0: return 0.0 x = int16_buf.astype(np.float32) / 32768.0 return float(np.sqrt((x * x).mean() + 1e-12)) def record(device: int) -> np.ndarray: print(f"Recording {DURATION_S}s from device {device} ({sd.query_devices(device)['name']!r})") total_frames = SAMPLE_RATE * DURATION_S buf = np.zeros((total_frames, CHANNELS), dtype=DTYPE) frames_written = 0 start = time.monotonic() def callback(indata, frames, time_info, status): nonlocal frames_written if status: print(f"\n[status] {status}", file=sys.stderr) end = min(frames_written + frames, total_frames) buf[frames_written:end] = indata[: end - frames_written] frames_written = end with sd.InputStream( samplerate=SAMPLE_RATE, channels=CHANNELS, dtype=DTYPE, device=device, blocksize=1024, callback=callback, ): while frames_written < total_frames: elapsed = time.monotonic() - start tail = buf[max(0, frames_written - 1600) : frames_written] level = rms(tail) bar = "#" * int(level * 60) print(f"\r{elapsed:4.1f}s |{bar:<60}|", end="", flush=True) time.sleep(0.05) print() return buf def main() -> int: device = find_usb_device() audio = record(device) sf.write(OUT_WAV, audio, SAMPLE_RATE) print(f"Wrote {OUT_WAV} ({audio.shape[0]} frames @ {SAMPLE_RATE} Hz)") return 0 if __name__ == "__main__": sys.exit(main())