diff --git a/include/modules/cava/cavaGLSL.hpp b/include/modules/cava/cavaGLSL.hpp new file mode 100644 index 000000000..d2632affa --- /dev/null +++ b/include/modules/cava/cavaGLSL.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "AModule.hpp" +#include "cava_backend.hpp" + +namespace waybar::modules::cava { + +class CavaGLSL final : public AModule, public Gtk::GLArea { + public: + CavaGLSL(const std::string&, const Json::Value&); + ~CavaGLSL() = default; + + private: + std::shared_ptr backend_; + struct ::cava::config_params prm_; + int frame_counter{0}; + bool silence_{false}; + bool hide_on_silence_{false}; + // Cava method + auto onUpdate(const ::cava::audio_raw& input) -> void; + auto onSilence() -> void; + // Member variable to store the shared pointer + std::shared_ptr<::cava::audio_raw> m_data_; + GLuint shaderProgram_; + // OpenGL variables + GLuint fbo_; + GLuint texture_; + GLint uniform_bars_; + GLint uniform_previous_bars_; + GLint uniform_bars_count_; + GLint uniform_time_; + // Methods + void onRealize(); + bool onRender(const Glib::RefPtr& context); + + void initShaders(); + void initSurface(); + void initGLSL(); + GLuint loadShader(const std::string& fileName, GLenum type); +}; +} // namespace waybar::modules::cava diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cavaRaw.hpp similarity index 96% rename from include/modules/cava/cava.hpp rename to include/modules/cava/cavaRaw.hpp index 6b13c4bd0..f4ceb603d 100644 --- a/include/modules/cava/cava.hpp +++ b/include/modules/cava/cavaRaw.hpp @@ -9,20 +9,20 @@ class Cava final : public ALabel, public sigc::trackable { public: Cava(const std::string&, const Json::Value&); ~Cava() = default; - auto onUpdate(const std::string& input) -> void; - auto onSilence() -> void; auto doAction(const std::string& name) -> void override; private: std::shared_ptr backend_; // Text to display - std::string label_text_{""}; + Glib::ustring label_text_{""}; + bool silence_{false}; bool hide_on_silence_{false}; std::string format_silent_{""}; int ascii_range_{0}; - bool silence_{false}; // Cava method void pause_resume(); + auto onUpdate(const std::string& input) -> void; + auto onSilence() -> void; // ModuleActionMap static inline std::map actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp index c88182b38..4022a2ff5 100644 --- a/include/modules/cava/cava_backend.hpp +++ b/include/modules/cava/cava_backend.hpp @@ -32,16 +32,22 @@ class CavaBackend final { int getAsciiRange(); void doPauseResume(); void Update(); + const struct ::cava::config_params* getPrm(); + std::chrono::milliseconds getFrameTimeMilsec(); + // Signal accessor using type_signal_update = sigc::signal; type_signal_update signal_update(); + using type_signal_audio_raw_update = sigc::signal; + type_signal_audio_raw_update signal_audio_raw_update(); using type_signal_silence = sigc::signal; type_signal_silence signal_silence(); private: CavaBackend(const Json::Value& config); - util::SleeperThread thread_; util::SleeperThread read_thread_; + util::SleeperThread out_thread_; + // Cava API to read audio source ::cava::ptr input_source_{NULL}; @@ -55,6 +61,7 @@ class CavaBackend final { // Delay to handle audio source std::chrono::milliseconds frame_time_milsec_{1s}; + const Json::Value& config_; int re_paint_{0}; bool silence_{false}; bool silence_prev_{false}; @@ -66,9 +73,12 @@ class CavaBackend final { void execute(); bool isSilence(); void doUpdate(bool force = false); + void loadConfig(); + void freeBackend(); // Signal type_signal_update m_signal_update_; + type_signal_audio_raw_update m_signal_audio_raw_; type_signal_silence m_signal_silence_; }; } // namespace waybar::modules::cava diff --git a/include/modules/cava/cava_frontend.hpp b/include/modules/cava/cava_frontend.hpp new file mode 100644 index 000000000..5a73f62fd --- /dev/null +++ b/include/modules/cava/cava_frontend.hpp @@ -0,0 +1,30 @@ +#pragma once + +#ifdef HAVE_LIBCAVA +#include "cavaRaw.hpp" +#include "cava_backend.hpp" +#ifdef HAVE_LIBCAVAGLSL +#include "cavaGLSL.hpp" +#endif +#endif + +namespace waybar::modules::cava { +AModule* getModule(const std::string& id, const Json::Value& config) { +#ifdef HAVE_LIBCAVA + const std::shared_ptr backend_{waybar::modules::cava::CavaBackend::inst(config)}; + switch (backend_->getPrm()->output) { + case ::cava::output_method::OUTPUT_RAW: + return new waybar::modules::cava::Cava(id, config); + break; +#ifdef HAVE_LIBCAVAGLSL + case ::cava::output_method::OUTPUT_SDL_GLSL: + return new waybar::modules::cava::CavaGLSL(id, config); + break; +#endif + default: + break; + } +#endif + throw std::runtime_error("Unknown module"); +}; +} // namespace waybar::modules::cava diff --git a/meson.build b/meson.build index 0675a963f..c6b7f90a4 100644 --- a/meson.build +++ b/meson.build @@ -497,16 +497,24 @@ else man_files += files('man/waybar-clock.5.scd') endif -cava = dependency('cava', - version : '>=0.10.6', +cava = dependency('libcava', + version : '>=0.10.7', required: get_option('cava'), - fallback : ['cava', 'cava_dep'], + fallback : ['libcava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') +eproxy = dependency('epoxy', required: false) + if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') + src_files += files('src/modules/cava/cavaRaw.cpp', + 'src/modules/cava/cava_backend.cpp') man_files += files('man/waybar-cava.5.scd') + + if eproxy.found() + add_project_arguments('-DHAVE_LIBCAVAGLSL', language: 'cpp') + src_files += files('src/modules/cava/cavaGLSL.cpp') + endif endif if libgps.found() @@ -554,6 +562,7 @@ executable( tz_dep, xkbregistry, cava, + eproxy, libgps ], include_directories: inc_dirs, diff --git a/src/factory.cpp b/src/factory.cpp index 7828ce750..2fd3e3b81 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -108,15 +108,13 @@ #ifdef HAVE_LIBWIREPLUMBER #include "modules/wireplumber.hpp" #endif -#ifdef HAVE_LIBCAVA -#include "modules/cava/cava.hpp" -#endif #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" #endif #ifdef HAVE_LIBGPS #include "modules/gps.hpp" #endif +#include "modules/cava/cava_frontend.hpp" #include "modules/cffi.hpp" #include "modules/custom.hpp" #include "modules/image.hpp" @@ -341,11 +339,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::Wireplumber(id, config_[name]); } #endif -#ifdef HAVE_LIBCAVA if (ref == "cava") { - return new waybar::modules::cava::Cava(id, config_[name]); + return waybar::modules::cava::getModule(id, config_[name]); } -#endif #ifdef HAVE_SYSTEMD_MONITOR if (ref == "systemd-failed-units") { return new waybar::modules::SystemdFailedUnits(id, config_[name]); diff --git a/src/modules/cava/cavaGLSL.cpp b/src/modules/cava/cavaGLSL.cpp new file mode 100644 index 000000000..4ab03bcd5 --- /dev/null +++ b/src/modules/cava/cavaGLSL.cpp @@ -0,0 +1,271 @@ +#include "modules/cava/cavaGLSL.hpp" + +#include + +#include + +waybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config) + : AModule(config, "cavaGLSL", id, false, false), + backend_{waybar::modules::cava::CavaBackend::inst(config)} { + set_name(name_); + if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); + if (!id.empty()) { + get_style_context()->add_class(id); + } + get_style_context()->add_class(MODULE_CLASS); + + set_use_es(true); + // set_auto_render(true); + signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize)); + signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false); + + // Get parameters_config struct from the backend + prm_ = *backend_->getPrm(); + + // Set widget length + int length{0}; + if (config_["min-length"].isUInt()) + length = config_["min-length"].asUInt(); + else if (config_["max-length"].isUInt()) + length = config_["max-length"].asUInt(); + else + length = prm_.sdl_width; + + set_size_request(length, prm_.sdl_height); + + // Subscribe for changes + backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate)); + // Subscribe for silence + backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence)); + event_box_.add(*this); +} + +auto waybar::modules::cava::CavaGLSL::onUpdate(const ::cava::audio_raw& input) -> void { + Glib::signal_idle().connect_once([this, input]() { + m_data_ = std::make_shared<::cava::audio_raw>(input); + if (silence_) { + get_style_context()->remove_class("silent"); + if (!get_style_context()->has_class("updated")) get_style_context()->add_class("updated"); + show(); + silence_ = false; + } + + queue_render(); + }); +} + +auto waybar::modules::cava::CavaGLSL::onSilence() -> void { + Glib::signal_idle().connect_once([this]() { + if (!silence_) { + if (get_style_context()->has_class("updated")) get_style_context()->remove_class("updated"); + + if (hide_on_silence_) hide(); + silence_ = true; + get_style_context()->add_class("silent"); + // Set clear color to black + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + queue_render(); + } + }); +} + +bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr& context) { + if (!m_data_) return true; + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_); + glUniform1i(glGetUniformLocation(shaderProgram_, "inputTexture"), 0); + + glUniform1fv(uniform_bars_, m_data_->number_of_bars, m_data_->bars_raw); + glUniform1fv(uniform_previous_bars_, m_data_->number_of_bars, m_data_->previous_bars_raw); + glUniform1i(uniform_bars_count_, m_data_->number_of_bars); + ++frame_counter; + glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3); + + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +void waybar::modules::cava::CavaGLSL::onRealize() { + make_current(); + initShaders(); + initGLSL(); + initSurface(); +} + +struct colors { + uint16_t R; + uint16_t G; + uint16_t B; +}; + +static void parse_color(char* color_string, struct colors* color) { + if (color_string[0] == '#') { + sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B); + } +} + +void waybar::modules::cava::CavaGLSL::initGLSL() { + GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, "vertexPosition_modelspace")}; + if (gVertexPos2DLocation == -1) { + spdlog::error("{0}. Could not find vertex position shader variable", name_); + } + + glClearColor(0.f, 0.f, 0.f, 1.f); + + GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; + GLint indexData[]{0, 1, 2, 3}; + + GLuint gVBO{0}; + glGenBuffers(1, &gVBO); + glBindBuffer(GL_ARRAY_BUFFER, gVBO); + glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW); + + GLuint gIBO{0}; + glGenBuffers(1, &gIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW); + + GLuint gVAO{0}; + glGenVertexArrays(1, &gVAO); + glBindVertexArray(gVAO); + glEnableVertexAttribArray(gVertexPos2DLocation); + + glBindBuffer(GL_ARRAY_BUFFER, gVBO); + glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO); + + glGenFramebuffers(1, &fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + + // Create a texture to attach the framebuffer + glGenTextures(1, &texture_); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, prm_.sdl_width, prm_.sdl_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0); + + // Check is framebuffer is complete + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + spdlog::error("{0}. Framebuffer not complete", name_); + } + + // Unbind the framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars"); + uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars"); + uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count"); + uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time"); + + GLuint err{glGetError()}; + if (err != 0) { + spdlog::error("{0}. Error on initGLSL: {1}", name_, err); + } +} + +void waybar::modules::cava::CavaGLSL::initSurface() { + colors color = {0}; + GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")}; + parse_color(prm_.bcolor, &color); + glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0, + (float)color.B / 255.0); + GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")}; + parse_color(prm_.color, &color); + glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0, + (float)color.B / 255.0); + GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")}; + glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f); + GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")}; + glUniform1i(uniform_bar_width, prm_.bar_width); + GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")}; + glUniform1i(uniform_bar_spacing, prm_.bar_spacing); + GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "gradient_count")}; + glUniform1i(uniform_gradient_count, prm_.gradient_count); + GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, "gradient_colors")}; + GLfloat gradient_colors[8][3]; + for (int i{0}; i < prm_.gradient_count; ++i) { + parse_color(prm_.gradient_colors[i], &color); + gradient_colors[i][0] = (float)color.R / 255.0; + gradient_colors[i][1] = (float)color.G / 255.0; + gradient_colors[i][2] = (float)color.B / 255.0; + } + glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); +} + +void waybar::modules::cava::CavaGLSL::initShaders() { + shaderProgram_ = glCreateProgram(); + + GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)}; + GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)}; + + glAttachShader(shaderProgram_, vertexShader); + glAttachShader(shaderProgram_, fragmentShader); + + glLinkProgram(shaderProgram_); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + // Check for linking errors + GLint success, len; + glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len); + GLchar* infoLog{(char*)'\0'}; + glGetProgramInfoLog(shaderProgram_, len, &len, infoLog); + spdlog::error("{0}. Shader linking error: {1}", name_, infoLog); + } + + glReleaseShaderCompiler(); + glUseProgram(shaderProgram_); +} + +GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) { + spdlog::debug("{0}. loadShader: {1}", name_, fileName); + + // Read shader source code from the file + std::ifstream shaderFile{fileName}; + + if (!shaderFile.is_open()) { + spdlog::error("{0}. Could not open shader file: {1}", name_, fileName); + } + + std::ostringstream buffer; + buffer << shaderFile.rdbuf(); // read file content into stringstream + std::string str{buffer.str()}; + const char* shaderSource = str.c_str(); + shaderFile.close(); + + GLuint shaderID{glCreateShader(type)}; + if (shaderID == 0) spdlog::error("{0}. Error creating shader type: {0}", type); + glShaderSource(shaderID, 1, &shaderSource, nullptr); + glCompileShader(shaderID); + + // Check for compilation errors + GLint success, len; + + glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success); + + if (!success) { + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len); + + GLchar* infoLog{(char*)'\0'}; + glGetShaderInfoLog(shaderID, len, nullptr, infoLog); + spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog); + } + + return shaderID; +} diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cavaRaw.cpp similarity index 54% rename from src/modules/cava/cava.cpp rename to src/modules/cava/cavaRaw.cpp index a2a74606e..edec902b8 100644 --- a/src/modules/cava/cava.cpp +++ b/src/modules/cava/cavaRaw.cpp @@ -1,4 +1,4 @@ -#include "modules/cava/cava.hpp" +#include "modules/cava/cavaRaw.hpp" #include @@ -24,28 +24,35 @@ auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void { // Cava actions void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { - if (silence_) { - label_.get_style_context()->remove_class("silent"); - label_.get_style_context()->add_class("updated"); - } - label_text_.clear(); - for (auto& ch : input) - label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1)); - - label_.set_markup(label_text_); - label_.show(); - ALabel::update(); + Glib::signal_idle().connect_once([this, input]() { + if (silence_) { + label_.get_style_context()->remove_class("silent"); + if (!label_.get_style_context()->has_class("updated")) + label_.get_style_context()->add_class("updated"); + } + label_text_.clear(); + for (auto& ch : input) + label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1)); + + label_.set_markup(label_text_); + label_.show(); + ALabel::update(); + }); silence_ = false; } + auto waybar::modules::cava::Cava::onSilence() -> void { - if (!silence_) { - label_.get_style_context()->remove_class("updated"); - - if (hide_on_silence_) - label_.hide(); - else if (config_["format_silent"].isString()) - label_.set_markup(format_silent_); - silence_ = true; - label_.get_style_context()->add_class("silent"); - } + Glib::signal_idle().connect_once([this]() { + if (!silence_) { + if (label_.get_style_context()->has_class("updated")) + label_.get_style_context()->remove_class("updated"); + + if (hide_on_silence_) + label_.hide(); + else if (config_["format_silent"].isString()) + label_.set_markup(format_silent_); + silence_ = true; + label_.get_style_context()->add_class("silent"); + } + }); } diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index c9dba67f9..917e165fa 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -9,91 +9,9 @@ std::shared_ptr waybar::modules::cava::CavaB return backend_ptr; } -waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { +waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) { // Load waybar module config - char cfgPath[PATH_MAX]; - cfgPath[0] = '\0'; - - if (config["cava_config"].isString()) strcpy(cfgPath, config["cava_config"].asString().data()); - // Load cava config - error_.length = 0; - - if (!load_config(cfgPath, &prm_, false, &error_, 0)) { - spdlog::error("cava backend. Error loading config. {0}", error_.message); - exit(EXIT_FAILURE); - } - - // Override cava parameters by the user config - prm_.inAtty = 0; - prm_.output = ::cava::output_method::OUTPUT_RAW; - if (prm_.data_format) free(prm_.data_format); - prm_.data_format = strdup("ascii"); - if (prm_.raw_target) free(prm_.raw_target); - prm_.raw_target = strdup("/dev/stdout"); - prm_.ascii_range = config["format-icons"].size() - 1; - - prm_.bar_width = 2; - prm_.bar_spacing = 0; - prm_.bar_height = 32; - prm_.bar_width = 1; - prm_.orientation = ::cava::ORIENT_TOP; - prm_.xaxis = ::cava::xaxis_scale::NONE; - prm_.mono_opt = ::cava::AVERAGE; - prm_.autobars = 0; - prm_.gravity = 0; - prm_.integral = 1; - - if (config["framerate"].isInt()) prm_.framerate = config["framerate"].asInt(); - // Calculate delay for Update() thread - frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate)); - if (config["autosens"].isInt()) prm_.autosens = config["autosens"].asInt(); - if (config["sensitivity"].isInt()) prm_.sens = config["sensitivity"].asInt(); - if (config["bars"].isInt()) prm_.fixedbars = config["bars"].asInt(); - if (config["lower_cutoff_freq"].isNumeric()) - prm_.lower_cut_off = config["lower_cutoff_freq"].asLargestInt(); - if (config["higher_cutoff_freq"].isNumeric()) - prm_.upper_cut_off = config["higher_cutoff_freq"].asLargestInt(); - if (config["sleep_timer"].isInt()) prm_.sleep_timer = config["sleep_timer"].asInt(); - if (config["method"].isString()) - prm_.input = ::cava::input_method_by_name(config["method"].asString().c_str()); - if (config["source"].isString()) { - if (prm_.audio_source) free(prm_.audio_source); - prm_.audio_source = config["source"].asString().data(); - } - if (config["sample_rate"].isNumeric()) prm_.samplerate = config["sample_rate"].asLargestInt(); - if (config["sample_bits"].isInt()) prm_.samplebits = config["sample_bits"].asInt(); - if (config["stereo"].isBool()) prm_.stereo = config["stereo"].asBool(); - if (config["reverse"].isBool()) prm_.reverse = config["reverse"].asBool(); - if (config["bar_delimiter"].isInt()) prm_.bar_delim = config["bar_delimiter"].asInt(); - if (config["monstercat"].isBool()) prm_.monstercat = config["monstercat"].asBool(); - if (config["waves"].isBool()) prm_.waves = config["waves"].asBool(); - if (config["noise_reduction"].isDouble()) - prm_.noise_reduction = config["noise_reduction"].asDouble(); - if (config["input_delay"].isInt()) - fetch_input_delay_ = std::chrono::seconds(config["input_delay"].asInt()); - - audio_raw_.height = prm_.ascii_range; - audio_data_.format = -1; - audio_data_.rate = 0; - audio_data_.samples_counter = 0; - audio_data_.channels = 2; - audio_data_.IEEE_FLOAT = 0; - audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels; - audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8; - audio_data_.terminate = 0; - audio_data_.suspendFlag = false; - input_source_ = get_input(&audio_data_, &prm_); - - if (!input_source_) { - spdlog::error("cava backend API didn't provide input audio source method"); - exit(EXIT_FAILURE); - } - - // Make cava parameters configuration - // Init cava plan, audio_raw structure - audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_); - if (!plan_) spdlog::error("cava backend plan is not provided"); - audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message + loadConfig(); // Read audio source trough cava API. Cava orginizes this process via infinity loop read_thread_ = [this] { try { @@ -102,41 +20,38 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { spdlog::warn("Cava backend. Read source error: {0}", e.what()); } read_thread_.sleep_for(fetch_input_delay_); + loadConfig(); }; - - thread_ = [this] { - doUpdate(); - thread_.sleep_for(frame_time_milsec_); + // Write outcoming data. Emit signals + out_thread_ = [this] { + doUpdate(false); + out_thread_.sleep_for(frame_time_milsec_); }; } waybar::modules::cava::CavaBackend::~CavaBackend() { - thread_.stop(); + out_thread_.stop(); read_thread_.stop(); - cava_destroy(plan_); - delete plan_; - plan_ = nullptr; - audio_raw_clean(&audio_raw_); - pthread_mutex_lock(&audio_data_.lock); - audio_data_.terminate = 1; - pthread_mutex_unlock(&audio_data_.lock); - config_clean(&prm_); - free(audio_data_.source); - free(audio_data_.cava_in); + + freeBackend(); } -static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { +static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { if (delta == std::chrono::seconds{0}) { delta += std::chrono::seconds{1}; delay += delta; + return true; } + return false; } -static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { +static bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { if (delta > std::chrono::seconds{0}) { delay -= delta; delta -= std::chrono::seconds{1}; + return true; } + return false; } bool waybar::modules::cava::CavaBackend::isSilence() { @@ -186,6 +101,7 @@ void waybar::modules::cava::CavaBackend::doPauseResume() { upThreadDelay(frame_time_milsec_, suspend_silence_delay_); } pthread_mutex_unlock(&audio_data_.lock); + Update(); } waybar::modules::cava::CavaBackend::type_signal_update @@ -193,6 +109,11 @@ waybar::modules::cava::CavaBackend::signal_update() { return m_signal_update_; } +waybar::modules::cava::CavaBackend::type_signal_audio_raw_update +waybar::modules::cava::CavaBackend::signal_audio_raw_update() { + return m_signal_audio_raw_; +} + waybar::modules::cava::CavaBackend::type_signal_silence waybar::modules::cava::CavaBackend::signal_silence() { return m_signal_silence_; @@ -215,12 +136,138 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) { } if (!silence_ || prm_.sleep_timer == 0) { - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); execute(); - if (re_paint_ == 1 || force) m_signal_update_.emit(output_); + if (re_paint_ == 1 || force || prm_.continuous_rendering) { + m_signal_update_.emit(output_); + m_signal_audio_raw_.emit(audio_raw_); + } } else { - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); } silence_prev_ = silence_; } + +void waybar::modules::cava::CavaBackend::freeBackend() { + if (plan_ != NULL) { + cava_destroy(plan_); + plan_ = NULL; + } + + audio_raw_clean(&audio_raw_); + pthread_mutex_lock(&audio_data_.lock); + audio_data_.terminate = 1; + pthread_mutex_unlock(&audio_data_.lock); + free_config(&prm_); + free(audio_data_.source); + free(audio_data_.cava_in); +} + +void waybar::modules::cava::CavaBackend::loadConfig() { + freeBackend(); + // Load waybar module config + char cfgPath[PATH_MAX]; + cfgPath[0] = '\0'; + + if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data()); + // Load cava config + error_.length = 0; + + if (!load_config(cfgPath, &prm_, &error_)) { + spdlog::error("cava backend. Error loading config. {0}", error_.message); + exit(EXIT_FAILURE); + } + + // Override cava parameters by the user config + prm_.inAtty = 0; + auto const output{prm_.output}; + // prm_.output = ::cava::output_method::OUTPUT_RAW; + if (config_["data_format"].isString()) { + if (prm_.data_format) free(prm_.data_format); + prm_.data_format = strdup(config_["data_format"].asString().c_str()); + } + if (config_["raw_target"].isString()) { + if (prm_.raw_target) free(prm_.raw_target); + prm_.raw_target = strdup(config_["raw_target"].asString().c_str()); + } + prm_.ascii_range = config_["format-icons"].size() - 1; + + if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt(); + if (config_["bar_width"].isInt()) prm_.bar_width = config_["bar_width"].asInt(); + if (config_["bar_height"].isInt()) prm_.bar_height = config_["bar_height"].asInt(); + prm_.orientation = ::cava::ORIENT_TOP; + prm_.xaxis = ::cava::xaxis_scale::NONE; + prm_.mono_opt = ::cava::AVERAGE; + prm_.autobars = 0; + if (config_["gravity"].isInt()) prm_.gravity = config_["gravity"].asInt(); + if (config_["integral"].isInt()) prm_.integral = config_["integral"].asInt(); + + if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt(); + // Calculate delay for Update() thread + frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate)); + if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt(); + if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt(); + if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt(); + if (config_["lower_cutoff_freq"].isNumeric()) + prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt(); + if (config_["higher_cutoff_freq"].isNumeric()) + prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt(); + if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt(); + if (config_["method"].isString()) + prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str()); + if (config_["source"].isString()) { + if (prm_.audio_source) free(prm_.audio_source); + prm_.audio_source = config_["source"].asString().data(); + } + if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); + if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); + if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool(); + if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool(); + if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt(); + if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool(); + if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool(); + if (config_["noise_reduction"].isDouble()) + prm_.noise_reduction = config_["noise_reduction"].asDouble(); + if (config_["input_delay"].isInt()) + fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt()); + if (config_["gradient"].isInt()) prm_.gradient = config_["gradient"].asInt(); + if (prm_.gradient == 0) + prm_.gradient_count = 0; + else if (config_["gradient_count"].isInt()) + prm_.gradient_count = config_["gradient_count"].asInt(); + if (config_["sdl_width"].isInt()) prm_.sdl_width = config_["sdl_width"].asInt(); + if (config_["sdl_height"].isInt()) prm_.sdl_height = config_["sdl_height"].asInt(); + + audio_raw_.height = prm_.ascii_range; + audio_data_.format = -1; + audio_data_.rate = 0; + audio_data_.samples_counter = 0; + audio_data_.channels = 2; + audio_data_.IEEE_FLOAT = 0; + audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels; + audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8; + audio_data_.terminate = 0; + audio_data_.suspendFlag = false; + input_source_ = get_input(&audio_data_, &prm_); + + if (!input_source_) { + spdlog::error("cava backend API didn't provide input audio source method"); + exit(EXIT_FAILURE); + } + + prm_.output = ::cava::output_method::OUTPUT_RAW; + + // Make cava parameters configuration + // Init cava plan, audio_raw structure + audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_); + if (!plan_) spdlog::error("cava backend plan is not provided"); + audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message + + prm_.output = output; +} + +const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; } +std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() { + return frame_time_milsec_; +}; diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap deleted file mode 100644 index e32bb0da0..000000000 --- a/subprojects/cava.wrap +++ /dev/null @@ -1,11 +0,0 @@ -[wrap-git] -url = https://github.com/LukashonakV/cava.git -revision = 23efcced43b5a395747b18a2e5f2171fc0925d18 -depth = 1 - -#directory = cava-0.10.6 -#source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz -#source_filename = cava-0.10.6.tar.gz -#source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4 -[provide] -cava = cava_dep diff --git a/subprojects/libcava.wrap b/subprojects/libcava.wrap new file mode 100644 index 000000000..466e22626 --- /dev/null +++ b/subprojects/libcava.wrap @@ -0,0 +1,12 @@ +#[wrap-git] +#url = https://github.com/LukashonakV/cava.git +#revision = 866cfec40b7b9d38e97148d004d3134c1385b52f +#depth = 1 + +[wrap-file] +directory = cava-0.10.7-beta +source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz +source_filename = cava-0.10.7.tar.gz +source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4 +[provide] +libcava = cava_dep