audio checkpoint
This commit is contained in:
@@ -4,7 +4,7 @@ project(metabolus LANGUAGES CXX)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia)
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
@@ -13,9 +13,15 @@ qt_add_executable(metabolus
|
|||||||
src/MainWindow.cpp
|
src/MainWindow.cpp
|
||||||
src/MainWindow.h
|
src/MainWindow.h
|
||||||
src/MainWindow.ui
|
src/MainWindow.ui
|
||||||
|
src/synth/AudioStream.cpp
|
||||||
|
src/synth/AudioStream.h
|
||||||
|
src/synth/Synth.cpp
|
||||||
|
src/synth/Synth.h
|
||||||
resources/resources.qrc
|
resources/resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(metabolus
|
target_link_libraries(metabolus
|
||||||
PRIVATE Qt6::Widgets
|
PRIVATE
|
||||||
|
Qt6::Widgets
|
||||||
|
Qt6::Multimedia
|
||||||
)
|
)
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "ui_MainWindow.h"
|
#include "ui_MainWindow.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
|
||||||
: QMainWindow(parent),
|
|
||||||
ui(new Ui::MainWindow)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
// Initialize UI
|
// Initialize UI
|
||||||
@@ -22,6 +20,13 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
connect(ui->inputStep, &QLineEdit::editingFinished, this, &MainWindow::onStepChanged);
|
connect(ui->inputStep, &QLineEdit::editingFinished, this, &MainWindow::onStepChanged);
|
||||||
connect(ui->inputValue, &QLineEdit::editingFinished, this, &MainWindow::onValueChanged);
|
connect(ui->inputValue, &QLineEdit::editingFinished, this, &MainWindow::onValueChanged);
|
||||||
|
|
||||||
|
// synth business
|
||||||
|
synth_ = new Synth(this);
|
||||||
|
audioStream_ = new AudioStream(synth_, this);
|
||||||
|
|
||||||
|
audioStream_->start();
|
||||||
|
synth_->audioSink()->start(audioStream_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
@@ -46,6 +51,8 @@ void MainWindow::updateCounterLabel() {
|
|||||||
void MainWindow::onSliderValueChanged(int value) {
|
void MainWindow::onSliderValueChanged(int value) {
|
||||||
QSignalBlocker blocker(ui->inputValue);
|
QSignalBlocker blocker(ui->inputValue);
|
||||||
ui->inputValue->setText(QString::number(value));
|
ui->inputValue->setText(QString::number(value));
|
||||||
|
|
||||||
|
synth_->setFrequency(static_cast<float>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// allows only values within the min, max to be set by the text field
|
// allows only values within the min, max to be set by the text field
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
#include "synth/Synth.h"
|
||||||
|
#include "synth/AudioStream.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
@@ -38,4 +41,8 @@ private:
|
|||||||
void applySliderRange();
|
void applySliderRange();
|
||||||
void applySliderStep();
|
void applySliderStep();
|
||||||
void syncValueToUi(int value);
|
void syncValueToUi(int value);
|
||||||
|
|
||||||
|
Synth *synth_ = nullptr;
|
||||||
|
AudioStream *audioStream_ = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
// std::cout << "Main()" << std::endl;
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
MainWindow window;
|
MainWindow window;
|
||||||
|
|||||||
27
src/synth/AudioStream.cpp
Normal file
27
src/synth/AudioStream.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
#include "AudioStream.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
AudioStream::AudioStream(Synth *synth, QObject *parent) : QIODevice(parent), synth_(synth) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::start() {
|
||||||
|
|
||||||
|
// std::cout << "AudioStream::start()" << std::endl;
|
||||||
|
open(QIODevice::ReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::stop() {
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AudioStream::readData(char *data, qint64 maxlen) {
|
||||||
|
|
||||||
|
// std::cout << "sample out " << std::endl;
|
||||||
|
QByteArray samples = synth_->generateSamples(maxlen);
|
||||||
|
memcpy(data, samples.constData(), samples.size());
|
||||||
|
return samples.size();
|
||||||
|
}
|
||||||
23
src/synth/AudioStream.h
Normal file
23
src/synth/AudioStream.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
|
#include "Synth.h"
|
||||||
|
|
||||||
|
class AudioStream : public QIODevice
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AudioStream(Synth *synth, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
qint64 readData(char *data, qint64 maxlen) override;
|
||||||
|
qint64 writeData(const char *, qint64) override { return 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Synth *synth_;
|
||||||
|
};
|
||||||
112
src/synth/Synth.cpp
Normal file
112
src/synth/Synth.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
|
||||||
|
#include "Synth.h"
|
||||||
|
|
||||||
|
#include <QMediaDevices>
|
||||||
|
#include <QtMath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Synth::Synth(QObject *parent) : QObject(parent) {
|
||||||
|
|
||||||
|
format_.setSampleRate(44100);
|
||||||
|
format_.setChannelCount(1);
|
||||||
|
format_.setSampleFormat(QAudioFormat::Int16);
|
||||||
|
|
||||||
|
QAudioDevice device = QMediaDevices::defaultAudioOutput();
|
||||||
|
|
||||||
|
if (!device.isFormatSupported(format_)) {
|
||||||
|
format_ = device.preferredFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSink_ = new QAudioSink(device, format_, this);
|
||||||
|
audioSink_->setBufferSize(4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
Synth::~Synth() {
|
||||||
|
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::start() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
// std::cout << "Synth::start()" << std::endl;
|
||||||
|
if (audioSink_->state() == QAudio::ActiveState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
audioDevice_ = audioSink_->start();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::stop() {
|
||||||
|
|
||||||
|
if (audioSink_) {
|
||||||
|
audioSink_->stop();
|
||||||
|
audioDevice_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::setFrequency(float frequency) {
|
||||||
|
|
||||||
|
frequency_ = qMax(1.0f, frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Synth::generateSamples(qint64 bytes) {
|
||||||
|
|
||||||
|
QByteArray buffer(bytes, Qt::Uninitialized);
|
||||||
|
|
||||||
|
const int channels = format_.channelCount();
|
||||||
|
const int sampleRate = format_.sampleRate();
|
||||||
|
//const float phaseInc = 2.0f * M_PI * frequency_ / sampleRate;
|
||||||
|
freq += 1.0f;
|
||||||
|
const float phaseInc = 2.0f * M_PI * freq / sampleRate;
|
||||||
|
|
||||||
|
if (format_.sampleFormat() == QAudioFormat::Int16) {
|
||||||
|
int16_t* out = reinterpret_cast<int16_t*>(buffer.data());
|
||||||
|
int frames = bytes / (sizeof(int16_t) * channels);
|
||||||
|
|
||||||
|
for (int i = 0; i < frames; ++i) {
|
||||||
|
int16_t s = static_cast<int16_t>(32767 * std::sin(phase_));
|
||||||
|
for (int c = 0; c < channels; ++c)
|
||||||
|
*out++ = s;
|
||||||
|
phase_ += phaseInc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (format_.sampleFormat() == QAudioFormat::Float) {
|
||||||
|
float* out = reinterpret_cast<float*>(buffer.data());
|
||||||
|
int frames = bytes / (sizeof(float) * channels);
|
||||||
|
|
||||||
|
for (int i = 0; i < frames; ++i) {
|
||||||
|
float s = std::sin(phase_);
|
||||||
|
for (int c = 0; c < channels; ++c)
|
||||||
|
*out++ = s;
|
||||||
|
phase_ += phaseInc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::applyConfig(const AudioConfig& config) {
|
||||||
|
|
||||||
|
// map struct values to the QAudioFormat
|
||||||
|
QAudioFormat format;
|
||||||
|
format.setSampleRate(config.sampleRate);
|
||||||
|
format.setChannelCount(config.channelCount);
|
||||||
|
format.setSampleFormat(config.sampleFormat);
|
||||||
|
|
||||||
|
// must create a new device
|
||||||
|
QAudioDevice device = QMediaDevices::defaultAudioOutput();
|
||||||
|
|
||||||
|
if (!device.isFormatSupported(format)) {
|
||||||
|
std::cout << "Requested format not supported, using preferred format" << std::endl;
|
||||||
|
format = device.preferredFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
format_ = format;
|
||||||
|
|
||||||
|
// and must create a new audioSink
|
||||||
|
delete audioSink_;
|
||||||
|
audioSink_ = new QAudioSink(device, format_, this);
|
||||||
|
|
||||||
|
audioSink_->setBufferSize(config.bufferSize);
|
||||||
|
}
|
||||||
48
src/synth/Synth.h
Normal file
48
src/synth/Synth.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QAudioFormat>
|
||||||
|
#include <QAudioSink>
|
||||||
|
#include <QIODevice>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
struct AudioConfig {
|
||||||
|
int sampleRate = 44100;
|
||||||
|
int channelCount = 1;
|
||||||
|
QAudioFormat::SampleFormat sampleFormat = QAudioFormat::Int16;
|
||||||
|
int bufferSize = 4096; // bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
class Synth : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Synth(QObject *parent = nullptr);
|
||||||
|
~Synth();
|
||||||
|
|
||||||
|
// audioSink is the media consumer for the audio data
|
||||||
|
QAudioSink* audioSink() { return audioSink_; }
|
||||||
|
|
||||||
|
// audio config setter/getter
|
||||||
|
void applyConfig(const AudioConfig& config);
|
||||||
|
const QAudioFormat& format() const { return format_; }
|
||||||
|
|
||||||
|
// synth commands
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void setFrequency(float frequency);
|
||||||
|
// bread and butter right here
|
||||||
|
QByteArray generateSamples(qint64 bytes);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QAudioFormat format_;
|
||||||
|
QAudioSink *audioSink_ = nullptr;
|
||||||
|
QIODevice *audioDevice_ = nullptr;
|
||||||
|
|
||||||
|
std::atomic<float> frequency_{440.0f};
|
||||||
|
float phase_ = 0.0f;
|
||||||
|
|
||||||
|
float freq = 400.0f;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user