Mic software gain, VAD threshold 0.5, speech event logging, greeting on connect
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
This commit is contained in:
@@ -73,10 +73,11 @@ class Assistant:
|
|||||||
def __init__(self, cfg: Config) -> None:
|
def __init__(self, cfg: Config) -> None:
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
self.client = AsyncOpenAI(api_key=cfg.openai_api_key)
|
self.client = AsyncOpenAI(api_key=cfg.openai_api_key)
|
||||||
self.mic = Mic(cfg.mic_device)
|
self.mic = Mic(cfg.mic_device, cfg.mic_gain)
|
||||||
self.speaker = Speaker(cfg.speaker_device)
|
self.speaker = Speaker(cfg.speaker_device)
|
||||||
self.conn = None
|
self.conn = None
|
||||||
self.jobs: set[asyncio.Task] = set()
|
self.jobs: set[asyncio.Task] = set()
|
||||||
|
self.greeted = False
|
||||||
|
|
||||||
# ---------- alati ----------
|
# ---------- alati ----------
|
||||||
async def tool_wikipedia(self, args: dict) -> str:
|
async def tool_wikipedia(self, args: dict) -> str:
|
||||||
@@ -203,12 +204,21 @@ class Assistant:
|
|||||||
if item.get("type") == "function_call":
|
if item.get("type") == "function_call":
|
||||||
await self.handle_function_call(item)
|
await self.handle_function_call(item)
|
||||||
elif t == "input_audio_buffer.speech_started":
|
elif t == "input_audio_buffer.speech_started":
|
||||||
|
log.info("govor počeo")
|
||||||
if self.speaker.is_playing(0.0):
|
if self.speaker.is_playing(0.0):
|
||||||
await self.speaker.clear()
|
await self.speaker.clear()
|
||||||
|
elif t == "input_audio_buffer.speech_stopped":
|
||||||
|
log.info("govor stao")
|
||||||
elif t == "error":
|
elif t == "error":
|
||||||
log.error("realtime greška: %s", event.error)
|
log.error("realtime greška: %s", event.error)
|
||||||
elif t in ("session.created", "session.updated"):
|
elif t == "session.created":
|
||||||
log.info("sesija: %s", t)
|
log.info("sesija: %s", t)
|
||||||
|
elif t == "session.updated":
|
||||||
|
log.info("sesija: %s", t)
|
||||||
|
if not self.greeted:
|
||||||
|
self.greeted = True
|
||||||
|
await self.conn.send({"type": "response.create", "response": {
|
||||||
|
"instructions": "Kratko se predstavi na bosanskom u jednoj ili dvije rečenice: ti si Idriz, i reci da možeš odštampati sažetak o nekoj temi ili bojanku."}})
|
||||||
|
|
||||||
async def session(self) -> None:
|
async def session(self) -> None:
|
||||||
async with self.client.realtime.connect(model=self.cfg.realtime_model) as conn:
|
async with self.client.realtime.connect(model=self.cfg.realtime_model) as conn:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Mikrofon i zvučnik preko PipeWire-a (pacat), PCM16 mono 24 kHz kako traži Realtime API."""
|
"""Mikrofon i zvučnik preko PipeWire-a (pacat), PCM16 mono 24 kHz kako traži Realtime API."""
|
||||||
|
import array
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@@ -17,9 +18,20 @@ def _pacat_args(mode: str, device: str) -> list[str]:
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def apply_gain(pcm: bytes, gain: float) -> bytes:
|
||||||
|
if gain == 1.0:
|
||||||
|
return pcm
|
||||||
|
samples = array.array("h", pcm)
|
||||||
|
for i, v in enumerate(samples):
|
||||||
|
x = int(v * gain)
|
||||||
|
samples[i] = 32767 if x > 32767 else (-32768 if x < -32768 else x)
|
||||||
|
return samples.tobytes()
|
||||||
|
|
||||||
|
|
||||||
class Mic:
|
class Mic:
|
||||||
def __init__(self, device: str = "") -> None:
|
def __init__(self, device: str = "", gain: float = 1.0) -> None:
|
||||||
self.device = device
|
self.device = device
|
||||||
|
self.gain = gain
|
||||||
self.proc: asyncio.subprocess.Process | None = None
|
self.proc: asyncio.subprocess.Process | None = None
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
@@ -32,7 +44,7 @@ class Mic:
|
|||||||
|
|
||||||
async def read(self) -> bytes:
|
async def read(self) -> bytes:
|
||||||
assert self.proc and self.proc.stdout
|
assert self.proc and self.proc.stdout
|
||||||
return await self.proc.stdout.read(CHUNK)
|
return apply_gain(await self.proc.stdout.read(CHUNK), self.gain)
|
||||||
|
|
||||||
async def stop(self) -> None:
|
async def stop(self) -> None:
|
||||||
if self.proc and self.proc.returncode is None:
|
if self.proc and self.proc.returncode is None:
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class Config:
|
|||||||
max_pages: int = field(default_factory=lambda: int(_env("IDRIZ_MAX_PAGES", "2")))
|
max_pages: int = field(default_factory=lambda: int(_env("IDRIZ_MAX_PAGES", "2")))
|
||||||
dry_run: bool = field(default_factory=lambda: _env("IDRIZ_DRY_RUN", "0") == "1")
|
dry_run: bool = field(default_factory=lambda: _env("IDRIZ_DRY_RUN", "0") == "1")
|
||||||
vad_silence_ms: int = field(default_factory=lambda: int(_env("IDRIZ_VAD_SILENCE_MS", "900")))
|
vad_silence_ms: int = field(default_factory=lambda: int(_env("IDRIZ_VAD_SILENCE_MS", "900")))
|
||||||
vad_threshold: float = field(default_factory=lambda: float(_env("IDRIZ_VAD_THRESHOLD", "0.6")))
|
mic_gain: float = field(default_factory=lambda: float(_env("IDRIZ_MIC_GAIN", "3.0")))
|
||||||
|
vad_threshold: float = field(default_factory=lambda: float(_env("IDRIZ_VAD_THRESHOLD", "0.5")))
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
self.out_dir.mkdir(parents=True, exist_ok=True)
|
self.out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user