parent
172c1a047d
commit
1984458565
12 changed files with 108 additions and 295 deletions
|
|
@ -35,20 +35,20 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
|
|||
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_setf(
|
||||
props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), ac::SAMPLE_RATE);
|
||||
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());
|
||||
std::vector<uint8_t> buffer(ac::CHUNK_SIZE);
|
||||
spa_pod_builder b;
|
||||
spa_pod_builder_init(&b, buffer.data(), static_cast<quint32>(buffer.size()));
|
||||
|
||||
spa_audio_info_raw info{};
|
||||
info.format = SPA_AUDIO_FORMAT_S16;
|
||||
info.rate = collector->sampleRate();
|
||||
info.rate = ac::SAMPLE_RATE;
|
||||
info.channels = 1;
|
||||
|
||||
const spa_pod* params[1];
|
||||
|
|
@ -66,7 +66,7 @@ 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);
|
||||
|
||||
const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, collector->nodeId(),
|
||||
const int success = 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 | PW_STREAM_FLAG_RT_PROCESS),
|
||||
params, 1);
|
||||
|
|
@ -162,49 +162,9 @@ unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) {
|
|||
return n;
|
||||
}
|
||||
|
||||
AudioCollector::AudioCollector(QObject* parent)
|
||||
: Service(parent)
|
||||
, 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) {}
|
||||
|
||||
AudioCollector::~AudioCollector() {
|
||||
stop();
|
||||
}
|
||||
|
||||
quint32 AudioCollector::sampleRate() const {
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
quint32 AudioCollector::chunkSize() const {
|
||||
return m_chunkSize;
|
||||
}
|
||||
|
||||
quint32 AudioCollector::nodeId() {
|
||||
QMutexLocker locker(&m_nodeIdMutex);
|
||||
return m_nodeId;
|
||||
}
|
||||
|
||||
void AudioCollector::setNodeId(quint32 nodeId) {
|
||||
{
|
||||
QMutexLocker locker(&m_nodeIdMutex);
|
||||
|
||||
if (nodeId == m_nodeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_nodeId = nodeId;
|
||||
}
|
||||
emit nodeIdChanged();
|
||||
|
||||
if (m_thread.joinable()) {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
AudioCollector& AudioCollector::instance() {
|
||||
static AudioCollector instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AudioCollector::clearBuffer() {
|
||||
|
|
@ -216,8 +176,8 @@ void AudioCollector::clearBuffer() {
|
|||
}
|
||||
|
||||
void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
|
||||
if (count > m_chunkSize) {
|
||||
count = m_chunkSize;
|
||||
if (count > ac::CHUNK_SIZE) {
|
||||
count = ac::CHUNK_SIZE;
|
||||
}
|
||||
|
||||
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
||||
|
|
@ -230,8 +190,8 @@ void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
|
|||
}
|
||||
|
||||
quint32 AudioCollector::readChunk(float* out, quint32 count) {
|
||||
if (count == 0 || count > m_chunkSize) {
|
||||
count = m_chunkSize;
|
||||
if (count == 0 || count > ac::CHUNK_SIZE) {
|
||||
count = ac::CHUNK_SIZE;
|
||||
}
|
||||
|
||||
auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
|
||||
|
|
@ -241,8 +201,8 @@ quint32 AudioCollector::readChunk(float* out, quint32 count) {
|
|||
}
|
||||
|
||||
quint32 AudioCollector::readChunk(double* out, quint32 count) {
|
||||
if (count == 0 || count > m_chunkSize) {
|
||||
count = m_chunkSize;
|
||||
if (count == 0 || count > ac::CHUNK_SIZE) {
|
||||
count = ac::CHUNK_SIZE;
|
||||
}
|
||||
|
||||
auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
|
||||
|
|
@ -253,6 +213,17 @@ quint32 AudioCollector::readChunk(double* out, quint32 count) {
|
|||
return count;
|
||||
}
|
||||
|
||||
AudioCollector::AudioCollector(QObject* parent)
|
||||
: Service(parent)
|
||||
, m_buffer1(ac::CHUNK_SIZE)
|
||||
, m_buffer2(ac::CHUNK_SIZE)
|
||||
, m_readBuffer(&m_buffer1)
|
||||
, m_writeBuffer(&m_buffer2) {}
|
||||
|
||||
AudioCollector::~AudioCollector() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void AudioCollector::start() {
|
||||
if (m_thread.joinable()) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@
|
|||
|
||||
namespace caelestia {
|
||||
|
||||
namespace ac {
|
||||
|
||||
constexpr quint32 SAMPLE_RATE = 44100;
|
||||
constexpr quint32 CHUNK_SIZE = 512;
|
||||
|
||||
} // namespace ac
|
||||
|
||||
class AudioCollector;
|
||||
|
||||
class PipeWireWorker {
|
||||
|
|
@ -29,47 +36,30 @@ private:
|
|||
std::stop_token m_token;
|
||||
AudioCollector* m_collector;
|
||||
|
||||
void cleanup();
|
||||
|
||||
static void handleTimeout(void* data, uint64_t expirations);
|
||||
void streamStateChanged(pw_stream_state state);
|
||||
void processStream();
|
||||
void processSamples(const qint16* samples, quint32 count);
|
||||
|
||||
[[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
|
||||
};
|
||||
|
||||
class AudioCollector : public Service {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(quint32 nodeId READ nodeId WRITE setNodeId NOTIFY nodeIdChanged)
|
||||
|
||||
public:
|
||||
explicit AudioCollector(QObject* parent = nullptr);
|
||||
~AudioCollector();
|
||||
AudioCollector(const AudioCollector&) = delete;
|
||||
AudioCollector& operator=(const AudioCollector&) = delete;
|
||||
|
||||
[[nodiscard]] quint32 sampleRate() const;
|
||||
[[nodiscard]] quint32 chunkSize() const;
|
||||
|
||||
[[nodiscard]] quint32 nodeId();
|
||||
void setNodeId(quint32 nodeId);
|
||||
static AudioCollector& instance();
|
||||
|
||||
void clearBuffer();
|
||||
void loadChunk(const qint16* samples, quint32 count);
|
||||
quint32 readChunk(float* out, quint32 count = 0);
|
||||
quint32 readChunk(double* out, quint32 count = 0);
|
||||
|
||||
signals:
|
||||
void sampleRateChanged();
|
||||
void chunkSizeChanged();
|
||||
void nodeIdChanged();
|
||||
|
||||
private:
|
||||
const quint32 m_sampleRate;
|
||||
const quint32 m_chunkSize;
|
||||
quint32 m_nodeId;
|
||||
QMutex m_nodeIdMutex;
|
||||
explicit AudioCollector(QObject* parent = nullptr);
|
||||
~AudioCollector();
|
||||
|
||||
std::jthread m_thread;
|
||||
std::vector<float> m_buffer1;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@
|
|||
|
||||
namespace caelestia {
|
||||
|
||||
AudioProcessor::AudioProcessor(AudioCollector* collector, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_collector(collector) {}
|
||||
AudioProcessor::AudioProcessor(QObject* parent)
|
||||
: QObject(parent) {}
|
||||
|
||||
AudioProcessor::~AudioProcessor() {
|
||||
stop();
|
||||
|
|
@ -17,53 +16,26 @@ AudioProcessor::~AudioProcessor() {
|
|||
|
||||
void AudioProcessor::init() {
|
||||
m_timer = new QTimer(this);
|
||||
if (m_collector) {
|
||||
m_timer->setInterval(static_cast<int>(m_collector->chunkSize() * 1000.0 / m_collector->sampleRate()));
|
||||
}
|
||||
m_timer->setInterval(static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE));
|
||||
connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
|
||||
}
|
||||
|
||||
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();
|
||||
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::ref);
|
||||
if (m_timer) {
|
||||
m_timer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessor::stop() {
|
||||
if (m_timer && m_collector) {
|
||||
if (m_timer) {
|
||||
m_timer->stop();
|
||||
m_collector->unref();
|
||||
}
|
||||
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::unref);
|
||||
}
|
||||
|
||||
AudioProvider::AudioProvider(QObject* parent)
|
||||
: Service(parent)
|
||||
, m_collector(nullptr)
|
||||
, m_processor(nullptr)
|
||||
, m_thread(nullptr) {}
|
||||
|
||||
|
|
@ -74,23 +46,6 @@ 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";
|
||||
|
|
@ -109,13 +64,14 @@ void AudioProvider::init() {
|
|||
|
||||
void AudioProvider::start() {
|
||||
if (m_processor) {
|
||||
QMetaObject::invokeMethod(m_processor, "start", Qt::QueuedConnection);
|
||||
AudioCollector::instance(); // Create instance on main thread
|
||||
QMetaObject::invokeMethod(m_processor, &AudioProcessor::start);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProvider::stop() {
|
||||
if (m_processor) {
|
||||
QMetaObject::invokeMethod(m_processor, "stop", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_processor, &AudioProcessor::stop);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "audiocollector.hpp"
|
||||
#include "service.hpp"
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtimer.h>
|
||||
|
|
@ -11,42 +10,30 @@ class AudioProcessor : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AudioProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||
explicit AudioProcessor(QObject* parent = nullptr);
|
||||
~AudioProcessor();
|
||||
|
||||
void init();
|
||||
|
||||
protected:
|
||||
AudioCollector* m_collector;
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
Q_INVOKABLE virtual void setCollector(AudioCollector* collector);
|
||||
protected:
|
||||
virtual void process() = 0;
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
Q_INVOKABLE void stop();
|
||||
|
||||
virtual void process() = 0;
|
||||
};
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -6,16 +6,11 @@
|
|||
|
||||
namespace caelestia {
|
||||
|
||||
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(QObject* parent)
|
||||
: AudioProcessor(parent)
|
||||
, m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE))
|
||||
, m_in(new_fvec(ac::CHUNK_SIZE))
|
||||
, m_out(new_fvec(2)) {};
|
||||
|
||||
BeatProcessor::~BeatProcessor() {
|
||||
if (m_tempo) {
|
||||
|
|
@ -27,31 +22,12 @@ BeatProcessor::~BeatProcessor() {
|
|||
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() {
|
||||
if (!m_collector || !m_tempo || !m_in) {
|
||||
if (!m_tempo || !m_in) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_collector->readChunk(m_in->data);
|
||||
AudioCollector::instance().readChunk(m_in->data);
|
||||
|
||||
aubio_tempo_do(m_tempo, m_in, m_out);
|
||||
if (!qFuzzyIsNull(m_out->data[0])) {
|
||||
|
|
@ -62,7 +38,7 @@ void BeatProcessor::process() {
|
|||
BeatTracker::BeatTracker(QObject* parent)
|
||||
: AudioProvider(parent)
|
||||
, m_bpm(120) {
|
||||
m_processor = new BeatProcessor(m_collector);
|
||||
m_processor = new BeatProcessor();
|
||||
init();
|
||||
|
||||
connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "audiocollector.hpp"
|
||||
#include "audioprovider.hpp"
|
||||
#include <aubio/aubio.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
|
@ -11,21 +10,19 @@ class BeatProcessor : public AudioProcessor {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BeatProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||
explicit BeatProcessor(QObject* parent = nullptr);
|
||||
~BeatProcessor();
|
||||
|
||||
signals:
|
||||
void beat(smpl_t bpm);
|
||||
|
||||
protected:
|
||||
void setCollector(AudioCollector* collector) override;
|
||||
void process() override;
|
||||
|
||||
private:
|
||||
aubio_tempo_t* m_tempo;
|
||||
fvec_t* m_in;
|
||||
fvec_t* m_out;
|
||||
|
||||
void process() override;
|
||||
};
|
||||
|
||||
class BeatTracker : public AudioProvider {
|
||||
|
|
|
|||
|
|
@ -9,38 +9,45 @@
|
|||
|
||||
namespace caelestia {
|
||||
|
||||
CavaProcessor::CavaProcessor(AudioCollector* collector, QObject* parent)
|
||||
: AudioProcessor(collector, parent)
|
||||
CavaProcessor::CavaProcessor(QObject* parent)
|
||||
: AudioProcessor(parent)
|
||||
, m_plan(nullptr)
|
||||
, m_in(nullptr)
|
||||
, m_in(new double[ac::CHUNK_SIZE])
|
||||
, m_out(nullptr)
|
||||
, m_bars(0) {
|
||||
if (collector) {
|
||||
m_in = new double[collector->chunkSize()];
|
||||
}
|
||||
};
|
||||
, m_bars(0) {};
|
||||
|
||||
CavaProcessor::~CavaProcessor() {
|
||||
cleanup();
|
||||
if (m_in) {
|
||||
delete[] m_in;
|
||||
}
|
||||
delete[] m_in;
|
||||
}
|
||||
|
||||
void CavaProcessor::setCollector(AudioCollector* collector) {
|
||||
AudioProcessor::setCollector(collector);
|
||||
|
||||
if (m_in) {
|
||||
delete[] m_in;
|
||||
void CavaProcessor::process() {
|
||||
if (!m_plan || m_bars == 0 || !m_out) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (collector) {
|
||||
m_in = new double[collector->chunkSize()];
|
||||
} else {
|
||||
m_in = nullptr;
|
||||
const int count = static_cast<int>(AudioCollector::instance().readChunk(m_in));
|
||||
|
||||
// Process in data via cava
|
||||
cava_execute(m_in, count, 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]);
|
||||
}
|
||||
}
|
||||
|
||||
reload();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void CavaProcessor::setBars(int bars) {
|
||||
|
|
@ -73,11 +80,11 @@ void CavaProcessor::cleanup() {
|
|||
}
|
||||
|
||||
void CavaProcessor::initCava() {
|
||||
if (m_plan || m_bars == 0 || !m_collector) {
|
||||
if (m_plan || m_bars == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_plan = cava_init(m_bars, m_collector->sampleRate(), 1, 1, 0.85, 50, 10000);
|
||||
m_plan = cava_init(m_bars, ac::SAMPLE_RATE, 1, 1, 0.85, 50, 10000);
|
||||
|
||||
if (m_plan->status == -1) {
|
||||
qWarning() << "CavaProcessor::initCava: failed to initialise cava plan";
|
||||
|
|
@ -88,40 +95,11 @@ void CavaProcessor::initCava() {
|
|||
m_out = new double[static_cast<size_t>(m_bars)];
|
||||
}
|
||||
|
||||
void CavaProcessor::process() {
|
||||
if (!m_plan || m_bars == 0 || !m_collector || !m_in || !m_out) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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(QObject* parent)
|
||||
: AudioProvider(parent)
|
||||
, m_bars(0)
|
||||
, m_values(m_bars, 0.0) {
|
||||
m_processor = new CavaProcessor(m_collector);
|
||||
m_processor = new CavaProcessor();
|
||||
init();
|
||||
|
||||
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "audiocollector.hpp"
|
||||
#include "audioprovider.hpp"
|
||||
#include <cava/cavacore.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
|
@ -11,14 +10,14 @@ class CavaProcessor : public AudioProcessor {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CavaProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||
explicit CavaProcessor(QObject* parent = nullptr);
|
||||
~CavaProcessor();
|
||||
|
||||
signals:
|
||||
void valuesChanged(QVector<double> values);
|
||||
|
||||
protected:
|
||||
void setCollector(AudioCollector* collector) override;
|
||||
void process() override;
|
||||
|
||||
private:
|
||||
struct cava_plan* m_plan;
|
||||
|
|
@ -33,8 +32,6 @@ private:
|
|||
void reload();
|
||||
void initCava();
|
||||
void cleanup();
|
||||
|
||||
void process() override;
|
||||
};
|
||||
|
||||
class CavaProvider : public AudioProvider {
|
||||
|
|
|
|||
|
|
@ -10,60 +10,28 @@ Service::Service(QObject* parent)
|
|||
, m_refCount(0) {}
|
||||
|
||||
int Service::refCount() const {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_refCount;
|
||||
}
|
||||
|
||||
void Service::ref() {
|
||||
bool needsStart = false;
|
||||
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_refCount == 0) {
|
||||
needsStart = true;
|
||||
}
|
||||
m_refCount++;
|
||||
if (m_refCount == 0) {
|
||||
start();
|
||||
}
|
||||
|
||||
m_refCount++;
|
||||
emit refCountChanged();
|
||||
|
||||
if (needsStart) {
|
||||
const QPointer<Service> self(this);
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[self]() {
|
||||
if (self) {
|
||||
self->start();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void Service::unref() {
|
||||
bool needsStop = false;
|
||||
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_refCount == 0) {
|
||||
return;
|
||||
}
|
||||
m_refCount--;
|
||||
if (m_refCount == 0) {
|
||||
needsStop = true;
|
||||
}
|
||||
if (m_refCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_refCount--;
|
||||
emit refCountChanged();
|
||||
|
||||
if (needsStop) {
|
||||
const QPointer<Service> self(this);
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[self]() {
|
||||
if (self) {
|
||||
self->stop();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
if (m_refCount == 0) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ signals:
|
|||
|
||||
private:
|
||||
int m_refCount;
|
||||
mutable QMutex m_mutex;
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "service.hpp"
|
||||
#include <qpointer.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
namespace caelestia {
|
||||
|
|
@ -22,7 +23,7 @@ signals:
|
|||
void serviceChanged();
|
||||
|
||||
private:
|
||||
Service* m_service;
|
||||
QPointer<Service> m_service;
|
||||
};
|
||||
|
||||
} // namespace caelestia
|
||||
|
|
|
|||
|
|
@ -78,20 +78,13 @@ Singleton {
|
|||
objects: [...root.sinks, ...root.sources]
|
||||
}
|
||||
|
||||
AudioCollector {
|
||||
id: collector
|
||||
}
|
||||
|
||||
CavaProvider {
|
||||
id: cava
|
||||
|
||||
collector: collector
|
||||
bars: Config.services.visualiserBars
|
||||
}
|
||||
|
||||
BeatTracker {
|
||||
id: beatTracker
|
||||
|
||||
collector: collector
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue