diff options
Diffstat (limited to 'plugin/src/Caelestia')
| -rw-r--r-- | plugin/src/Caelestia/audiocollector.cpp | 56 | ||||
| -rw-r--r-- | plugin/src/Caelestia/audiocollector.hpp | 28 | ||||
| -rw-r--r-- | plugin/src/Caelestia/audioprovider.cpp | 59 | ||||
| -rw-r--r-- | plugin/src/Caelestia/audioprovider.hpp | 18 | ||||
| -rw-r--r-- | plugin/src/Caelestia/beattracker.cpp | 50 | ||||
| -rw-r--r-- | plugin/src/Caelestia/beattracker.hpp | 7 | ||||
| -rw-r--r-- | plugin/src/Caelestia/cavaprovider.cpp | 54 | ||||
| -rw-r--r-- | plugin/src/Caelestia/cavaprovider.hpp | 6 |
8 files changed, 212 insertions, 66 deletions
diff --git a/plugin/src/Caelestia/audiocollector.cpp b/plugin/src/Caelestia/audiocollector.cpp index b9dfc23..04d609c 100644 --- a/plugin/src/Caelestia/audiocollector.cpp +++ b/plugin/src/Caelestia/audiocollector.cpp @@ -3,9 +3,9 @@ #include "service.hpp" #include <algorithm> #include <cstdint> -#include <mutex> #include <pipewire/pipewire.h> #include <qdebug.h> +#include <qmutex.h> #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <stop_token> @@ -25,6 +25,7 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector) m_loop = pw_main_loop_new(nullptr); if (!m_loop) { qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop"; + pw_deinit(); return; } @@ -66,10 +67,17 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector) m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "caelestia-shell", props, &events, this); - pw_stream_connect(m_stream, PW_DIRECTION_INPUT, PW_ID_ANY, + const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, collector->nodeId(), static_cast<pw_stream_flags>( PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), params, 1); + if (success < 0) { + qWarning() << "PipeWireWorker::init: failed to connect stream"; + pw_stream_destroy(m_stream); + pw_main_loop_destroy(m_loop); + pw_deinit(); + return; + } pw_main_loop_run(m_loop); @@ -155,27 +163,20 @@ unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) { return n; } -AudioCollector::AudioCollector(uint32_t sampleRate, uint32_t chunkSize, QObject* parent) +AudioCollector::AudioCollector(QObject* parent) : Service(parent) - , m_buffer1(chunkSize) - , m_buffer2(chunkSize) + , m_sampleRate(44100) + , m_chunkSize(512) + , m_nodeId(PW_ID_ANY) + , m_buffer1(m_chunkSize) + , m_buffer2(m_chunkSize) , m_readBuffer(&m_buffer1) - , m_writeBuffer(&m_buffer2) - , m_sampleRate(sampleRate) - , m_chunkSize(chunkSize) {} + , m_writeBuffer(&m_buffer2) {} AudioCollector::~AudioCollector() { stop(); } -AudioCollector* AudioCollector::instance() { - std::lock_guard<std::mutex> lock(s_mutex); - if (s_instance == nullptr) { - s_instance = new AudioCollector(); - } - return s_instance; -} - uint32_t AudioCollector::sampleRate() const { return m_sampleRate; } @@ -184,6 +185,29 @@ uint32_t AudioCollector::chunkSize() const { return m_chunkSize; } +uint32_t AudioCollector::nodeId() { + QMutexLocker locker(&m_nodeIdMutex); + return m_nodeId; +} + +void AudioCollector::setNodeId(uint32_t nodeId) { + { + QMutexLocker locker(&m_nodeIdMutex); + + if (nodeId == m_nodeId) { + return; + } + + m_nodeId = nodeId; + } + emit nodeIdChanged(); + + if (m_thread.joinable()) { + stop(); + start(); + } +} + void AudioCollector::clearBuffer() { auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed); std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f); diff --git a/plugin/src/Caelestia/audiocollector.hpp b/plugin/src/Caelestia/audiocollector.hpp index 283b758..ecfae09 100644 --- a/plugin/src/Caelestia/audiocollector.hpp +++ b/plugin/src/Caelestia/audiocollector.hpp @@ -3,8 +3,9 @@ #include "service.hpp" #include <atomic> #include <cstdint> -#include <mutex> #include <pipewire/pipewire.h> +#include <qmutex.h> +#include <qqmlintegration.h> #include <spa/param/audio/format-utils.h> #include <stop_token> #include <thread> @@ -41,24 +42,35 @@ private: class AudioCollector : public Service { Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(uint32_t nodeId READ nodeId WRITE setNodeId NOTIFY nodeIdChanged) public: - explicit AudioCollector(uint32_t sampleRate = 44100, uint32_t chunkSize = 512, QObject* parent = nullptr); + explicit AudioCollector(QObject* parent = nullptr); ~AudioCollector(); - static AudioCollector* instance(); - [[nodiscard]] uint32_t sampleRate() const; [[nodiscard]] uint32_t chunkSize() const; + [[nodiscard]] uint32_t nodeId(); + void setNodeId(uint32_t nodeId); + void clearBuffer(); void loadChunk(const int16_t* samples, uint32_t count); uint32_t readChunk(float* out, uint32_t count = 0); uint32_t readChunk(double* out, uint32_t count = 0); +signals: + void sampleRateChanged(); + void chunkSizeChanged(); + void nodeIdChanged(); + private: - inline static AudioCollector* s_instance = nullptr; - inline static std::mutex s_mutex; + const uint32_t m_sampleRate; + const uint32_t m_chunkSize; + uint32_t m_nodeId; + QMutex m_nodeIdMutex; std::jthread m_thread; std::vector<float> m_buffer1; @@ -67,9 +79,7 @@ private: std::atomic<std::vector<float>*> m_writeBuffer; uint32_t m_sampleCount; - const uint32_t m_sampleRate; - const uint32_t m_chunkSize; - + void reload(); void start() override; void stop() override; }; diff --git a/plugin/src/Caelestia/audioprovider.cpp b/plugin/src/Caelestia/audioprovider.cpp index 4188460..f31480e 100644 --- a/plugin/src/Caelestia/audioprovider.cpp +++ b/plugin/src/Caelestia/audioprovider.cpp @@ -7,10 +7,9 @@ namespace caelestia { -AudioProcessor::AudioProcessor(QObject* parent) +AudioProcessor::AudioProcessor(AudioCollector* collector, QObject* parent) : QObject(parent) - , m_sampleRate(AudioCollector::instance()->sampleRate()) - , m_chunkSize(AudioCollector::instance()->chunkSize()) {} + , m_collector(collector) {} AudioProcessor::~AudioProcessor() { stop(); @@ -18,26 +17,53 @@ AudioProcessor::~AudioProcessor() { void AudioProcessor::init() { m_timer = new QTimer(this); - m_timer->setInterval(static_cast<int>(m_chunkSize * 1000.0 / m_sampleRate)); + if (m_collector) { + m_timer->setInterval(static_cast<int>(m_collector->chunkSize() * 1000.0 / m_collector->sampleRate())); + } connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process); } -void AudioProcessor::start() { - AudioCollector::instance()->ref(); +void AudioProcessor::setCollector(AudioCollector* collector) { + if (m_collector == collector) { + return; + } + if (m_timer) { + if (m_timer->isActive()) { + if (m_collector) { + m_collector->unref(); + } + if (collector) { + collector->ref(); + } + } + if (collector) { + m_timer->setInterval(static_cast<int>(collector->chunkSize() * 1000.0 / collector->sampleRate())); + } else { + m_timer->stop(); + } + } + + m_collector = collector; +} + +void AudioProcessor::start() { + if (m_timer && m_collector) { + m_collector->ref(); m_timer->start(); } } void AudioProcessor::stop() { - if (m_timer) { + if (m_timer && m_collector) { m_timer->stop(); + m_collector->unref(); } - AudioCollector::instance()->unref(); } AudioProvider::AudioProvider(QObject* parent) : Service(parent) + , m_collector(nullptr) , m_processor(nullptr) , m_thread(nullptr) {} @@ -48,6 +74,23 @@ AudioProvider::~AudioProvider() { } } +AudioCollector* AudioProvider::collector() const { + return m_collector; +} + +void AudioProvider::setCollector(AudioCollector* collector) { + if (m_collector == collector) { + return; + } + + m_collector = collector; + emit collectorChanged(); + + if (m_processor) { + QMetaObject::invokeMethod(m_processor, "setCollector", Qt::QueuedConnection, Q_ARG(AudioCollector*, collector)); + } +} + void AudioProvider::init() { if (!m_processor) { qWarning() << "AudioProvider::init: attempted to init with no processor set"; diff --git a/plugin/src/Caelestia/audioprovider.hpp b/plugin/src/Caelestia/audioprovider.hpp index aa7fb83..c92b965 100644 --- a/plugin/src/Caelestia/audioprovider.hpp +++ b/plugin/src/Caelestia/audioprovider.hpp @@ -1,7 +1,7 @@ #pragma once +#include "audiocollector.hpp" #include "service.hpp" -#include <cstdint> #include <qqmlintegration.h> #include <qtimer.h> @@ -11,14 +11,15 @@ class AudioProcessor : public QObject { Q_OBJECT public: - explicit AudioProcessor(QObject* parent = nullptr); + explicit AudioProcessor(AudioCollector* collector, QObject* parent = nullptr); ~AudioProcessor(); void init(); protected: - uint32_t m_sampleRate; - uint32_t m_chunkSize; + AudioCollector* m_collector; + + Q_INVOKABLE virtual void setCollector(AudioCollector* collector); private: QTimer* m_timer; @@ -32,11 +33,20 @@ private: class AudioProvider : public Service { Q_OBJECT + Q_PROPERTY(AudioCollector* collector READ collector WRITE setCollector NOTIFY collectorChanged) + public: explicit AudioProvider(QObject* parent = nullptr); ~AudioProvider(); + AudioCollector* collector() const; + void setCollector(AudioCollector* collector); + +signals: + void collectorChanged(); + protected: + AudioCollector* m_collector; AudioProcessor* m_processor; void init(); diff --git a/plugin/src/Caelestia/beattracker.cpp b/plugin/src/Caelestia/beattracker.cpp index a23d052..501739f 100644 --- a/plugin/src/Caelestia/beattracker.cpp +++ b/plugin/src/Caelestia/beattracker.cpp @@ -6,20 +6,52 @@ namespace caelestia { -BeatProcessor::BeatProcessor(QObject* parent) - : AudioProcessor(parent) - , m_tempo(new_aubio_tempo("default", 1024, m_chunkSize, m_sampleRate)) - , m_in(new_fvec(m_chunkSize)) - , m_out(new_fvec(2)) {}; +BeatProcessor::BeatProcessor(AudioCollector* collector, QObject* parent) + : AudioProcessor(collector, parent) + , m_tempo(nullptr) + , m_in(nullptr) + , m_out(new_fvec(2)) { + if (collector) { + m_tempo = new_aubio_tempo("default", 1024, collector->chunkSize(), collector->sampleRate()); + m_in = new_fvec(collector->chunkSize()); + } +}; BeatProcessor::~BeatProcessor() { - del_aubio_tempo(m_tempo); - del_fvec(m_in); + if (m_tempo) { + del_aubio_tempo(m_tempo); + } + if (m_in) { + del_fvec(m_in); + } del_fvec(m_out); } +void BeatProcessor::setCollector(AudioCollector* collector) { + AudioProcessor::setCollector(collector); + + if (m_tempo) { + del_aubio_tempo(m_tempo); + } + if (m_in) { + del_fvec(m_in); + } + + if (collector) { + m_tempo = new_aubio_tempo("default", 1024, collector->chunkSize(), collector->sampleRate()); + m_in = new_fvec(collector->chunkSize()); + } else { + m_tempo = nullptr; + m_in = nullptr; + } +} + void BeatProcessor::process() { - AudioCollector::instance()->readChunk(m_in->data, m_chunkSize); + if (!m_collector || !m_tempo || !m_in) { + return; + } + + m_collector->readChunk(m_in->data); aubio_tempo_do(m_tempo, m_in, m_out); if (m_out->data[0] != 0.0f) { @@ -30,7 +62,7 @@ void BeatProcessor::process() { BeatTracker::BeatTracker(QObject* parent) : AudioProvider(parent) , m_bpm(120) { - m_processor = new BeatProcessor(); + m_processor = new BeatProcessor(m_collector); init(); connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm); diff --git a/plugin/src/Caelestia/beattracker.hpp b/plugin/src/Caelestia/beattracker.hpp index c7737e1..ab18373 100644 --- a/plugin/src/Caelestia/beattracker.hpp +++ b/plugin/src/Caelestia/beattracker.hpp @@ -1,5 +1,6 @@ #pragma once +#include "audiocollector.hpp" #include "audioprovider.hpp" #include <aubio/aubio.h> #include <qqmlintegration.h> @@ -10,12 +11,15 @@ class BeatProcessor : public AudioProcessor { Q_OBJECT public: - explicit BeatProcessor(QObject* parent = nullptr); + explicit BeatProcessor(AudioCollector* collector, QObject* parent = nullptr); ~BeatProcessor(); signals: void beat(smpl_t bpm); +protected: + void setCollector(AudioCollector* collector) override; + private: aubio_tempo_t* m_tempo; fvec_t* m_in; @@ -27,7 +31,6 @@ private: class BeatTracker : public AudioProvider { Q_OBJECT QML_ELEMENT - QML_SINGLETON Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged) diff --git a/plugin/src/Caelestia/cavaprovider.cpp b/plugin/src/Caelestia/cavaprovider.cpp index d053702..76a1a10 100644 --- a/plugin/src/Caelestia/cavaprovider.cpp +++ b/plugin/src/Caelestia/cavaprovider.cpp @@ -9,16 +9,38 @@ namespace caelestia { -CavaProcessor::CavaProcessor(QObject* parent) - : AudioProcessor(parent) +CavaProcessor::CavaProcessor(AudioCollector* collector, QObject* parent) + : AudioProcessor(collector, parent) , m_plan(nullptr) - , m_in(new double[static_cast<size_t>(m_chunkSize)]) + , m_in(nullptr) , m_out(nullptr) - , m_bars(0) {}; + , m_bars(0) { + if (collector) { + m_in = new double[collector->chunkSize()]; + } +}; CavaProcessor::~CavaProcessor() { cleanup(); - delete[] m_in; + if (m_in) { + delete[] m_in; + } +} + +void CavaProcessor::setCollector(AudioCollector* collector) { + AudioProcessor::setCollector(collector); + + if (m_in) { + delete[] m_in; + } + + if (collector) { + m_in = new double[collector->chunkSize()]; + } else { + m_in = nullptr; + } + + reload(); } void CavaProcessor::setBars(int bars) { @@ -39,13 +61,11 @@ void CavaProcessor::reload() { } void CavaProcessor::cleanup() { - if (!m_plan) { - return; + if (m_plan) { + cava_destroy(m_plan); + m_plan = nullptr; } - cava_destroy(m_plan); - m_plan = nullptr; - if (m_out) { delete[] m_out; m_out = nullptr; @@ -53,11 +73,11 @@ void CavaProcessor::cleanup() { } void CavaProcessor::initCava() { - if (m_plan || m_bars == 0) { + if (m_plan || m_bars == 0 || !m_collector) { return; } - m_plan = cava_init(m_bars, static_cast<unsigned int>(m_sampleRate), 1, 1, 0.85, 50, 10000); + m_plan = cava_init(m_bars, m_collector->sampleRate(), 1, 1, 0.85, 50, 10000); if (m_plan->status == -1) { qWarning() << "CavaProcessor::initCava: failed to initialise cava plan"; @@ -69,11 +89,11 @@ void CavaProcessor::initCava() { } void CavaProcessor::process() { - if (!m_plan || m_bars == 0) { + if (!m_plan || m_bars == 0 || !m_collector || !m_in || !m_out) { return; } - const int count = static_cast<int>(AudioCollector::instance()->readChunk(m_in)); + const int count = static_cast<int>(m_collector->readChunk(m_in)); // Process in data via cava cava_execute(m_in, count, m_out, m_plan); @@ -100,8 +120,8 @@ void CavaProcessor::process() { CavaProvider::CavaProvider(QObject* parent) : AudioProvider(parent) , m_bars(0) - , m_values(m_bars) { - m_processor = new CavaProcessor(); + , m_values(m_bars, 0.0) { + m_processor = new CavaProcessor(m_collector); init(); connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues); @@ -121,7 +141,7 @@ void CavaProvider::setBars(int bars) { return; } - m_values.resize(bars); + m_values.resize(bars, 0.0); m_bars = bars; emit barsChanged(); emit valuesChanged(); diff --git a/plugin/src/Caelestia/cavaprovider.hpp b/plugin/src/Caelestia/cavaprovider.hpp index 131e166..6dab635 100644 --- a/plugin/src/Caelestia/cavaprovider.hpp +++ b/plugin/src/Caelestia/cavaprovider.hpp @@ -1,5 +1,6 @@ #pragma once +#include "audiocollector.hpp" #include "audioprovider.hpp" #include <cava/cavacore.h> #include <qqmlintegration.h> @@ -10,12 +11,15 @@ class CavaProcessor : public AudioProcessor { Q_OBJECT public: - explicit CavaProcessor(QObject* parent = nullptr); + explicit CavaProcessor(AudioCollector* collector, QObject* parent = nullptr); ~CavaProcessor(); signals: void valuesChanged(QVector<double> values); +protected: + void setCollector(AudioCollector* collector) override; + private: struct cava_plan* m_plan; double* m_in; |