plugin: add cava provider

This commit is contained in:
2 * r + 2 * t 2025-09-07 15:18:23 +10:00
parent 2c5801237c
commit 07bee23126
4 changed files with 212 additions and 3 deletions

View file

@ -22,6 +22,7 @@
qt6,
quickshell,
aubio,
libcava,
xkeyboard-config,
cmake,
ninja,
@ -86,7 +87,7 @@
};
nativeBuildInputs = [cmake ninja pkg-config];
buildInputs = [qt6.qtbase qt6.qtdeclarative qt6.qtmultimedia libqalculate aubio];
buildInputs = [qt6.qtbase qt6.qtdeclarative qt6.qtmultimedia libqalculate aubio libcava];
dontWrapQtApps = true;
cmakeFlags =

View file

@ -1,6 +1,7 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(QALCULATE REQUIRED libqalculate)
pkg_check_modules(AUBIO REQUIRED aubio)
pkg_check_modules(CAVA REQUIRED cava)
qt_add_qml_module(caelestia
URI Caelestia
@ -14,6 +15,7 @@ qt_add_qml_module(caelestia
service.hpp service.cpp
serviceref.hpp serviceref.cpp
audioprovider.hpp audioprovider.cpp
cavaprovider.hpp cavaprovider.cpp
)
qt_query_qml_module(caelestia
@ -33,8 +35,10 @@ install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RU
install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
target_include_directories(caelestia SYSTEM PRIVATE ${QALCULATE_INCLUDE_DIRS} ${AUBIO_INCLUDE_DIRS})
target_include_directories(caelestia SYSTEM PRIVATE
${QALCULATE_INCLUDE_DIRS} ${AUBIO_INCLUDE_DIRS} ${CAVA_INCLUDE_DIRS}
)
target_link_libraries(caelestia PRIVATE
Qt::Core Qt::Qml Qt::Gui Qt::Concurrent Qt::Multimedia
${QALCULATE_LIBRARIES} ${AUBIO_LIBRARIES}
${QALCULATE_LIBRARIES} ${AUBIO_LIBRARIES} ${CAVA_LIBRARIES}
)

View file

@ -0,0 +1,140 @@
#include "cavaprovider.hpp"
#include "audioprovider.hpp"
#include <QDebug>
#include <QObject>
#include <cava/cavacore.h>
#include <cmath>
#include <cstddef>
namespace caelestia {
CavaProcessor::CavaProcessor(AudioProvider* provider, QObject* parent)
: AudioProcessor(provider, parent)
, m_plan(nullptr)
, m_in(new double[static_cast<size_t>(m_chunkSize)])
, m_out(nullptr)
, m_bars(0) {};
CavaProcessor::~CavaProcessor() {
cleanup();
delete[] m_in;
}
void CavaProcessor::setBars(int bars) {
if (bars < 0) {
qWarning() << "CavaProcessor::setBars: bars must be greater than 0. Setting to 0.";
bars = 0;
}
if (m_bars != bars) {
m_bars = bars;
reload();
}
}
void CavaProcessor::reload() {
cleanup();
initCava();
}
void CavaProcessor::cleanup() {
if (!m_plan) {
return;
}
cava_destroy(m_plan);
m_plan = nullptr;
if (m_out) {
delete[] m_out;
m_out = nullptr;
}
}
void CavaProcessor::initCava() {
if (m_plan || m_bars == 0) {
return;
}
m_plan = cava_init(m_bars, static_cast<unsigned int>(m_sampleRate), 1, 1, 0.85, 50, 10000);
if (m_plan->status == -1) {
qWarning() << "CavaProcessor::initCava: failed to initialise cava plan";
cleanup();
return;
}
m_out = new double[static_cast<size_t>(m_bars)];
}
void CavaProcessor::processChunk(const QVector<double>& chunk) {
if (!m_plan || m_bars == 0) {
return;
}
std::copy(chunk.constBegin(), chunk.constEnd(), m_in);
// Process in data via cava
cava_execute(m_in, m_chunkSize, m_out, m_plan);
// Apply monstercat filter
for (int i = 0; i < m_bars; i++) {
for (int j = i - 1; j >= 0; j--) {
m_out[j] = std::max(m_out[i] / std::pow(1.5, i - j), m_out[j]);
}
for (int j = i + 1; j < m_bars; j++) {
m_out[j] = std::max(m_out[i] / std::pow(1.5, j - i), m_out[j]);
}
}
// Update values
QVector<double> values(m_bars);
std::copy(m_out, m_out + m_bars, values.begin());
if (values != m_values) {
m_values = std::move(values);
emit valuesChanged(m_values);
}
}
CavaProvider::CavaProvider(int sampleRate, int chunkSize, QObject* parent)
: AudioProvider(sampleRate, chunkSize, parent)
, m_bars(0) {
m_processor = new CavaProcessor(this);
init();
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
}
int CavaProvider::bars() const {
return m_bars;
}
void CavaProvider::setBars(int bars) {
if (bars < 0) {
qWarning() << "CavaProvider::setBars: bars must be greater than 0. Setting to 0.";
bars = 0;
}
if (m_bars == bars) {
return;
}
m_bars = bars;
emit barsChanged();
QMetaObject::invokeMethod(m_processor, "setBars", Qt::QueuedConnection, Q_ARG(int, bars));
}
QVector<double> CavaProvider::values() const {
return m_values;
}
void CavaProvider::updateValues(QVector<double> values) {
if (values != m_values) {
m_values = std::move(values);
emit valuesChanged();
}
}
} // namespace caelestia

View file

@ -0,0 +1,64 @@
#pragma once
#include "audioprovider.hpp"
#include <QObject>
#include <cava/cavacore.h>
#include <qqmlintegration.h>
namespace caelestia {
class CavaProcessor : public AudioProcessor {
Q_OBJECT
public:
explicit CavaProcessor(AudioProvider* provider, QObject* parent = nullptr);
~CavaProcessor();
signals:
void valuesChanged(QVector<double> values);
private:
struct cava_plan* m_plan;
double* m_in;
double* m_out;
int m_bars;
QVector<double> m_values;
Q_INVOKABLE void setBars(int bars);
void reload();
void initCava();
void cleanup();
void processChunk(const QVector<double>& chunk) override;
};
class CavaProvider : public AudioProvider {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged)
Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
public:
explicit CavaProvider(int sampleRate = 48000, int chunkSize = 512, QObject* parent = nullptr);
[[nodiscard]] int bars() const;
void setBars(int bars);
[[nodiscard]] QVector<double> values() const;
signals:
void barsChanged();
void valuesChanged();
private:
int m_bars;
QVector<double> m_values;
void updateValues(QVector<double> values);
};
} // namespace caelestia