"""Diagnose Blurt's capture path: record, measure, transcribe, save the WAV. Usage: uv run mic_check.py [device] [seconds] device is a sounddevice index or name substring; omitted = whatever Blurt itself is configured to use (device.txt, else system default). """ import io import sys import tempfile from pathlib import Path import numpy as np import sounddevice as sd from scipy.io import wavfile import blurt OUT_WAV = Path(tempfile.gettempdir()) / "blurt_mic_check.wav" def report(label: str, samples: np.ndarray) -> None: s = blurt.audio_stats(samples) frame = blurt.SAMPLE_RATE // 50 energy = np.array([ np.sqrt(np.mean(np.square(samples[i:i + frame])) + 1e-20) for i in range(0, max(len(samples) - frame, 1), frame) ]) snr = 20 * np.log10(np.percentile(energy, 90) / (np.percentile(energy, 10) + 1e-12)) print(f" {label}") print(f" rms {s['rms_dbfs']:+7.1f} dBFS (speech wants -30..-15)") print(f" peak {s['peak_dbfs']:+7.1f} dBFS") print(f" dc bias {s['dc']:+7.4f} (wants ~0.0000)") print(f" clipped {s['clipped_pct']:7.3f} %") print(f" loud/quiet spread {snr:.1f} dB (real speech wants > 25)") def main() -> None: args = sys.argv[1:] device = blurt.load_input_device_name() or None if args and args[0]: try: device = int(args[0]) except ValueError: device = args[0] seconds = float(args[1]) if len(args) > 1 else 6.0 print("Input devices:") for i, d in enumerate(sd.query_devices()): if d["max_input_channels"] > 0: print(f" {i:>3} {d['name']}") resolved = sd.query_devices(device, "input") if device else sd.query_devices(kind="input") rate, channels = blurt.input_stream_settings(device) print(f"\nRecording {seconds:g}s from: {resolved['name']} at {rate} Hz / {channels} ch — speak now.") frames = sd.rec( int(seconds * rate), samplerate=rate, channels=channels, dtype=blurt.DTYPE, device=device, ) sd.wait() audio = frames.mean(axis=1) if channels > 1 else frames[:, 0] audio = blurt.resample_to_target(audio, rate) click = blurt.leading_click_samples(audio) audio = audio[click:] print(f"\nCapture (trimmed {click * 1000 / blurt.SAMPLE_RATE:.0f} ms of stream-open click):") report("raw", audio) conditioned = blurt.condition_audio(audio) report("after Blurt conditioning", conditioned) buffer = io.BytesIO() wavfile.write(buffer, blurt.SAMPLE_RATE, (conditioned * 32767).astype(np.int16)) wav_bytes = buffer.getvalue() OUT_WAV.write_bytes(wav_bytes) print(f"\nWAV saved: {OUT_WAV} ({len(wav_bytes)} bytes) — play it to hear what the API hears") app = blurt.BlurtApp.__new__(blurt.BlurtApp) app.api_keys = {"openai": blurt.load_api_key(), "openrouter": blurt.load_openrouter_key()} app.transcription_model = blurt.load_transcription_model() app.transcription_language = blurt.load_transcription_language() app.session = blurt.requests.Session() provider = app._provider_for_model(app.transcription_model) if not app.api_keys.get(provider): print(f"\nNo {provider} key available; skipping transcription.") return print(f"\nTranscribing with {app.transcription_model} ({app.transcription_language})…") result = ( app._transcribe_openrouter(wav_bytes) if provider == "openrouter" else app._transcribe_openai(wav_bytes) ) print("\n--- transcript ---") print(result.get("text", result)) if __name__ == "__main__": main()