summaryrefslogtreecommitdiff
path: root/plugin/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt4
-rw-r--r--plugin/src/Caelestia/audiocollector.cpp248
-rw-r--r--plugin/src/Caelestia/audiocollector.hpp76
-rw-r--r--plugin/src/Caelestia/audioprovider.cpp176
-rw-r--r--plugin/src/Caelestia/audioprovider.hpp59
-rw-r--r--plugin/src/Caelestia/beattracker.cpp21
-rw-r--r--plugin/src/Caelestia/beattracker.hpp6
-rw-r--r--plugin/src/Caelestia/cavaprovider.cpp17
-rw-r--r--plugin/src/Caelestia/cavaprovider.hpp6
-rw-r--r--plugin/src/Caelestia/service.cpp41
-rw-r--r--plugin/src/Caelestia/service.hpp4
11 files changed, 412 insertions, 246 deletions
diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt
index 3618eb8..bff65c5 100644
--- a/plugin/src/Caelestia/CMakeLists.txt
+++ b/plugin/src/Caelestia/CMakeLists.txt
@@ -1,5 +1,6 @@
find_package(PkgConfig REQUIRED)
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
+pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
@@ -14,6 +15,7 @@ qt_add_qml_module(caelestia
beattracker.hpp beattracker.cpp
service.hpp service.cpp
serviceref.hpp serviceref.cpp
+ audiocollector.hpp audiocollector.cpp
audioprovider.hpp audioprovider.cpp
cavaprovider.hpp cavaprovider.cpp
)
@@ -37,5 +39,5 @@ install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
target_link_libraries(caelestia PRIVATE
Qt::Core Qt::Qml Qt::Gui Qt::Concurrent Qt::Multimedia
- PkgConfig::Qalculate PkgConfig::Aubio PkgConfig::Cava
+ PkgConfig::Qalculate PkgConfig::Pipewire PkgConfig::Aubio PkgConfig::Cava
)
diff --git a/plugin/src/Caelestia/audiocollector.cpp b/plugin/src/Caelestia/audiocollector.cpp
new file mode 100644
index 0000000..817a310
--- /dev/null
+++ b/plugin/src/Caelestia/audiocollector.cpp
@@ -0,0 +1,248 @@
+#include "audiocollector.hpp"
+#include "service.hpp"
+
+#include <QDebug>
+#include <QVector>
+#include <algorithm>
+#include <cstdint>
+#include <mutex>
+#include <pipewire/pipewire.h>
+#include <qmutex.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/latency-utils.h>
+#include <stop_token>
+#include <vector>
+
+namespace caelestia {
+
+PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
+ : m_loop(nullptr)
+ , m_stream(nullptr)
+ , m_timer(nullptr)
+ , m_token(token)
+ , m_collector(collector) {
+ pw_init(nullptr, nullptr);
+
+ m_loop = pw_main_loop_new(nullptr);
+ if (!m_loop) {
+ qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
+ return;
+ }
+
+ timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
+ m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
+ pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
+
+ auto props = pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr);
+ pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * collector->sampleRate() / 48000),
+ collector->sampleRate());
+ pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
+ pw_properties_set(props, "channelmix.upmix", "true");
+
+ std::vector<uint8_t> buffer(collector->chunkSize());
+ spa_pod_builder b;
+ spa_pod_builder_init(&b, buffer.data(), static_cast<uint32_t>(buffer.size()));
+
+ spa_audio_info_raw info{};
+ info.format = SPA_AUDIO_FORMAT_S16;
+ info.rate = collector->sampleRate();
+ info.channels = 1;
+
+ const spa_pod* params[1];
+ params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
+
+ pw_stream_events events{};
+ events.process = [](void* data) {
+ auto* self = static_cast<PipeWireWorker*>(data);
+ self->processStream();
+ };
+
+ 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,
+ static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params, 1);
+
+ pw_main_loop_run(m_loop);
+
+ pw_stream_destroy(m_stream);
+ pw_main_loop_destroy(m_loop);
+ pw_deinit();
+}
+
+void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) {
+ Q_UNUSED(expirations);
+ auto* self = static_cast<PipeWireWorker*>(data);
+
+ if (self->m_token.stop_requested()) {
+ pw_main_loop_quit(self->m_loop);
+ }
+}
+
+void PipeWireWorker::processStream() {
+ if (m_token.stop_requested()) {
+ pw_main_loop_quit(m_loop);
+ return;
+ }
+
+ pw_buffer* buffer = pw_stream_dequeue_buffer(m_stream);
+ if (buffer == nullptr) {
+ return;
+ }
+
+ const spa_buffer* buf = buffer->buffer;
+ const int16_t* samples = reinterpret_cast<const int16_t*>(buf->datas[0].data);
+ if (samples == nullptr) {
+ return;
+ }
+
+ const uint32_t count = buf->datas[0].chunk->size / 2;
+ m_collector->loadChunk(samples, count);
+
+ pw_stream_queue_buffer(m_stream, buffer);
+}
+
+unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) {
+ if (n == 0) {
+ return 1;
+ }
+
+ n--;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n++;
+
+ return n;
+}
+
+AudioCollector::AudioCollector(uint32_t sampleRate, uint32_t chunkSize, uint32_t bufferSize, QObject* parent)
+ : Service(parent)
+ , m_buffer(bufferSize)
+ , m_bufferIndex(0)
+ , m_bufferFull(false)
+ , m_sampleRate(sampleRate)
+ , m_chunkSize(chunkSize)
+ , m_bufferSize(bufferSize) {}
+
+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;
+}
+
+uint32_t AudioCollector::chunkSize() const {
+ return m_chunkSize;
+}
+
+uint32_t AudioCollector::bufferSize() const {
+ return m_bufferSize;
+}
+
+void AudioCollector::loadChunk(const int16_t* samples, uint32_t count) {
+ std::lock_guard<std::mutex> lock(m_bufferMutex);
+
+ while (count > 0) {
+ const auto spaceToEnd = m_bufferSize - m_bufferIndex;
+ const auto toCopy = (count < spaceToEnd) ? count : spaceToEnd;
+
+ std::transform(samples, samples + toCopy, m_buffer.begin() + m_bufferIndex, [](int16_t sample) {
+ return sample / 32768.0f;
+ });
+
+ m_bufferIndex = (m_bufferIndex + toCopy) % m_bufferSize;
+ if (m_bufferIndex == 0) {
+ m_bufferFull = true;
+ }
+
+ samples += toCopy;
+ count -= toCopy;
+ }
+}
+
+uint32_t AudioCollector::readChunk(float* out, uint32_t count) {
+ std::lock_guard<std::mutex> lock(m_bufferMutex);
+
+ const auto available = m_bufferFull ? m_bufferSize : m_bufferIndex;
+ if (count == 0 || count > available) {
+ count = available;
+ }
+
+ const auto start = m_bufferFull ? (m_bufferIndex + m_bufferSize - count) % m_bufferSize
+ : (m_bufferIndex >= count ? m_bufferIndex - count : 0);
+ const auto firstChunk = std::min(count, m_bufferSize - start);
+
+ std::copy(m_buffer.begin() + start, m_buffer.begin() + start + firstChunk, out);
+
+ if (firstChunk < count) {
+ std::copy(m_buffer.begin(), m_buffer.begin() + (count - firstChunk), out + firstChunk);
+ }
+
+ return count;
+}
+
+uint32_t AudioCollector::readChunk(double* out, uint32_t count) {
+ std::lock_guard<std::mutex> lock(m_bufferMutex);
+
+ const auto available = m_bufferFull ? m_bufferSize : m_bufferIndex;
+ if (count == 0 || count > available) {
+ count = available;
+ }
+
+ const auto start = m_bufferFull ? (m_bufferIndex + m_bufferSize - count) % m_bufferSize
+ : (m_bufferIndex >= count ? m_bufferIndex - count : 0);
+ const auto firstChunk = std::min(count, m_bufferSize - start);
+
+ std::transform(m_buffer.begin() + start, m_buffer.begin() + start + firstChunk, out, [](float sample) {
+ return static_cast<double>(sample);
+ });
+
+ if (firstChunk < count) {
+ std::transform(m_buffer.begin(), m_buffer.begin() + (count - firstChunk), out + firstChunk, [](float sample) {
+ return static_cast<double>(sample);
+ });
+ }
+
+ return count;
+}
+
+void AudioCollector::start() {
+ if (m_thread.joinable()) {
+ return;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(m_bufferMutex);
+ m_buffer.clear();
+ m_bufferIndex = 0;
+ m_bufferFull = false;
+ }
+
+ m_thread = std::jthread([this](std::stop_token token) {
+ PipeWireWorker worker(token, this);
+ });
+}
+
+void AudioCollector::stop() {
+ if (m_thread.joinable()) {
+ m_thread.request_stop();
+ m_thread.join();
+ }
+}
+
+} // namespace caelestia
diff --git a/plugin/src/Caelestia/audiocollector.hpp b/plugin/src/Caelestia/audiocollector.hpp
new file mode 100644
index 0000000..3db294f
--- /dev/null
+++ b/plugin/src/Caelestia/audiocollector.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "service.hpp"
+#include <QObject>
+#include <cstdint>
+#include <mutex>
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <stop_token>
+#include <thread>
+#include <vector>
+
+namespace caelestia {
+
+class AudioCollector;
+
+class PipeWireWorker {
+public:
+ explicit PipeWireWorker(std::stop_token token, AudioCollector* collector);
+
+ void run();
+
+private:
+ pw_main_loop* m_loop;
+ pw_stream* m_stream;
+ spa_source* m_timer;
+
+ std::stop_token m_token;
+ AudioCollector* m_collector;
+
+ void cleanup();
+
+ static void handleTimeout(void* data, uint64_t expirations);
+ void processStream();
+ void processSamples(const int16_t* samples, uint32_t count);
+
+ [[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
+};
+
+class AudioCollector : public Service {
+ Q_OBJECT
+
+public:
+ explicit AudioCollector(
+ uint32_t sampleRate = 44100, uint32_t chunkSize = 512, uint32_t bufferSize = 1024, QObject* parent = nullptr);
+ ~AudioCollector();
+
+ static AudioCollector* instance();
+
+ [[nodiscard]] uint32_t sampleRate() const;
+ [[nodiscard]] uint32_t chunkSize() const;
+ [[nodiscard]] uint32_t bufferSize() const;
+
+ 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);
+
+private:
+ inline static AudioCollector* s_instance = nullptr;
+ inline static std::mutex s_mutex;
+
+ std::jthread m_thread;
+ std::vector<float> m_buffer;
+ uint32_t m_bufferIndex;
+ bool m_bufferFull;
+ std::mutex m_bufferMutex;
+
+ const uint32_t m_sampleRate;
+ const uint32_t m_chunkSize;
+ const uint32_t m_bufferSize;
+
+ void start() override;
+ void stop() override;
+};
+
+} // namespace caelestia
diff --git a/plugin/src/Caelestia/audioprovider.cpp b/plugin/src/Caelestia/audioprovider.cpp
index becfb63..008e360 100644
--- a/plugin/src/Caelestia/audioprovider.cpp
+++ b/plugin/src/Caelestia/audioprovider.cpp
@@ -1,5 +1,6 @@
#include "audioprovider.hpp"
+#include "audiocollector.hpp"
#include "service.hpp"
#include <QAudioSource>
#include <QDebug>
@@ -9,121 +10,27 @@
#include <QObject>
#include <QThread>
#include <QVector>
-#include <algorithm>
-#include <cstddef>
-#include <cstdint>
-#include <cstdlib>
namespace caelestia {
-AudioCollector::AudioCollector(AudioProvider* provider, QObject* parent)
+AudioProcessor::AudioProcessor(QObject* parent)
: QObject(parent)
- , m_source(nullptr)
- , m_device(nullptr)
- , m_provider(provider)
- , m_sampleRate(provider->sampleRate())
- , m_chunkSize(provider->chunkSize())
- , m_chunk(m_chunkSize)
- , m_chunkOffset(0) {}
-
-AudioCollector::~AudioCollector() {
- m_source->stop();
-}
-
-void AudioCollector::init() {
- QAudioFormat format;
- format.setSampleRate(m_sampleRate);
- format.setChannelCount(1);
- format.setSampleFormat(QAudioFormat::Int16);
-
- m_source = new QAudioSource(QMediaDevices::defaultAudioInput(), format, this);
- connect(m_source, &QAudioSource::stateChanged, this, &AudioCollector::handleStateChanged);
-};
-
-void AudioCollector::start() {
- if (!m_source) {
- return;
- }
-
- m_device = m_source->start();
- connect(m_device, &QIODevice::readyRead, this, &AudioCollector::loadChunk);
-}
-
-void AudioCollector::stop() {
- if (m_source) {
- m_source->stop();
- m_device = nullptr;
- }
-}
-
-void AudioCollector::loadChunk() {
- const QByteArray data = m_device->readAll();
- const int16_t* samples = reinterpret_cast<const int16_t*>(data.constData());
- const size_t count = static_cast<size_t>(data.size()) / sizeof(int16_t);
-
- size_t i = 0;
- while (i < count) {
- const int spaceLeft = m_chunkSize - m_chunkOffset;
- const auto toCopy = std::min<size_t>(static_cast<size_t>(spaceLeft), count - i);
-
- std::transform(samples + i, samples + i + toCopy, m_chunk.begin() + m_chunkOffset, [](int16_t sample) {
- return sample / 32768.0;
- });
-
- m_chunkOffset += toCopy;
- i += toCopy;
-
- if (m_chunkOffset == m_chunkSize) {
- if (std::any_of(m_chunk.constBegin(), m_chunk.constEnd(), [](double d) {
- return std::abs(d) > 1e-6;
- })) {
- m_provider->loadChunk(m_chunk);
- }
- m_chunkOffset = 0;
- }
- }
-}
-
-void AudioCollector::handleStateChanged(QtAudio::State state) const {
- if (state == QtAudio::StoppedState && m_source->error() != QtAudio::NoError) {
- switch (m_source->error()) {
- case QtAudio::OpenError:
- qWarning() << "AudioWorker: failed to open audio device";
- break;
- case QtAudio::IOError:
- qWarning() << "AudioWorker: an error occurred during read/write of audio device";
- break;
- case QtAudio::UnderrunError:
- qWarning() << "AudioWorker: audio data is not being fed to audio device fast enough";
- break;
- case QtAudio::FatalError:
- qCritical() << "AudioWorker: fatal error in audio device";
- break;
- default:
- break;
- }
- }
-}
-
-AudioProcessor::AudioProcessor(AudioProvider* provider, QObject* parent)
- : QObject(parent)
- , m_sampleRate(provider->sampleRate())
- , m_chunkSize(provider->chunkSize())
- , m_provider(provider) {}
+ , m_sampleRate(AudioCollector::instance()->sampleRate())
+ , m_chunkSize(AudioCollector::instance()->chunkSize())
+ , m_bufferSize(AudioCollector::instance()->bufferSize()) {}
AudioProcessor::~AudioProcessor() {
- if (m_timer) {
- m_timer->stop();
- }
+ stop();
}
void AudioProcessor::init() {
m_timer = new QTimer(this);
m_timer->setInterval(static_cast<int>(m_chunkSize * 1000.0 / m_sampleRate));
- connect(m_timer, &QTimer::timeout, this, &AudioProcessor::handleTimeout);
+ connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
}
void AudioProcessor::start() {
+ AudioCollector::instance()->ref();
if (m_timer) {
m_timer->start();
}
@@ -133,60 +40,19 @@ void AudioProcessor::stop() {
if (m_timer) {
m_timer->stop();
}
+ AudioCollector::instance()->unref();
}
-void AudioProcessor::handleTimeout() {
- const QVector<double> chunk = m_provider->nextChunk();
- if (!chunk.isEmpty()) {
- processChunk(chunk);
- }
-}
-
-AudioProvider::AudioProvider(int sampleRate, int chunkSize, QObject* parent)
+AudioProvider::AudioProvider(QObject* parent)
: Service(parent)
- , m_sampleRate(sampleRate)
- , m_chunkSize(chunkSize)
- , m_collector(new AudioCollector(this))
, m_processor(nullptr)
- , m_collectorThread(new QThread(this))
- , m_processorThread(nullptr) {
- m_collector->moveToThread(m_collectorThread);
-
- connect(m_collectorThread, &QThread::started, m_collector, &AudioCollector::init);
- connect(m_collectorThread, &QThread::finished, m_collector, &AudioCollector::deleteLater);
- connect(m_collectorThread, &QThread::finished, m_collectorThread, &QThread::deleteLater);
-
- m_collectorThread->start();
-}
+ , m_thread(nullptr) {}
AudioProvider::~AudioProvider() {
- m_collectorThread->quit();
- if (m_processorThread) {
- m_processorThread->quit();
- m_processorThread->wait();
- }
- m_collectorThread->wait();
-}
-
-int AudioProvider::sampleRate() const {
- return m_sampleRate;
-}
-
-int AudioProvider::chunkSize() const {
- return m_chunkSize;
-}
-
-QVector<double> AudioProvider::nextChunk() {
- QMutexLocker lock(&m_mutex);
- if (m_chunks.isEmpty()) {
- return {};
+ if (m_thread) {
+ m_thread->quit();
+ m_thread->wait();
}
- return m_chunks.dequeue();
-}
-
-void AudioProvider::loadChunk(const QVector<double>& chunk) {
- QMutexLocker lock(&m_mutex);
- m_chunks.enqueue(chunk);
}
void AudioProvider::init() {
@@ -195,25 +61,23 @@ void AudioProvider::init() {
return;
}
- m_processorThread = new QThread(this);
- m_processor->moveToThread(m_processorThread);
+ m_thread = new QThread(this);
+ m_processor->moveToThread(m_thread);
- connect(m_processorThread, &QThread::started, m_processor, &AudioProcessor::init);
- connect(m_processorThread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
- connect(m_processorThread, &QThread::finished, m_processorThread, &QThread::deleteLater);
+ connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init);
+ connect(m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
+ connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
- m_processorThread->start();
+ m_thread->start();
}
void AudioProvider::start() {
- QMetaObject::invokeMethod(m_collector, "start", Qt::QueuedConnection);
if (m_processor) {
QMetaObject::invokeMethod(m_processor, "start", Qt::QueuedConnection);
}
}
void AudioProvider::stop() {
- QMetaObject::invokeMethod(m_collector, "stop", Qt::QueuedConnection);
if (m_processor) {
QMetaObject::invokeMethod(m_processor, "stop", Qt::QueuedConnection);
}
diff --git a/plugin/src/Caelestia/audioprovider.hpp b/plugin/src/Caelestia/audioprovider.hpp
index 44c6890..51ee930 100644
--- a/plugin/src/Caelestia/audioprovider.hpp
+++ b/plugin/src/Caelestia/audioprovider.hpp
@@ -9,91 +9,48 @@
#include <QThread>
#include <QTimer>
#include <QVector>
+#include <cstdint>
#include <qqmlintegration.h>
namespace caelestia {
-class AudioProvider;
-
-class AudioCollector : public QObject {
- Q_OBJECT
-
-public:
- explicit AudioCollector(AudioProvider* provider, QObject* parent = nullptr);
- ~AudioCollector();
-
- void init();
-
-private:
- QAudioSource* m_source;
- QIODevice* m_device;
-
- AudioProvider* m_provider;
- int m_sampleRate;
- int m_chunkSize;
-
- QVector<double> m_chunk;
- int m_chunkOffset;
-
- Q_INVOKABLE void start();
- Q_INVOKABLE void stop();
-
- void handleStateChanged(QtAudio::State state) const;
- void loadChunk();
-};
-
class AudioProcessor : public QObject {
Q_OBJECT
public:
- explicit AudioProcessor(AudioProvider* provider, QObject* parent = nullptr);
+ explicit AudioProcessor(QObject* parent = nullptr);
~AudioProcessor();
void init();
protected:
- int m_sampleRate;
- int m_chunkSize;
+ uint32_t m_sampleRate;
+ uint32_t m_chunkSize;
+ uint32_t m_bufferSize;
private:
- AudioProvider* m_provider;
QTimer* m_timer;
Q_INVOKABLE void start();
Q_INVOKABLE void stop();
- void handleTimeout();
- virtual void processChunk(const QVector<double>& chunk) = 0;
+ virtual void process() = 0;
};
class AudioProvider : public Service {
Q_OBJECT
public:
- explicit AudioProvider(int sampleRate = 44100, int chunkSize = 512, QObject* parent = nullptr);
+ explicit AudioProvider(QObject* parent = nullptr);
~AudioProvider();
- [[nodiscard]] int sampleRate() const;
- [[nodiscard]] int chunkSize() const;
-
- [[nodiscard]] QVector<double> nextChunk();
- void loadChunk(const QVector<double>& chunk);
-
protected:
- int m_sampleRate;
- int m_chunkSize;
-
- QMutex m_mutex;
- QQueue<QVector<double>> m_chunks;
-
- AudioCollector* m_collector;
AudioProcessor* m_processor;
void init();
private:
- QThread* m_collectorThread;
- QThread* m_processorThread;
+ QThread* m_thread;
void start() override;
void stop() override;
diff --git a/plugin/src/Caelestia/beattracker.cpp b/plugin/src/Caelestia/beattracker.cpp
index 11938d0..d82da6e 100644
--- a/plugin/src/Caelestia/beattracker.cpp
+++ b/plugin/src/Caelestia/beattracker.cpp
@@ -1,15 +1,16 @@
#include "beattracker.hpp"
+#include "audiocollector.hpp"
#include "audioprovider.hpp"
#include <QObject>
#include <aubio/aubio.h>
namespace caelestia {
-BeatProcessor::BeatProcessor(AudioProvider* provider, QObject* parent)
- : AudioProcessor(provider, parent)
- , m_tempo(new_aubio_tempo("default", 1024, static_cast<uint_t>(m_chunkSize), static_cast<uint_t>(m_sampleRate)))
- , m_in(new_fvec(static_cast<uint_t>(m_chunkSize)))
+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() {
@@ -18,10 +19,8 @@ BeatProcessor::~BeatProcessor() {
del_fvec(m_out);
}
-void BeatProcessor::processChunk(const QVector<double>& chunk) {
- std::transform(chunk.constBegin(), chunk.constEnd(), m_in->data, [](double d) {
- return static_cast<float>(d);
- });
+void BeatProcessor::process() {
+ AudioCollector::instance()->readChunk(m_in->data, m_chunkSize);
aubio_tempo_do(m_tempo, m_in, m_out);
if (m_out->data[0] != 0.0f) {
@@ -29,10 +28,10 @@ void BeatProcessor::processChunk(const QVector<double>& chunk) {
}
}
-BeatTracker::BeatTracker(int sampleRate, int chunkSize, QObject* parent)
- : AudioProvider(sampleRate, chunkSize, parent)
+BeatTracker::BeatTracker(QObject* parent)
+ : AudioProvider(parent)
, m_bpm(120) {
- m_processor = new BeatProcessor(this);
+ m_processor = new BeatProcessor();
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 a490f9d..fa0ce8a 100644
--- a/plugin/src/Caelestia/beattracker.hpp
+++ b/plugin/src/Caelestia/beattracker.hpp
@@ -11,7 +11,7 @@ class BeatProcessor : public AudioProcessor {
Q_OBJECT
public:
- explicit BeatProcessor(AudioProvider* provider, QObject* parent = nullptr);
+ explicit BeatProcessor(QObject* parent = nullptr);
~BeatProcessor();
signals:
@@ -22,7 +22,7 @@ private:
fvec_t* m_in;
fvec_t* m_out;
- void processChunk(const QVector<double>& chunk) override;
+ void process() override;
};
class BeatTracker : public AudioProvider {
@@ -33,7 +33,7 @@ class BeatTracker : public AudioProvider {
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
public:
- explicit BeatTracker(int sampleRate = 44100, int chunkSize = 512, QObject* parent = nullptr);
+ explicit BeatTracker(QObject* parent = nullptr);
[[nodiscard]] smpl_t bpm() const;
diff --git a/plugin/src/Caelestia/cavaprovider.cpp b/plugin/src/Caelestia/cavaprovider.cpp
index 8841ded..024c75d 100644
--- a/plugin/src/Caelestia/cavaprovider.cpp
+++ b/plugin/src/Caelestia/cavaprovider.cpp
@@ -1,5 +1,6 @@
#include "cavaprovider.hpp"
+#include "audiocollector.hpp"
#include "audioprovider.hpp"
#include <QDebug>
#include <QObject>
@@ -9,8 +10,8 @@
namespace caelestia {
-CavaProcessor::CavaProcessor(AudioProvider* provider, QObject* parent)
- : AudioProcessor(provider, parent)
+CavaProcessor::CavaProcessor(QObject* parent)
+ : AudioProcessor(parent)
, m_plan(nullptr)
, m_in(new double[static_cast<size_t>(m_chunkSize)])
, m_out(nullptr)
@@ -68,15 +69,15 @@ void CavaProcessor::initCava() {
m_out = new double[static_cast<size_t>(m_bars)];
}
-void CavaProcessor::processChunk(const QVector<double>& chunk) {
+void CavaProcessor::process() {
if (!m_plan || m_bars == 0) {
return;
}
- std::copy(chunk.constBegin(), chunk.constEnd(), m_in);
+ const int count = static_cast<int>(AudioCollector::instance()->readChunk(m_in));
// Process in data via cava
- cava_execute(m_in, m_chunkSize, m_out, m_plan);
+ cava_execute(m_in, count, m_out, m_plan);
// Apply monstercat filter
for (int i = 0; i < m_bars; i++) {
@@ -97,11 +98,11 @@ void CavaProcessor::processChunk(const QVector<double>& chunk) {
}
}
-CavaProvider::CavaProvider(int sampleRate, int chunkSize, QObject* parent)
- : AudioProvider(sampleRate, chunkSize, parent)
+CavaProvider::CavaProvider(QObject* parent)
+ : AudioProvider(parent)
, m_bars(0)
, m_values(m_bars) {
- m_processor = new CavaProcessor(this);
+ m_processor = new CavaProcessor();
init();
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
diff --git a/plugin/src/Caelestia/cavaprovider.hpp b/plugin/src/Caelestia/cavaprovider.hpp
index d819f54..6cc6aba 100644
--- a/plugin/src/Caelestia/cavaprovider.hpp
+++ b/plugin/src/Caelestia/cavaprovider.hpp
@@ -11,7 +11,7 @@ class CavaProcessor : public AudioProcessor {
Q_OBJECT
public:
- explicit CavaProcessor(AudioProvider* provider, QObject* parent = nullptr);
+ explicit CavaProcessor(QObject* parent = nullptr);
~CavaProcessor();
signals:
@@ -31,7 +31,7 @@ private:
void initCava();
void cleanup();
- void processChunk(const QVector<double>& chunk) override;
+ void process() override;
};
class CavaProvider : public AudioProvider {
@@ -43,7 +43,7 @@ class CavaProvider : public AudioProvider {
Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
public:
- explicit CavaProvider(int sampleRate = 48000, int chunkSize = 512, QObject* parent = nullptr);
+ explicit CavaProvider(QObject* parent = nullptr);
[[nodiscard]] int bars() const;
void setBars(int bars);
diff --git a/plugin/src/Caelestia/service.cpp b/plugin/src/Caelestia/service.cpp
index 9c13421..5d5232b 100644
--- a/plugin/src/Caelestia/service.cpp
+++ b/plugin/src/Caelestia/service.cpp
@@ -1,6 +1,7 @@
#include "service.hpp"
#include <QDebug>
+#include <QMutexLocker>
#include <QObject>
namespace caelestia {
@@ -9,30 +10,46 @@ Service::Service(QObject* parent)
: QObject(parent)
, m_refCount(0) {}
-int Service::refCount() const {
+int Service::refCount() {
+ QMutexLocker locker(&m_mutex);
return m_refCount;
}
void Service::ref() {
- if (m_refCount == 0) {
- start();
- }
+ bool needsStart = false;
- m_refCount++;
+ {
+ QMutexLocker locker(&m_mutex);
+ if (m_refCount == 0) {
+ needsStart = true;
+ }
+ m_refCount++;
+ }
emit refCountChanged();
+
+ if (needsStart) {
+ QMetaObject::invokeMethod(this, &Service::start, Qt::QueuedConnection);
+ }
}
void Service::unref() {
- if (m_refCount == 0) {
- qWarning() << "ServiceRef::unref: attempted to unref service with no active refs";
- return;
- }
+ bool needsStop = false;
- m_refCount--;
+ {
+ QMutexLocker locker(&m_mutex);
+ if (m_refCount == 0) {
+ qWarning() << "ServiceRef::unref: attempted to unref service with no active refs";
+ return;
+ }
+ m_refCount--;
+ if (m_refCount == 0) {
+ needsStop = true;
+ }
+ }
emit refCountChanged();
- if (m_refCount == 0) {
- stop();
+ if (needsStop) {
+ QMetaObject::invokeMethod(this, &Service::stop, Qt::QueuedConnection);
}
}
diff --git a/plugin/src/Caelestia/service.hpp b/plugin/src/Caelestia/service.hpp
index 861d715..40e9110 100644
--- a/plugin/src/Caelestia/service.hpp
+++ b/plugin/src/Caelestia/service.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <QMutex>
#include <QObject>
namespace caelestia {
@@ -12,7 +13,7 @@ class Service : public QObject {
public:
explicit Service(QObject* parent = nullptr);
- [[nodiscard]] int refCount() const;
+ [[nodiscard]] int refCount();
void ref();
void unref();
@@ -22,6 +23,7 @@ signals:
private:
int m_refCount;
+ QMutex m_mutex;
virtual void start() = 0;
virtual void stop() = 0;