Cava back/front end transformation
This commit is contained in:
@ -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 <cava/common.h>
|
|
||||||
|
|
||||||
#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<const std::string, void (waybar::modules::Cava::* const)()> actionMap_{
|
|
||||||
{"mode", &waybar::modules::Cava::pause_resume}};
|
|
||||||
};
|
|
||||||
} // namespace waybar::modules
|
|
30
include/modules/cava/cava.hpp
Normal file
30
include/modules/cava/cava.hpp
Normal file
@ -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<CavaBackend> 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<const std::string, void (waybar::modules::cava::Cava::* const)()>
|
||||||
|
actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}};
|
||||||
|
};
|
||||||
|
} // namespace waybar::modules::cava
|
74
include/modules/cava/cava_backend.hpp
Normal file
74
include/modules/cava/cava_backend.hpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <sigc++/sigc++.h>
|
||||||
|
|
||||||
|
#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 <cava/common.h>
|
||||||
|
|
||||||
|
#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<CavaBackend> inst(const Json::Value& config);
|
||||||
|
|
||||||
|
virtual ~CavaBackend();
|
||||||
|
// Methods
|
||||||
|
int getAsciiRange();
|
||||||
|
void doPauseResume();
|
||||||
|
void Update();
|
||||||
|
// Signal accessor
|
||||||
|
using type_signal_update = sigc::signal<void(const std::string&)>;
|
||||||
|
type_signal_update signal_update();
|
||||||
|
using type_signal_silence = sigc::signal<void()>;
|
||||||
|
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
|
@ -505,7 +505,7 @@ cava = dependency('cava',
|
|||||||
|
|
||||||
if cava.found()
|
if cava.found()
|
||||||
add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')
|
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')
|
man_files += files('man/waybar-cava.5.scd')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
#include "modules/wireplumber.hpp"
|
#include "modules/wireplumber.hpp"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBCAVA
|
#ifdef HAVE_LIBCAVA
|
||||||
#include "modules/cava.hpp"
|
#include "modules/cava/cava.hpp"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SYSTEMD_MONITOR
|
#ifdef HAVE_SYSTEMD_MONITOR
|
||||||
#include "modules/systemd_failed_units.hpp"
|
#include "modules/systemd_failed_units.hpp"
|
||||||
@ -343,7 +343,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBCAVA
|
#ifdef HAVE_LIBCAVA
|
||||||
if (ref == "cava") {
|
if (ref == "cava") {
|
||||||
return new waybar::modules::Cava(id, config_[name]);
|
return new waybar::modules::cava::Cava(id, config_[name]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SYSTEMD_MONITOR
|
#ifdef HAVE_SYSTEMD_MONITOR
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
#include "modules/cava.hpp"
|
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
51
src/modules/cava/cava.cpp
Normal file
51
src/modules/cava/cava.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "modules/cava/cava.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
223
src/modules/cava/cava_backend.cpp
Normal file
223
src/modules/cava/cava_backend.cpp
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
#include "modules/cava/cava_backend.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
std::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(
|
||||||
|
const Json::Value& config) {
|
||||||
|
static auto* backend = new CavaBackend(config);
|
||||||
|
static std::shared_ptr<CavaBackend> 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_;
|
||||||
|
}
|
Reference in New Issue
Block a user