#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) : config_(config) { // Load waybar module config loadConfig(); // 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_); loadConfig(); }; // Write outcoming data. Emit signals out_thread_ = [this] { doUpdate(false); out_thread_.sleep_for(frame_time_milsec_); }; } waybar::modules::cava::CavaBackend::~CavaBackend() { out_thread_.stop(); read_thread_.stop(); freeBackend(); } 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 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() { 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); Update(); } waybar::modules::cava::CavaBackend::type_signal_update 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_; } 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) { if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); execute(); if (re_paint_ == 1 || force || prm_.continuous_rendering) { m_signal_update_.emit(output_); m_signal_audio_raw_.emit(audio_raw_); } } else { 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 (prm_.data_format) free(prm_.data_format); // Default to ascii for format-icons output; allow user override prm_.data_format = strdup( config_["data_format"].isString() ? config_["data_format"].asString().c_str() : "ascii"); 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 = strdup(config_["source"].asString().c_str()); } 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_; };