comments
This commit is contained in:
@@ -21,9 +21,10 @@ AudioEngine::~AudioEngine() {
|
||||
|
||||
bool AudioEngine::start() {
|
||||
|
||||
// initialize the audio engine
|
||||
RtAudio::StreamParameters params;
|
||||
params.deviceId = audio_.getDefaultOutputDevice();
|
||||
params.nChannels = channels_;
|
||||
params.nChannels = channels_; // we're doing two duplicate channels for pseudo-mono
|
||||
params.firstChannel = 0;
|
||||
|
||||
RtAudio::StreamOptions options;
|
||||
@@ -47,7 +48,8 @@ void AudioEngine::stop() {
|
||||
|
||||
int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) {
|
||||
|
||||
if (status) std::cerr << "Stream underflow!" << std::endl;
|
||||
// error if process is too slow for the callback. If this is consistent, then need to optimize synth.process() or whatever cascades from it
|
||||
if (status) std::cerr << "Stream underflow" << std::endl;
|
||||
|
||||
return static_cast<AudioEngine*>(userData)->process(static_cast<float*>(outputBuffer), nFrames);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ void Envelope::noteOff() {
|
||||
if(state_ != State::Idle) state_ = State::Release;
|
||||
}
|
||||
|
||||
// returns current value based on state and steps forward one sample
|
||||
float Envelope::process() {
|
||||
|
||||
switch (state_) {
|
||||
|
||||
@@ -13,6 +13,7 @@ void Filter::setSampleRate(float sampleRate) {
|
||||
calculateCoefficients();
|
||||
}
|
||||
|
||||
// recalculate filter based on params
|
||||
void Filter::setParams(Type type, float frequency, float q) {
|
||||
type_ = type;
|
||||
frequency_ = std::min(frequency, sampleRate_ / 2.0f * 0.999f);
|
||||
@@ -20,6 +21,7 @@ void Filter::setParams(Type type, float frequency, float q) {
|
||||
calculateCoefficients();
|
||||
}
|
||||
|
||||
// update current state and output filtered value
|
||||
float Filter::biquadProcess(float in) {
|
||||
|
||||
// calculate filtered sample
|
||||
@@ -32,6 +34,7 @@ float Filter::biquadProcess(float in) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// internal control system emulation
|
||||
void Filter::calculateCoefficients() {
|
||||
|
||||
if(q_ < 0.001f) q_ = 0.001f;
|
||||
@@ -72,7 +75,7 @@ void Filter::calculateCoefficients() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
// values need to be normalized
|
||||
b0_ = b0 / a0;
|
||||
b1_ = b1 / a0;
|
||||
b2_ = b2 / a0;
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
#include "Oscillator.h"
|
||||
|
||||
// placeholder
|
||||
|
||||
// will eventually hold wavetable sampling, store phase state, and all that silliness
|
||||
|
||||
@@ -5,11 +5,13 @@ ScopeBuffer::ScopeBuffer(size_t size) : buffer_(size) {
|
||||
|
||||
}
|
||||
|
||||
// add a single sample to the scope buffer, called by synth
|
||||
void ScopeBuffer::push(float sample) {
|
||||
size_t w = writeIndex_.fetch_add(1, std::memory_order_relaxed);
|
||||
buffer_[w % buffer_.size()] = sample;
|
||||
}
|
||||
|
||||
// outputs value from the scope buffer, called by the scope widget
|
||||
void ScopeBuffer::read(std::vector<float>& out) const {
|
||||
size_t w = writeIndex_.load(std::memory_order_relaxed);
|
||||
for (size_t i = 0; i < out.size(); i++) {
|
||||
|
||||
@@ -30,6 +30,8 @@ inline float Synth::getParam(ParamId id) {
|
||||
return params_[static_cast<size_t>(id)].current;
|
||||
}
|
||||
|
||||
// called by audio engine before process (because it has the reference to the noteEvent store)
|
||||
// this function consumes the noteEvents in the noteEvent store (whether produced by MIDI or keyboard)
|
||||
void Synth::handleNoteEvent(const NoteEvent& event) {
|
||||
|
||||
lastTime = event.timestamp;
|
||||
@@ -45,7 +47,6 @@ void Synth::handleNoteEvent(const NoteEvent& event) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find quietest voice and assign a note to it instead of just the first inactive one
|
||||
// find inactive voice and start it with the given note
|
||||
for(Voice& v : voices_) {
|
||||
if(!v.isActive()) {
|
||||
@@ -85,7 +86,7 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
float params[PARAM_COUNT] = {0.0f};
|
||||
for(int i = 0; i < PARAM_COUNT; i++) {
|
||||
params[i] = params_[i].current;
|
||||
}
|
||||
} // maybe take this outside the loop if performance is an issue
|
||||
|
||||
// foreach voice, process...
|
||||
float mix = 0.0f;
|
||||
@@ -94,6 +95,7 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
}
|
||||
mix /= 4.0f; // for number of voices to prevent clipping
|
||||
mix = tanh(mix); // really prevents clipping
|
||||
// TODO: these saturation function work kinda like magic, use them elsewhere
|
||||
|
||||
sampleOut = mix;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ Voice::Voice(SmoothedParam* params) : params_(params) {
|
||||
|
||||
}
|
||||
|
||||
// cascade sample rate to all descendant objects
|
||||
void Voice::setSampleRate(float sampleRate) {
|
||||
sampleRate_ = sampleRate;
|
||||
|
||||
@@ -23,6 +24,7 @@ void Voice::setSampleRate(float sampleRate) {
|
||||
//osc1_.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
// calculates oscillator frequency based on midi note
|
||||
inline float Voice::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));
|
||||
}
|
||||
@@ -55,6 +57,7 @@ bool Voice::isActive() {
|
||||
return active_;
|
||||
}
|
||||
|
||||
// generates a single sample, called from synth.process()
|
||||
float Voice::process(float* params, bool& scopeTrigger) {
|
||||
|
||||
// process all envelopes
|
||||
@@ -69,11 +72,10 @@ float Voice::process(float* params, bool& scopeTrigger) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// process all envelopes
|
||||
float gainEnv = gainEnvelope_.process();
|
||||
float cutoffEnv = cutoffEnvelope_.process();
|
||||
float resonanceEnv = resonanceEnvelope_.process();
|
||||
// TODO: envelope is shared between all notes so this sequence involves a note change but only one envelope attack:
|
||||
// NOTE_A_ON > NOTE_B_ON > NOTE_A_OFF and note B starts playing part-way through note A's envelope
|
||||
|
||||
// TODO: make pitchOffset variable for each oscillator (maybe three values like octave, semitone offset, and pitch offset in cents)
|
||||
float pitchOffset = 1.0f;
|
||||
@@ -120,6 +122,7 @@ float Voice::process(float* params, bool& scopeTrigger) {
|
||||
sampleOut = filter1_.biquadProcess(sampleOut);
|
||||
sampleOut = filter2_.biquadProcess(sampleOut);
|
||||
|
||||
// state tracking, may keep this here even if oscillators store their own phase because it might help with scope triggering
|
||||
phase_ += phaseInc;
|
||||
if (phase_ > 2.0f * M_PI) {
|
||||
phase_ -= 2.0f * M_PI;
|
||||
|
||||
Reference in New Issue
Block a user