Files
CyberGril/src/cybergril/providers/vad/energy.py
T
mobai 1012d8bb79 chore: project foundation
Contracts (the shared language):
- percept / state / context / utterance / render / audio / capabilities
- Capability model instead of vendor schemas (see below)

Providers (capability-based abstraction):
- LLM + Embedding: OpenAI-compatible shape IS the standard, adopted directly
- ASR: internal contract is an event stream (partial -> final), because
  /v1/audio/transcriptions has no partial and dialogue needs it for
  endpointing. OpenAI adapter degrades to supports_partial=False.
- TTS: internal contract carries tags/emotion/style, because
  /v1/audio/speech has no paralinguistics and [laughter]/[breath] are
  the core of feeling alive. Unsupported providers are stripped AND
  reported via Degradation -- silent degradation is forbidden.
- Avatar: no de-facto standard, custom RenderCmd protocol driven by State.

Core:
- mood: dynamical system, slow variables, negativity bias (grudge),
  numbing after prolonged negativity
- bazi: four pillars -> numeric vector; 'ming' is data, not prose
- projection: phi, system event -> in-world statement; uncovered events
  are counted as projection gaps

Storage:
- SQLite schema, three memory layers, dyad ledger
- accessibility floor 0.03 (not 0): forgettable but still there

Config split:
- models.yaml is the ONLY place model versions live
- character.yaml holds world rules / emotion params, no version numbers

58 tests pass; ruff clean.
2026-10-19 20:08:52 +08:00

50 lines
1.6 KiB
Python

"""能量 VAD。离线与兜底用。
**不是生产方案。** 生产要用 Silero v6 + 语义端点。
它的价值是把"VAD 接口长什么样"固定下来,让上层可以先跑通。
同时它是一个反面教材:纯能量判定会抢话。
规格里明确要求:能量 VAD 只做门控,不做端点判定。
"""
from __future__ import annotations
import math
import struct
from ...contracts import AudioChunk, AudioFrameStats, Capabilities, HealthStatus, Modality
from .base import VADProvider
class EnergyVAD(VADProvider):
name = "energy"
def __init__(self, *, rms_threshold: float = 0.012) -> None:
self._thr = rms_threshold
@property
def capabilities(self) -> Capabilities:
return Capabilities(modality=Modality.AUDIO)
async def health(self) -> HealthStatus:
return HealthStatus(ok=True, detail="energy-vad (fallback,非生产用)")
async def process(self, frame: AudioChunk) -> AudioFrameStats:
pcm = frame.data
n = len(pcm) // 2
if n == 0:
return AudioFrameStats(ts=frame.ts, rms=0.0, peak=0.0)
samples = struct.unpack(f"<{n}h", pcm[: n * 2])
peak = max(abs(s) for s in samples) / 32768.0
rms = math.sqrt(sum(s * s for s in samples) / n) / 32768.0
return AudioFrameStats(
ts=frame.ts, rms=rms, peak=peak, is_speech=rms > self._thr
)
async def stream(self, audio): # type: ignore[no-untyped-def]
async for frame in audio:
yield await self.process(frame)
async def reset(self) -> None:
return None