Merge pull request #2102 from LukashonakV/Cava
commit
8aafe817bf
|
@ -80,6 +80,9 @@
|
||||||
#ifdef HAVE_LIBWIREPLUMBER
|
#ifdef HAVE_LIBWIREPLUMBER
|
||||||
#include "modules/wireplumber.hpp"
|
#include "modules/wireplumber.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_LIBCAVA
|
||||||
|
#include "modules/cava.hpp"
|
||||||
|
#endif
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
#include "modules/image.hpp"
|
#include "modules/image.hpp"
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <cava/common.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
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 error_s error_{}; //cava errors
|
||||||
|
struct config_params prm_{}; //cava parameters
|
||||||
|
struct audio_raw audio_raw_{}; //cava handled raw audio data(is based on audio_data)
|
||||||
|
struct audio_data audio_data_{}; //cava audio data
|
||||||
|
struct cava_plan* plan_;//{new cava_plan{}};
|
||||||
|
// Cava API to read audio source
|
||||||
|
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};
|
||||||
|
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}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
22
meson.build
22
meson.build
|
@ -2,7 +2,7 @@ project(
|
||||||
'waybar', 'cpp', 'c',
|
'waybar', 'cpp', 'c',
|
||||||
version: '0.9.17',
|
version: '0.9.17',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
meson_version: '>= 0.49.0',
|
meson_version: '>= 0.50.0',
|
||||||
default_options : [
|
default_options : [
|
||||||
'cpp_std=c++17',
|
'cpp_std=c++17',
|
||||||
'buildtype=release',
|
'buildtype=release',
|
||||||
|
@ -175,6 +175,8 @@ src_files = files(
|
||||||
'src/util/rewrite_string.cpp'
|
'src/util/rewrite_string.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
inc_dirs = ['include']
|
||||||
|
|
||||||
if is_linux
|
if is_linux
|
||||||
add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp')
|
add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp')
|
||||||
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
|
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
|
||||||
|
@ -334,6 +336,19 @@ if get_option('experimental')
|
||||||
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
cava = compiler.find_library('cava',
|
||||||
|
required: get_option('cava'))
|
||||||
|
if not cava.found() and not get_option('cava').disabled()
|
||||||
|
cava = dependency('cava',
|
||||||
|
required: false,
|
||||||
|
fallback: [ 'cava', 'cava_dep' ])
|
||||||
|
endif
|
||||||
|
|
||||||
|
if cava.found()
|
||||||
|
add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')
|
||||||
|
src_files += 'src/modules/cava.cpp'
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('protocol')
|
subdir('protocol')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
|
@ -367,9 +382,10 @@ executable(
|
||||||
gtk_layer_shell,
|
gtk_layer_shell,
|
||||||
libsndio,
|
libsndio,
|
||||||
tz_dep,
|
tz_dep,
|
||||||
xkbregistry
|
xkbregistry,
|
||||||
|
cava
|
||||||
],
|
],
|
||||||
include_directories: [include_directories('include')],
|
include_directories: inc_dirs,
|
||||||
install: true,
|
install: true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,5 @@ option('logind', type: 'feature', value: 'auto', description: 'Enable support fo
|
||||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||||
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||||
option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')
|
option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')
|
||||||
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
|
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
|
||||||
|
option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
|
||||||
|
|
|
@ -156,6 +156,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "wireplumber") {
|
if (ref == "wireplumber") {
|
||||||
return new waybar::modules::Wireplumber(id, config_[name]);
|
return new waybar::modules::Wireplumber(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_LIBCAVA
|
||||||
|
if (ref == "cava") {
|
||||||
|
return new waybar::modules::Cava(id, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ref == "temperature") {
|
if (ref == "temperature") {
|
||||||
return new waybar::modules::Temperature(id, config_[name]);
|
return new waybar::modules::Temperature(id, config_[name]);
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
#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()) {
|
||||||
|
std::string strPath{config_["cava_config"].asString()};
|
||||||
|
const std::string fnd{"XDG_CONFIG_HOME"};
|
||||||
|
const std::string::size_type npos{strPath.find("$" + fnd)};
|
||||||
|
if (npos != std::string::npos) strPath.replace(npos, fnd.length() + 1, getenv(fnd.c_str()));
|
||||||
|
strcpy(cfgPath, strPath.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 = 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 = ORIENT_TOP;
|
||||||
|
prm_.xaxis = xaxis_scale::NONE;
|
||||||
|
prm_.mono_opt = 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 = 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_.fifoSample = config_["sample_rate"].asLargestInt();
|
||||||
|
if (config_["sample_bits"].isInt()) prm_.fifoSampleBits = 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_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_);
|
||||||
|
dp.emit();
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (sleep_counter_ <=
|
||||||
|
(int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
|
||||||
|
++sleep_counter_;
|
||||||
|
silence_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silence_) {
|
||||||
|
downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
|
||||||
|
// Process: execute cava
|
||||||
|
pthread_mutex_lock(&audio_data_.lock);
|
||||||
|
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_);
|
||||||
|
|
||||||
|
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_);
|
||||||
|
ALabel::update();
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
[wrap-file]
|
||||||
|
directory = cava-0.8.3
|
||||||
|
source_url = https://github.com/LukashonakV/cava/archive/0.8.3.tar.gz
|
||||||
|
source_filename = cava-0.8.3.tar.gz
|
||||||
|
source_hash = 10f9ec910682102c47dc39d684fd3fc90d38a4d1c2e5a310f132f70ad0e00850
|
||||||
|
[provide]
|
||||||
|
cava = cava_dep
|
Loading…
Reference in New Issue