aluf/src/AudioEngine.cpp

251 lines
7.2 KiB
C++

// src/AudioEngine.cpp
#include "AudioEngine.h"
#include <QMediaDevices>
#include <QAudioDevice>
#include <QAudioFormat>
#include <QtEndian>
#include <QUrl>
#include <QAudioBuffer>
#include <QDebug>
#include <QtGlobal>
#include <algorithm>
AudioEngine::AudioEngine(QObject* parent) : QObject(parent) {
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
m_processors.push_back(new Processor(m_frameSize, m_sampleRate));
m_processTimer = new QTimer(this);
m_processTimer->setInterval(16);
connect(m_processTimer, &QTimer::timeout, this, &AudioEngine::onProcessTimer);
}
AudioEngine::~AudioEngine() {
stop();
for(auto p : m_processors) delete p;
if (m_fileSource) delete m_fileSource;
}
void AudioEngine::setNumBins(int n) {
for(auto p : m_processors) p->setNumBins(n);
}
void AudioEngine::loadTrack(const QString& filePath) {
stop();
m_pcmData.clear();
m_buffer.close();
if (m_fileSource) {
m_fileSource->close();
delete m_fileSource;
m_fileSource = nullptr;
}
if (m_decoder) delete m_decoder;
m_decoder = new QAudioDecoder(this);
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleFormat(QAudioFormat::Int16);
m_decoder->setAudioFormat(format);
connect(m_decoder, &QAudioDecoder::bufferReady, this, &AudioEngine::onBufferReady);
connect(m_decoder, &QAudioDecoder::finished, this, &AudioEngine::onFinished);
connect(m_decoder, QOverload<QAudioDecoder::Error>::of(&QAudioDecoder::error), this, &AudioEngine::onError);
qDebug() << "AudioEngine: Attempting to load" << filePath;
#ifdef Q_OS_ANDROID
m_fileSource = new QFile(filePath);
if (m_fileSource->open(QIODevice::ReadOnly)) {
m_decoder->setSourceDevice(m_fileSource);
} else {
delete m_fileSource;
m_fileSource = nullptr;
if (filePath.startsWith("content://")) {
m_decoder->setSource(QUrl(filePath));
} else {
m_decoder->setSource(QUrl::fromLocalFile(filePath));
}
}
#else
m_decoder->setSource(QUrl::fromLocalFile(filePath));
#endif
m_decoder->start();
}
void AudioEngine::onError(QAudioDecoder::Error error) {
qWarning() << "AudioEngine: Decoder Error:" << error << m_decoder->errorString();
emit trackLoaded(false);
}
void AudioEngine::onBufferReady() {
QAudioBuffer buffer = m_decoder->read();
if (!buffer.isValid()) return;
const int frames = static_cast<int>(buffer.frameCount());
const int channels = buffer.format().channelCount();
auto sampleType = buffer.format().sampleFormat();
// We store everything as Stereo Int16 to ensure compatibility with all sinks
if (sampleType == QAudioFormat::Int16) {
const int16_t* src = buffer.constData<int16_t>();
if (!src) return;
for (int i = 0; i < frames; ++i) {
int16_t left = 0;
int16_t right = 0;
if (channels == 1) {
left = src[i];
right = left;
} else if (channels >= 2) {
left = src[i * channels];
right = src[i * channels + 1];
}
m_pcmData.append(reinterpret_cast<const char*>(&left), sizeof(int16_t));
m_pcmData.append(reinterpret_cast<const char*>(&right), sizeof(int16_t));
}
}
else if (sampleType == QAudioFormat::Float) {
const float* src = buffer.constData<float>();
if (!src) return;
auto toInt16 = [](float x) -> int16_t {
return static_cast<int16_t>(std::clamp(x, -1.0f, 1.0f) * 32767.0f);
};
for (int i = 0; i < frames; ++i) {
float l = 0.0f;
float r = 0.0f;
if (channels == 1) {
l = src[i];
r = l;
} else if (channels >= 2) {
l = src[i * channels];
r = src[i * channels + 1];
}
int16_t left = toInt16(l);
int16_t right = toInt16(r);
m_pcmData.append(reinterpret_cast<const char*>(&left), sizeof(int16_t));
m_pcmData.append(reinterpret_cast<const char*>(&right), sizeof(int16_t));
}
}
}
void AudioEngine::onFinished() {
if (m_pcmData.isEmpty()) {
emit trackLoaded(false);
return;
}
m_buffer.setData(m_pcmData);
if (!m_buffer.open(QIODevice::ReadOnly)) {
emit trackLoaded(false);
return;
}
emit trackLoaded(true);
}
void AudioEngine::play() {
if (!m_buffer.isOpen() || m_pcmData.isEmpty()) return;
if (m_sink) {
m_sink->resume();
m_processTimer->start();
return;
}
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleFormat(QAudioFormat::Int16); // Universal format
QAudioDevice device = QMediaDevices::defaultAudioOutput();
if (device.isNull()) {
qWarning() << "AudioEngine: No audio output device found.";
return;
}
if (!device.isFormatSupported(format)) {
qWarning() << "AudioEngine: Int16 format not supported, using preferred format.";
format = device.preferredFormat();
}
m_sink = new QAudioSink(device, format, this);
connect(m_sink, &QAudioSink::stateChanged, this, [this](QAudio::State state){
if (state == QAudio::IdleState && m_sink->error() == QAudio::NoError) {
if (m_buffer.bytesAvailable() == 0) {
m_processTimer->stop();
emit playbackFinished();
}
}
});
m_sink->start(&m_buffer);
m_processTimer->start();
}
void AudioEngine::pause() {
if (m_sink) m_sink->suspend();
m_processTimer->stop();
}
void AudioEngine::stop() {
m_processTimer->stop();
if (m_sink) {
m_sink->stop();
delete m_sink;
m_sink = nullptr;
}
}
void AudioEngine::seek(float position) {
if (m_pcmData.isEmpty()) return;
qint64 pos = position * m_pcmData.size();
// Align to 4 bytes (2 channels * 2 bytes per sample)
pos -= pos % 4;
if (m_buffer.isOpen()) m_buffer.seek(pos);
}
void AudioEngine::setDspParams(int frameSize, int hopSize) {
m_frameSize = frameSize;
m_hopSize = hopSize;
for(auto p : m_processors) p->setFrameSize(frameSize);
}
void AudioEngine::onProcessTimer() {
if (!m_buffer.isOpen()) return;
qint64 currentPos = m_buffer.pos();
emit positionChanged((float)currentPos / m_pcmData.size());
// Convert Int16 back to Float for DSP
const int16_t* samples = reinterpret_cast<const int16_t*>(m_pcmData.constData());
qint64 sampleIdx = currentPos / sizeof(int16_t);
qint64 totalSamples = m_pcmData.size() / sizeof(int16_t);
if (sampleIdx + m_frameSize * 2 >= totalSamples) return;
std::vector<float> ch0(m_frameSize), ch1(m_frameSize);
for (int i = 0; i < m_frameSize; ++i) {
ch0[i] = samples[sampleIdx + i*2] / 32768.0f;
ch1[i] = samples[sampleIdx + i*2 + 1] / 32768.0f;
}
m_processors[0]->pushData(ch0);
m_processors[1]->pushData(ch1);
std::vector<FrameData> results;
for (auto p : m_processors) {
auto spec = p->getSpectrum();
results.push_back({spec.freqs, spec.db});
}
emit spectrumReady(results);
}