-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e7102a5
commit d155e27
Showing
9 changed files
with
184 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[tool.poetry] | ||
name = "stark-place" | ||
version = "1.0.2" | ||
version = "1.1.0" | ||
description = "S.T.A.R.K. Platform Library And Community Extensions" | ||
authors = ["Mark Parker <[email protected]>"] | ||
license = "CC BY-NC-SA 4.0" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
''' | ||
Requirements except stark: | ||
- pvporcupine | ||
''' | ||
|
||
import asyncer | ||
from stark import CommandsContext, CommandsManager, Response | ||
from stark.general.blockage_detector import BlockageDetector | ||
from stark.interfaces.protocols import SpeechRecognizer, SpeechSynthesizer | ||
from stark.interfaces.vosk import VoskSpeechRecognizer | ||
from stark.interfaces.silero import SileroSpeechSynthesizer | ||
from stark.voice_assistant import VoiceAssistant, Mode | ||
|
||
from stark_place.triggers import porcupine # import the trigger | ||
from stark_place.notifications import sound | ||
|
||
|
||
vosk_model_ur = 'YOUR_CHOSEN_VOSK_MODEL_URL' | ||
silero_model_ur = 'YOUR_CHOSEN_SILERO_MODEL_URL' | ||
|
||
# register at https://console.picovoice.ai and copy the access key | ||
# train and download a model for the your platform at https://console.picovoice.ai/ppn | ||
# don't rename either the model file or the keyword files | ||
access_key = 'YOUR_ACCESS_KEY' | ||
keyword_paths = ['YOUR_KEYWORD_PATH.ppn',] | ||
model_path = 'YOUR_MODEL_PATH.pv' | ||
|
||
recognizer = VoskSpeechRecognizer(model_url = vosk_model_ur) | ||
synthesizer = SileroSpeechSynthesizer(model_url = silero_model_ur) | ||
|
||
manager = CommandsManager() | ||
|
||
@manager.new('hello') | ||
async def hello_command() -> Response: | ||
text = voice = 'Hello, world!' | ||
return Response(text=text, voice=voice) | ||
|
||
async def run( | ||
manager: CommandsManager, | ||
speech_recognizer: SpeechRecognizer, | ||
speech_synthesizer: SpeechSynthesizer | ||
): | ||
async with asyncer.create_task_group() as main_task_group: | ||
context = CommandsContext( | ||
task_group = main_task_group, | ||
commands_manager = manager | ||
) | ||
voice_assistant = VoiceAssistant( | ||
speech_recognizer = speech_recognizer, | ||
speech_synthesizer = speech_synthesizer, | ||
commands_context = context | ||
) | ||
speech_recognizer.delegate = voice_assistant | ||
context.delegate = voice_assistant | ||
|
||
voice_assistant.mode = Mode.external() # stop listening after first response | ||
|
||
# trigger-listener setup | ||
|
||
def add_porcupine_listener(): | ||
# soonify returns immediately, but the wrapped function is added to the task group (main loop) | ||
# porcuping and speech recognizer use the same microphone device, so they can't run at the same time | ||
# porcupine.start runs until the first wake-word is detected and needs to be restarted after the speech recognizer is stopped | ||
def on_wake_word(): | ||
sound.play() # optional: play a sound when the wake-word is detected, check the realisation in stark_place/notifications/sound.py | ||
main_task_group.soonify(start_speech_recognizer)() | ||
|
||
main_task_group.soonify(porcupine.start)( | ||
access_key = access_key, | ||
keyword_paths = keyword_paths, | ||
model_path = model_path, | ||
callback = on_wake_word | ||
) | ||
|
||
async def start_speech_recognizer(): | ||
await speech_recognizer.start_listening() # awaits until the speech recognizer is stopped | ||
add_porcupine_listener() # start listening for the wake-word after the speech recognizer is stopped | ||
|
||
add_porcupine_listener() # start listening for the wake-word | ||
|
||
# other tasks | ||
|
||
main_task_group.soonify(context.handle_responses)() | ||
|
||
detector = BlockageDetector() | ||
main_task_group.soonify(detector.monitor)() | ||
|
||
async def main(): | ||
await run(manager, recognizer, synthesizer) | ||
|
||
if __name__ == '__main__': | ||
asyncer.runnify(main)() # or anyio.run(main), same thing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import os | ||
|
||
|
||
def play(): | ||
''' | ||
play all sounds in macos using shell: sh`for s in /System/Library/Sounds/*; do echo "$s" && afplay "$s"; done` | ||
for linux: check the `/usr/share/sounds/` directory and use `aplay` instead of `afplay` | ||
as an alternative, you can use the cross-platform system-sounds pypi library to list and play sounds | ||
pypi.org/project/system-sounds | ||
or github.com/MarkParker5/system-sounds | ||
or use the a SpeechSynthesizer to say something like "Yes, sir?" | ||
''' | ||
os.system('afplay /System/Library/Sounds/Blow.aiff &') # play the sound in the background (non-blocking, immediate return) (macos) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from typing import Any, Callable | ||
import anyio | ||
import pvporcupine | ||
from pvrecorder import PvRecorder | ||
|
||
|
||
async def start( | ||
access_key: str, | ||
keyword_paths: list[str], | ||
model_path: str, | ||
callback: Callable[[], Any | None] | ||
): | ||
# register at https://console.picovoice.ai and copy the access key | ||
# train and download a model for the your platform at https://console.picovoice.ai/ppn | ||
# don't rename either the model file or the keyword files | ||
porcupine = pvporcupine.create( | ||
access_key = access_key, | ||
keyword_paths = keyword_paths, | ||
model_path = model_path | ||
) | ||
|
||
recorder = PvRecorder( | ||
frame_length = porcupine.frame_length, | ||
device_index = 0 | ||
) | ||
recorder.start() | ||
|
||
try: | ||
while True: # this loop runs in the main thread | ||
await anyio.sleep(0.01) # release the thread for other tasks | ||
|
||
pcm = recorder.read() | ||
keyword_index = porcupine.process(pcm) | ||
|
||
if keyword_index == -1: | ||
continue | ||
|
||
callback() | ||
break # stop listening after the first keyword is detected to release the microphone device | ||
finally: | ||
# stop the recorder, delete the porcupine instances, and release the microphone device | ||
porcupine.delete() | ||
recorder.delete() |