commit 159a721ff0ab01417963bbb7b23badb440e7e54e Author: Volkor The Barbarian Warrior Date: Thu Nov 17 15:56:07 2022 +1100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1efb83d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SFX/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b62d26c --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# VolkorSoundBoard (VSB) + +VSB is a cool python-based, OSC-enabled vrchat soundboard. +It uses a avatars radial menu to trigger .wav files to play via a audio device, mostly voicemeeter. + +## Software setup + +0. Ensure python 3.10 (or older!!) is installed, and you have the requirements installed. `pip install -r requirements.txt` +1. In the SFX folder, create a folder for the avatarid, for example `avtr_3ec08725-aa6c-42ec-b411-a1466f69fb61`, and then move the audio files in. +2. Ensure that the files are named properly, with proper numbering at the start of the filename. +3. Ensure that the files are in WAV format. I don't think the sound library supports playing anything else (and I haven't added it working) +4. Edit the config.toml to match your settings. + +## Avatar setup + +1. Create a submenu with the corresponding `osc-parameter` settings triggered by a button. + 1. TODO: add a photo or something to show my example +2. Enable OSC in game (or test with [AV3Emulator!](https://github.com/lyuma/Av3Emulator)) +3. Upload avatar (or play mode if AV3Emu) and run the button, and see if it is registered in the program. + +## Python 3.11 or newer + +We use the tomli library for toml, but python 3.11 and newer has tomllib built into python, so you'll have to change the import and references. + +## Stuff I learned making this + +- How OSC works! (osc4py3 is neat, especially multithreading stuff) +- signals library for clean ctrl+c exiting +- playing audio on windows from python (sounddevice, soundfile) +- reading toml files from python. + +## File naming + +Files MUST be named correctly. `1. `. +The number in the filename corresponds to the number in the parameter menu in VRC. +Files MUST be in .wav format. Seriously. I'm not requiring ffmpeg for a small size reduction. diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..08a57d6 --- /dev/null +++ b/config.toml @@ -0,0 +1,13 @@ +[networking] +# Change me to talk to a different pc (although idk how you're piping audio to another pc but nice) +ip = "127.0.0.1" +port = 9001 + +[audio] +# Sets the default playback device, use `python -m sounddevice` to list all devices, and select the one you want by setting below. Moron. +# You'd want to find the VoiceMeeter (MME) ones. +# You can also use a name for this, it'll match text. +audio-device = "VoiceMeeter Aux Input (VB-Audio" + +[vrchat] +osc-parameter = "OSC-VoiceLine" \ No newline at end of file diff --git a/osc-soundboard.py b/osc-soundboard.py new file mode 100644 index 0000000..f0cfcf2 --- /dev/null +++ b/osc-soundboard.py @@ -0,0 +1,72 @@ +import time +from osc4py3.as_comthreads import * +from osc4py3 import oscmethod as osm +import sounddevice as sd +import soundfile as sf +import glob +from pathlib import Path +import tomli +import signal + +# README + +# TODO +## 1. set up persistence and have different folders for different avid's +## will need to listen for '/avatar/change' messages, adding that into the play_voiceline thing. + +# Setup config stuffs +with open("config.toml", mode="rb") as fp: + config = tomli.load(fp) +osc_parameter = "/avatar/parameters/" + config["vrchat"]["osc-parameter"] + +sd.default.device = config["audio"]["audio-device"] + +def get_voiceline_path(VoiceLine): + # Returns the FIRST result from finding "." + print(f"[VSB] Searching for file: {VoiceLine}") + # Calculates the file to search for. + search_path = Path('./SFX')/ f'{VoiceLine}.*.wav' + print(f"[VSB] Searching for path: {search_path}") + # Do the search and save the output + file = glob.glob(str(search_path)) + return file[0] + +def play_voiceline(VoiceLine, VoiceLinePath): + # Load selected file into ram. (I imagine this will be GC'd at some point) + print(f"[VSB] Loading Voiceline: ({VoiceLine}) {VoiceLinePath}") + data, samplerate = sf.read(VoiceLinePath) + sd.play(data, samplerate) + print(f"[VSB] Playing Voiceline: ({VoiceLine}) {VoiceLinePath}") + sd.wait() # This forces the program to wait until playback is finished!!! + print(f"[VSB] Finished Voiceline: ({VoiceLine}) {VoiceLinePath}") + +# Only called when voiceline detected. +def main(VoiceLine): + print(f"[VSB] Received message: {VoiceLine}") + VoiceLinePath = get_voiceline_path(VoiceLine) + play_voiceline(VoiceLine, VoiceLinePath) + +# Startup the server (this is a function because im shit at coding) +def init_osc(): + osc_startup() + osc_udp_server(config["networking"]["ip"], config["networking"]["port"], "VolkorSoundBoard") + osc_method(osc_parameter, main) + +def exit_handler(signum, frame): + print(f"[VSB] Exiting, thanks for flying VSB Airlines!") + osc_terminate() + exit(0) + +# Start the system. +print(f"[VSB] Initializing...") +signal.signal(signal.SIGINT, exit_handler) +init_osc() + +# Periodically call osc4py3 processing method in your event loop. +finished = False +slep = 0 +killtime = 100 +while not finished: + osc_process() + time.sleep(0.1) # Stops it from using 10% cpu on my 5800x (delays processing by ~50ms) + slep = slep + 1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..57319ce --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +sounddevice +soundfile +osc4py3 \ No newline at end of file