summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nix/default.nix3
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt8
-rw-r--r--plugin/src/Caelestia/cavaprovider.cpp140
-rw-r--r--plugin/src/Caelestia/cavaprovider.hpp64
4 files changed, 212 insertions, 3 deletions
diff --git a/nix/default.nix b/nix/default.nix
index 7d7cc23..d18ca57 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -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 =
diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt
index 5ce0800..c1f3d15 100644
--- a/plugin/src/Caelestia/CMakeLists.txt
+++ b/plugin/src/Caelestia/CMakeLists.txt
@@ -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}
)
diff --git a/plugin/src/Caelestia/cavaprovider.cpp b/plugin/src/Caelestia/cavaprovider.cpp
new file mode 100644
index 0000000..ffc98a6
--- /dev/null
+++ b/plugin/src/Caelestia/cavaprovider.cpp
@@ -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
diff --git a/plugin/src/Caelestia/cavaprovider.hpp b/plugin/src/Caelestia/cavaprovider.hpp
new file mode 100644
index 0000000..d819f54
--- /dev/null
+++ b/plugin/src/Caelestia/cavaprovider.hpp
@@ -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