plugin/ac: not singleton
Also add nodeId prop BeatTracker not singleton Move BeatTracker and Cava to Audio service
This commit is contained in:
parent
23d1d2987a
commit
6ae1a8e811
13 changed files with 241 additions and 94 deletions
|
|
@ -16,7 +16,7 @@ Item {
|
||||||
required property Wallpaper wallpaper
|
required property Wallpaper wallpaper
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Cava.provider
|
service: Audio.cava
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
|
|
@ -65,7 +65,7 @@ Item {
|
||||||
id: bar
|
id: bar
|
||||||
|
|
||||||
required property int modelData
|
required property int modelData
|
||||||
property real value: Math.max(0, Math.min(1, Cava.values[side.isRight ? modelData : side.count - modelData - 1]))
|
property real value: Math.max(0, Math.min(1, Audio.cava.values[side.isRight ? modelData : side.count - modelData - 1]))
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.components.effects
|
import qs.components.effects
|
||||||
import qs.components.misc
|
|
||||||
import qs.components.controls
|
import qs.components.controls
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.utils
|
import qs.utils
|
||||||
|
|
@ -56,11 +55,11 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: Cava.provider
|
service: Audio.cava
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: BeatTracker
|
service: Audio.beatTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|
@ -91,7 +90,7 @@ Item {
|
||||||
id: visualiserBar
|
id: visualiserBar
|
||||||
|
|
||||||
required property int modelData
|
required property int modelData
|
||||||
readonly property real value: Math.max(1e-3, Math.min(1, Cava.values[modelData]))
|
readonly property real value: Math.max(1e-3, Math.min(1, Audio.cava.values[modelData]))
|
||||||
|
|
||||||
readonly property real angle: modelData * 2 * Math.PI / Config.services.visualiserBars
|
readonly property real angle: modelData * 2 * Math.PI / Config.services.visualiserBars
|
||||||
readonly property real magnitude: value * Config.dashboard.sizes.mediaVisualiserSize
|
readonly property real magnitude: value * Config.dashboard.sizes.mediaVisualiserSize
|
||||||
|
|
@ -530,7 +529,7 @@ Item {
|
||||||
height: visualiser.height * 0.75
|
height: visualiser.height * 0.75
|
||||||
|
|
||||||
playing: Players.active?.isPlaying ?? false
|
playing: Players.active?.isPlaying ?? false
|
||||||
speed: BeatTracker.bpm / 300
|
speed: Audio.beatTracker.bpm / 300
|
||||||
source: Paths.absolutePath(Config.paths.mediaGif)
|
source: Paths.absolutePath(Config.paths.mediaGif)
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: AnimatedImage.PreserveAspectFit
|
fillMode: AnimatedImage.PreserveAspectFit
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.components.misc
|
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
import qs.config
|
||||||
import qs.utils
|
import qs.utils
|
||||||
|
|
@ -34,7 +33,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceRef {
|
ServiceRef {
|
||||||
service: BeatTracker
|
service: Audio.beatTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|
@ -214,7 +213,7 @@ Item {
|
||||||
anchors.margins: Appearance.padding.large * 2
|
anchors.margins: Appearance.padding.large * 2
|
||||||
|
|
||||||
playing: Players.active?.isPlaying ?? false
|
playing: Players.active?.isPlaying ?? false
|
||||||
speed: BeatTracker.bpm / 300
|
speed: Audio.beatTracker.bpm / 300
|
||||||
source: Paths.absolutePath(Config.paths.mediaGif)
|
source: Paths.absolutePath(Config.paths.mediaGif)
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: AnimatedImage.PreserveAspectFit
|
fillMode: AnimatedImage.PreserveAspectFit
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
#include "service.hpp"
|
#include "service.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
#include <qmutex.h>
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
#include <spa/param/latency-utils.h>
|
#include <spa/param/latency-utils.h>
|
||||||
#include <stop_token>
|
#include <stop_token>
|
||||||
|
|
@ -25,6 +25,7 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
|
||||||
m_loop = pw_main_loop_new(nullptr);
|
m_loop = pw_main_loop_new(nullptr);
|
||||||
if (!m_loop) {
|
if (!m_loop) {
|
||||||
qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
|
qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
|
||||||
|
pw_deinit();
|
||||||
return;
|
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);
|
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>(
|
static_cast<pw_stream_flags>(
|
||||||
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
|
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
|
||||||
params, 1);
|
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);
|
pw_main_loop_run(m_loop);
|
||||||
|
|
||||||
|
|
@ -155,27 +163,20 @@ unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioCollector::AudioCollector(uint32_t sampleRate, uint32_t chunkSize, QObject* parent)
|
AudioCollector::AudioCollector(QObject* parent)
|
||||||
: Service(parent)
|
: Service(parent)
|
||||||
, m_buffer1(chunkSize)
|
, m_sampleRate(44100)
|
||||||
, m_buffer2(chunkSize)
|
, m_chunkSize(512)
|
||||||
|
, m_nodeId(PW_ID_ANY)
|
||||||
|
, m_buffer1(m_chunkSize)
|
||||||
|
, m_buffer2(m_chunkSize)
|
||||||
, m_readBuffer(&m_buffer1)
|
, m_readBuffer(&m_buffer1)
|
||||||
, m_writeBuffer(&m_buffer2)
|
, m_writeBuffer(&m_buffer2) {}
|
||||||
, m_sampleRate(sampleRate)
|
|
||||||
, m_chunkSize(chunkSize) {}
|
|
||||||
|
|
||||||
AudioCollector::~AudioCollector() {
|
AudioCollector::~AudioCollector() {
|
||||||
stop();
|
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 {
|
uint32_t AudioCollector::sampleRate() const {
|
||||||
return m_sampleRate;
|
return m_sampleRate;
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +185,29 @@ uint32_t AudioCollector::chunkSize() const {
|
||||||
return m_chunkSize;
|
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() {
|
void AudioCollector::clearBuffer() {
|
||||||
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
||||||
std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
|
std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
#include "service.hpp"
|
#include "service.hpp"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
#include <qmutex.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
#include <stop_token>
|
#include <stop_token>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
@ -41,24 +42,35 @@ private:
|
||||||
|
|
||||||
class AudioCollector : public Service {
|
class AudioCollector : public Service {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(uint32_t nodeId READ nodeId WRITE setNodeId NOTIFY nodeIdChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioCollector(uint32_t sampleRate = 44100, uint32_t chunkSize = 512, QObject* parent = nullptr);
|
explicit AudioCollector(QObject* parent = nullptr);
|
||||||
~AudioCollector();
|
~AudioCollector();
|
||||||
|
|
||||||
static AudioCollector* instance();
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t sampleRate() const;
|
[[nodiscard]] uint32_t sampleRate() const;
|
||||||
[[nodiscard]] uint32_t chunkSize() const;
|
[[nodiscard]] uint32_t chunkSize() const;
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t nodeId();
|
||||||
|
void setNodeId(uint32_t nodeId);
|
||||||
|
|
||||||
void clearBuffer();
|
void clearBuffer();
|
||||||
void loadChunk(const int16_t* samples, uint32_t count);
|
void loadChunk(const int16_t* samples, uint32_t count);
|
||||||
uint32_t readChunk(float* out, uint32_t count = 0);
|
uint32_t readChunk(float* out, uint32_t count = 0);
|
||||||
uint32_t readChunk(double* out, uint32_t count = 0);
|
uint32_t readChunk(double* out, uint32_t count = 0);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sampleRateChanged();
|
||||||
|
void chunkSizeChanged();
|
||||||
|
void nodeIdChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline static AudioCollector* s_instance = nullptr;
|
const uint32_t m_sampleRate;
|
||||||
inline static std::mutex s_mutex;
|
const uint32_t m_chunkSize;
|
||||||
|
uint32_t m_nodeId;
|
||||||
|
QMutex m_nodeIdMutex;
|
||||||
|
|
||||||
std::jthread m_thread;
|
std::jthread m_thread;
|
||||||
std::vector<float> m_buffer1;
|
std::vector<float> m_buffer1;
|
||||||
|
|
@ -67,9 +79,7 @@ private:
|
||||||
std::atomic<std::vector<float>*> m_writeBuffer;
|
std::atomic<std::vector<float>*> m_writeBuffer;
|
||||||
uint32_t m_sampleCount;
|
uint32_t m_sampleCount;
|
||||||
|
|
||||||
const uint32_t m_sampleRate;
|
void reload();
|
||||||
const uint32_t m_chunkSize;
|
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@
|
||||||
|
|
||||||
namespace caelestia {
|
namespace caelestia {
|
||||||
|
|
||||||
AudioProcessor::AudioProcessor(QObject* parent)
|
AudioProcessor::AudioProcessor(AudioCollector* collector, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_sampleRate(AudioCollector::instance()->sampleRate())
|
, m_collector(collector) {}
|
||||||
, m_chunkSize(AudioCollector::instance()->chunkSize()) {}
|
|
||||||
|
|
||||||
AudioProcessor::~AudioProcessor() {
|
AudioProcessor::~AudioProcessor() {
|
||||||
stop();
|
stop();
|
||||||
|
|
@ -18,26 +17,53 @@ AudioProcessor::~AudioProcessor() {
|
||||||
|
|
||||||
void AudioProcessor::init() {
|
void AudioProcessor::init() {
|
||||||
m_timer = new QTimer(this);
|
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);
|
connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProcessor::start() {
|
void AudioProcessor::setCollector(AudioCollector* collector) {
|
||||||
AudioCollector::instance()->ref();
|
if (m_collector == collector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_timer) {
|
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();
|
m_timer->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProcessor::stop() {
|
void AudioProcessor::stop() {
|
||||||
if (m_timer) {
|
if (m_timer && m_collector) {
|
||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
|
m_collector->unref();
|
||||||
}
|
}
|
||||||
AudioCollector::instance()->unref();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioProvider::AudioProvider(QObject* parent)
|
AudioProvider::AudioProvider(QObject* parent)
|
||||||
: Service(parent)
|
: Service(parent)
|
||||||
|
, m_collector(nullptr)
|
||||||
, m_processor(nullptr)
|
, m_processor(nullptr)
|
||||||
, m_thread(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() {
|
void AudioProvider::init() {
|
||||||
if (!m_processor) {
|
if (!m_processor) {
|
||||||
qWarning() << "AudioProvider::init: attempted to init with no processor set";
|
qWarning() << "AudioProvider::init: attempted to init with no processor set";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audiocollector.hpp"
|
||||||
#include "service.hpp"
|
#include "service.hpp"
|
||||||
#include <cstdint>
|
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qtimer.h>
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
|
@ -11,14 +11,15 @@ class AudioProcessor : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioProcessor(QObject* parent = nullptr);
|
explicit AudioProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||||
~AudioProcessor();
|
~AudioProcessor();
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32_t m_sampleRate;
|
AudioCollector* m_collector;
|
||||||
uint32_t m_chunkSize;
|
|
||||||
|
Q_INVOKABLE virtual void setCollector(AudioCollector* collector);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTimer* m_timer;
|
QTimer* m_timer;
|
||||||
|
|
@ -32,11 +33,20 @@ private:
|
||||||
class AudioProvider : public Service {
|
class AudioProvider : public Service {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(AudioCollector* collector READ collector WRITE setCollector NOTIFY collectorChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioProvider(QObject* parent = nullptr);
|
explicit AudioProvider(QObject* parent = nullptr);
|
||||||
~AudioProvider();
|
~AudioProvider();
|
||||||
|
|
||||||
|
AudioCollector* collector() const;
|
||||||
|
void setCollector(AudioCollector* collector);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void collectorChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
AudioCollector* m_collector;
|
||||||
AudioProcessor* m_processor;
|
AudioProcessor* m_processor;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,52 @@
|
||||||
|
|
||||||
namespace caelestia {
|
namespace caelestia {
|
||||||
|
|
||||||
BeatProcessor::BeatProcessor(QObject* parent)
|
BeatProcessor::BeatProcessor(AudioCollector* collector, QObject* parent)
|
||||||
: AudioProcessor(parent)
|
: AudioProcessor(collector, parent)
|
||||||
, m_tempo(new_aubio_tempo("default", 1024, m_chunkSize, m_sampleRate))
|
, m_tempo(nullptr)
|
||||||
, m_in(new_fvec(m_chunkSize))
|
, m_in(nullptr)
|
||||||
, m_out(new_fvec(2)) {};
|
, 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() {
|
BeatProcessor::~BeatProcessor() {
|
||||||
|
if (m_tempo) {
|
||||||
del_aubio_tempo(m_tempo);
|
del_aubio_tempo(m_tempo);
|
||||||
|
}
|
||||||
|
if (m_in) {
|
||||||
del_fvec(m_in);
|
del_fvec(m_in);
|
||||||
|
}
|
||||||
del_fvec(m_out);
|
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() {
|
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);
|
aubio_tempo_do(m_tempo, m_in, m_out);
|
||||||
if (m_out->data[0] != 0.0f) {
|
if (m_out->data[0] != 0.0f) {
|
||||||
|
|
@ -30,7 +62,7 @@ void BeatProcessor::process() {
|
||||||
BeatTracker::BeatTracker(QObject* parent)
|
BeatTracker::BeatTracker(QObject* parent)
|
||||||
: AudioProvider(parent)
|
: AudioProvider(parent)
|
||||||
, m_bpm(120) {
|
, m_bpm(120) {
|
||||||
m_processor = new BeatProcessor();
|
m_processor = new BeatProcessor(m_collector);
|
||||||
init();
|
init();
|
||||||
|
|
||||||
connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm);
|
connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audiocollector.hpp"
|
||||||
#include "audioprovider.hpp"
|
#include "audioprovider.hpp"
|
||||||
#include <aubio/aubio.h>
|
#include <aubio/aubio.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
|
@ -10,12 +11,15 @@ class BeatProcessor : public AudioProcessor {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BeatProcessor(QObject* parent = nullptr);
|
explicit BeatProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||||
~BeatProcessor();
|
~BeatProcessor();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void beat(smpl_t bpm);
|
void beat(smpl_t bpm);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setCollector(AudioCollector* collector) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
aubio_tempo_t* m_tempo;
|
aubio_tempo_t* m_tempo;
|
||||||
fvec_t* m_in;
|
fvec_t* m_in;
|
||||||
|
|
@ -27,7 +31,6 @@ private:
|
||||||
class BeatTracker : public AudioProvider {
|
class BeatTracker : public AudioProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
|
||||||
|
|
||||||
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
|
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,39 @@
|
||||||
|
|
||||||
namespace caelestia {
|
namespace caelestia {
|
||||||
|
|
||||||
CavaProcessor::CavaProcessor(QObject* parent)
|
CavaProcessor::CavaProcessor(AudioCollector* collector, QObject* parent)
|
||||||
: AudioProcessor(parent)
|
: AudioProcessor(collector, parent)
|
||||||
, m_plan(nullptr)
|
, m_plan(nullptr)
|
||||||
, m_in(new double[static_cast<size_t>(m_chunkSize)])
|
, m_in(nullptr)
|
||||||
, m_out(nullptr)
|
, m_out(nullptr)
|
||||||
, m_bars(0) {};
|
, m_bars(0) {
|
||||||
|
if (collector) {
|
||||||
|
m_in = new double[collector->chunkSize()];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CavaProcessor::~CavaProcessor() {
|
CavaProcessor::~CavaProcessor() {
|
||||||
cleanup();
|
cleanup();
|
||||||
|
if (m_in) {
|
||||||
delete[] 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) {
|
void CavaProcessor::setBars(int bars) {
|
||||||
if (bars < 0) {
|
if (bars < 0) {
|
||||||
|
|
@ -39,12 +61,10 @@ void CavaProcessor::reload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CavaProcessor::cleanup() {
|
void CavaProcessor::cleanup() {
|
||||||
if (!m_plan) {
|
if (m_plan) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cava_destroy(m_plan);
|
cava_destroy(m_plan);
|
||||||
m_plan = nullptr;
|
m_plan = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_out) {
|
if (m_out) {
|
||||||
delete[] m_out;
|
delete[] m_out;
|
||||||
|
|
@ -53,11 +73,11 @@ void CavaProcessor::cleanup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CavaProcessor::initCava() {
|
void CavaProcessor::initCava() {
|
||||||
if (m_plan || m_bars == 0) {
|
if (m_plan || m_bars == 0 || !m_collector) {
|
||||||
return;
|
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) {
|
if (m_plan->status == -1) {
|
||||||
qWarning() << "CavaProcessor::initCava: failed to initialise cava plan";
|
qWarning() << "CavaProcessor::initCava: failed to initialise cava plan";
|
||||||
|
|
@ -69,11 +89,11 @@ void CavaProcessor::initCava() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CavaProcessor::process() {
|
void CavaProcessor::process() {
|
||||||
if (!m_plan || m_bars == 0) {
|
if (!m_plan || m_bars == 0 || !m_collector || !m_in || !m_out) {
|
||||||
return;
|
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
|
// Process in data via cava
|
||||||
cava_execute(m_in, count, m_out, m_plan);
|
cava_execute(m_in, count, m_out, m_plan);
|
||||||
|
|
@ -100,8 +120,8 @@ void CavaProcessor::process() {
|
||||||
CavaProvider::CavaProvider(QObject* parent)
|
CavaProvider::CavaProvider(QObject* parent)
|
||||||
: AudioProvider(parent)
|
: AudioProvider(parent)
|
||||||
, m_bars(0)
|
, m_bars(0)
|
||||||
, m_values(m_bars) {
|
, m_values(m_bars, 0.0) {
|
||||||
m_processor = new CavaProcessor();
|
m_processor = new CavaProcessor(m_collector);
|
||||||
init();
|
init();
|
||||||
|
|
||||||
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
|
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
|
||||||
|
|
@ -121,7 +141,7 @@ void CavaProvider::setBars(int bars) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_values.resize(bars);
|
m_values.resize(bars, 0.0);
|
||||||
m_bars = bars;
|
m_bars = bars;
|
||||||
emit barsChanged();
|
emit barsChanged();
|
||||||
emit valuesChanged();
|
emit valuesChanged();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audiocollector.hpp"
|
||||||
#include "audioprovider.hpp"
|
#include "audioprovider.hpp"
|
||||||
#include <cava/cavacore.h>
|
#include <cava/cavacore.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
|
@ -10,12 +11,15 @@ class CavaProcessor : public AudioProcessor {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CavaProcessor(QObject* parent = nullptr);
|
explicit CavaProcessor(AudioCollector* collector, QObject* parent = nullptr);
|
||||||
~CavaProcessor();
|
~CavaProcessor();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valuesChanged(QVector<double> values);
|
void valuesChanged(QVector<double> values);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setCollector(AudioCollector* collector) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct cava_plan* m_plan;
|
struct cava_plan* m_plan;
|
||||||
double* m_in;
|
double* m_in;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import qs.config
|
import qs.config
|
||||||
|
import Caelestia
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
|
|
||||||
|
|
@ -32,6 +33,9 @@ Singleton {
|
||||||
readonly property bool sourceMuted: !!source?.audio?.muted
|
readonly property bool sourceMuted: !!source?.audio?.muted
|
||||||
readonly property real sourceVolume: source?.audio?.volume ?? 0
|
readonly property real sourceVolume: source?.audio?.volume ?? 0
|
||||||
|
|
||||||
|
readonly property alias cava: cava
|
||||||
|
readonly property alias beatTracker: beatTracker
|
||||||
|
|
||||||
function setVolume(newVolume: real): void {
|
function setVolume(newVolume: real): void {
|
||||||
if (sink?.ready && sink?.audio) {
|
if (sink?.ready && sink?.audio) {
|
||||||
sink.audio.muted = false;
|
sink.audio.muted = false;
|
||||||
|
|
@ -73,4 +77,21 @@ Singleton {
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [...root.sinks, ...root.sources]
|
objects: [...root.sinks, ...root.sources]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioCollector {
|
||||||
|
id: collector
|
||||||
|
}
|
||||||
|
|
||||||
|
CavaProvider {
|
||||||
|
id: cava
|
||||||
|
|
||||||
|
collector: collector
|
||||||
|
bars: Config.services.visualiserBars
|
||||||
|
}
|
||||||
|
|
||||||
|
BeatTracker {
|
||||||
|
id: beatTracker
|
||||||
|
|
||||||
|
collector: collector
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
import qs.config
|
|
||||||
import Caelestia
|
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property alias provider: provider
|
|
||||||
readonly property alias values: provider.values
|
|
||||||
|
|
||||||
CavaProvider {
|
|
||||||
id: provider
|
|
||||||
|
|
||||||
bars: Config.services.visualiserBars
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue