add parameter store
This commit is contained in:
@@ -44,6 +44,7 @@ qt_add_executable(metabolus
|
|||||||
src/MainWindow.cpp
|
src/MainWindow.cpp
|
||||||
src/MainWindow.h
|
src/MainWindow.h
|
||||||
src/MainWindow.ui
|
src/MainWindow.ui
|
||||||
|
src/ParameterStore.h
|
||||||
src/synth/AudioEngine.cpp
|
src/synth/AudioEngine.cpp
|
||||||
src/synth/AudioEngine.h
|
src/synth/AudioEngine.h
|
||||||
src/synth/Synth.cpp
|
src/synth/Synth.cpp
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
|
||||||
#include "ui_MainWindow.h"
|
#include "ui_MainWindow.h"
|
||||||
|
#include "ParameterStore.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
|
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
connect(ui->buttonReset, &QPushButton::clicked, this, &MainWindow::onResetClicked);
|
connect(ui->buttonReset, &QPushButton::clicked, this, &MainWindow::onResetClicked);
|
||||||
|
|
||||||
// slider business
|
// slider business
|
||||||
|
// TODO: smart slider widget
|
||||||
connect(ui->slider, &QSlider::valueChanged, this, &MainWindow::onSliderValueChanged);
|
connect(ui->slider, &QSlider::valueChanged, this, &MainWindow::onSliderValueChanged);
|
||||||
connect(ui->inputMin, &QLineEdit::editingFinished, this, &MainWindow::onMinChanged);
|
connect(ui->inputMin, &QLineEdit::editingFinished, this, &MainWindow::onMinChanged);
|
||||||
connect(ui->inputMax, &QLineEdit::editingFinished, this, &MainWindow::onMaxChanged);
|
connect(ui->inputMax, &QLineEdit::editingFinished, this, &MainWindow::onMaxChanged);
|
||||||
@@ -24,6 +27,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
audio_ = new AudioEngine();
|
audio_ = new AudioEngine();
|
||||||
audio_->start();
|
audio_->start();
|
||||||
|
|
||||||
|
// init defaults
|
||||||
|
// TODO:: there's gotta be a better way
|
||||||
|
ui->slider->setValue(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].def);
|
||||||
|
ui->slider->setMinimum(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].min);
|
||||||
|
ui->slider->setMaximum(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].max);
|
||||||
|
ui->inputValue->setText(QString::number(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].def));
|
||||||
|
ui->inputMin->setText(QString::number(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].min));
|
||||||
|
ui->inputMax->setText(QString::number(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1Frequency)].max));
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
@@ -49,7 +60,8 @@ 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));
|
||||||
|
|
||||||
audio_->setFrequency(static_cast<float>(value));
|
// forward value so synthesizer can read
|
||||||
|
audio_->parameters()->set(ParamId::Osc1Frequency, 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
|
||||||
|
|||||||
@@ -42,5 +42,4 @@ private:
|
|||||||
void syncValueToUi(int value);
|
void syncValueToUi(int value);
|
||||||
|
|
||||||
AudioEngine* audio_ = nullptr;
|
AudioEngine* audio_ = nullptr;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
80
src/ParameterStore.h
Normal file
80
src/ParameterStore.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
enum class ParamId : uint16_t {
|
||||||
|
Osc1Frequency,
|
||||||
|
Osc1Gain,
|
||||||
|
Osc1VolumeEnvA,
|
||||||
|
Osc1VolumeEnvD,
|
||||||
|
Osc1VolumeEnvS,
|
||||||
|
Osc1VolumeEnvR,
|
||||||
|
FilterCutoffEnvA,
|
||||||
|
FilterCutoffEnvD,
|
||||||
|
FilterCutoffEnvS,
|
||||||
|
FilterCutoffEnvR,
|
||||||
|
FilterResonanceEnvA,
|
||||||
|
FilterResonanceEnvD,
|
||||||
|
FilterResonanceEnvS,
|
||||||
|
FilterResonanceEnvR,
|
||||||
|
// ... and so on
|
||||||
|
// this list could be like 200 long if I really wanted to
|
||||||
|
Count // for sizing
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParamDef {
|
||||||
|
float def;
|
||||||
|
float min;
|
||||||
|
float max;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: make these configurable via yml file too
|
||||||
|
// TODO: and then when I have full on profile saving there will be a default profile to load from
|
||||||
|
constexpr std::array<ParamDef, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{
|
||||||
|
{ 100.0f, 20.0f, 600.0f}, // Osc1Freq
|
||||||
|
{ 0.8f, 0.0f, 1.0f}, // Osc1Gain
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvA,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvD,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvS,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // Osc1VolumeEnvR,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvA,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvD,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvS,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterCutoffEnvR,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvA,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvD,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvS,
|
||||||
|
{ 10.0f, 0.0f, 1000.0f}, // FilterResonanceEnvR,
|
||||||
|
}};
|
||||||
|
|
||||||
|
constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count);
|
||||||
|
|
||||||
|
class ParameterStore {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ParameterStore() { resetToDefaults(); }
|
||||||
|
~ParameterStore() = default;
|
||||||
|
|
||||||
|
// TODO: move into implementation file ? idk
|
||||||
|
void set(ParamId id, float value) {
|
||||||
|
values_[static_cast<size_t>(id)].store(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
float get(ParamId id) const {
|
||||||
|
return values_[static_cast<size_t>(id)].load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetToDefaults() {
|
||||||
|
for(size_t i = 0; i < PARAM_COUNT; i++) {
|
||||||
|
values_[i].store(PARAM_DEFS[i].def, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::array<std::atomic<float>, PARAM_COUNT> values_;
|
||||||
|
|
||||||
|
};
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
|
|
||||||
#include "AudioEngine.h"
|
#include "AudioEngine.h"
|
||||||
|
|
||||||
#define _USE_MATH_DEFINES
|
|
||||||
#include <cmath>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#ifndef M_PI // I hate my stupid chungus life
|
AudioEngine::AudioEngine() : synth_(params_) {
|
||||||
#define M_PI 3.14159265358979323846
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AudioEngine::AudioEngine() {
|
|
||||||
if(audio_.getDeviceCount() < 1) {
|
if(audio_.getDeviceCount() < 1) {
|
||||||
throw std::runtime_error("No audio devices found");
|
throw std::runtime_error("No audio devices found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: get audio configurations
|
||||||
|
|
||||||
|
synth_.setSampleRate(sampleRate_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEngine::~AudioEngine() {
|
AudioEngine::~AudioEngine() {
|
||||||
@@ -23,24 +22,17 @@ bool AudioEngine::start() {
|
|||||||
|
|
||||||
RtAudio::StreamParameters params;
|
RtAudio::StreamParameters params;
|
||||||
params.deviceId = audio_.getDefaultOutputDevice();
|
params.deviceId = audio_.getDefaultOutputDevice();
|
||||||
params.nChannels = 2;
|
params.nChannels = channels_;
|
||||||
params.firstChannel = 0;
|
params.firstChannel = 0;
|
||||||
|
|
||||||
RtAudio::StreamOptions options;
|
RtAudio::StreamOptions options;
|
||||||
options.flags = RTAUDIO_MINIMIZE_LATENCY;
|
options.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||||
|
|
||||||
/*
|
// TODO: error check this please
|
||||||
try {
|
|
||||||
audio_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options);
|
|
||||||
audio_.startStream();
|
|
||||||
} catch(RtAudioError& e) {
|
|
||||||
std::cerr << e.getMessage() << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
audio_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options);
|
audio_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options);
|
||||||
audio_.startStream();
|
audio_.startStream();
|
||||||
|
|
||||||
|
// sanity check
|
||||||
std::cout << "sample rate: " << sampleRate_ << " buffer frames: " << bufferFrames_ << std::endl;
|
std::cout << "sample rate: " << sampleRate_ << " buffer frames: " << bufferFrames_ << std::endl;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -52,10 +44,6 @@ void AudioEngine::stop() {
|
|||||||
if(audio_.isStreamOpen()) audio_.closeStream();
|
if(audio_.isStreamOpen()) audio_.closeStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioEngine::setFrequency(float freq) {
|
|
||||||
targetFreq_.store(freq, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) {
|
int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) {
|
||||||
|
|
||||||
if (status) std::cerr << "Stream underflow!" << std::endl;
|
if (status) std::cerr << "Stream underflow!" << std::endl;
|
||||||
@@ -64,21 +52,9 @@ int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t AudioEngine::process(float* out, uint32_t nFrames) {
|
int32_t AudioEngine::process(float* out, uint32_t nFrames) {
|
||||||
const float sr = static_cast<float>(sampleRate_);
|
|
||||||
float target = targetFreq_.load(std::memory_order_relaxed);
|
|
||||||
float freqStep = (target - currentFreq_) / nFrames;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < nFrames; ++i) {
|
// pass to synth
|
||||||
currentFreq_ += freqStep;
|
synth_.process(out, nFrames, sampleRate_);
|
||||||
|
|
||||||
float phaseInc = 2.0f * M_PI * currentFreq_ / sr;
|
|
||||||
|
|
||||||
out[2*i] = std::sin(phase_); // left
|
|
||||||
out[2*i+1] = std::sin(phase_); // right
|
|
||||||
|
|
||||||
phase_ += phaseInc;
|
|
||||||
if (phase_ > 2.0f * M_PI) phase_ -= 2.0f * M_PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "Synth.h"
|
||||||
|
|
||||||
class AudioEngine {
|
class AudioEngine {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -13,19 +15,19 @@ public:
|
|||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
void stop();
|
void stop();
|
||||||
|
ParameterStore* parameters() { return ¶ms_; }
|
||||||
void setFrequency(float freq);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int32_t audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double streamTime, RtAudioStreamStatus status, void* userData);
|
static int32_t audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double streamTime, RtAudioStreamStatus status, void* userData);
|
||||||
int32_t process(float* out, uint32_t nFrames);
|
int32_t process(float* out, uint32_t nFrames);
|
||||||
|
|
||||||
|
ParameterStore params_;
|
||||||
|
Synth synth_;
|
||||||
|
|
||||||
|
// TODO: id like a yml config file or something for these
|
||||||
RtAudio audio_;
|
RtAudio audio_;
|
||||||
uint32_t sampleRate_ = 44100;
|
uint32_t sampleRate_ = 44100;
|
||||||
uint32_t bufferFrames_ = 256;
|
uint32_t bufferFrames_ = 256; // time per buffer = BF/SR (256/44100 = 5.8ms)
|
||||||
|
uint32_t channels_ = 2; // stereo
|
||||||
std::atomic<float> targetFreq_{ 400.0f };
|
|
||||||
float currentFreq_ = 440.0f;
|
|
||||||
float phase_ = 0.0f;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,115 +1,51 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
#include "Synth.h"
|
#include "Synth.h"
|
||||||
|
|
||||||
#include <QMediaDevices>
|
|
||||||
#include <QtMath>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
Synth::Synth(QObject *parent) : QObject(parent) {
|
#ifndef M_PI // I hate my stupid chungus life
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
format_.setSampleRate(44100);
|
Synth::Synth(const ParameterStore& params) : paramStore_(params) {
|
||||||
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() {
|
void Synth::updateParams() {
|
||||||
|
for(size_t i = 0; i < PARAM_COUNT; i++) {
|
||||||
if (audioSink_) {
|
params_[i].target = paramStore_.get(static_cast<ParamId>(i));
|
||||||
audioSink_->stop();
|
|
||||||
audioDevice_ = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::setFrequency(float frequency) {
|
inline float Synth::getParam(ParamId id) {
|
||||||
|
return params_[static_cast<size_t>(id)].current;
|
||||||
frequency_ = qMax(1.0f, frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Synth::generateSamples(qint64 bytes) {
|
void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||||
|
|
||||||
QByteArray buffer(bytes, Qt::Uninitialized);
|
// yeah really only need to update this once per buffer if its ~6ms latency
|
||||||
|
updateParams();
|
||||||
|
|
||||||
const int channels = format_.channelCount();
|
for (uint32_t i = 0; i < nFrames; i++) {
|
||||||
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) {
|
for(auto& p : params_) p.update(); // TODO: profile this
|
||||||
int16_t* out = reinterpret_cast<int16_t*>(buffer.data());
|
|
||||||
int frames = bytes / (sizeof(int16_t) * channels);
|
|
||||||
|
|
||||||
for (int i = 0; i < frames; ++i) {
|
// based on oscillator frequency
|
||||||
int16_t s = static_cast<int16_t>(32767 * std::sin(phase_));
|
float frequency = getParam(ParamId::Osc1Frequency); // this will come from a midi controller
|
||||||
for (int c = 0; c < channels; ++c)
|
float phaseInc = 2.0f * M_PI * frequency / static_cast<float>(sampleRate);
|
||||||
*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) {
|
// sample generation
|
||||||
float s = std::sin(phase_);
|
float gain = getParam(ParamId::Osc1Gain);
|
||||||
for (int c = 0; c < channels; ++c)
|
float sample = std::sin(phase_) * gain;
|
||||||
*out++ = s;
|
|
||||||
phase_ += phaseInc;
|
// write to buffer
|
||||||
}
|
out[2*i] = sample; // left
|
||||||
|
out[2*i+1] = sample; // right
|
||||||
|
|
||||||
|
// sampling business
|
||||||
|
phase_ += phaseInc;
|
||||||
|
if (phase_ > 2.0f * M_PI) phase_ -= 2.0f * M_PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -1,52 +1,38 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/*
|
#include "../ParameterStore.h"
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QAudioFormat>
|
|
||||||
#include <QAudioSink>
|
|
||||||
#include <QIODevice>
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
struct AudioConfig {
|
struct SmoothedParam {
|
||||||
int sampleRate = 44100;
|
float current = 0.0f;
|
||||||
int channelCount = 1;
|
float target = 0.0f;
|
||||||
QAudioFormat::SampleFormat sampleFormat = QAudioFormat::Int16;
|
float gain = 0.001f;
|
||||||
int bufferSize = 4096; // bytes
|
|
||||||
|
inline void update() {
|
||||||
|
current += gain * (target - current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Synth : public QObject {
|
class Synth {
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Synth(QObject *parent = nullptr);
|
Synth(const ParameterStore& params);
|
||||||
~Synth();
|
~Synth() = default;
|
||||||
|
|
||||||
// audioSink is the media consumer for the audio data
|
void process(float* out, uint32_t nFrames, uint32_t sampleRate);
|
||||||
QAudioSink* audioSink() { return audioSink_; }
|
void setSampleRate(uint32_t sampleRate) { sampleRate_ = sampleRate; }
|
||||||
|
|
||||||
// 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:
|
private:
|
||||||
QAudioFormat format_;
|
|
||||||
QAudioSink *audioSink_ = nullptr;
|
|
||||||
QIODevice *audioDevice_ = nullptr;
|
|
||||||
|
|
||||||
std::atomic<float> frequency_{440.0f};
|
void updateParams();
|
||||||
|
inline float getParam(ParamId);
|
||||||
|
|
||||||
|
const ParameterStore& paramStore_;
|
||||||
|
// smoothed params creates a buffer in case the thread controlling paramStore gets blocked
|
||||||
|
std::array<SmoothedParam, PARAM_COUNT> params_;
|
||||||
|
uint32_t sampleRate_;
|
||||||
|
|
||||||
float phase_ = 0.0f;
|
float phase_ = 0.0f;
|
||||||
|
|
||||||
float freq = 400.0f;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user