Local STT (Qwen3-ASR), VLM (Gemma 4 26B-A4B), and TTS (Spark-TTS) running on Apple Silicon via MLX, with bracket-tag action system for nod, shake, wiggle, dance, photo, and pre-recorded emotions.
49 lines
1.6 KiB
Bash
Executable File
49 lines
1.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Record a voice reference via the Reachy Mini's microphone.
|
|
# Usage: ./record_voice.sh [seconds] [output.wav]
|
|
set -e
|
|
|
|
DURATION="${1:-12}"
|
|
OUTPUT="${2:-voice_ref.wav}"
|
|
|
|
echo "Recording ${DURATION}s via Reachy Mini's microphone to ${OUTPUT}"
|
|
echo "Stand/sit near the robot. Starting in 3 seconds..."
|
|
|
|
sshpass -p 'root' ssh -o StrictHostKeyChecking=no pollen@reachy-mini.local "/venvs/mini_daemon/bin/python -c \"
|
|
import time, wave, numpy as np
|
|
from reachy_mini import ReachyMini
|
|
|
|
with ReachyMini() as mini:
|
|
mini.media.start_recording()
|
|
time.sleep(1.5) # warm-up + countdown buffer
|
|
print('GO — speak now', flush=True)
|
|
|
|
sr = mini.media.get_input_audio_samplerate()
|
|
chunks = []
|
|
start = time.time()
|
|
while time.time() - start < ${DURATION}:
|
|
sample = mini.media.get_audio_sample()
|
|
if sample is not None:
|
|
mono = sample.mean(axis=1) if sample.ndim == 2 else sample
|
|
chunks.append(mono)
|
|
else:
|
|
time.sleep(0.01)
|
|
mini.media.stop_recording()
|
|
|
|
audio = np.concatenate(chunks).astype(np.float32)
|
|
peak = float(np.abs(audio).max())
|
|
print(f'Captured {len(audio)/sr:.1f}s @ {sr}Hz, peak={peak:.3f}', flush=True)
|
|
|
|
# Normalize + convert to int16
|
|
if peak > 0:
|
|
audio = audio / peak * 0.9
|
|
pcm = (audio * 32767).astype(np.int16)
|
|
|
|
with wave.open('/tmp/voice_ref.wav', 'wb') as f:
|
|
f.setnchannels(1); f.setsampwidth(2); f.setframerate(sr)
|
|
f.writeframes(pcm.tobytes())
|
|
\""
|
|
|
|
sshpass -p 'root' scp pollen@reachy-mini.local:/tmp/voice_ref.wav "${OUTPUT}"
|
|
echo "Saved ${OUTPUT}"
|