plugin: add cava provider
This commit is contained in:
parent
2c5801237c
commit
07bee23126
4 changed files with 212 additions and 3 deletions
|
|
@ -22,6 +22,7 @@
|
||||||
qt6,
|
qt6,
|
||||||
quickshell,
|
quickshell,
|
||||||
aubio,
|
aubio,
|
||||||
|
libcava,
|
||||||
xkeyboard-config,
|
xkeyboard-config,
|
||||||
cmake,
|
cmake,
|
||||||
ninja,
|
ninja,
|
||||||
|
|
@ -86,7 +87,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = [cmake ninja pkg-config];
|
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;
|
dontWrapQtApps = true;
|
||||||
cmakeFlags =
|
cmakeFlags =
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(QALCULATE REQUIRED libqalculate)
|
pkg_check_modules(QALCULATE REQUIRED libqalculate)
|
||||||
pkg_check_modules(AUBIO REQUIRED aubio)
|
pkg_check_modules(AUBIO REQUIRED aubio)
|
||||||
|
pkg_check_modules(CAVA REQUIRED cava)
|
||||||
|
|
||||||
qt_add_qml_module(caelestia
|
qt_add_qml_module(caelestia
|
||||||
URI Caelestia
|
URI Caelestia
|
||||||
|
|
@ -14,6 +15,7 @@ qt_add_qml_module(caelestia
|
||||||
service.hpp service.cpp
|
service.hpp service.cpp
|
||||||
serviceref.hpp serviceref.cpp
|
serviceref.hpp serviceref.cpp
|
||||||
audioprovider.hpp audioprovider.cpp
|
audioprovider.hpp audioprovider.cpp
|
||||||
|
cavaprovider.hpp cavaprovider.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_query_qml_module(caelestia
|
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_qmldir}" DESTINATION "${module_dir}")
|
||||||
install(FILES "${module_typeinfo}" 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
|
target_link_libraries(caelestia PRIVATE
|
||||||
Qt::Core Qt::Qml Qt::Gui Qt::Concurrent Qt::Multimedia
|
Qt::Core Qt::Qml Qt::Gui Qt::Concurrent Qt::Multimedia
|
||||||
${QALCULATE_LIBRARIES} ${AUBIO_LIBRARIES}
|
${QALCULATE_LIBRARIES} ${AUBIO_LIBRARIES} ${CAVA_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
140
plugin/src/Caelestia/cavaprovider.cpp
Normal file
140
plugin/src/Caelestia/cavaprovider.cpp
Normal 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
|
||||||
64
plugin/src/Caelestia/cavaprovider.hpp
Normal file
64
plugin/src/Caelestia/cavaprovider.hpp
Normal 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
|
||||||
Loading…
Add table
Reference in a new issue