From 76d3b47ffdbdbc41e4a28645c5f7cb07e9b1d63d Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Fri, 26 Sep 2025 23:34:11 +0300 Subject: [PATCH] Cava back/front end transformation --- include/modules/cava.hpp | 59 ------- include/modules/cava/cava.hpp | 30 ++++ include/modules/cava/cava_backend.hpp | 74 +++++++++ meson.build | 2 +- src/factory.cpp | 4 +- src/modules/cava.cpp | 211 ------------------------ src/modules/cava/cava.cpp | 51 ++++++ src/modules/cava/cava_backend.cpp | 223 ++++++++++++++++++++++++++ 8 files changed, 381 insertions(+), 273 deletions(-) delete mode 100644 include/modules/cava.hpp create mode 100644 include/modules/cava/cava.hpp create mode 100644 include/modules/cava/cava_backend.hpp delete mode 100644 src/modules/cava.cpp create mode 100644 src/modules/cava/cava.cpp create mode 100644 src/modules/cava/cava_backend.cpp diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp deleted file mode 100644 index 1a88c7b7..00000000 --- a/include/modules/cava.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "ALabel.hpp" -#include "util/sleeper_thread.hpp" - -namespace cava { -extern "C" { -// Need sdl_glsl output feature to be enabled on libcava -#ifndef SDL_GLSL -#define SDL_GLSL -#endif - -#include - -#ifdef SDL_GLSL -#undef SDL_GLSL -#endif -} -} // namespace cava - -namespace waybar::modules { -using namespace std::literals::chrono_literals; - -class Cava final : public ALabel { - public: - Cava(const std::string&, const Json::Value&); - virtual ~Cava(); - auto update() -> void override; - auto doAction(const std::string& name) -> void override; - - private: - util::SleeperThread thread_; - util::SleeperThread thread_fetch_input_; - - struct cava::error_s error_{}; // cava errors - struct cava::config_params prm_{}; // cava parameters - struct cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data) - struct cava::audio_data audio_data_{}; // cava audio data - struct cava::cava_plan* plan_; //{new cava_plan{}}; - // Cava API to read audio source - cava::ptr input_source_; - // Delay to handle audio source - std::chrono::milliseconds frame_time_milsec_{1s}; - // Text to display - std::string text_{""}; - int rePaint_{1}; - std::chrono::seconds fetch_input_delay_{4}; - std::chrono::seconds suspend_silence_delay_{0}; - bool silence_{false}; - bool hide_on_silence_{false}; - std::string format_silent_{""}; - int sleep_counter_{0}; - // Cava method - void pause_resume(); - // ModuleActionMap - static inline std::map actionMap_{ - {"mode", &waybar::modules::Cava::pause_resume}}; -}; -} // namespace waybar::modules diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cava.hpp new file mode 100644 index 00000000..6b13c4bd --- /dev/null +++ b/include/modules/cava/cava.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "ALabel.hpp" +#include "cava_backend.hpp" + +namespace waybar::modules::cava { + +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_{""}; + bool hide_on_silence_{false}; + std::string format_silent_{""}; + int ascii_range_{0}; + bool silence_{false}; + // Cava method + void pause_resume(); + // ModuleActionMap + static inline std::map + actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; +}; +} // namespace waybar::modules::cava diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp new file mode 100644 index 00000000..d8a2ce06 --- /dev/null +++ b/include/modules/cava/cava_backend.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "util/sleeper_thread.hpp" + +namespace cava { +extern "C" { +// Need sdl_glsl output feature to be enabled on libcava +#ifndef SDL_GLSL +#define SDL_GLSL +#endif + +#include + +#ifdef SDL_GLSL +#undef SDL_GLSL +#endif +} +} // namespace cava + +namespace waybar::modules::cava { +using namespace std::literals::chrono_literals; + +class CavaBackend final { + public: + static std::shared_ptr inst(const Json::Value& config); + + virtual ~CavaBackend(); + // Methods + int getAsciiRange(); + void doPauseResume(); + void Update(); + // Signal accessor + using type_signal_update = sigc::signal; + type_signal_update signal_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_; + // Cava API to read audio source + ::cava::ptr input_source_; + + struct ::cava::error_s error_{}; // cava errors + struct ::cava::config_params prm_{}; // cava parameters + struct ::cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data) + struct ::cava::audio_data audio_data_{}; // cava audio data + struct ::cava::cava_plan* plan_; //{new cava_plan{}}; + + std::chrono::seconds fetch_input_delay_{4}; + // Delay to handle audio source + std::chrono::milliseconds frame_time_milsec_{1s}; + + int re_paint_{0}; + bool silence_{false}; + bool silence_prev_{false}; + std::chrono::seconds suspend_silence_delay_{0}; + int sleep_counter_{0}; + std::string output_{}; + // Methods + void invoke(); + void execute(); + bool isSilence(); + void doUpdate(bool force = false); + + // Signal + type_signal_update m_signal_update_; + type_signal_silence m_signal_silence_; +}; +} // namespace waybar::modules::cava diff --git a/meson.build b/meson.build index b3cfb5fa..822c566b 100644 --- a/meson.build +++ b/meson.build @@ -505,7 +505,7 @@ cava = dependency('cava', if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += files('src/modules/cava.cpp') + src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') man_files += files('man/waybar-cava.5.scd') endif diff --git a/src/factory.cpp b/src/factory.cpp index 20408106..7828ce75 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -109,7 +109,7 @@ #include "modules/wireplumber.hpp" #endif #ifdef HAVE_LIBCAVA -#include "modules/cava.hpp" +#include "modules/cava/cava.hpp" #endif #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" @@ -343,7 +343,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, #endif #ifdef HAVE_LIBCAVA if (ref == "cava") { - return new waybar::modules::Cava(id, config_[name]); + return new waybar::modules::cava::Cava(id, config_[name]); } #endif #ifdef HAVE_SYSTEMD_MONITOR diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp deleted file mode 100644 index 405a351a..00000000 --- a/src/modules/cava.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "modules/cava.hpp" - -#include - -waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config) - : ALabel(config, "cava", id, "{}", 60, false, false, false) { - // 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_)) { - spdlog::error("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; - strcpy(prm_.data_format, "ascii"); - strcpy(prm_.raw_target, "/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(); - 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()) 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_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); - if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString(); - // Make cava parameters configuration - plan_ = new cava::cava_plan{}; - - audio_raw_.height = prm_.ascii_range; - audio_data_.format = -1; - audio_data_.source = new char[1 + strlen(prm_.audio_source)]; - audio_data_.source[0] = '\0'; - strcpy(audio_data_.source, prm_.audio_source); - - 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_.cava_in = new double[audio_data_.cava_buffer_size]{0.0}; - - audio_data_.terminate = 0; - audio_data_.suspendFlag = false; - input_source_ = get_input(&audio_data_, &prm_); - - if (!input_source_) { - spdlog::error("cava API didn't provide input audio source method"); - exit(EXIT_FAILURE); - } - // Calculate delay for Update() thread - frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate)); - - // Init cava plan, audio_raw structure - audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_); - if (!plan_) spdlog::error("cava plan is not provided"); - audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message - // Read audio source trough cava API. Cava orginizes this process via infinity loop - thread_fetch_input_ = [this] { - thread_fetch_input_.sleep_for(fetch_input_delay_); - input_source_(&audio_data_); - }; - - thread_ = [this] { - dp.emit(); - thread_.sleep_for(frame_time_milsec_); - }; -} - -waybar::modules::Cava::~Cava() { - thread_fetch_input_.stop(); - thread_.stop(); - delete plan_; - plan_ = nullptr; -} - -void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { - if (delta == std::chrono::seconds{0}) { - delta += std::chrono::seconds{1}; - delay += delta; - } -} - -void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { - if (delta > std::chrono::seconds{0}) { - delay -= delta; - delta -= std::chrono::seconds{1}; - } -} - -auto waybar::modules::Cava::update() -> void { - if (audio_data_.suspendFlag) return; - silence_ = true; - - for (int i{0}; i < audio_data_.input_buffer_size; ++i) { - if (audio_data_.cava_in[i]) { - silence_ = false; - sleep_counter_ = 0; - break; - } - } - - if (silence_ && prm_.sleep_timer != 0) { - if (sleep_counter_ <= - (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { - ++sleep_counter_; - silence_ = false; - } - } - - if (!silence_ || prm_.sleep_timer == 0) { - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); - // Process: execute cava - pthread_mutex_lock(&audio_data_.lock); - cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, - plan_); - if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0; - pthread_mutex_unlock(&audio_data_.lock); - - // Do transformation under raw data - audio_raw_fetch(&audio_raw_, &prm_, &rePaint_, plan_); - - if (rePaint_ == 1) { - text_.clear(); - - for (int i{0}; i < audio_raw_.number_of_bars; ++i) { - audio_raw_.previous_frame[i] = audio_raw_.bars[i]; - text_.append( - getIcon((audio_raw_.bars[i] > prm_.ascii_range) ? prm_.ascii_range : audio_raw_.bars[i], - "", prm_.ascii_range + 1)); - if (prm_.bar_delim != 0) text_.push_back(prm_.bar_delim); - } - - label_.set_markup(text_); - label_.show(); - ALabel::update(); - label_.get_style_context()->add_class("updated"); - } - - label_.get_style_context()->remove_class("silent"); - } else { - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); - if (hide_on_silence_) - label_.hide(); - else if (config_["format_silent"].isString()) - label_.set_markup(format_silent_); - - label_.get_style_context()->add_class("silent"); - label_.get_style_context()->remove_class("updated"); - } -} - -auto waybar::modules::Cava::doAction(const std::string& name) -> void { - if ((actionMap_[name])) { - (this->*actionMap_[name])(); - } else - spdlog::error("Cava. Unsupported action \"{0}\"", name); -} - -// Cava actions -void waybar::modules::Cava::pause_resume() { - pthread_mutex_lock(&audio_data_.lock); - if (audio_data_.suspendFlag) { - audio_data_.suspendFlag = false; - pthread_cond_broadcast(&audio_data_.resumeCond); - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); - } else { - audio_data_.suspendFlag = true; - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); - } - pthread_mutex_unlock(&audio_data_.lock); -} diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cava.cpp new file mode 100644 index 00000000..a2a74606 --- /dev/null +++ b/src/modules/cava/cava.cpp @@ -0,0 +1,51 @@ +#include "modules/cava/cava.hpp" + +#include + +waybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config) + : ALabel(config, "cava", id, "{}", 60, false, false, false), + backend_{waybar::modules::cava::CavaBackend::inst(config)} { + if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); + if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString(); + + ascii_range_ = backend_->getAsciiRange(); + backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate)); + backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence)); + backend_->Update(); +} + +auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void { + if ((actionMap_[name])) { + (this->*actionMap_[name])(); + } else + spdlog::error("Cava. Unsupported action \"{0}\"", name); +} + +// 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(); + 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"); + } +} diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp new file mode 100644 index 00000000..ec32261c --- /dev/null +++ b/src/modules/cava/cava_backend.cpp @@ -0,0 +1,223 @@ +#include "modules/cava/cava_backend.hpp" + +#include + +std::shared_ptr waybar::modules::cava::CavaBackend::inst( + const Json::Value& config) { + static auto* backend = new CavaBackend(config); + static std::shared_ptr backend_ptr{backend}; + return backend_ptr; +} + +waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& 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_)) { + 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; + strcpy(prm_.data_format, "ascii"); + strcpy(prm_.raw_target, "/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()) 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()); + + // Make cava parameters configuration + plan_ = new ::cava::cava_plan{}; + + audio_raw_.height = prm_.ascii_range; + audio_data_.format = -1; + audio_data_.source = new char[1 + strlen(prm_.audio_source)]; + audio_data_.source[0] = '\0'; + strcpy(audio_data_.source, prm_.audio_source); + + 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_.cava_in = new double[audio_data_.cava_buffer_size]{0.0}; + + 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); + } + + // 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 + // Read audio source trough cava API. Cava orginizes this process via infinity loop + read_thread_ = [this] { + try { + input_source_(&audio_data_); + } catch (const std::runtime_error& e) { + spdlog::warn("Cava backend. Read source error: {0}", e.what()); + } + read_thread_.sleep_for(fetch_input_delay_); + }; + + thread_ = [this] { + doUpdate(); + thread_.sleep_for(frame_time_milsec_); + }; +} + +waybar::modules::cava::CavaBackend::~CavaBackend() { + thread_.stop(); + read_thread_.stop(); + delete plan_; + plan_ = nullptr; +} + +static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { + if (delta == std::chrono::seconds{0}) { + delta += std::chrono::seconds{1}; + delay += delta; + } +} + +static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { + if (delta > std::chrono::seconds{0}) { + delay -= delta; + delta -= std::chrono::seconds{1}; + } +} + +bool waybar::modules::cava::CavaBackend::isSilence() { + for (int i{0}; i < audio_data_.input_buffer_size; ++i) { + if (audio_data_.cava_in[i]) { + return false; + } + } + + return true; +} + +int waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; } + +// Process: execute cava +void waybar::modules::cava::CavaBackend::invoke() { + pthread_mutex_lock(&audio_data_.lock); + ::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, + plan_); + if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0; + pthread_mutex_unlock(&audio_data_.lock); +} + +// Do transformation under raw data +void waybar::modules::cava::CavaBackend::execute() { + invoke(); + audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_); + + if (re_paint_ == 1) { + output_.clear(); + for (int i{0}; i < audio_raw_.number_of_bars; ++i) { + audio_raw_.previous_frame[i] = audio_raw_.bars[i]; + output_.push_back(audio_raw_.bars[i]); + if (prm_.bar_delim != 0) output_.push_back(prm_.bar_delim); + } + } +} + +void waybar::modules::cava::CavaBackend::doPauseResume() { + pthread_mutex_lock(&audio_data_.lock); + if (audio_data_.suspendFlag) { + audio_data_.suspendFlag = false; + pthread_cond_broadcast(&audio_data_.resumeCond); + downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + } else { + audio_data_.suspendFlag = true; + upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + } + pthread_mutex_unlock(&audio_data_.lock); +} + +waybar::modules::cava::CavaBackend::type_signal_update +waybar::modules::cava::CavaBackend::signal_update() { + return m_signal_update_; +} + +waybar::modules::cava::CavaBackend::type_signal_silence +waybar::modules::cava::CavaBackend::signal_silence() { + return m_signal_silence_; +} + +void waybar::modules::cava::CavaBackend::Update() { doUpdate(true); } + +void waybar::modules::cava::CavaBackend::doUpdate(bool force) { + if (audio_data_.suspendFlag && !force) return; + + silence_ = isSilence(); + if (!silence_) sleep_counter_ = 0; + + if (silence_ && prm_.sleep_timer != 0) { + if (sleep_counter_ <= + (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { + ++sleep_counter_; + silence_ = false; + } + } + + if (!silence_ || prm_.sleep_timer == 0) { + downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + execute(); + if (re_paint_ == 1 || force) m_signal_update_.emit(output_); + } else { + upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); + } + silence_prev_ = silence_; +}