Switched to PortAudio
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "rtmidi"]
|
|
||||||
path = rtmidi
|
|
||||||
url = git@github.com:thestk/rtmidi.git
|
|
||||||
|
@ -6,19 +6,14 @@ release: QMAKE_CXXFLAGS_RELEASE -= -O1
|
|||||||
release: QMAKE_CXXFLAGS_RELEASE -= -O2
|
release: QMAKE_CXXFLAGS_RELEASE -= -O2
|
||||||
release: QMAKE_CXXFLAGS_RELEASE += -O3 -ffast-math -march=native -mtune=native
|
release: QMAKE_CXXFLAGS_RELEASE += -O3 -ffast-math -march=native -mtune=native
|
||||||
|
|
||||||
win32: {
|
LIBS += -lrtmidi -lportaudio
|
||||||
DEFINES += __WINDOWS_MM__
|
|
||||||
LIBS += -lwinmm
|
|
||||||
}
|
|
||||||
|
|
||||||
unix: {
|
|
||||||
DEFINES += __LINUX_ALSA__
|
|
||||||
LIBS += -lasound
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
audiodecoder.cpp \
|
||||||
|
audioformat.cpp \
|
||||||
|
audioplayer.cpp \
|
||||||
filesmodel.cpp \
|
filesmodel.cpp \
|
||||||
jsonconverters.cpp \
|
jsonconverters.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
@ -28,12 +23,14 @@ SOURCES += \
|
|||||||
presetdetailwidget.cpp \
|
presetdetailwidget.cpp \
|
||||||
presets.cpp \
|
presets.cpp \
|
||||||
presetsmodel.cpp \
|
presetsmodel.cpp \
|
||||||
rtmidi/RtMidi.cpp \
|
|
||||||
sampleswidget.cpp \
|
sampleswidget.cpp \
|
||||||
samplewidget.cpp \
|
samplewidget.cpp \
|
||||||
sequencerwidget.cpp
|
sequencerwidget.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
audiodecoder.h \
|
||||||
|
audioformat.h \
|
||||||
|
audioplayer.h \
|
||||||
filesmodel.h \
|
filesmodel.h \
|
||||||
jsonconverters.h \
|
jsonconverters.h \
|
||||||
mainwindow.h \
|
mainwindow.h \
|
||||||
@ -42,7 +39,6 @@ HEADERS += \
|
|||||||
presetdetailwidget.h \
|
presetdetailwidget.h \
|
||||||
presets.h \
|
presets.h \
|
||||||
presetsmodel.h \
|
presetsmodel.h \
|
||||||
rtmidi/RtMidi.h \
|
|
||||||
sampleswidget.h \
|
sampleswidget.h \
|
||||||
samplewidget.h \
|
samplewidget.h \
|
||||||
sequencerwidget.h
|
sequencerwidget.h
|
||||||
|
83
audiodecoder.cpp
Normal file
83
audiodecoder.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#include "audiodecoder.h"
|
||||||
|
|
||||||
|
#include <QAudioBuffer>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "audioformat.h"
|
||||||
|
|
||||||
|
AudioDecoder::AudioDecoder(QObject *parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
QObject::connect(&m_decoder, qOverload<QAudioDecoder::Error>(&QAudioDecoder::error),
|
||||||
|
this, &AudioDecoder::error);
|
||||||
|
QObject::connect(&m_decoder, &QAudioDecoder::finished,
|
||||||
|
this, &AudioDecoder::finished);
|
||||||
|
QObject::connect(&m_decoder, &QAudioDecoder::bufferReady,
|
||||||
|
this, &AudioDecoder::bufferReady);
|
||||||
|
QObject::connect(&m_decoder, &QAudioDecoder::durationChanged,
|
||||||
|
this, &AudioDecoder::durationChanged);
|
||||||
|
|
||||||
|
m_decoder.setAudioFormat(audioFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoder::startDecoding(std::shared_ptr<QIODevice> device)
|
||||||
|
{
|
||||||
|
qDebug() << "called" << device.get();
|
||||||
|
|
||||||
|
if (m_decoder.state() == QAudioDecoder::DecodingState)
|
||||||
|
m_decoder.stop();
|
||||||
|
|
||||||
|
m_decoder.setSourceDevice(device.get());
|
||||||
|
m_device = device;
|
||||||
|
m_bytearray.clear();
|
||||||
|
m_decoder.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoder::error(const QAudioDecoder::Error error)
|
||||||
|
{
|
||||||
|
qDebug() << "error:" << error << m_decoder.errorString() << m_decoder.sourceFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoder::finished()
|
||||||
|
{
|
||||||
|
qDebug() << "called";
|
||||||
|
emit decodingFinished(QAudioBuffer{std::move(m_bytearray), audioFormat()});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoder::bufferReady()
|
||||||
|
{
|
||||||
|
const auto &format = m_decoder.audioFormat();
|
||||||
|
const auto buffer = m_decoder.read();
|
||||||
|
|
||||||
|
Q_ASSERT(buffer.byteCount() == buffer.sampleCount() * (format.sampleSize()/8));
|
||||||
|
|
||||||
|
m_bytearray.append(buffer.constData<char>(), buffer.byteCount());
|
||||||
|
|
||||||
|
const auto now = QDateTime::currentDateTime();
|
||||||
|
if (m_lastProgressUpdate.isNull() || m_lastProgressUpdate.msecsTo(now) > 100)
|
||||||
|
{
|
||||||
|
emit progress(m_decoder.position(), m_decoder.duration());
|
||||||
|
m_lastProgressUpdate = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDecoder::durationChanged(const qint64 duration)
|
||||||
|
{
|
||||||
|
if (duration == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto &format = m_decoder.audioFormat();
|
||||||
|
const auto reserve = (format.sampleSize()/8) * format.sampleRate() * format.channelCount() * (duration + 1000) / 1000;
|
||||||
|
m_bytearray.reserve(reserve);
|
||||||
|
qDebug() << "duration:" << duration << reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void registerMetaType()
|
||||||
|
{
|
||||||
|
qRegisterMetaType<std::shared_ptr<QIODevice>>();
|
||||||
|
}
|
||||||
|
Q_COREAPP_STARTUP_FUNCTION(registerMetaType)
|
||||||
|
}
|
40
audiodecoder.h
Normal file
40
audiodecoder.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QAudioDecoder>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
class QAudioBuffer;
|
||||||
|
class QIODevice;
|
||||||
|
|
||||||
|
class AudioDecoder : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AudioDecoder(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progress(int progress, int total);
|
||||||
|
void decodingFinished(const QAudioBuffer &buffer);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void startDecoding(std::shared_ptr<QIODevice> device);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void error(const QAudioDecoder::Error error);
|
||||||
|
void finished();
|
||||||
|
void bufferReady();
|
||||||
|
void durationChanged(const qint64 duration);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QIODevice> m_device;
|
||||||
|
QAudioDecoder m_decoder;
|
||||||
|
QByteArray m_bytearray;
|
||||||
|
QDateTime m_lastProgressUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(std::shared_ptr<QIODevice>)
|
17
audioformat.cpp
Normal file
17
audioformat.cpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "audioformat.h"
|
||||||
|
|
||||||
|
const QAudioFormat &audioFormat()
|
||||||
|
{
|
||||||
|
static const QAudioFormat audioFormat = [](){
|
||||||
|
QAudioFormat format;
|
||||||
|
format.setSampleRate(sampleRate);
|
||||||
|
format.setChannelCount(channelCount);
|
||||||
|
format.setSampleSize(sampleSize);
|
||||||
|
format.setCodec(codec);
|
||||||
|
format.setByteOrder(byteOrder);
|
||||||
|
format.setSampleType(sampleType);
|
||||||
|
return format;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return audioFormat;
|
||||||
|
}
|
14
audioformat.h
Normal file
14
audioformat.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAudioFormat>
|
||||||
|
|
||||||
|
static constexpr int sampleRate = 44100;
|
||||||
|
static constexpr int channelCount = 2;
|
||||||
|
static constexpr int sampleSize = 32;
|
||||||
|
static constexpr auto codec = "audio/pcm";
|
||||||
|
static constexpr QAudioFormat::Endian byteOrder = QAudioFormat::LittleEndian;
|
||||||
|
static constexpr QAudioFormat::SampleType sampleType = QAudioFormat::Float;
|
||||||
|
using sample_t = float;
|
||||||
|
using frame_t = std::array<sample_t, channelCount>;
|
||||||
|
|
||||||
|
const QAudioFormat &audioFormat();
|
52
audioplayer.cpp
Normal file
52
audioplayer.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include "audioplayer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
AudioPlayer::AudioPlayer(QObject *parent) :
|
||||||
|
QObject{parent}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioPlayer::AudioPlayer(const QAudioBuffer &buffer, QObject *parent) :
|
||||||
|
QObject{parent},
|
||||||
|
m_buffer{buffer}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::writeSamples(frame_t *begin, frame_t *end)
|
||||||
|
{
|
||||||
|
if (!m_playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto frames = std::min<size_t>(std::distance(begin, end), m_buffer.frameCount()-m_position);
|
||||||
|
|
||||||
|
if (!frames)
|
||||||
|
{
|
||||||
|
m_playing = false;
|
||||||
|
emit playingChanged(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(begin+frames), m_buffer.constData<frame_t>() + m_position, begin,
|
||||||
|
[](const frame_t &frame, const frame_t &frame2)->frame_t{
|
||||||
|
frame_t newFrame;
|
||||||
|
std::transform(std::begin(frame), std::end(frame), std::begin(frame2), std::begin(newFrame),
|
||||||
|
[](const sample_t &left, const sample_t &right) { return left + right; });
|
||||||
|
return newFrame;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_position += frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::restart()
|
||||||
|
{
|
||||||
|
setPosition(0);
|
||||||
|
setPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioPlayer::stop()
|
||||||
|
{
|
||||||
|
setPosition(0);
|
||||||
|
setPlaying(false);
|
||||||
|
}
|
45
audioplayer.h
Normal file
45
audioplayer.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QAudioBuffer>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "audioformat.h"
|
||||||
|
|
||||||
|
class AudioPlayer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AudioPlayer(QObject *parent = nullptr);
|
||||||
|
explicit AudioPlayer(const QAudioBuffer &buffer, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void writeSamples(frame_t *begin, frame_t *end);
|
||||||
|
|
||||||
|
bool playing() const { return m_playing; }
|
||||||
|
void setPlaying(bool playing) { m_playing = playing; emit playingChanged(playing); }
|
||||||
|
|
||||||
|
std::size_t position() const { return m_position; }
|
||||||
|
void setPosition(std::size_t position) { m_position = position; emit positionChanged(position); }
|
||||||
|
|
||||||
|
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||||
|
void setBuffer(const QAudioBuffer &buffer) { emit bufferChanged(m_buffer = buffer); }
|
||||||
|
|
||||||
|
void restart();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void playingChanged(bool playing);
|
||||||
|
void positionChanged(std::size_t position);
|
||||||
|
void bufferChanged(const QAudioBuffer &buffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_playing{false};
|
||||||
|
std::size_t m_position{};
|
||||||
|
QAudioBuffer m_buffer;
|
||||||
|
|
||||||
|
QDateTime m_lastPositionUpdate;
|
||||||
|
};
|
39
main.cpp
39
main.cpp
@ -8,9 +8,36 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "portaudio.h"
|
||||||
|
|
||||||
#include "jsonconverters.h"
|
#include "jsonconverters.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T>
|
||||||
|
class CleanupHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CleanupHelper(T &&callback) :
|
||||||
|
m_callback{std::move(callback)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
~CleanupHelper()
|
||||||
|
{
|
||||||
|
m_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T m_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto makeCleanupHelper(T &&callback)
|
||||||
|
{
|
||||||
|
return CleanupHelper<T>{std::move(callback)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
@ -23,6 +50,18 @@ int main(int argc, char *argv[])
|
|||||||
qDebug() << "sslLibraryVersionString" << QSslSocket::sslLibraryVersionString();
|
qDebug() << "sslLibraryVersionString" << QSslSocket::sslLibraryVersionString();
|
||||||
qDebug() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
|
qDebug() << "sslLibraryBuildVersionString" << QSslSocket::sslLibraryBuildVersionString();
|
||||||
|
|
||||||
|
if (PaError err = Pa_Initialize(); err != paNoError)
|
||||||
|
{
|
||||||
|
QMessageBox::warning({}, QApplication::translate("main", "Error initializing PortAudio!"), QApplication::translate("main", "Error initializing PortAudio!") + "\n\n" + Pa_GetErrorText(err));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto helper0 = makeCleanupHelper([](){
|
||||||
|
qDebug() << "helper0";
|
||||||
|
if (PaError err = Pa_Terminate(); err != paNoError)
|
||||||
|
fprintf(stderr, "Could not terminate PortAudio!\n");
|
||||||
|
});
|
||||||
|
|
||||||
qSetMessagePattern("%{time dd.MM.yyyy HH:mm:ss.zzz} "
|
qSetMessagePattern("%{time dd.MM.yyyy HH:mm:ss.zzz} "
|
||||||
"["
|
"["
|
||||||
"%{if-debug}D%{endif}"
|
"%{if-debug}D%{endif}"
|
||||||
|
122
mainwindow.cpp
122
mainwindow.cpp
@ -6,14 +6,54 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QAbstractEventDispatcher>
|
#include <QAbstractEventDispatcher>
|
||||||
#include <QAudioDeviceInfo>
|
#include <QAudioDeviceInfo>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#include "presets.h"
|
#include "presets.h"
|
||||||
#include "midiinwrapper.h"
|
#include "midiinwrapper.h"
|
||||||
#include "midicontainers.h"
|
#include "midicontainers.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void DummyDeleter(PaStream *stream) {}
|
||||||
|
void PaStreamCloser(PaStream *stream)
|
||||||
|
{
|
||||||
|
if (PaError err = Pa_CloseStream(stream); err != paNoError)
|
||||||
|
fprintf(stderr, "Could not close stream!\n");
|
||||||
|
}
|
||||||
|
void PaStreamStopperAndCloser(PaStream *stream)
|
||||||
|
{
|
||||||
|
if (PaError err = Pa_StopStream(stream); err != paNoError)
|
||||||
|
fprintf(stderr, "Could not stop stream!\n");
|
||||||
|
PaStreamCloser(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paStreamFinished(void* userData)
|
||||||
|
{
|
||||||
|
printf("Stream Completed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
|
unsigned long framesPerBuffer,
|
||||||
|
const PaStreamCallbackTimeInfo* timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags,
|
||||||
|
void *userData)
|
||||||
|
{
|
||||||
|
Q_UNUSED(inputBuffer)
|
||||||
|
Q_ASSERT(outputBuffer);
|
||||||
|
Q_ASSERT(framesPerBuffer);
|
||||||
|
Q_UNUSED(timeInfo)
|
||||||
|
Q_UNUSED(statusFlags)
|
||||||
|
Q_ASSERT(userData);
|
||||||
|
|
||||||
|
auto begin = static_cast<frame_t*>(outputBuffer);
|
||||||
|
|
||||||
|
static_cast<MainWindow*>(userData)->writeSamples(begin, begin+framesPerBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent) :
|
MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent) :
|
||||||
QMainWindow{parent},
|
QMainWindow{parent},
|
||||||
m_ui{std::make_unique<Ui::MainWindow>()},
|
m_ui{std::make_unique<Ui::MainWindow>()},
|
||||||
|
m_paStream(nullptr, DummyDeleter),
|
||||||
m_presetsModel{*presetsConfig.presets}
|
m_presetsModel{*presetsConfig.presets}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
@ -40,19 +80,9 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
|
|||||||
|
|
||||||
updateAudioDevices();
|
updateAudioDevices();
|
||||||
|
|
||||||
{
|
m_ui->comboBoxAudioDevice->setCurrentIndex(Pa_GetDefaultOutputDevice());
|
||||||
const auto index = m_devices.indexOf(QAudioDeviceInfo::defaultOutputDevice());
|
|
||||||
if (index != -1)
|
|
||||||
m_ui->comboBoxAudioDevice->setCurrentIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice);
|
||||||
const auto callback = [this](int index){
|
|
||||||
m_ui->samplesWidget->setAudioDevice(m_devices.at(index));
|
|
||||||
};
|
|
||||||
connect(m_ui->comboBoxAudioDevice, qOverload<int>(&QComboBox::currentIndexChanged), m_ui->samplesWidget, callback);
|
|
||||||
callback(m_ui->comboBoxAudioDevice->currentIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
m_presetsProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||||
m_presetsProxyModel.setSourceModel(&m_presetsModel);
|
m_presetsProxyModel.setSourceModel(&m_presetsModel);
|
||||||
@ -84,6 +114,65 @@ void MainWindow::selectFirstPreset()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::writeSamples(frame_t *begin, frame_t *end)
|
||||||
|
{
|
||||||
|
std::fill(begin, end, frame_t{0.,0.});
|
||||||
|
|
||||||
|
m_ui->samplesWidget->writeSamples(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::openAudioDevice()
|
||||||
|
{
|
||||||
|
if (m_paStream)
|
||||||
|
{
|
||||||
|
m_paStream = nullptr;
|
||||||
|
m_ui->pushButtonAudioDevice->setText(tr("Open"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PaDeviceIndex index = m_ui->comboBoxAudioDevice->currentIndex();
|
||||||
|
|
||||||
|
const PaDeviceInfo* pInfo = Pa_GetDeviceInfo(index);
|
||||||
|
|
||||||
|
const PaStreamParameters outputParameters {
|
||||||
|
.device = index,
|
||||||
|
.channelCount = channelCount,
|
||||||
|
.sampleFormat = paFloat32, /* 32 bit floating point output */
|
||||||
|
.suggestedLatency = pInfo->defaultLowOutputLatency,
|
||||||
|
.hostApiSpecificStreamInfo = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
PaStream *stream{};
|
||||||
|
|
||||||
|
if (PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, sampleRate, m_ui->spinBoxBufferSize->value(), paNoFlag, &paCallback, this); err != paNoError)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Error opening stream!"), tr("Error opening stream!") + "\n\n" + Pa_GetErrorText(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto smartPtr = std::unique_ptr<PaStream, void(*)(PaStream*)>(stream, PaStreamCloser);
|
||||||
|
stream = nullptr;
|
||||||
|
|
||||||
|
if (PaError err = Pa_SetStreamFinishedCallback(smartPtr.get(), &paStreamFinished); err != paNoError)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Error setting finished callback!"), tr("Error setting finished callback!") + "\n\n" + Pa_GetErrorText(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PaError err = Pa_StartStream(smartPtr.get()); err != paNoError)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Error starting stream!"), tr("Error starting stream!") + "\n\n" + Pa_GetErrorText(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream has been started and from now on we not only need to delete it, but also stop it first
|
||||||
|
smartPtr.get_deleter() = PaStreamStopperAndCloser;
|
||||||
|
|
||||||
|
m_paStream = std::move(smartPtr);
|
||||||
|
m_ui->pushButtonAudioDevice->setText(tr("Close"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::messageReceived(const midi::MidiMessage &message)
|
void MainWindow::messageReceived(const midi::MidiMessage &message)
|
||||||
{
|
{
|
||||||
m_ui->statusbar->showMessage(tr("Received midi message: flag: %0 cmd: %1 channel: %2 note: %3 velocity: %4")
|
m_ui->statusbar->showMessage(tr("Received midi message: flag: %0 cmd: %1 channel: %2 note: %3 velocity: %4")
|
||||||
@ -120,8 +209,11 @@ void MainWindow::updateAudioDevices()
|
|||||||
{
|
{
|
||||||
m_ui->comboBoxAudioDevice->clear();
|
m_ui->comboBoxAudioDevice->clear();
|
||||||
|
|
||||||
m_devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
|
const auto count = Pa_GetDeviceCount();
|
||||||
|
|
||||||
for (const auto &device : m_devices)
|
for (PaDeviceIndex i = 0; i < count; i++)
|
||||||
m_ui->comboBoxAudioDevice->addItem(device.deviceName());
|
{
|
||||||
|
const auto info = Pa_GetDeviceInfo(i);
|
||||||
|
m_ui->comboBoxAudioDevice->addItem(info->name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
11
mainwindow.h
11
mainwindow.h
@ -5,6 +5,9 @@
|
|||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
#include "portaudio.h"
|
||||||
|
|
||||||
|
#include "audioformat.h"
|
||||||
#include "presetsmodel.h"
|
#include "presetsmodel.h"
|
||||||
#include "filesmodel.h"
|
#include "filesmodel.h"
|
||||||
#include "midiinwrapper.h"
|
#include "midiinwrapper.h"
|
||||||
@ -12,7 +15,6 @@
|
|||||||
namespace Ui { class MainWindow; }
|
namespace Ui { class MainWindow; }
|
||||||
namespace presets { struct PresetsConfig; }
|
namespace presets { struct PresetsConfig; }
|
||||||
namespace midi { struct MidiMessage; }
|
namespace midi { struct MidiMessage; }
|
||||||
class QAudioDeviceInfo;
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@ -24,7 +26,10 @@ public:
|
|||||||
|
|
||||||
void selectFirstPreset();
|
void selectFirstPreset();
|
||||||
|
|
||||||
|
void writeSamples(frame_t *begin, frame_t *end);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void openAudioDevice();
|
||||||
void messageReceived(const midi::MidiMessage &message);
|
void messageReceived(const midi::MidiMessage &message);
|
||||||
void currentRowChanged(const QModelIndex ¤t);
|
void currentRowChanged(const QModelIndex ¤t);
|
||||||
|
|
||||||
@ -34,9 +39,9 @@ private:
|
|||||||
|
|
||||||
const std::unique_ptr<Ui::MainWindow> m_ui;
|
const std::unique_ptr<Ui::MainWindow> m_ui;
|
||||||
|
|
||||||
MidiInWrapper m_midiIn;
|
std::unique_ptr<PaStream, void(*)(PaStream*)> m_paStream;
|
||||||
|
|
||||||
QList<QAudioDeviceInfo> m_devices;
|
MidiInWrapper m_midiIn;
|
||||||
|
|
||||||
PresetsModel m_presetsModel;
|
PresetsModel m_presetsModel;
|
||||||
QSortFilterProxyModel m_presetsProxyModel;
|
QSortFilterProxyModel m_presetsProxyModel;
|
||||||
|
@ -124,6 +124,23 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="comboBoxAudioDevice"/>
|
<widget class="QComboBox" name="comboBoxAudioDevice"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBoxBufferSize">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>32</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButtonAudioDevice">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
1
rtmidi
1
rtmidi
Submodule rtmidi deleted from 7ab18ef06b
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "midicontainers.h"
|
#include "midicontainers.h"
|
||||||
@ -14,10 +13,13 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
|||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
m_cache.setCacheDirectory("cache");
|
||||||
|
m_networkAccessManager.setCache(&m_cache);
|
||||||
|
|
||||||
{
|
{
|
||||||
QEventLoop eventLoop;
|
QEventLoop eventLoop;
|
||||||
connect(&m_audioThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
connect(&m_decoderThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
||||||
m_audioThread.start(QThread::HighestPriority);
|
m_decoderThread.start(QThread::HighestPriority);
|
||||||
eventLoop.exec();
|
eventLoop.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +28,11 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
|||||||
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
|
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
|
||||||
|
|
||||||
for (const auto &ref : getWidgets())
|
for (const auto &ref : getWidgets())
|
||||||
|
{
|
||||||
|
ref.get().injectNetworkAccessManager(m_networkAccessManager);
|
||||||
|
ref.get().injectDecodingThread(m_decoderThread);
|
||||||
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
m_ui->sampleWidget_1->setNote(48);
|
m_ui->sampleWidget_1->setNote(48);
|
||||||
m_ui->sampleWidget_2->setNote(50);
|
m_ui->sampleWidget_2->setNote(50);
|
||||||
@ -48,8 +54,8 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
|||||||
|
|
||||||
SamplesWidget::~SamplesWidget()
|
SamplesWidget::~SamplesWidget()
|
||||||
{
|
{
|
||||||
m_audioThread.exit();
|
m_decoderThread.exit();
|
||||||
m_audioThread.wait();
|
m_decoderThread.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplesWidget::setPreset(const presets::Preset &preset)
|
void SamplesWidget::setPreset(const presets::Preset &preset)
|
||||||
@ -82,13 +88,10 @@ void SamplesWidget::messageReceived(const midi::MidiMessage &message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplesWidget::setAudioDevice(const QAudioDeviceInfo &device)
|
void SamplesWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||||
{
|
{
|
||||||
for (const auto &ref : getWidgets())
|
for (const auto &ref : getWidgets())
|
||||||
{
|
ref.get().writeSamples(begin, end);
|
||||||
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
|
||||||
ref.get().setupAudioThread(device, m_audioThread);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplesWidget::sequencerTriggerSample(int index)
|
void SamplesWidget::sequencerTriggerSample(int index)
|
||||||
|
@ -5,14 +5,16 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkDiskCache>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "audioformat.h"
|
||||||
#include "presets.h"
|
#include "presets.h"
|
||||||
|
|
||||||
namespace Ui { class SamplesWidget; }
|
namespace Ui { class SamplesWidget; }
|
||||||
namespace midi { class MidiMessage; }
|
namespace midi { class MidiMessage; }
|
||||||
class SampleWidget;
|
class SampleWidget;
|
||||||
class QAudioDeviceInfo;
|
|
||||||
|
|
||||||
class SamplesWidget : public QWidget
|
class SamplesWidget : public QWidget
|
||||||
{
|
{
|
||||||
@ -26,7 +28,7 @@ public:
|
|||||||
|
|
||||||
void messageReceived(const midi::MidiMessage &message);
|
void messageReceived(const midi::MidiMessage &message);
|
||||||
|
|
||||||
void setAudioDevice(const QAudioDeviceInfo &device);
|
void writeSamples(frame_t *begin, frame_t *end);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sequencerTriggerSample(int index);
|
void sequencerTriggerSample(int index);
|
||||||
@ -41,7 +43,10 @@ private:
|
|||||||
|
|
||||||
const std::unique_ptr<Ui::SamplesWidget> m_ui;
|
const std::unique_ptr<Ui::SamplesWidget> m_ui;
|
||||||
|
|
||||||
presets::Preset m_preset;
|
QNetworkDiskCache m_cache;
|
||||||
|
QNetworkAccessManager m_networkAccessManager;
|
||||||
|
|
||||||
QThread m_audioThread;
|
QThread m_decoderThread;
|
||||||
|
|
||||||
|
presets::Preset m_preset;
|
||||||
};
|
};
|
||||||
|
134
samplewidget.cpp
134
samplewidget.cpp
@ -3,24 +3,18 @@
|
|||||||
|
|
||||||
#include <QAbstractEventDispatcher>
|
#include <QAbstractEventDispatcher>
|
||||||
#include <QSoundEffect>
|
#include <QSoundEffect>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QMetaEnum>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "audiodecoder.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
QString toString(QString value) { return value; }
|
QString toString(QString value) { return value; }
|
||||||
QString toString(int value) { return QString::number(value); }
|
QString toString(int value) { return QString::number(value); }
|
||||||
QString toString(bool value) { return value?"true":"false"; }
|
QString toString(bool value) { return value?"true":"false"; }
|
||||||
QString toString(QSoundEffect::Status value)
|
|
||||||
{
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case QSoundEffect::Null: return "Null";
|
|
||||||
case QSoundEffect::Loading: return "Loading";
|
|
||||||
case QSoundEffect::Ready: return "Ready";
|
|
||||||
case QSoundEffect::Error: return "Error";
|
|
||||||
}
|
|
||||||
|
|
||||||
return QString{"Unknown (%0)"}.arg(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleWidget::SampleWidget(QWidget *parent) :
|
SampleWidget::SampleWidget(QWidget *parent) :
|
||||||
@ -29,31 +23,22 @@ SampleWidget::SampleWidget(QWidget *parent) :
|
|||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(&m_player, &AudioPlayer::playingChanged, this, &SampleWidget::updateStatus);
|
||||||
|
|
||||||
connect(m_ui->pushButton, &QAbstractButton::pressed, this, [this](){ pressed(127); });
|
connect(m_ui->pushButton, &QAbstractButton::pressed, this, [this](){ pressed(127); });
|
||||||
connect(m_ui->pushButton, &QAbstractButton::released, this, &SampleWidget::released);
|
connect(m_ui->pushButton, &QAbstractButton::released, this, &SampleWidget::released);
|
||||||
connect(m_ui->toolButtonReload, &QAbstractButton::pressed, this, &SampleWidget::createEffect);
|
|
||||||
|
|
||||||
updateStatus();
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleWidget::~SampleWidget()
|
SampleWidget::~SampleWidget() = default;
|
||||||
{
|
|
||||||
destroyEffect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SampleWidget::setFile(const QString &presetId, const presets::File &file)
|
void SampleWidget::setFile(const QString &presetId, const presets::File &file)
|
||||||
{
|
{
|
||||||
m_presetId = presetId;
|
m_presetId = presetId;
|
||||||
m_file = file;
|
m_file = file;
|
||||||
|
|
||||||
if (m_effect)
|
startRequest();
|
||||||
{
|
|
||||||
auto sampleUrl = this->sampleUrl();
|
|
||||||
if (!sampleUrl.isEmpty())
|
|
||||||
QMetaObject::invokeMethod(m_effect.get(), [&effect=*m_effect,sampleUrl=std::move(sampleUrl)](){
|
|
||||||
effect.setSource(sampleUrl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto setupLabel = [&](const auto &value, QLabel *label){
|
const auto setupLabel = [&](const auto &value, QLabel *label){
|
||||||
QString text;
|
QString text;
|
||||||
@ -110,10 +95,7 @@ void SampleWidget::pressed(quint8 velocity)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(velocity)
|
Q_UNUSED(velocity)
|
||||||
|
|
||||||
if (m_effect)
|
m_player.restart();
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(m_effect.get(), &QSoundEffect::play);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_file && m_file->choke && *m_file->choke)
|
if (m_file && m_file->choke && *m_file->choke)
|
||||||
emit chokeTriggered(*m_file->choke);
|
emit chokeTriggered(*m_file->choke);
|
||||||
@ -125,41 +107,39 @@ void SampleWidget::released()
|
|||||||
|
|
||||||
void SampleWidget::forceStop()
|
void SampleWidget::forceStop()
|
||||||
{
|
{
|
||||||
if (m_effect)
|
m_player.setPlaying(false);
|
||||||
QMetaObject::invokeMethod(m_effect.get(), &QSoundEffect::stop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SampleWidget::setupAudioThread(const QAudioDeviceInfo &device, QThread &thread)
|
void SampleWidget::injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager)
|
||||||
{
|
{
|
||||||
m_device = device;
|
m_networkAccessManager = &networkAccessManager;
|
||||||
m_thread = &thread;
|
if (m_file)
|
||||||
|
startRequest();
|
||||||
createEffect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SampleWidget::createEffect()
|
void SampleWidget::injectDecodingThread(QThread &thread)
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(m_thread), [this](){
|
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){
|
||||||
m_effect = std::make_unique<QSoundEffect>(m_device);
|
m_decoder = std::make_unique<AudioDecoder>();
|
||||||
|
connect(this, &SampleWidget::startDecoding, m_decoder.get(), &AudioDecoder::startDecoding);
|
||||||
connect(m_effect.get(), &QSoundEffect::playingChanged, this, &SampleWidget::updateStatus);
|
connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &SampleWidget::decodingFinished);
|
||||||
connect(m_effect.get(), &QSoundEffect::statusChanged, this, &SampleWidget::updateStatus);
|
if (m_reply && m_reply->isFinished() && m_reply->error() == QNetworkReply::NoError)
|
||||||
|
m_decoder->startDecoding(m_reply);
|
||||||
const auto sampleUrl = this->sampleUrl();
|
|
||||||
if (!sampleUrl.isEmpty())
|
|
||||||
m_effect->setSource(sampleUrl);
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, &SampleWidget::updateStatus);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SampleWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||||
|
{
|
||||||
|
m_player.writeSamples(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
void SampleWidget::updateStatus()
|
void SampleWidget::updateStatus()
|
||||||
{
|
{
|
||||||
QPalette pal;
|
QPalette pal;
|
||||||
if (m_effect && m_file && m_file->color)
|
if (m_file && m_file->color)
|
||||||
{
|
{
|
||||||
const auto bright = m_effect->isPlaying() ? 255 : 155;
|
const auto bright = m_player.playing() ? 255 : 155;
|
||||||
const auto dark = m_effect->isPlaying() ?
|
const auto dark = m_player.playing() ?
|
||||||
#if !defined(Q_OS_WIN)
|
#if !defined(Q_OS_WIN)
|
||||||
80 : 0
|
80 : 0
|
||||||
#else
|
#else
|
||||||
@ -183,22 +163,52 @@ void SampleWidget::updateStatus()
|
|||||||
}
|
}
|
||||||
setPalette(pal);
|
setPalette(pal);
|
||||||
|
|
||||||
if (!m_effect)
|
if (m_reply)
|
||||||
m_ui->statusLabel->setText(tr("No player"));
|
{
|
||||||
|
if (!m_reply->isFinished())
|
||||||
|
m_ui->statusLabel->setText(tr("Downloading..."));
|
||||||
|
else if (m_reply->error() != QNetworkReply::NoError)
|
||||||
|
m_ui->statusLabel->setText(QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(m_reply->error()));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!m_decoder)
|
||||||
|
m_ui->statusLabel->setText(tr("Waiting for decoder thread..."));
|
||||||
|
else
|
||||||
|
m_ui->statusLabel->setText(tr("Decoding"));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
m_ui->statusLabel->setText(toString(m_effect->status()));
|
m_ui->statusLabel->setText(m_player.playing() ? tr("Playing") : tr("Ready"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SampleWidget::destroyEffect()
|
void SampleWidget::requestFinished()
|
||||||
{
|
{
|
||||||
if (m_effect)
|
qDebug() << "called" << m_reply->error() << m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute);
|
||||||
QMetaObject::invokeMethod(m_effect.get(), [effect=m_effect.release()](){ delete effect; });
|
if (m_reply->error() == QNetworkReply::NoError)
|
||||||
|
{
|
||||||
|
emit startDecoding(m_reply);
|
||||||
|
}
|
||||||
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl SampleWidget::sampleUrl() const
|
void SampleWidget::decodingFinished(const QAudioBuffer &buffer)
|
||||||
{
|
{
|
||||||
if (!m_file || !m_file->filename)
|
qDebug() << "called";
|
||||||
return {};
|
m_reply = nullptr;
|
||||||
|
m_player.setBuffer(buffer);
|
||||||
return QUrl{QString{"https://brunner.ninja/komposthaufen/dpm/presets/extracted/%0/%1"}.arg(m_presetId, *m_file->filename)};
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleWidget::startRequest()
|
||||||
|
{
|
||||||
|
if (m_networkAccessManager && m_file->filename)
|
||||||
|
{
|
||||||
|
QNetworkRequest request{QUrl{QString{"https://brunner.ninja/komposthaufen/dpm/presets/extracted/%0/%1"}.arg(m_presetId, *m_file->filename)}};
|
||||||
|
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||||
|
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true);
|
||||||
|
m_reply = std::shared_ptr<QNetworkReply>{m_networkAccessManager->get(request)};
|
||||||
|
connect(m_reply.get(), &QNetworkReply::finished, this, &SampleWidget::requestFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,16 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QAudioDeviceInfo>
|
|
||||||
|
|
||||||
|
#include "audioformat.h"
|
||||||
#include "presets.h"
|
#include "presets.h"
|
||||||
|
#include "audioplayer.h"
|
||||||
|
|
||||||
namespace Ui { class SampleWidget; }
|
namespace Ui { class SampleWidget; }
|
||||||
class QThread;
|
class QNetworkAccessManager;
|
||||||
class QSoundEffect;
|
class QNetworkReply;
|
||||||
|
class QAudioBuffer;
|
||||||
|
class AudioDecoder;
|
||||||
|
|
||||||
class SampleWidget : public QFrame
|
class SampleWidget : public QFrame
|
||||||
{
|
{
|
||||||
@ -34,27 +37,33 @@ public:
|
|||||||
|
|
||||||
void forceStop();
|
void forceStop();
|
||||||
|
|
||||||
void setupAudioThread(const QAudioDeviceInfo &device, QThread &thread);
|
void injectNetworkAccessManager(QNetworkAccessManager &networkAccessManager);
|
||||||
|
void injectDecodingThread(QThread &thread);
|
||||||
|
|
||||||
|
void writeSamples(frame_t *begin, frame_t *end);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void chokeTriggered(int choke);
|
void chokeTriggered(int choke);
|
||||||
|
void startDecoding(const std::shared_ptr<QIODevice> &device);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void createEffect();
|
|
||||||
void updateStatus();
|
void updateStatus();
|
||||||
|
void requestFinished();
|
||||||
|
void decodingFinished(const QAudioBuffer &buffer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void destroyEffect();
|
void startRequest();
|
||||||
|
|
||||||
QUrl sampleUrl() const;
|
|
||||||
|
|
||||||
const std::unique_ptr<Ui::SampleWidget> m_ui;
|
const std::unique_ptr<Ui::SampleWidget> m_ui;
|
||||||
|
|
||||||
|
std::shared_ptr<QNetworkReply> m_reply;
|
||||||
|
|
||||||
|
std::unique_ptr<AudioDecoder> m_decoder;
|
||||||
|
|
||||||
|
AudioPlayer m_player;
|
||||||
|
|
||||||
QString m_presetId;
|
QString m_presetId;
|
||||||
std::optional<presets::File> m_file;
|
std::optional<presets::File> m_file;
|
||||||
|
|
||||||
std::unique_ptr<QSoundEffect> m_effect;
|
QNetworkAccessManager *m_networkAccessManager{};
|
||||||
|
|
||||||
QThread *m_thread{};
|
|
||||||
QAudioDeviceInfo m_device;
|
|
||||||
};
|
};
|
||||||
|
@ -32,15 +32,15 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="toolButtonReload">
|
|
||||||
<property name="text">
|
|
||||||
<string>↻</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="value">
|
||||||
|
<number>24</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="pushButton">
|
<widget class="QPushButton" name="pushButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
Reference in New Issue
Block a user