Skip to content

Commit

Permalink
Project: Improve scripting, improvements and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
DatCaptainHorse committed Aug 19, 2024
1 parent eece969 commit efd9bef
Show file tree
Hide file tree
Showing 12 changed files with 315 additions and 207 deletions.
2 changes: 1 addition & 1 deletion Scripts/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ def on_load():
chatnotifier.play_oneshot_file(str(sound_path))

def on_message(msg):
print("Hello from Python: {}".format(msg))
print("Hello from Python: {} - {}".format(msg.user, msg.message))
62 changes: 47 additions & 15 deletions Scripts/tts.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,58 @@
from piper import PiperVoice
from Deps.piper import PiperVoice
import numpy as np
import pathlib
import random

# List of available voices
voices: list[str] = []
# Cache dict of users and their "custom" voices
user_voices: dict[str, str] = {}


def on_load():
# Get all available voices
voices_path = pathlib.Path(chatnotifier.get_tts_assets_path())
for voice in voices_path.glob("*.onnx"):
voices.append(voice.stem)


def load_voice(name: str) -> tuple[str, PiperVoice] | None:
# Load given voice by name
voices_path = pathlib.Path(chatnotifier.get_tts_assets_path())
for voice in voices_path.glob("*.onnx"):
voice_name = voice.stem
# Check if file name contains name
if name.lower() not in voice_name.lower():
continue

# Config always ends with "onnx.json"
config = voices_path / f"{voice_name}.onnx.json"
if config.exists():
return voice_name, PiperVoice.load(model_path=str(voice),
config_path=str(config),
use_cuda=False)
else:
print(f"Voice config not found: {str(config)}")
return None


def get_user_voice(user: str) -> PiperVoice | None:
# Check if user has a custom voice already, if not, assign random one from loaded voices
if user not in user_voices:
voice_name = random.choice(voices)
user_voices[user] = voice_name

return load_voice(user_voices[user])[1]


def on_message(msg):
model_path = pathlib.Path(chatnotifier.get_tts_assets_path()) / "fi_FI-harri-medium.onnx"
config_path = pathlib.Path(
chatnotifier.get_tts_assets_path()) / "fi_fi_FI_harri_medium_fi_FI-harri-medium.onnx.json"
if not model_path.exists() or not config_path.exists():
print(f"Model not found: {model_path}")
# Get user voice
voice = get_user_voice(msg.user)
if voice is None:
return

print(f"Using model: {model_path}")
voice = PiperVoice.load(model_path=str(model_path),
config_path=str(config_path),
use_cuda=False)

print("Synthesizing audio")
audio_stream = voice.synthesize_stream_raw(
text=msg,
text=msg.message,
length_scale=1.0,
noise_scale=0.667,
noise_w=0.8,
Expand All @@ -35,6 +69,4 @@ def on_message(msg):
# Convert audio to list of floats
collected_audio = collected_audio.tolist()

print("Audio synthesized")
chatnotifier.play_oneshot_memory(collected_audio, voice.config.sample_rate, 1)
print("Audio played")
1 change: 1 addition & 0 deletions Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_executable(ChatNotifier
# C++20 module files
target_sources(ChatNotifier PUBLIC FILE_SET CXX_MODULES FILES
common.cppm
types.cppm
opengl.cppm
assets.cppm
commands.cppm
Expand Down
1 change: 1 addition & 0 deletions Source/commands.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module;

export module commands;

import types;
import config;
import common;
import assets;
Expand Down
153 changes: 1 addition & 152 deletions Source/common.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -286,155 +286,4 @@ export template <typename T>
requires(std::is_enum_v<T> and requires(T e) { enable_bitmask_operators(e); })
constexpr auto operator*(const T e) -> std::underlying_type_t<T> {
return std::to_underlying(e);
}

// Struct for Twitch message data
// TODO: Move to general module
export struct TwitchChatMessage {
std::string user, message, command;
std::chrono::time_point<std::chrono::steady_clock> time;
std::map<std::string, std::vector<std::string>> args;

TwitchChatMessage(std::string user, std::string message)
: user(std::move(user)), message(std::move(message)),
time(std::chrono::steady_clock::now()) {
args = get_command_args()[0];
command = get_command();
}

TwitchChatMessage(std::string user, std::string message, std::string command,
std::chrono::time_point<std::chrono::steady_clock> time,
std::map<std::string, std::vector<std::string>> groupArgs)
: user(std::move(user)), message(std::move(message)), command(std::move(command)),
time(time), args(std::move(groupArgs)) {}

// Commands can be given arguments like so
// "!cmd <arg1=value1,arg2=value2> message <arg3=value3|value4|value5> another message" etc.
// @return map of arg/values groups (each <> pair is a group)
// to another map of arg to it's values
[[nodiscard]] auto get_command_args() const
-> std::map<std::uint32_t, std::map<std::string, std::vector<std::string>>> {
// Get each argument group
auto result = std::map<std::uint32_t, std::map<std::string, std::vector<std::string>>>{};
const auto argGroups = get_strings_between(message, "<", ">");
for (std::uint32_t i = 0; i < argGroups.size(); ++i) {
for (const auto argList = split_string(argGroups[i], ","); const auto &arg : argList) {
const auto splitted = split_string(arg, "=");
if (splitted.size() != 2) continue;
if (const auto vals = split_string(splitted[1], "|"); !vals.empty())
result[i][splitted[0]] = vals;
else
result[i][splitted[0]].emplace_back(splitted[1]);
}
}
return result;
}

// A nicer way of getting command arguments
// @return optional value of the argument within specified group
template <typename T>
auto get_command_arg(const std::string &argName) const -> std::optional<T> {
if (!is_command()) return std::nullopt;
if (!args.contains(argName)) return std::nullopt;
if constexpr (std::same_as<T, std::string>)
return args.at(argName)[0];
else if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>)
return t_from_string<T>(args.at(argName)[0]);
// Vector handling and Position 2D/3D handling
else if constexpr (std::same_as<T, std::vector<std::string>>)
return args.at(argName);
else if constexpr (std::same_as<T, Position2D>) {
if (args.at(argName).size() < 2) return std::nullopt;
return Position2D{t_from_string<float>(args.at(argName)[0]),
t_from_string<float>(args.at(argName)[1])};
} else if constexpr (std::same_as<T, Position3D>) {
if (args.at(argName).size() < 2) return std::nullopt;
// Allow for 2D positions to be used as 3D
if (args.at(argName).size() == 2)
return Position3D{t_from_string<float>(args.at(argName)[0]),
t_from_string<float>(args.at(argName)[1]), 0.0f};

return Position3D{t_from_string<float>(args.at(argName)[0]),
t_from_string<float>(args.at(argName)[1]),
t_from_string<float>(args.at(argName)[2])};
}
return std::nullopt;
}

// Splits this message into multiple submessages
[[nodiscard]] auto split_into_submessages() const -> std::vector<TwitchChatMessage> {
if (!is_command()) return {*this};
if (const auto argGroups = get_strings_between(message, "<", ">"); argGroups.empty())
return {*this};
else {
auto command = get_command();
auto groups = std::vector<TwitchChatMessage>{};
for (const auto &argGroup : argGroups) {
auto groupArgs = std::map<std::string, std::vector<std::string>>{};
auto groupMsg = get_string_after(message, argGroup + ">");
groupMsg = get_string_until(groupMsg, "<");
for (const auto &arg : split_string(argGroup, ",")) {
const auto argSplit = split_string(arg, "=");
if (argSplit.size() != 2) continue;
groupArgs[argSplit[0]] = split_string(argSplit[1], "|");
}
groups.emplace_back(user, groupMsg, command, time, groupArgs);
}
return groups;
}
}

[[nodiscard]] auto is_command() const -> bool {
return message.starts_with("!") || !command.empty();
}

[[nodiscard]] auto get_command() const -> std::string {
if (!is_command()) return "";
if (command.empty()) {
if (message.starts_with("!")) {
auto command = get_string_after(message, "!");
command = get_string_until(command, " ");
command = get_string_until(command, "<");
return trim_string(command);
} else
return "";
} else
return command;
}

[[nodiscard]] auto get_message() const -> std::string {
if (!is_command()) return message;
if (const auto argGroups = get_strings_between(message, "<", ">"); argGroups.empty()) {
if (message.starts_with("!") && !command.empty())
return get_string_after(message, command);
else
return message;
} else {
// Erase all argument groups from the message, joining remaining parts by space
std::vector<std::string> messages;
for (const auto &argGroup : argGroups) {
const auto groupMsg = get_string_after(message, argGroup + ">");
messages.emplace_back(get_string_until(groupMsg, "<"));
}
return std::accumulate(
messages.begin(), messages.end(), std::string{},
[](const std::string &a, const std::string &b) { return a + " " + b; });
}
}
};

// Struct for holding user data
// TODO: Move to general module
export struct TwitchUser {
std::string name;
bool bypassCooldown = false;
TwitchChatMessage lastMessage;
std::int32_t userVoice = -1;

explicit TwitchUser(std::string name, TwitchChatMessage lastMessage)
: name(std::move(name)), lastMessage(std::move(lastMessage)) {}
};

// Cache of users
// TODO: Move to general module
export std::map<std::string, std::shared_ptr<TwitchUser>> global_users;
}
1 change: 1 addition & 0 deletions Source/gui.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module;

export module gui;

import types;
import opengl;
import config;
import common;
Expand Down
2 changes: 1 addition & 1 deletion Source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

import types;
import config;
import common;
import assets;
Expand All @@ -25,7 +26,6 @@ import commands;
import filesystem;
import scripting;

// Method for printing Result errors
void print_error(const Result &res) {
if (!res) std::println("Error: {}", res.message);
}
Expand Down
1 change: 1 addition & 0 deletions Source/notification.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module;

export module notification;

import types;
import common;
import effect;
import config;
Expand Down
Loading

0 comments on commit efd9bef

Please sign in to comment.