241 lines
6.8 KiB
C++
241 lines
6.8 KiB
C++
#include "AudioEngine.h"
|
|
#include <QMediaDevices>
|
|
#include <QAudioDevice>
|
|
#include <QAudioFormat>
|
|
#include <QtEndian>
|
|
#include <QUrl>
|
|
#include <QAudioBuffer>
|
|
#include <QDebug>
|
|
#include <QtGlobal>
|
|
|
|
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;
|
|
// Fix: Handle content:// URIs correctly
|
|
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;
|
|
|
|
// Fix: Explicit cast to int to avoid warning
|
|
const int frames = static_cast<int>(buffer.frameCount());
|
|
const int channels = buffer.format().channelCount();
|
|
auto sampleType = buffer.format().sampleFormat();
|
|
|
|
if (sampleType == QAudioFormat::Int16) {
|
|
const int16_t* src = buffer.constData<int16_t>();
|
|
if (!src) return;
|
|
|
|
for (int i = 0; i < frames; ++i) {
|
|
float left = 0.0f;
|
|
float right = 0.0f;
|
|
|
|
if (channels == 1) {
|
|
left = src[i] / 32768.0f;
|
|
right = left;
|
|
} else if (channels >= 2) {
|
|
left = src[i * channels] / 32768.0f;
|
|
right = src[i * channels + 1] / 32768.0f;
|
|
}
|
|
|
|
m_pcmData.append(reinterpret_cast<const char*>(&left), sizeof(float));
|
|
m_pcmData.append(reinterpret_cast<const char*>(&right), sizeof(float));
|
|
}
|
|
}
|
|
else if (sampleType == QAudioFormat::Float) {
|
|
const float* src = buffer.constData<float>();
|
|
if (!src) return;
|
|
|
|
for (int i = 0; i < frames; ++i) {
|
|
float left = 0.0f;
|
|
float right = 0.0f;
|
|
|
|
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(float));
|
|
m_pcmData.append(reinterpret_cast<const char*>(&right), sizeof(float));
|
|
}
|
|
}
|
|
}
|
|
|
|
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::Float);
|
|
|
|
QAudioDevice device = QMediaDevices::defaultAudioOutput();
|
|
if (device.isNull()) {
|
|
qWarning() << "AudioEngine: No audio output device found.";
|
|
return;
|
|
}
|
|
|
|
if (!device.isFormatSupported(format)) {
|
|
qWarning() << "AudioEngine: Float 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();
|
|
pos -= pos % 8;
|
|
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());
|
|
|
|
const float* samples = reinterpret_cast<const float*>(m_pcmData.constData());
|
|
qint64 sampleIdx = currentPos / sizeof(float);
|
|
qint64 totalSamples = m_pcmData.size() / sizeof(float);
|
|
|
|
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];
|
|
ch1[i] = samples[sampleIdx + i*2 + 1];
|
|
}
|
|
|
|
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);
|
|
} |