From 97dcd16492b4ce85138988461a85f6694fd7b264 Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 25 Feb 2023 19:49:02 -0800 Subject: Drop ryml Rapidyaml started refusing to parse config files so I dropped it. * Add ConfigMarshal clas to support very simple config marshalling * No versioning, no type indicators, nothing. * Supports int, bool, and string. * Bool are serialized as int. * Log no longer segfaults if given nullptr wxTextCtrl*. * Fix how whisper CPP GUI fields restore from config --- Designs/config_parser_design.md | 55 ++++++++++ GUI/GUI/GUI/Config.cpp | 199 +++++++++++++++++----------------- GUI/GUI/GUI/Config.h | 18 +++- GUI/GUI/GUI/ConfigMarshal.h | 131 +++++++++++++++++++++++ GUI/GUI/GUI/Frame.cpp | 231 ++++++++++++++++++++-------------------- GUI/GUI/GUI/Frame.h | 3 +- GUI/GUI/GUI/GUI.vcxproj | 2 +- GUI/GUI/GUI/GUI.vcxproj.filters | 6 +- GUI/GUI/GUI/Logging.cpp | 7 +- GUI/GUI/GUI/WhisperCPP.cpp | 4 +- GUI/Libraries/fetch.ps1 | 16 --- 11 files changed, 423 insertions(+), 249 deletions(-) create mode 100644 Designs/config_parser_design.md create mode 100644 GUI/GUI/GUI/ConfigMarshal.h diff --git a/Designs/config_parser_design.md b/Designs/config_parser_design.md new file mode 100644 index 0000000..84d160a --- /dev/null +++ b/Designs/config_parser_design.md @@ -0,0 +1,55 @@ +RYML has decided to stop working and I don't want to spend any more time trying +to figure out why. + +Let's make a parser that implements the small subset of YAML that +Config.{h,cpp} rely on. + +Config needs to serialize the following types: + +* std::string +* bool +* int + +Bool will be serialized like ints, with additional requirements on +deserialization. + +Serialization looks like this: +``` +ConfigMarshall cm(out_); +cm.Append("microphone", microphone_); // string +cm.Append("rows", rows_); // int +cm.Append("use_cpu", use_cpu_); // bool +cm.Write("config.yml"); +``` + +Deserialization looks like this: +``` +ConfigMarshall cm(out_); +cm.Load("config.yml"); +cm.Get("microphone", microphone_); // string +cm.Get("rows", rows_); // int +cm.Get("use_cpu", use_cpu_); // bool +``` + +Interface: +``` +class ConfigMarshal { +public: + ConfigMarshall(wxTextCtrl *out); + + bool Save(const std::filesystem::path& path); + bool Load(const std::filesystem::path& path); + + template + bool Append(const std::string& key, const T& value); + + template + bool Get(const std::string& key, T& value); + +private: + std::map kv_str_; + std::map kv_int_; + std::map kv_bool_; +} +``` + diff --git a/GUI/GUI/GUI/Config.cpp b/GUI/GUI/GUI/Config.cpp index 1a5f6de..6ef8bbd 100644 --- a/GUI/GUI/GUI/Config.cpp +++ b/GUI/GUI/GUI/Config.cpp @@ -4,45 +4,49 @@ #include #endif -#define RYML_SINGLE_HDR_DEFINE_NOW -#include "ryml.h" - #include "Config.h" +#include "ConfigMarshal.h" +#include "Logging.h" #include #include #include +using ::Logging::Log; + bool Config::Serialize(const std::filesystem::path& path, - const ryml::Tree* const t) { + const ConfigMarshal& cm) { + // If there's an old config, delete it. + struct stat tmpstat; + if (stat(path.string().c_str(), &tmpstat) == 0) { + if (::_unlink(path.string().c_str())) { + Log(out_, "Failed to delete old config at {}: {}\n", + path.string().c_str(), strerror(errno)); + return false; + } + } // Write the config to a tmp file. If we crash in the middle of this, it // doesn't matter, since the next process will just overwrite it. std::filesystem::path tmp_path = path; - tmp_path += ".tmp"; - FILE* fp = fopen(tmp_path.string().c_str(), "wb"); - if (!fp) { - wxLogError("Failed to open %s: %s", path.string().c_str(), strerror(errno)); - return false; - } - ryml::emit_yaml(t, fp); // For now we assume this didn't fail. - fclose(fp); - fp = nullptr; - // If there's an old config, delete it. - struct stat tmpstat; - if (stat(path.string().c_str(), &tmpstat) == 0) { - if (::_unlink(path.string().c_str())) { - wxLogError("Failed to delete old config at %s: %s", path.string().c_str(), - strerror(errno)); + if (stat(tmp_path.string().c_str(), &tmpstat) == 0) { + if (::_unlink(tmp_path.string().c_str())) { + Log(out_, "Failed to delete old tmp config at {}: {}\n", + tmp_path.string().c_str(), strerror(errno)); return false; } } + if (!cm.Save(tmp_path)) { + Log(out_, "Failed to save config to {}\n", tmp_path.string()); + return false; + } + // File renames within the same filesystem are atomic, so there's no risk // of leaving a corrupt file on disk. if (rename(tmp_path.string().c_str(), path.string().c_str()) != 0) { - wxLogError("Failed to save config to %s: %s", path.string().c_str(), + Log(out_, "Failed to save config to {}: {}\n", path.string().c_str(), strerror(errno)); return false; } @@ -51,24 +55,14 @@ bool Config::Serialize(const std::filesystem::path& path, } bool Config::Deserialize(const std::filesystem::path& path, - ryml::Tree* t) { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - return false; - } - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector yaml_buf(size); - if (!file.read(yaml_buf.data(), size)) { - return false; - } - - *t = ryml::parse_in_place(ryml::to_substr(yaml_buf.data())); - return true; + ConfigMarshal& cm) { + return cm.Load(path); } -AppConfig::AppConfig() - : microphone("index"), +AppConfig::AppConfig(wxTextCtrl *out) + : Config(out), + + microphone("index"), language("english"), model("base.en"), button("left joystick"), @@ -99,84 +93,83 @@ AppConfig::AppConfig() {} bool AppConfig::Serialize(const std::filesystem::path& path) { - ryml::Tree t; - ryml::NodeRef root = t.rootref(); - root |= ryml::MAP; - root["microphone"] << ryml::to_substr(microphone); - root["language"] << ryml::to_substr(language); - root["model"] << ryml::to_substr(model); - root["button"] << ryml::to_substr(button); - root["window_duration"] << ryml::to_substr(window_duration); - - root["enable_local_beep"] << enable_local_beep; - root["use_cpu"] << use_cpu; - root["use_builtin"] << use_builtin; - - root["chars_per_sync"] << chars_per_sync; - root["bytes_per_char"] << bytes_per_char; - root["rows"] << rows; - root["cols"] << cols; - - root["assets_path"] << ryml::to_substr(assets_path); - root["fx_path"] << ryml::to_substr(fx_path); - root["params_path"] << ryml::to_substr(params_path); - root["menu_path"] << ryml::to_substr(menu_path); - root["clear_osc"] << clear_osc; - - root["whisper_model"] << whisper_model; - root["whisper_mic"] << whisper_mic; - - root["browser_src_port"] << browser_src_port; - root["whisper_enable_builtin"] << whisper_enable_builtin; - root["whisper_enable_custom"] << whisper_enable_custom; - root["whisper_enable_browser_src"] << whisper_enable_browser_src; - - return Config::Serialize(path, &t); + ConfigMarshal cm(out_); + + cm.Set("microphone", microphone); + cm.Set("language", language); + cm.Set("model", model); + cm.Set("button", button); + cm.Set("window_duration", window_duration); + + cm.Set("enable_local_beep", enable_local_beep); + cm.Set("use_cpu", use_cpu); + cm.Set("use_builtin", use_builtin); + + cm.Set("chars_per_sync", chars_per_sync); + cm.Set("bytes_per_char", bytes_per_char); + cm.Set("rows", rows); + cm.Set("cols", cols); + + cm.Set("assets_path", assets_path); + cm.Set("fx_path", fx_path); + cm.Set("params_path", params_path); + cm.Set("menu_path", menu_path); + cm.Set("clear_osc", clear_osc); + + cm.Set("whisper_model", whisper_model); + cm.Set("whisper_mic", whisper_mic); + + cm.Set("browser_src_port", browser_src_port); + cm.Set("whisper_enable_builtin", whisper_enable_builtin); + cm.Set("whisper_enable_custom", whisper_enable_custom); + cm.Set("whisper_enable_browser_src", whisper_enable_browser_src); + + return Config::Serialize(path, cm); } bool AppConfig::Deserialize(const std::filesystem::path& path) { std::error_code err; if (!std::filesystem::exists(path, err)) { - *this = AppConfig(); + *this = AppConfig(out_); + Log(out_, "Cannot deserialize config at path {}: Does not exist!\n", path.string()); return true; } - ryml::Tree t{}; - if (!Config::Deserialize(path, &t)) { - wxLogError("Deserialization failed at %s", path.string()); + ConfigMarshal cm(out_); + if (!Config::Deserialize(path, cm)) { + Log(out_, "Deserialization failed at {}\n", path.string()); return false; } - ryml::ConstNodeRef root = t.rootref(); - AppConfig c; - root.get_if("microphone", &c.microphone); - root.get_if("language", &c.language); - root.get_if("model", &c.model); - root.get_if("button", &c.button); - root.get_if("window_duration", &c.window_duration); - - root.get_if("enable_local_beep", &c.enable_local_beep); - root.get_if("use_cpu", &c.use_cpu); - root.get_if("use_builtin", &c.use_builtin); - - root.get_if("chars_per_sync", &c.chars_per_sync); - root.get_if("bytes_per_char", &c.bytes_per_char); - root.get_if("rows", &c.rows); - root.get_if("cols", &c.cols); - - root.get_if("assets_path", &c.assets_path); - root.get_if("fx_path", &c.fx_path); - root.get_if("params_path", &c.params_path); - root.get_if("menu_path", &c.menu_path); - root.get_if("clear_osc", &c.clear_osc); - - root.get_if("whisper_model", &c.whisper_model); - root.get_if("whisper_mic", &c.whisper_mic); - - root.get_if("browser_src_port", &c.browser_src_port); - root.get_if("whisper_enable_builtin", &c.whisper_enable_builtin); - root.get_if("whisper_enable_custom", &c.whisper_enable_custom); - root.get_if("whisper_enable_browser_src", &c.whisper_enable_browser_src); + AppConfig c(out_); + cm.Get("microphone", c.microphone); + cm.Get("language", c.language); + cm.Get("model", c.model); + cm.Get("button", c.button); + cm.Get("window_duration", c.window_duration); + + cm.Get("enable_local_beep", c.enable_local_beep); + cm.Get("use_cpu", c.use_cpu); + cm.Get("use_builtin", c.use_builtin); + + cm.Get("chars_per_sync", c.chars_per_sync); + cm.Get("bytes_per_char", c.bytes_per_char); + cm.Get("rows", c.rows); + cm.Get("cols", c.cols); + + cm.Get("assets_path", c.assets_path); + cm.Get("fx_path", c.fx_path); + cm.Get("params_path", c.params_path); + cm.Get("menu_path", c.menu_path); + cm.Get("clear_osc", c.clear_osc); + + cm.Get("whisper_model", c.whisper_model); + cm.Get("whisper_mic", c.whisper_mic); + + cm.Get("browser_src_port", c.browser_src_port); + cm.Get("whisper_enable_builtin", c.whisper_enable_builtin); + cm.Get("whisper_enable_custom", c.whisper_enable_custom); + cm.Get("whisper_enable_browser_src", c.whisper_enable_browser_src); *this = std::move(c); return true; diff --git a/GUI/GUI/GUI/Config.h b/GUI/GUI/GUI/Config.h index bf5bfb0..14dcc58 100644 --- a/GUI/GUI/GUI/Config.h +++ b/GUI/GUI/GUI/Config.h @@ -1,6 +1,12 @@ #pragma once -#include "ryml.h" +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include "ConfigMarshal.h" #include @@ -8,6 +14,8 @@ // (Serialize) and restore from disk (Deserialize). class Config { public: + Config(wxTextCtrl* out) : out_(out) {} + virtual ~Config() {} virtual bool Serialize(const std::filesystem::path& path) = 0; @@ -16,10 +24,12 @@ public: protected: virtual bool Serialize(const std::filesystem::path& path, - const ryml::Tree* t); + const ConfigMarshal& cm); virtual bool Deserialize(const std::filesystem::path& path, - ryml::Tree* t); + ConfigMarshal& cm); + + wxTextCtrl* out_; }; // Represents the configurable fields for the GUI. Used by both the @@ -28,7 +38,7 @@ class AppConfig : public Config { public: virtual ~AppConfig() {} - AppConfig(); + AppConfig(wxTextCtrl* out); bool Serialize(const std::filesystem::path& path) override; diff --git a/GUI/GUI/GUI/ConfigMarshal.h b/GUI/GUI/GUI/ConfigMarshal.h new file mode 100644 index 0000000..a2f17f9 --- /dev/null +++ b/GUI/GUI/GUI/ConfigMarshal.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include "Logging.h" + +#include +#include +#include +#include +#include +#include + +class ConfigMarshal +{ +public: + ConfigMarshal(wxTextCtrl* const out) + : out_(out) + {} + + bool Save(const std::filesystem::path& path) const { + std::ostringstream oss; + for (const auto& [k, v] : kv_str_) { + oss << k << ": " << v << std::endl; + } + for (const auto& [k, v] : kv_int_) { + oss << k << ": " << std::to_string(v) << std::endl; + } + + std::ofstream ofs(path.string()); + ofs << oss.str(); + ofs.close(); + + return true; + } + + bool Load(const std::filesystem::path& path) { + std::ifstream ifs(path.string()); + std::string line; + while (std::getline(ifs, line)) { + int n_words = 0; + + std::string delim = ": "; + size_t delim_pos = line.find(delim, 0); + if (delim_pos == std::string::npos) { + Logging::Log(out_, "Invalid config file: line {} has no delimiter\n", line); + return false; + } + std::string key = line.substr(0, delim_pos); + std::string val = line.substr(delim_pos + delim.length()); + + try { + size_t pos; + int val_i = std::stoi(val, &pos); + if (pos == val.length()) { + // The entire value is an int -> interpret as an int. Corollary: users + // can't store ints as strings! + kv_int_[key] = val_i; + continue; + } + } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} + + kv_str_[key] = val; + } + return true; + } + + template + bool Set(const std::string& key, const T& value) { + if constexpr (std::is_same_v) { + kv_str_[key] = value; + return true; + } + if constexpr (std::is_same_v || std::is_same_v) { + kv_int_[key] = static_cast(value); + return true; + } + Logging::Log(out_, "ConfigMarshal unsupported type: {}\n", typeid(T).name()); + return false; + } + + template + bool Get(const std::string& key, T& value) const { + if constexpr (std::is_same_v) { + auto iter = kv_str_.find(key); + if (iter == kv_str_.end()) { + // Edge case: string may be represented entirely as an int, so + // it was parsed out as an int. + auto iter = kv_int_.find(key); + if (iter == kv_int_.end()) { + Logging::Log(out_, "Config contains no field named `{}`\n", key); + return false; + } + value = std::to_string(iter->second); + return true; + } + value = iter->second; + return true; + } + if constexpr (std::is_same_v || std::is_same_v) { + auto iter = kv_int_.find(key); + if (iter == kv_int_.end()) { + Logging::Log(out_, "Config contains no field named `{}`\n", key); + return false; + } + if constexpr (std::is_same_v) { + if (iter->second < 0 || iter->second > 1) { + Logging::Log(out_, "Config field {} is out of boolean range: {}\n", key, iter->second); + return false; + } + } + value = static_cast(iter->second); + return true; + } + Logging::Log(out_, "ConfigMarshal unsupported type: {}\n", typeid(T).name()); + return false; + } + + +private: + wxTextCtrl* out_; + + std::map kv_str_; + std::map kv_int_; +}; diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp index 01b3140..de99bdc 100644 --- a/GUI/GUI/GUI/Frame.cpp +++ b/GUI/GUI/GUI/Frame.cpp @@ -82,7 +82,6 @@ namespace { ID_WHISPER_BUTTON, ID_WHISPER_ROWS, ID_WHISPER_COLS, - ID_WHISPER_WINDOW_DURATION, ID_WHISPER_BROWSER_SRC_PORT, ID_WHISPER_ENABLE_LOCAL_BEEP, ID_WHISPER_USE_CPU, @@ -331,7 +330,7 @@ Frame::Frame() env_proc_(nullptr), py_app_drain_(this, ID_PY_APP_DRAIN) { - app_c_.Deserialize(AppConfig::kConfigPath); + app_c_ = std::make_unique(nullptr); auto* main_panel = new wxPanel(this, ID_MAIN_PANEL); main_panel_ = main_panel; @@ -340,12 +339,12 @@ Frame::Frame() { auto* navbar_button_transcribe = new wxButton(navbar, ID_NAVBAR_BUTTON_TRANSCRIBE, "Transcription (PY)"); + auto* navbar_button_whisper = new wxButton(navbar, + ID_NAVBAR_BUTTON_WHISPER, "Transcription (CPP)"); auto* navbar_button_unity = new wxButton(navbar, ID_NAVBAR_BUTTON_UNITY, "Unity"); auto* navbar_button_debug = new wxButton(navbar, ID_NAVBAR_BUTTON_DEBUG, "Debug"); - auto* navbar_button_whisper = new wxButton(navbar, - ID_NAVBAR_BUTTON_WHISPER, "Transcription (CPP)"); auto* sizer = new wxBoxSizer(wxVERTICAL); navbar->SetSizer(sizer); @@ -455,14 +454,14 @@ Frame::Frame() py_app_button_ = py_app_button; auto* py_app_rows = new wxTextCtrl(py_app_config_panel_pairs, - ID_PY_APP_ROWS, std::to_string(app_c_.rows), + ID_PY_APP_ROWS, std::to_string(app_c_->rows), wxDefaultPosition, wxDefaultSize, /*style=*/0); py_app_rows->SetToolTip( "The number of rows on the text box."); py_app_rows_ = py_app_rows; auto* py_app_cols = new wxTextCtrl(py_app_config_panel_pairs, - ID_PY_APP_COLS, std::to_string(app_c_.cols), + ID_PY_APP_COLS, std::to_string(app_c_->cols), wxDefaultPosition, wxDefaultSize, /*style=*/0); py_app_cols->SetToolTip( "The number of columns on the text box."); @@ -470,7 +469,7 @@ Frame::Frame() auto* py_app_window_duration = new wxTextCtrl( py_app_config_panel_pairs, ID_PY_APP_WINDOW_DURATION, - app_c_.window_duration, wxDefaultPosition, + app_c_->window_duration, wxDefaultPosition, wxDefaultSize, /*style=*/0); py_app_window_duration->SetToolTip( "This controls how long the slice of audio that " @@ -532,7 +531,7 @@ Frame::Frame() auto* py_app_enable_local_beep = new wxCheckBox(py_config_panel, ID_PY_APP_ENABLE_LOCAL_BEEP, "Enable local beep"); - py_app_enable_local_beep->SetValue(app_c_.enable_local_beep); + py_app_enable_local_beep->SetValue(app_c_->enable_local_beep); py_app_enable_local_beep->SetToolTip( "By default, TaSTT will play a sound (audible only to " "you) when it begins transcription and when it stops. " @@ -542,7 +541,7 @@ Frame::Frame() auto* py_app_use_cpu = new wxCheckBox(py_config_panel, ID_PY_APP_USE_CPU, "Use CPU"); - py_app_use_cpu->SetValue(app_c_.use_cpu); + py_app_use_cpu->SetValue(app_c_->use_cpu); py_app_use_cpu->SetToolTip( "If checked, the transcription engine will run on your " "CPU instead of your GPU. This is typically much slower " @@ -553,7 +552,7 @@ Frame::Frame() auto* py_app_use_builtin = new wxCheckBox(py_config_panel, ID_PY_APP_USE_BUILTIN, "Use built-in chatbox"); - py_app_use_builtin->SetValue(app_c_.use_builtin); + py_app_use_builtin->SetValue(app_c_->use_builtin); py_app_use_builtin->SetToolTip( "If checked, text will be sent to the built-in text box " "instead of one attached to the current avatar." @@ -613,7 +612,7 @@ Frame::Frame() auto* unity_assets_file_picker = new wxDirPickerCtrl( unity_config_panel_pairs, ID_UNITY_ASSETS_FILE_PICKER, - /*path=*/app_c_.assets_path, + /*path=*/app_c_->assets_path, /*message=*/"Unity Assets folder" ); unity_assets_file_picker->SetToolTip( @@ -625,7 +624,7 @@ Frame::Frame() auto* unity_animator_file_picker = new wxFilePickerCtrl( unity_config_panel_pairs, ID_UNITY_ANIMATOR_FILE_PICKER, - /*path=*/app_c_.fx_path, + /*path=*/app_c_->fx_path, /*message=*/"FX controller path", /*wildcard=*/wxFileSelectorDefaultWildcardStr, /*pos=*/wxDefaultPosition, @@ -640,7 +639,7 @@ Frame::Frame() auto* unity_parameters_file_picker = new wxFilePickerCtrl( unity_config_panel_pairs, ID_UNITY_PARAMETERS_FILE_PICKER, - /*path=*/app_c_.params_path, + /*path=*/app_c_->params_path, /*message=*/"Avatar parameters path", /*wildcard=*/wxFileSelectorDefaultWildcardStr, /*pos=*/wxDefaultPosition, @@ -656,7 +655,7 @@ Frame::Frame() auto* unity_menu_file_picker = new wxFilePickerCtrl( unity_config_panel_pairs, ID_UNITY_MENU_FILE_PICKER, - /*path=*/app_c_.menu_path, + /*path=*/app_c_->menu_path, /*message=*/"Avatar menu path", /*wildcard=*/wxFileSelectorDefaultWildcardStr, /*pos=*/wxDefaultPosition, @@ -745,14 +744,14 @@ Frame::Frame() unity_bytes_per_char_ = unity_bytes_per_char; auto* unity_rows = new wxTextCtrl(unity_config_panel_pairs, - ID_UNITY_ROWS, std::to_string(app_c_.rows), + ID_UNITY_ROWS, std::to_string(app_c_->rows), wxDefaultPosition, wxDefaultSize, /*style=*/0); unity_rows->SetToolTip( "The number of rows on the text box."); unity_rows_ = unity_rows; auto* unity_cols = new wxTextCtrl(unity_config_panel_pairs, - ID_UNITY_COLS, std::to_string(app_c_.cols), + ID_UNITY_COLS, std::to_string(app_c_->cols), wxDefaultPosition, wxDefaultSize, /*style=*/0); unity_cols->SetToolTip( "The number of columns on the text box."); @@ -816,7 +815,7 @@ Frame::Frame() auto* clear_osc = new wxCheckBox(unity_config_panel, ID_UNITY_CLEAR_OSC, "Clear OSC configs"); - clear_osc->SetValue(app_c_.clear_osc); + clear_osc->SetValue(app_c_->clear_osc); clear_osc->SetToolTip( "If checked, VRChat's OSC configs will be cleared. " "VRC SDK has a bug where parameters added to an " @@ -925,35 +924,22 @@ Frame::Frame() whisper_button_ = whisper_button; auto* whisper_rows = new wxTextCtrl(whisper_config_panel_pairs, - ID_WHISPER_ROWS, std::to_string(app_c_.rows), + ID_WHISPER_ROWS, std::to_string(app_c_->rows), wxDefaultPosition, wxDefaultSize, /*style=*/0); whisper_rows->SetToolTip( "The number of rows on the text box."); whisper_rows_ = whisper_rows; auto* whisper_cols = new wxTextCtrl(whisper_config_panel_pairs, - ID_WHISPER_COLS, std::to_string(app_c_.cols), + ID_WHISPER_COLS, std::to_string(app_c_->cols), wxDefaultPosition, wxDefaultSize, /*style=*/0); whisper_cols->SetToolTip( "The number of columns on the text box."); whisper_cols_ = whisper_cols; - auto* whisper_window_duration = new wxTextCtrl( - whisper_config_panel_pairs, ID_WHISPER_WINDOW_DURATION, - app_c_.window_duration, wxDefaultPosition, - wxDefaultSize, /*style=*/0); - whisper_window_duration->SetToolTip( - "This controls how long the slice of audio that " - "we feed the transcription algorithm is, in seconds. " - "Shorter values (as low as 10 seconds) can be whisperd " - "more quickly, but are less accurate. Longer values " - "(as high as 28 seconds) take longer to whisper, " - "but are far more accurate."); - whisper_window_duration_ = whisper_window_duration; - auto* whisper_browser_src_port = new wxTextCtrl( whisper_config_panel_pairs, ID_WHISPER_BROWSER_SRC_PORT, - std::to_string(app_c_.browser_src_port), wxDefaultPosition, + std::to_string(app_c_->browser_src_port), wxDefaultPosition, wxDefaultSize, /*style=*/0); whisper_browser_src_port->SetToolTip( "This is the port that the browser source is hosted " @@ -1004,11 +990,6 @@ Frame::Frame() sizer->Add(whisper_cols, /*proportion=*/0, /*flags=*/wxEXPAND); - sizer->Add(new wxStaticText(whisper_config_panel_pairs, - wxID_ANY, /*label=*/"Window duration (s):")); - sizer->Add(whisper_window_duration, /*proportion=*/0, - /*flags=*/wxEXPAND); - sizer->Add(new wxStaticText(whisper_config_panel_pairs, wxID_ANY, /*label=*/"Browser source port:")); sizer->Add(whisper_browser_src_port, /*proportion=*/0, @@ -1017,7 +998,7 @@ Frame::Frame() auto* whisper_enable_local_beep = new wxCheckBox(whisper_config_panel, ID_WHISPER_ENABLE_LOCAL_BEEP, "Enable local beep"); - whisper_enable_local_beep->SetValue(app_c_.enable_local_beep); + whisper_enable_local_beep->SetValue(app_c_->enable_local_beep); whisper_enable_local_beep->SetToolTip( "By default, TaSTT will play a sound (audible only to " "you) when it begins transcription and when it stops. " @@ -1027,7 +1008,7 @@ Frame::Frame() auto* whisper_use_cpu = new wxCheckBox(whisper_config_panel, ID_WHISPER_USE_CPU, "Use CPU"); - whisper_use_cpu->SetValue(app_c_.use_cpu); + whisper_use_cpu->SetValue(app_c_->use_cpu); whisper_use_cpu->SetToolTip( "If checked, the transcription engine will run on your " "CPU instead of your GPU. This is typically much slower " @@ -1038,7 +1019,7 @@ Frame::Frame() auto* whisper_enable_builtin = new wxCheckBox(whisper_config_panel, ID_WHISPER_ENABLE_BUILTIN, "Send to built-in chatbox"); - whisper_enable_builtin->SetValue(app_c_.whisper_enable_builtin); + whisper_enable_builtin->SetValue(app_c_->whisper_enable_builtin); whisper_enable_builtin->SetToolTip( "If checked, text will be sent to the built-in text box." ); @@ -1046,7 +1027,7 @@ Frame::Frame() auto* whisper_enable_custom = new wxCheckBox(whisper_config_panel, ID_WHISPER_ENABLE_CUSTOM, "Send to custom chatbox"); - whisper_enable_custom->SetValue(app_c_.whisper_enable_custom); + whisper_enable_custom->SetValue(app_c_->whisper_enable_custom); whisper_enable_custom->SetToolTip( "If checked, text will be sent to the custom text box." ); @@ -1054,7 +1035,7 @@ Frame::Frame() auto* whisper_enable_browser_src = new wxCheckBox(whisper_config_panel, ID_WHISPER_ENABLE_BROWSER_SRC, "Send to browser source"); - whisper_enable_browser_src->SetValue(app_c_.whisper_enable_browser_src); + whisper_enable_browser_src->SetValue(app_c_->whisper_enable_browser_src); whisper_enable_browser_src->SetToolTip( "If checked, text will be sent to a browser source. If " "you're not using TaSTT to stream, you can ignore this option."); @@ -1199,6 +1180,11 @@ Frame::Frame() sizer->Add(whisper_panel, /*proportion=*/1, /*flags=*/wxEXPAND); } + // Now that transcribe_out_ has been created, we can deserialize. + app_c_ = std::make_unique(transcribe_out_); + Log(transcribe_out_, "Deserializing config\n"); + app_c_->Deserialize(AppConfig::kConfigPath); + Bind(wxEVT_CLOSE_WINDOW, &Frame::OnExit, this, wxID_EXIT); Bind(wxEVT_BUTTON, &Frame::OnNavbarTranscribe, this, ID_NAVBAR_BUTTON_TRANSCRIBE); @@ -1251,47 +1237,47 @@ void Frame::ApplyConfigToInputFields() // Transcription panel auto* py_app_mic = static_cast(FindWindowById(ID_PY_APP_MIC)); int mic_idx = GetDropdownChoiceIndex(kMicChoices, - kNumMicChoices, app_c_.microphone, kMicDefault); + kNumMicChoices, app_c_->microphone, kMicDefault); py_app_mic->SetSelection(mic_idx); auto* py_app_lang = static_cast(FindWindowById(ID_PY_APP_LANG)); int lang_idx = GetDropdownChoiceIndex(kLangChoices, - kNumLangChoices, app_c_.language, kLangDefault); + kNumLangChoices, app_c_->language, kLangDefault); py_app_lang->SetSelection(lang_idx); auto* py_app_model = static_cast(FindWindowById(ID_PY_APP_MODEL)); int model_idx = GetDropdownChoiceIndex(kModelChoices, - kNumModelChoices, app_c_.model, kModelDefault); + kNumModelChoices, app_c_->model, kModelDefault); py_app_model->SetSelection(model_idx); auto* py_app_button = static_cast(FindWindowById(ID_PY_APP_BUTTON)); int button_idx = GetDropdownChoiceIndex(kButton, - kNumButtons, app_c_.button, kButtonDefault); + kNumButtons, app_c_->button, kButtonDefault); py_app_button->SetSelection(button_idx); auto* py_app_chars_per_sync = static_cast(FindWindowById(ID_PY_APP_CHARS_PER_SYNC)); int chars_idx = GetDropdownChoiceIndex(kCharsPerSync, - kNumCharsPerSync, std::to_string(app_c_.chars_per_sync), + kNumCharsPerSync, std::to_string(app_c_->chars_per_sync), kCharsDefault); py_app_chars_per_sync->SetSelection(chars_idx); auto* py_app_bytes_per_char = static_cast(FindWindowById(ID_PY_APP_BYTES_PER_CHAR)); int bytes_idx = GetDropdownChoiceIndex(kBytesPerChar, - kNumBytesPerChar, std::to_string(app_c_.bytes_per_char), + kNumBytesPerChar, std::to_string(app_c_->bytes_per_char), kBytesDefault); py_app_bytes_per_char->SetSelection(bytes_idx); auto* py_app_rows = static_cast(FindWindowById(ID_PY_APP_ROWS)); py_app_rows->Clear(); - py_app_rows->AppendText(std::to_string(app_c_.rows)); + py_app_rows->AppendText(std::to_string(app_c_->rows)); auto* py_app_cols = static_cast(FindWindowById(ID_PY_APP_COLS)); py_app_cols->Clear(); - py_app_cols->AppendText(std::to_string(app_c_.cols)); + py_app_cols->AppendText(std::to_string(app_c_->cols)); // Whisper panel auto* whisper_mic = static_cast(FindWindowById(ID_WHISPER_MIC)); - int whisper_mic_idx = app_c_.whisper_mic; + int whisper_mic_idx = app_c_->whisper_mic; whisper_mic->SetSelection(whisper_mic_idx); auto* whisper_lang = static_cast(FindWindowById(ID_WHISPER_LANG)); @@ -1299,7 +1285,7 @@ void Frame::ApplyConfigToInputFields() auto* whisper_model = static_cast(FindWindowById(ID_WHISPER_MODEL)); int whisper_model_idx = GetDropdownChoiceIndex(kWhisperModelChoices, - kNumWhisperModelChoices, app_c_.whisper_model, kWhisperModelDefault); + kNumWhisperModelChoices, app_c_->whisper_model, kWhisperModelDefault); whisper_model->SetSelection(whisper_model_idx); auto* whisper_button = static_cast(FindWindowById(ID_WHISPER_BUTTON)); @@ -1313,11 +1299,30 @@ void Frame::ApplyConfigToInputFields() auto* whisper_rows = static_cast(FindWindowById(ID_WHISPER_ROWS)); whisper_rows->Clear(); - whisper_rows->AppendText(std::to_string(app_c_.rows)); + whisper_rows->AppendText(std::to_string(app_c_->rows)); auto* whisper_cols = static_cast(FindWindowById(ID_WHISPER_COLS)); whisper_cols->Clear(); - whisper_cols->AppendText(std::to_string(app_c_.cols)); + whisper_cols->AppendText(std::to_string(app_c_->cols)); + + auto* whisper_browser_src_port = static_cast(FindWindowById(ID_WHISPER_BROWSER_SRC_PORT)); + whisper_browser_src_port->Clear(); + whisper_browser_src_port->AppendText(std::to_string(app_c_->browser_src_port)); + + auto* whisper_enable_local_beep = static_cast(FindWindowById(ID_WHISPER_ENABLE_LOCAL_BEEP)); + whisper_enable_local_beep->SetValue(app_c_->enable_local_beep); + + auto* whisper_use_cpu = static_cast(FindWindowById(ID_WHISPER_USE_CPU)); + whisper_use_cpu->SetValue(app_c_->use_cpu); + + auto* whisper_enable_builtin = static_cast(FindWindowById(ID_WHISPER_ENABLE_BUILTIN)); + whisper_enable_builtin->SetValue(app_c_->whisper_enable_builtin); + + auto* whisper_enable_custom = static_cast(FindWindowById(ID_WHISPER_ENABLE_CUSTOM)); + whisper_enable_custom->SetValue(app_c_->whisper_enable_custom); + + auto* whisper_enable_browser_src = static_cast(FindWindowById(ID_WHISPER_ENABLE_BROWSER_SRC)); + whisper_enable_browser_src->SetValue(app_c_->whisper_enable_browser_src); // Unity panel auto* unity_chars_per_sync = static_cast(FindWindowById(ID_UNITY_CHARS_PER_SYNC)); @@ -1328,11 +1333,11 @@ void Frame::ApplyConfigToInputFields() auto* unity_rows = static_cast(FindWindowById(ID_UNITY_ROWS)); unity_rows->Clear(); - unity_rows->AppendText(std::to_string(app_c_.rows)); + unity_rows->AppendText(std::to_string(app_c_->rows)); auto* unity_cols = static_cast(FindWindowById(ID_UNITY_COLS)); unity_cols->Clear(); - unity_cols->AppendText(std::to_string(app_c_.cols)); + unity_cols->AppendText(std::to_string(app_c_->cols)); } void Frame::PopulateDynamicInputFields() @@ -1555,20 +1560,20 @@ void Frame::OnGenerateFX(wxCommandEvent& event) return; } - app_c_.assets_path = unity_assets_path.string(); - app_c_.fx_path = unity_animator_path.string(); - app_c_.params_path = unity_parameters_path.string(); - app_c_.menu_path = unity_menu_path.string(); - app_c_.bytes_per_char = bytes_per_char; - app_c_.chars_per_sync = chars_per_sync; - app_c_.rows = rows; - app_c_.cols = cols; - app_c_.clear_osc = unity_clear_osc_->GetValue(); - app_c_.Serialize(AppConfig::kConfigPath); + app_c_->assets_path = unity_assets_path.string(); + app_c_->fx_path = unity_animator_path.string(); + app_c_->params_path = unity_parameters_path.string(); + app_c_->menu_path = unity_menu_path.string(); + app_c_->bytes_per_char = bytes_per_char; + app_c_->chars_per_sync = chars_per_sync; + app_c_->rows = rows; + app_c_->cols = cols; + app_c_->clear_osc = unity_clear_osc_->GetValue(); + app_c_->Serialize(AppConfig::kConfigPath); std::string out; if (!PythonWrapper::GenerateAnimator( - app_c_, + *app_c_, unity_animator_generated_dir, unity_animator_generated_name, unity_parameters_generated_name, @@ -1860,26 +1865,26 @@ void Frame::OnAppStart(wxCommandEvent& event) { return; } - app_c_.microphone = kMicChoices[which_mic].ToStdString(); - app_c_.language = kLangChoices[which_lang].ToStdString(); - app_c_.model = kModelChoices[which_model].ToStdString(); - app_c_.chars_per_sync = chars_per_sync; - app_c_.bytes_per_char = bytes_per_char; - app_c_.button = kButton[button_idx].ToStdString(); - app_c_.rows = rows; - app_c_.cols = cols; - app_c_.window_duration = std::to_string(window_duration); - app_c_.enable_local_beep = enable_local_beep; - app_c_.use_cpu = use_cpu; - app_c_.use_builtin = use_builtin; - app_c_.Serialize(AppConfig::kConfigPath); + app_c_->microphone = kMicChoices[which_mic].ToStdString(); + app_c_->language = kLangChoices[which_lang].ToStdString(); + app_c_->model = kModelChoices[which_model].ToStdString(); + app_c_->chars_per_sync = chars_per_sync; + app_c_->bytes_per_char = bytes_per_char; + app_c_->button = kButton[button_idx].ToStdString(); + app_c_->rows = rows; + app_c_->cols = cols; + app_c_->window_duration = std::to_string(window_duration); + app_c_->enable_local_beep = enable_local_beep; + app_c_->use_cpu = use_cpu; + app_c_->use_builtin = use_builtin; + app_c_->Serialize(AppConfig::kConfigPath); auto cb = [&](wxProcess* proc, int ret) -> void { Log(transcribe_out_, "Transcription engine exited with code {}\n", ret); DrainAsyncOutput(proc, transcribe_out_); return; }; - wxProcess* p = PythonWrapper::StartApp(std::move(cb), app_c_); + wxProcess* p = PythonWrapper::StartApp(std::move(cb), *app_c_); if (!p) { Log(transcribe_out_, "Failed to launch transcription engine\n"); return; @@ -1967,46 +1972,37 @@ void Frame::OnWhisperStart(wxCommandEvent& event) { kCharsPerSync[chars_per_sync_idx].ToStdString(); std::string bytes_per_char_str = kBytesPerChar[bytes_per_char_idx].ToStdString(); - std::string window_duration_str = - whisper_window_duration_->GetValue().ToStdString(); std::string browser_src_port_str = whisper_browser_src_port_->GetValue().ToStdString(); - int rows, cols, chars_per_sync, bytes_per_char, window_duration, browser_src_port; + int rows, cols, chars_per_sync, bytes_per_char, browser_src_port; try { rows = std::stoi(rows_str); cols = std::stoi(cols_str); chars_per_sync = std::stoi(chars_per_sync_str); bytes_per_char = std::stoi(bytes_per_char_str); - window_duration = std::stoi(window_duration_str); browser_src_port = std::stoi(browser_src_port_str); } catch (const std::invalid_argument&) { Log(whisper_out_, "Could not parse rows \"{}\", cols \"{}\", chars " - "per sync \"{}\", bytes per char \"{}\" or window duration \"{}\" " + "per sync \"{}\", or bytes per char \"{}\" " "as an integer\n", rows_str, cols_str, chars_per_sync_str, - bytes_per_char_str, window_duration_str); + bytes_per_char_str); return; } catch (const std::out_of_range&) { Log(whisper_out_, "Rows \"{}\", cols \"{}\", chars per sync " - "\"{}\", bytes per char \"{}\" or window duration \"{}\" are out " + "\"{}\", or bytes per char \"{}\" are out " "of range\n", rows_str, cols_str, chars_per_sync_str, - bytes_per_char_str, window_duration_str); + bytes_per_char_str); return; } const int max_rows = 10; const int max_cols = 240; - const int min_window_duration_s = 10; - const int max_window_duration_s = 28; if (rows < 0 || rows > max_rows || - cols < 0 || cols > max_cols || - window_duration < min_window_duration_s || - window_duration > max_window_duration_s) { - Log(whisper_out_, "Rows not on [{},{}] or cols not on [{},{}] or " - "window_duration not on [{},{}]\n", + cols < 0 || cols > max_cols) { + Log(whisper_out_, "Rows not on [{},{}] or cols not on [{},{}]\n", 0, max_rows, - 0, max_cols, - min_window_duration_s, max_window_duration_s); + 0, max_cols); return; } @@ -2018,27 +2014,26 @@ void Frame::OnWhisperStart(wxCommandEvent& event) { return; } - app_c_.whisper_mic = which_mic; - app_c_.language = kLangChoices[which_lang].ToStdString(); - app_c_.whisper_model = kWhisperModelChoices[which_model].ToStdString(); - app_c_.chars_per_sync = chars_per_sync; - app_c_.bytes_per_char = bytes_per_char; - app_c_.button = kButton[button_idx].ToStdString(); - app_c_.rows = rows; - app_c_.cols = cols; - app_c_.window_duration = std::to_string(window_duration); - app_c_.enable_local_beep = enable_local_beep; - app_c_.use_cpu = use_cpu; - app_c_.browser_src_port = browser_src_port; - app_c_.whisper_enable_browser_src = whisper_enable_browser_src_->GetValue(); - app_c_.whisper_enable_builtin = whisper_enable_builtin_->GetValue(); - app_c_.whisper_enable_custom = whisper_enable_custom_->GetValue(); - app_c_.Serialize(AppConfig::kConfigPath); - - whisper_->Start(app_c_); + app_c_->whisper_mic = which_mic; + app_c_->language = kLangChoices[which_lang].ToStdString(); + app_c_->whisper_model = kWhisperModelChoices[which_model].ToStdString(); + app_c_->chars_per_sync = chars_per_sync; + app_c_->bytes_per_char = bytes_per_char; + app_c_->button = kButton[button_idx].ToStdString(); + app_c_->rows = rows; + app_c_->cols = cols; + app_c_->enable_local_beep = enable_local_beep; + app_c_->use_cpu = use_cpu; + app_c_->browser_src_port = browser_src_port; + app_c_->whisper_enable_browser_src = whisper_enable_browser_src_->GetValue(); + app_c_->whisper_enable_builtin = whisper_enable_builtin_->GetValue(); + app_c_->whisper_enable_custom = whisper_enable_custom_->GetValue(); + app_c_->Serialize(AppConfig::kConfigPath); + + whisper_->Start(*app_c_); if (whisper_enable_browser_src_->GetValue()) { Log(whisper_out_, "Frame launching browser src\n"); - whisper_->StartBrowserSource(app_c_); + whisper_->StartBrowserSource(*app_c_); } } diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h index 7da65a3..4aa6a72 100644 --- a/GUI/GUI/GUI/Frame.h +++ b/GUI/GUI/GUI/Frame.h @@ -43,7 +43,6 @@ private: wxTextCtrl* unity_cols_; wxTextCtrl* whisper_rows_; wxTextCtrl* whisper_cols_; - wxTextCtrl* whisper_window_duration_; wxTextCtrl* whisper_browser_src_port_; wxDirPickerCtrl* unity_assets_file_picker_; @@ -84,7 +83,7 @@ private: wxProcess* whisper_app_; wxTimer whisper_app_drain_; - AppConfig app_c_; + std::unique_ptr app_c_; std::unique_ptr whisper_; diff --git a/GUI/GUI/GUI/GUI.vcxproj b/GUI/GUI/GUI/GUI.vcxproj index f683c4a..6976c31 100644 --- a/GUI/GUI/GUI/GUI.vcxproj +++ b/GUI/GUI/GUI/GUI.vcxproj @@ -167,13 +167,13 @@ + - diff --git a/GUI/GUI/GUI/GUI.vcxproj.filters b/GUI/GUI/GUI/GUI.vcxproj.filters index 49b2a85..6a41329 100644 --- a/GUI/GUI/GUI/GUI.vcxproj.filters +++ b/GUI/GUI/GUI/GUI.vcxproj.filters @@ -74,9 +74,6 @@ Header Files - - Header Files - Header Files @@ -104,6 +101,9 @@ WebServer + + Header Files + diff --git a/GUI/GUI/GUI/Logging.cpp b/GUI/GUI/GUI/Logging.cpp index f799e31..5741341 100644 --- a/GUI/GUI/GUI/Logging.cpp +++ b/GUI/GUI/GUI/Logging.cpp @@ -35,7 +35,12 @@ void Logging::ThreadLogger::Drain() std::ofstream log_ofs("Resources/log.txt", std::ios_base::app); for (const auto& [frame, messages] : messages_) { for (const auto& message : messages) { - frame->AppendText(message); + if (frame) { + frame->AppendText(message); + } + else { + wxLogError("%s", message); + } log_ofs << message; } } diff --git a/GUI/GUI/GUI/WhisperCPP.cpp b/GUI/GUI/GUI/WhisperCPP.cpp index 6028e26..a82dc59 100644 --- a/GUI/GUI/GUI/WhisperCPP.cpp +++ b/GUI/GUI/GUI/WhisperCPP.cpp @@ -327,6 +327,7 @@ void WhisperCPP::Start(const AppConfig& c) { const sSegment* const segments = results->getSegments(); const sToken* const tokens = results->getTokens(); const int s0 = length.countSegments - n_new; + int n_tok = 0; for (int i = s0; i < length.countSegments; i++) { const sSegment& seg = segments[i]; bool is_metadata = false; @@ -350,11 +351,12 @@ void WhisperCPP::Start(const AppConfig& c) { if (word_iter != banned_words.end()) { continue; } + ++n_tok; Log(app->out_, "{}", tok.text); app->transcript_.Append(tok.text); } } - if (n_new) { + if (n_tok) { Log(app->out_, "\n"); } diff --git a/GUI/Libraries/fetch.ps1 b/GUI/Libraries/fetch.ps1 index 22b088b..ac9ccd5 100644 --- a/GUI/Libraries/fetch.ps1 +++ b/GUI/Libraries/fetch.ps1 @@ -31,22 +31,6 @@ if (-Not (Test-Path wx)) { popd > $null } -# RAPIDYAML -if ((Test-Path rapidyaml) -And ($overwrite)) { - rm -Recurse rapidyaml -} - -if (-Not (Test-Path rapidyaml)) { - git clone https://github.com/biojppm/rapidyaml - pushd rapidyaml > $null - git checkout v0.5.0 - git submodule update --init --recursive - - python3 tools/amalgamate.py ryml.h - cp ryml.h ../../GUI/GUI/ryml.h - popd -} - if ((Test-Path whisper) -And ($overwrite)) { rm -Recurse whisper } -- cgit v1.2.3