refactor: replace porcupine wake-word with vosk grammar-constrained recognizer
uses a second KaldiRecognizer with a JSON grammar of WAKE_WORDS + [unk] for the wake phase, then hands off to the full-vocab recognizer for the 10s command window. drops the pvporcupine dependency entirely.
This commit is contained in:
parent
8019ee0baa
commit
a4b919c11e
2 changed files with 35 additions and 27 deletions
10
config.py
10
config.py
|
|
@ -6,10 +6,15 @@ load_dotenv("dev.env")
|
||||||
|
|
||||||
# Конфигурация
|
# Конфигурация
|
||||||
VA_NAME = 'Jarvis'
|
VA_NAME = 'Jarvis'
|
||||||
VA_VER = "0.1.0"
|
VA_VER = "0.2.0"
|
||||||
VA_ALIAS = ('джарвис',)
|
VA_ALIAS = ('джарвис',)
|
||||||
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
|
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
|
||||||
|
|
||||||
|
# Wake-words распознаются Vosk'ом с grammar-constraint. Добавляйте сюда
|
||||||
|
# варианты написания — Vosk с русской моделью может расшифровать "jarvis"
|
||||||
|
# как "джарвис", "жарвис" и т.п.
|
||||||
|
WAKE_WORDS = ('jarvis', 'джарвис')
|
||||||
|
|
||||||
# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
|
# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
|
||||||
# -1 это стандартное записывающее устройство
|
# -1 это стандартное записывающее устройство
|
||||||
MICROPHONE_INDEX = -1
|
MICROPHONE_INDEX = -1
|
||||||
|
|
@ -17,9 +22,6 @@ MICROPHONE_INDEX = -1
|
||||||
# Путь к браузеру Google Chrome
|
# Путь к браузеру Google Chrome
|
||||||
CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
|
CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
|
||||||
|
|
||||||
# Токен Picovoice
|
|
||||||
PICOVOICE_TOKEN = os.getenv('PICOVOICE_TOKEN')
|
|
||||||
|
|
||||||
# Токен Groq
|
# Токен Groq
|
||||||
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
||||||
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
||||||
|
|
|
||||||
46
main.py
46
main.py
|
|
@ -11,7 +11,6 @@ from ctypes import POINTER, cast
|
||||||
|
|
||||||
import openai
|
import openai
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
import pvporcupine
|
|
||||||
import simpleaudio as sa
|
import simpleaudio as sa
|
||||||
import vosk
|
import vosk
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -39,18 +38,14 @@ message_log = [system_message]
|
||||||
|
|
||||||
client = OpenAI(api_key=config.GROQ_TOKEN, base_url=config.GROQ_BASE_URL)
|
client = OpenAI(api_key=config.GROQ_TOKEN, base_url=config.GROQ_BASE_URL)
|
||||||
|
|
||||||
# PORCUPINE
|
|
||||||
porcupine = pvporcupine.create(
|
|
||||||
access_key=config.PICOVOICE_TOKEN,
|
|
||||||
keywords=['jarvis'],
|
|
||||||
sensitivities=[1]
|
|
||||||
)
|
|
||||||
# print(pvporcupine.KEYWORDS)
|
|
||||||
|
|
||||||
# VOSK
|
|
||||||
model = vosk.Model("model_small")
|
model = vosk.Model("model_small")
|
||||||
samplerate = 16000
|
samplerate = 16000
|
||||||
device = config.MICROPHONE_INDEX
|
device = config.MICROPHONE_INDEX
|
||||||
|
|
||||||
|
# wake-word phase uses a grammar-constrained recognizer so only WAKE_WORDS
|
||||||
|
# (plus [unk] filler) can match — command phase needs full vocab, hence two.
|
||||||
|
wake_grammar = json.dumps(list(config.WAKE_WORDS) + ["[unk]"], ensure_ascii=False)
|
||||||
|
wake_rec = vosk.KaldiRecognizer(model, samplerate, wake_grammar)
|
||||||
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
|
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
|
||||||
q = queue.Queue()
|
q = queue.Queue()
|
||||||
|
|
||||||
|
|
@ -268,32 +263,41 @@ def execute_cmd(cmd: str, voice: str):
|
||||||
|
|
||||||
elif cmd == 'off':
|
elif cmd == 'off':
|
||||||
play("off", True)
|
play("off", True)
|
||||||
|
|
||||||
porcupine.delete()
|
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
# `-1` is the default input audio device.
|
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
|
||||||
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=porcupine.frame_length)
|
|
||||||
recorder.start()
|
recorder.start()
|
||||||
print('Using device: %s' % recorder.selected_device)
|
print('Using device: %s' % recorder.selected_device)
|
||||||
|
|
||||||
print(f"Jarvis (v0.1.0) начал свою работу ...")
|
print(f"Jarvis (v{config.VA_VER}) начал свою работу ...")
|
||||||
play("run")
|
play("run")
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
ltc = time.time() - 1000
|
|
||||||
|
def heard_wake_word(text: str) -> bool:
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
lowered = text.lower()
|
||||||
|
return any(w in lowered for w in config.WAKE_WORDS)
|
||||||
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
pcm = recorder.read()
|
pcm = recorder.read()
|
||||||
keyword_index = porcupine.process(pcm)
|
sp = struct.pack("h" * len(pcm), *pcm)
|
||||||
|
|
||||||
|
if not wake_rec.AcceptWaveform(sp):
|
||||||
|
continue
|
||||||
|
|
||||||
|
result_text = json.loads(wake_rec.Result()).get("text", "")
|
||||||
|
if not heard_wake_word(result_text):
|
||||||
|
continue
|
||||||
|
|
||||||
if keyword_index >= 0:
|
|
||||||
recorder.stop()
|
|
||||||
play("greet", True)
|
play("greet", True)
|
||||||
print("Yes, sir.")
|
print("Yes, sir.")
|
||||||
recorder.start() # prevent self recording
|
# reset the command recognizer so stale audio doesn't bleed into it
|
||||||
|
kaldi_rec.Reset()
|
||||||
ltc = time.time()
|
ltc = time.time()
|
||||||
|
|
||||||
while time.time() - ltc <= 10:
|
while time.time() - ltc <= 10:
|
||||||
|
|
@ -306,6 +310,8 @@ while True:
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
wake_rec.Reset()
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"Unexpected {err=}, {type(err)=}")
|
print(f"Unexpected {err=}, {type(err)=}")
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue