added simple keyboard control
This commit is contained in:
@@ -9,7 +9,6 @@ AudioEngine::AudioEngine() : synth_(params_) {
|
||||
}
|
||||
|
||||
// TODO: get audio configurations
|
||||
|
||||
synth_.setSampleRate(sampleRate_);
|
||||
|
||||
}
|
||||
@@ -53,7 +52,13 @@ int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames,
|
||||
|
||||
int32_t AudioEngine::process(float* out, uint32_t nFrames) {
|
||||
|
||||
// pass to synth
|
||||
// pass note handling to synth
|
||||
NoteEvent event;
|
||||
while(noteQueue_.pop(event)) {
|
||||
synth_.handleNoteEvent(event);
|
||||
}
|
||||
|
||||
// pass sample generation to synth
|
||||
synth_.process(out, nFrames, sampleRate_);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <atomic>
|
||||
|
||||
#include "Synth.h"
|
||||
#include "../KeyboardController.h"
|
||||
|
||||
class AudioEngine {
|
||||
|
||||
@@ -19,8 +20,9 @@ public:
|
||||
// stops the audio stream.
|
||||
void stop();
|
||||
|
||||
// params getter
|
||||
// getters
|
||||
ParameterStore* parameters() { return ¶ms_; }
|
||||
NoteQueue& noteQueue() { return noteQueue_; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -31,6 +33,7 @@ private:
|
||||
int32_t process(float* out, uint32_t nFrames);
|
||||
|
||||
ParameterStore params_;
|
||||
NoteQueue noteQueue_;
|
||||
Synth synth_;
|
||||
|
||||
// TODO: id like a yml config file or something for these
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define SYNTH_PITCH_STANDARD 432.0f // frequency of home pitch
|
||||
#define SYNTH_MIDI_HOME 69 // midi note index of home pitch
|
||||
#define SYNTH_NOTES_PER_OCTAVE 12
|
||||
|
||||
Synth::Synth(const ParameterStore& params) : paramStore_(params) {
|
||||
|
||||
}
|
||||
@@ -22,26 +26,69 @@ inline float Synth::getParam(ParamId id) {
|
||||
return params_[static_cast<size_t>(id)].current;
|
||||
}
|
||||
|
||||
inline float Synth::noteToFrequency(uint8_t note) {
|
||||
return SYNTH_PITCH_STANDARD * pow(2.0f, static_cast<float>(note - SYNTH_MIDI_HOME) / static_cast<float>(SYNTH_NOTES_PER_OCTAVE));
|
||||
}
|
||||
|
||||
// TODO: stop popping on note-offs
|
||||
void Synth::handleNoteEvent(const NoteEvent& event) {
|
||||
if(event.type == NoteEventType::NoteOn) {
|
||||
// add note to activeNotes list
|
||||
if (std::find(heldNotes_.begin(), heldNotes_.end(), event.note) == heldNotes_.end()) {
|
||||
heldNotes_.push_back(event.note);
|
||||
}
|
||||
} else {
|
||||
// remove note from activeNotes list
|
||||
auto it = std::find(heldNotes_.begin(), heldNotes_.end(), event.note);
|
||||
if (it != heldNotes_.end()) {
|
||||
heldNotes_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentNote();
|
||||
}
|
||||
|
||||
void Synth::updateCurrentNote() {
|
||||
if(heldNotes_.empty()) {
|
||||
noteActive_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t note = heldNotes_.back();
|
||||
frequency_ = noteToFrequency(note);
|
||||
noteActive_ = true;
|
||||
}
|
||||
|
||||
void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
|
||||
// yeah really only need to update this once per buffer if its ~6ms latency
|
||||
updateParams();
|
||||
|
||||
float sampleOut = 0.0f;
|
||||
|
||||
for (uint32_t i = 0; i < nFrames; i++) {
|
||||
|
||||
// updates internal buffered parameters for smoothing
|
||||
for(auto& p : params_) p.update(); // TODO: profile this
|
||||
|
||||
// based on oscillator frequency
|
||||
float frequency = getParam(ParamId::Osc1Frequency); // this will come from a midi controller
|
||||
float phaseInc = 2.0f * M_PI * frequency / static_cast<float>(sampleRate);
|
||||
// skip if no note is being played
|
||||
// TODO: this will be handled by an idle envelope eventually
|
||||
// could also say gain = 0.0f; but w/e, this saves computing
|
||||
if(!noteActive_) {
|
||||
out[2*i] = 0.0f;
|
||||
out[2*i+1] = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
float phaseInc = 2.0f * M_PI * frequency_ / static_cast<float>(sampleRate);
|
||||
|
||||
// sample generation
|
||||
float gain = getParam(ParamId::Osc1Gain);
|
||||
float sample = std::sin(phase_) * gain;
|
||||
sampleOut = std::sin(phase_) * gain;
|
||||
|
||||
// write to buffer
|
||||
out[2*i] = sample; // left
|
||||
out[2*i+1] = sample; // right
|
||||
out[2*i] = sampleOut; // left
|
||||
out[2*i+1] = sampleOut; // right
|
||||
|
||||
// sampling business
|
||||
phase_ += phaseInc;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ParameterStore.h"
|
||||
#include "../NoteQueue.h"
|
||||
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
struct SmoothedParam {
|
||||
@@ -10,9 +12,7 @@ struct SmoothedParam {
|
||||
float target = 0.0f;
|
||||
float gain = 0.001f;
|
||||
|
||||
inline void update() {
|
||||
current += gain * (target - current);
|
||||
}
|
||||
inline void update() { current += gain * (target - current); }
|
||||
};
|
||||
|
||||
class Synth {
|
||||
@@ -24,6 +24,9 @@ public:
|
||||
// generates a buffer of audio samples nFrames long
|
||||
void process(float* out, uint32_t nFrames, uint32_t sampleRate);
|
||||
|
||||
// handles note events
|
||||
void handleNoteEvent(const NoteEvent& event);
|
||||
|
||||
// sample rate setter
|
||||
void setSampleRate(uint32_t sampleRate) { sampleRate_ = sampleRate; }
|
||||
|
||||
@@ -35,10 +38,24 @@ private:
|
||||
// small getter that abstracts all the static casts and such
|
||||
inline float getParam(ParamId);
|
||||
|
||||
// for calculating frequency based on midi note id
|
||||
inline float noteToFrequency(uint8_t note);
|
||||
|
||||
// finds the active voice
|
||||
void updateCurrentNote();
|
||||
|
||||
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_;
|
||||
|
||||
// here's where the actual sound generation happens
|
||||
// TODO: put this in an oscillator class
|
||||
bool noteActive_ = false;
|
||||
float frequency_ = 220.0f;
|
||||
float phase_ = 0.0f;
|
||||
|
||||
// TODO: might make this a fixed array where index=midi-note and the value=velocity
|
||||
// so non-zero elements are the ones currently being played
|
||||
std::vector<uint8_t> heldNotes_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user