Skip to content

Commit

Permalink
TTS/GUI/Config: Improvements
Browse files Browse the repository at this point in the history
Too much time was spent on templates :)

- New cooldown type: Per Command
- Allow mixing cooldown types by bitmask
- Per-user session persistent voices
  • Loading branch information
DatCaptainHorse committed Mar 26, 2024
1 parent bb5b2ba commit 25c2e65
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 150 deletions.
69 changes: 43 additions & 26 deletions Source/commands.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module;
#include <map>
#include <string>
#include <ranges>
#include <chrono>
#include <functional>
#include <filesystem>

Expand All @@ -29,11 +30,12 @@ constexpr std::array ttsTestMessages = {
export struct Command {
bool enabled;
std::string callstr, description;
std::function<void(const std::string &)> func;
std::function<void(const TwitchChatMessage &)> func;
std::chrono::time_point<std::chrono::steady_clock> lastExecuted;

Command() = default;
Command(const std::string &call, const std::string &desc,
const std::function<void(const std::string &)> &f)
const std::function<void(const TwitchChatMessage &)> &f)
: enabled(true), callstr(call), description(desc), func(f) {}
};

Expand All @@ -49,13 +51,17 @@ public:
-> Result {
if (m_commandsMap.empty()) {
m_commandsMap["text_to_speech"] =
Command("tts", "TTS Notification", [](const std::string &msg) {
const std::string notifMsg = msg;
TTSHandler::voiceString(notifMsg);
Command("tts", "TTS Notification", [](const TwitchChatMessage &msg) {
const std::string notifMsg = msg.get_message();
auto speakerID = -1;
if (global_users.contains(msg.user))
speakerID = global_users[msg.user]->userVoice;

TTSHandler::voiceString(notifMsg, speakerID);
});
m_commandsMap["custom_notification"] =
Command("cc", "Custom Notification", [launch_notification](const std::string &msg) {
std::string notifMsg = msg;
m_commandsMap["custom_notification"] = Command(
"cc", "Custom Notification", [launch_notification](const TwitchChatMessage &msg) {
std::string notifMsg = msg.get_message();
// Split to words (space-separated)
const auto words = split_string(notifMsg, " ");

Expand All @@ -81,8 +87,7 @@ public:
}
}
// Play easter egg sounds
if (!sounds.empty())
AudioPlayer::play_sequential(sounds);
if (!sounds.empty()) AudioPlayer::play_sequential(sounds);

launch_notification(notifMsg);
});
Expand All @@ -97,12 +102,10 @@ public:
// Tests a command with random message from testMessages
static void test_command(const std::string &command) {
// If the command does not exist, skip
if (!m_commandsMap.contains(command))
return;
if (!m_commandsMap.contains(command)) return;

// Make sure the command is enabled
if (!m_commandsMap[command].enabled)
return;
if (!m_commandsMap[command].enabled) return;

std::string testMsg;
if (command == "text_to_speech")
Expand All @@ -120,7 +123,15 @@ public:
}

// Execute the command with the test message
execute_command(command, testMsg);
execute_command(command, TwitchChatMessage("testUser", testMsg));
}

// Returns key for command with given call string
static auto get_command_key(const std::string &call) -> std::string {
for (const auto &[key, cmd] : m_commandsMap)
if (cmd.callstr == call) return key;

return "";
}

// Returns a non-modifiable command map
Expand All @@ -131,37 +142,43 @@ public:
// Sets whether command is enabled
static void set_command_enabled(const std::string &key, const bool enabled) {
// If the key does not exist, skip
if (!m_commandsMap.contains(key))
return;
if (!m_commandsMap.contains(key)) return;

m_commandsMap[key].enabled = enabled;
}

// Method for changing the call string of a command
static void change_command_call(const std::string &key, const std::string &newCall) {
// If the key does not exist, skip
if (!m_commandsMap.contains(key))
return;
if (!m_commandsMap.contains(key)) return;

// Make sure the new call is not empty
if (newCall.empty())
return;
if (newCall.empty()) return;

// Change the callstr
m_commandsMap[key].callstr = newCall;
}

// Method for executing a command
static void execute_command(const std::string &key, const std::string &msg) {
static void execute_command(const std::string &key, const TwitchChatMessage &msg) {
// If the command does not exist, skip
if (!m_commandsMap.contains(key))
return;
if (!m_commandsMap.contains(key)) return;

// Make sure the command is enabled
if (!m_commandsMap[key].enabled)
return;
if (!m_commandsMap[key].enabled) return;

// Execute the command
m_commandsMap[key].lastExecuted = std::chrono::steady_clock::now();
m_commandsMap[key].func(msg);
}

// Method for returning time when command was last executed
static auto get_last_executed_time(const std::string &key)
-> std::chrono::time_point<std::chrono::steady_clock> {
// If the key does not exist, return epoch
if (!m_commandsMap.contains(key))
return std::chrono::time_point<std::chrono::steady_clock>();

return m_commandsMap[key].lastExecuted;
}
};
183 changes: 183 additions & 0 deletions Source/common.cppm
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module;

#include <map>
#include <array>
#include <tuple>
#include <utility>
#include <vector>
#include <ranges>
#include <string>
#include <random>
#include <format>
#include <algorithm>
#include <type_traits>

export module common;

Expand Down Expand Up @@ -122,3 +127,181 @@ export struct Result {

explicit operator bool() const { return code == 0; }
};

// Template method for converting string to integer
export template <typename T>
requires std::is_integral_v<T>
constexpr auto integral_from_string(const std::string &str) -> T {
if constexpr (std::same_as<T, std::int8_t>)
return std::stoi(str);
else if constexpr (std::same_as<T, std::int16_t>)
return std::stoi(str);
else if constexpr (std::same_as<T, std::int32_t>)
return std::stoi(str);
else if constexpr (std::same_as<T, std::int64_t>)
return std::stoll(str);
else if constexpr (std::same_as<T, std::uint8_t>)
return std::stoul(str);
else if constexpr (std::same_as<T, std::uint16_t>)
return std::stoul(str);
else if constexpr (std::same_as<T, std::uint32_t>)
return std::stoul(str);
else if constexpr (std::same_as<T, std::uint64_t>)
return std::stoull(str);
else
static_assert(false, "Unsupported type for integral_from_string");
}

// Tempalte method for converting string to floating point
export template <typename T>
requires std::is_floating_point_v<T>
constexpr auto floating_from_string(const std::string &str) -> T {
if constexpr (std::same_as<T, float>)
return std::stof(str);
else if constexpr (std::same_as<T, double>)
return std::stod(str);
else if constexpr (std::same_as<T, long double>)
return std::stold(str);
else
static_assert(false, "Unsupported type for floating_from_string");
}

// Stringable concept for checking if a type is convertible to a string
export template <typename T>
concept Stringable =
std::same_as<T, bool> || std::is_integral_v<T> || std::is_floating_point_v<T> ||
std::same_as<T, std::string> || std::is_integral_v<std::underlying_type_t<T>> ||
std::is_floating_point_v<std::underlying_type_t<T>>;

// Template method for converting a string to a specific type
// takes care of calling proper conversion
export template <typename T>
requires Stringable<T>
constexpr auto t_from_string(const std::string &str) -> T {
if constexpr (std::same_as<T, bool>)
return str == "true";
else if constexpr (std::same_as<T, std::string>)
return std::string(str);
else if constexpr (std::is_floating_point_v<T>)
return floating_from_string<T>(str);
else if constexpr (std::is_integral_v<T>)
return integral_from_string<T>(str);
else if constexpr (std::is_integral_v<std::underlying_type_t<T>>)
return static_cast<T>(integral_from_string<std::underlying_type_t<T>>(str));
else if constexpr (std::is_floating_point_v<std::underlying_type_t<T>>)
return static_cast<T>(floating_from_string<std::underlying_type_t<T>>(str));
else
static_assert(false, "Unsupported type for t_from_string");
}

// Template method for converting a specific type to a string
// takes care of calling proper conversion
export template <typename T>
requires Stringable<T>
constexpr auto t_to_string(const T &value) -> std::string {
if constexpr (std::same_as<T, bool>)
return value ? "true" : "false";
else if constexpr (std::same_as<T, std::string>)
return value;
else if constexpr (std::is_floating_point_v<T>)
return std::to_string(value);
else if constexpr (std::is_integral_v<T>)
return std::to_string(value);
else if constexpr (std::is_integral_v<std::underlying_type_t<T>>)
return std::to_string(static_cast<std::underlying_type_t<T>>(value));
else if constexpr (std::is_floating_point_v<std::underlying_type_t<T>>)
return std::to_string(static_cast<std::underlying_type_t<T>>(value));
else
static_assert(false, "Unsupported type for t_to_string");
}

// Templates to make enum classes work as bitmasks
// <3 https://voithos.io/articles/enum-class-bitmasks/ <3
export template <typename E>
struct FEnableBitmaskOperators {
static constexpr bool enable = false;
};
export template <typename E>
concept EnableBitmaskOperators = FEnableBitmaskOperators<E>::enable;

export template <typename E>
requires EnableBitmaskOperators<E>
constexpr auto operator|(E l, E r) -> E {
return static_cast<E>(static_cast<std::underlying_type_t<E>>(l) |
static_cast<std::underlying_type_t<E>>(r));
}
export template <typename E>
requires EnableBitmaskOperators<E>
constexpr auto operator&(E l, E r) -> bool {
return (static_cast<std::underlying_type_t<E>>(l) &
static_cast<std::underlying_type_t<E>>(r)) != 0;
}
export template <typename E>
requires EnableBitmaskOperators<E>
constexpr auto operator^(E l, E r) -> E {
return static_cast<E>(static_cast<std::underlying_type_t<E>>(l) ^
static_cast<std::underlying_type_t<E>>(r));
}
export template <typename E>
requires EnableBitmaskOperators<E>
constexpr auto operator~(E e) -> E {
return static_cast<E>(~static_cast<std::underlying_type_t<E>>(e));
}
export template <typename E>
requires EnableBitmaskOperators<E>
auto operator|=(E &l, E r) -> E & {
return l = l | r;
}
export template <typename E>
requires EnableBitmaskOperators<E>
auto operator&=(E &l, E r) -> E & {
return l = l & r;
}
export template <typename E>
requires EnableBitmaskOperators<E>
auto operator^=(E &l, E r) -> E & {
return l = l ^ r;
}
// operator* which gets the underlying value of the enum
export template <typename E>
requires EnableBitmaskOperators<E>
constexpr auto operator*(E &e) -> std::underlying_type_t<E> & {
return reinterpret_cast<std::underlying_type_t<E> &>(e);
}

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

TwitchChatMessage(std::string user, std::string message)
: user(std::move(user)), message(std::move(message)),
time(std::chrono::steady_clock::now()) {}

[[nodiscard]] auto is_command() const -> bool { return message.starts_with('!'); }
[[nodiscard]] auto get_command() const -> std::string {
return get_string_between(message, "!", " ");
}

[[nodiscard]] auto get_message() const -> std::string {
return message.substr(message.find(' ') + 1);
}
};

// Struct for holding user data
// TODO: Move to general module
export struct TwitchUser {
std::string name;
bool bypassCooldown = false;
TwitchChatMessage lastMessage;
int 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;
Loading

0 comments on commit 25c2e65

Please sign in to comment.