diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index 0081ff534..8d8ce6204 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -249,6 +249,7 @@ HEADERS += \ ../../Storage/Tape/Parsers/*.hpp \ \ audiobuffer.h \ + functionthread.h \ mainwindow.h \ scantargetwidget.h \ timer.h diff --git a/OSBindings/Qt/audiobuffer.h b/OSBindings/Qt/audiobuffer.h index 016201af2..d4f4cb0cb 100644 --- a/OSBindings/Qt/audiobuffer.h +++ b/OSBindings/Qt/audiobuffer.h @@ -24,6 +24,7 @@ struct AudioBuffer: public QIODevice { } void setDepth(size_t depth) { + std::lock_guard lock(mutex); buffer.resize(depth); } @@ -35,7 +36,7 @@ struct AudioBuffer: public QIODevice { } std::lock_guard lock(mutex); - if(readPointer == writePointer) return 0; + if(readPointer == writePointer || buffer.empty()) return 0; const size_t dataAvailable = std::min(writePointer - readPointer, size_t(maxlen)); size_t bytesToCopy = dataAvailable; @@ -66,6 +67,7 @@ struct AudioBuffer: public QIODevice { // after the buffer is full will overwrite the newest data. void write(const std::vector &source) { std::lock_guard lock(mutex); + if(buffer.empty()) return; const size_t sourceSize = source.size() * sizeof(int16_t); size_t bytesToCopy = sourceSize; diff --git a/OSBindings/Qt/functionthread.h b/OSBindings/Qt/functionthread.h new file mode 100644 index 000000000..63e237547 --- /dev/null +++ b/OSBindings/Qt/functionthread.h @@ -0,0 +1,31 @@ +#ifndef FUNCTIONTHREAD_H +#define FUNCTIONTHREAD_H + +#include + +/*! + * \brief The LambdaThread class + * + * Provides a QThread which performs a supplied lambda before kicking off its event loop. + * + * Disclaimer: this might be a crutch that reveals a misunderstanding of the Qt + * threading infrastructure. We'll see. + */ +class FunctionThread: public QThread { + public: + FunctionThread() : QThread() {} + + void setFunction(const std::function &function) { + this->function = function; + } + + void run() override { + function(); + exec(); + } + + private: + std::function function; +}; + +#endif // FUNCTIONTHREAD_H diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index ae3013608..89f822d40 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -133,12 +133,6 @@ void MainWindow::launchMachine() { ui->missingROMsBox->setVisible(false); uiPhase = UIPhase::RunningMachine; - // Install user-friendly options. TODO: plus user overrides. -// const auto configurable = machine->configurable_device(); -// if(configurable) { -// configurable->set_options(configurable->get_options()); -// } - // Supply the scan target. // TODO: in the future, hypothetically, deal with non-scan producers. const auto scan_producer = machine->scan_producer(); @@ -159,19 +153,23 @@ void MainWindow::launchMachine() { // are available, and — at least for now — assume a good buffer size. audioIsStereo = (idealFormat.channelCount() > 1) && speaker->get_is_stereo(); audioIs8bit = idealFormat.sampleSize() < 16; - speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo); - - // Adjust format appropriately, and create an audio output. idealFormat.setChannelCount(1 + int(audioIsStereo)); idealFormat.setSampleSize(audioIs8bit ? 8 : 16); - audioOutput = std::make_unique(idealFormat, this); - // Start the output. The additional `audioBuffer` is meant to minimise latency, - // believe it or not, given Qt's semantics. + speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo); speaker->set_delegate(this); - audioOutput->setBufferSize(samplesPerBuffer * sizeof(int16_t)); - audioOutput->start(&audioBuffer); - audioBuffer.setDepth(audioOutput->bufferSize()); + + audioThread.setFunction([this, idealFormat] { + // Create an audio output. + audioOutput = std::make_unique(idealFormat); + + // Start the output. The additional `audioBuffer` is meant to minimise latency, + // believe it or not, given Qt's semantics. + audioOutput->setBufferSize(samplesPerBuffer * sizeof(int16_t)); + audioOutput->start(&audioBuffer); + audioBuffer.setDepth(audioOutput->bufferSize()); + }); + audioThread.start(); } } @@ -213,13 +211,6 @@ void MainWindow::launchMachine() { void MainWindow::speaker_did_complete_samples(Outputs::Speaker::Speaker *, const std::vector &buffer) { audioBuffer.write(buffer); -// const char *data = reinterpret_cast(buffer.data()); -// size_t sizeLeft = buffer.size() * sizeof(int16_t); -// while(sizeLeft) { -// const auto bytesWritten = audioIODevice->write(data, qint64(sizeLeft)); -// sizeLeft -= bytesWritten; -// data += bytesWritten; -// } } void MainWindow::dragEnterEvent(QDragEnterEvent* event) { diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index 4a1eda8ac..ff5ddc53d 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -8,6 +8,7 @@ #include "audiobuffer.h" #include "timer.h" #include "ui_mainwindow.h" +#include "functionthread.h" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../../Machines/Utility/MachineForTarget.hpp" @@ -54,6 +55,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat bool audioIs8bit = false, audioIsStereo = false; void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) override; AudioBuffer audioBuffer; + FunctionThread audioThread; bool processEvent(QKeyEvent *);