#!/usr/bin/env python3 """ TEMPLATE — G1 Audio Script Copy-paste starter demonstrating every reliable primitive for G1 audio: 1. Auto-detect and prep the Hollyland mic (g1_audio_devices helpers) 2. Record N seconds via parec (the method that actually works) 3. Load / generate PCM (16 kHz mono int16) 4. Play on the G1 speaker in ONE call (_CallRequestWithParamAndBin) 5. Always STOP the stream at the end (mandatory reset) Do NOT use: - PlayStream (chunked — unreliable, see voice_note.txt) - PyAudio input against the pulse device on the robot (gives silence) - aplay / paplay to the built-in speaker (no physical path) Run on the robot, gemini conda env (needs _CallRequestWithParamAndBin): /home/unitree/miniconda3/envs/gemini/bin/python3 template_audio_script.py Run on the workstation (edit MODE below to "tone" — no robot needed). """ from __future__ import annotations import argparse import json import math import struct import subprocess import sys import time from pathlib import Path # ─── Constants ────────────────────────────────────────────────────────────── TARGET_RATE = 16000 BIT_DEPTH = 16 CHANNELS = 1 APP_NAME = "my_app" # pick any unique label for YOUR script DDS_IFACE = "eth0" # on workstation use "enp3s0" VOLUME = 100 # ─── 1. Mic prep (optional — skip if recording is handled elsewhere) ──────── def prep_mic(keywords=("hollyland", "wireless_microphone")): """Auto-detect and unmute the first PulseAudio source matching keywords.""" rc = subprocess.run(["pactl", "list", "short", "sources"], capture_output=True, text=True) for line in rc.stdout.splitlines(): parts = line.split("\t") if len(parts) < 2: continue idx, name = parts[0], parts[1] if any(k in name.lower() for k in keywords): subprocess.run(["pactl", "set-default-source", name]) subprocess.run(["pactl", "set-source-mute", idx, "0"]) subprocess.run(["pactl", "set-source-volume", idx, "100%"]) return int(idx), name return None, None # ─── 2. Record via parec (the method that works on the robot) ─────────────── def record(seconds: float, source_index: int) -> bytes: proc = subprocess.Popen( ["parec", "-d", str(source_index), "--format=s16le", "--rate=16000", "--channels=1", "--raw"], stdout=subprocess.PIPE, ) time.sleep(seconds) proc.terminate() return proc.stdout.read() if proc.stdout else b"" # ─── 3. Generate a demo PCM (used when MODE=tone) ─────────────────────────── def sine_tone(freq_hz: float, seconds: float) -> bytes: n = int(TARGET_RATE * seconds) out = bytearray() for i in range(n): val = int(0.3 * 32767 * math.sin(2 * math.pi * freq_hz * i / TARGET_RATE)) out += struct.pack(" int: p = argparse.ArgumentParser(description="G1 audio template") p.add_argument("mode", choices=["tone", "echo", "prep-mic"], help="tone=440Hz beep; echo=record+play; prep-mic=auto-detect mic only") p.add_argument("--seconds", "-s", type=float, default=3.0, help="Recording / tone duration (default 3)") args = p.parse_args() if args.mode == "prep-mic": idx, name = prep_mic() if idx is None: print("No wireless mic found.") return 1 print(f"Prepped: [{idx}] {name}") return 0 if args.mode == "tone": pcm = sine_tone(440.0, args.seconds) play_on_g1(pcm) return 0 if args.mode == "echo": idx, name = prep_mic() if idx is None: print("No wireless mic found.") return 1 print(f"Mic: [{idx}] {name}") print(f"Recording {args.seconds:.1f}s ... speak now") pcm = record(args.seconds, idx) if not pcm: print("No audio captured.") return 2 print(f"Captured {len(pcm):,} bytes. Playing back on G1 ...") play_on_g1(pcm) return 0 return 1 if __name__ == "__main__": sys.exit(main())