Upload files to "Source"

This commit is contained in:
ed
2025-10-22 16:58:43 +00:00
parent 9a452b7c1b
commit 989632d1e0
3 changed files with 778 additions and 371 deletions

View File

@@ -1,247 +1,417 @@
#include "SynthVoice.h"
#include <cmath>
//==============================================================================
NeuralSynthVoice::NeuralSynthVoice(NeuralSharedParams& sp) : shared(sp) {}
//==============================================================================
void NeuralSynthVoice::prepare(const juce::dsp::ProcessSpec& spec)
{
setWaveform(0);
tempBlock = juce::dsp::AudioBlock<float>(heapBlock, spec.numChannels, spec.maximumBlockSize);
processorChain.prepare(spec);
adsr.setSampleRate(spec.sampleRate);
this->spec = spec;
}
//==============================================================================
void NeuralSynthVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
if (numSamples <= 0) return;
if (!adsr.isActive())
clearCurrentNote();
if (waveform != -1) {
setWaveform(waveform);
waveform = -1;
}
const int numChannels = outputBuffer.getNumChannels();
auto block = tempBlock.getSubBlock(0, (size_t)numSamples);
block.clear();
// =====================================================================
// Oscillator
// =====================================================================
auto& osc = processorChain.get<oscIndex>();
juce::dsp::ProcessContextReplacing<float> oscContext(block);
osc.process(oscContext);
// =====================================================================
// Distortion
// =====================================================================
const float driveDb = shared.distortionDrive->load(); // 0..30
//const float distMix = juce::jlimit(0.0f, 1.0f, shared.distortionMix->load());
const float bias = juce::jlimit(-1.0f, 1.0f, shared.distortionBias->load());
const float toneHz = juce::jlimit(100.0f, 8000.0f, shared.distortionTone->load());
const int shape = (int)std::lround(juce::jlimit(0.0f, 2.0f, shared.distortionShape->load()));
auto& distDry = processorChain.get<distortionPreGain>();
auto& distWaveshaper = processorChain.template get<distortionIndex>();
if (shape == 0) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
return std::tanh(x + bias);
};
}
else if (shape == 1) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
const float v = x + bias;
return juce::jlimit(-1.0f, 1.0f, v);
};
}
else if (shape == 2) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
const float v = x + bias;
return (float)(std::atan(v) * (2.0 / juce::MathConstants<double>::pi));
};
}
auto& distPreGain = processorChain.template get<distortionPreGain>(); // [5]
distPreGain.setGainDecibels(driveDb); // [6]
auto& distPostLPF = processorChain.template get<distortionPostLPF>();
distPostLPF.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
toneHz, // cutoff
0.707f, // Q
juce::Decibels::decibelsToGain(shared.highGainDbls->load())
);
// =====================================================================
// Flanger
// =====================================================================
// Get pointer to writable data
auto flanger = processorChain.get<flangerIndex>();
auto rate = shared.flangerPhase->load();
auto lfoPhase = shared.flangerPhase->load();
auto flangerDepth = shared.flangerDepth->load();
auto mix = shared.flangerDryMix->load();
auto feedback = shared.flangerFeedback->load();
// Step 2: Apply flanger sample-by-sample to the block
auto* raw = block.getChannelPointer(0);
for (int i = 0; i < numSamples; ++i)
{
float in = raw[i];
float lfo = std::sin(lfoPhase);
float delayTime = (1.0f + lfo) * 0.5f * flangerDepth * spec.sampleRate;
flanger.setDelay(delayTime);
float delayed = flanger.popSample(0);
flanger.pushSample(0, in + delayed * feedback);
raw[i] = in * (1.0f - mix) + delayed * mix;
lfoPhase += juce::MathConstants<float>::twoPi * rate / spec.sampleRate;
if (lfoPhase > juce::MathConstants<float>::twoPi)
lfoPhase -= juce::MathConstants<float>::twoPi;
}
// Step 3: Run through ProcessorChain (filter + distortion)
juce::dsp::ProcessContextReplacing<float> fxContext(block);
processorChain.process(fxContext);
auto& master = processorChain.get<masterIndex>();
const auto ex = shared.masterDbls->load();
master.setGainDecibels(shared.masterDbls->load());
auto& lowEQ = processorChain.get<eqLowIndex>();
lowEQ.coefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf(
spec.sampleRate,
100.0f, // cutoff
0.707f, // Q, not used by all filters
juce::Decibels::decibelsToGain(shared.lowGainDbls->load())
);
auto& midEQ = processorChain.get<eqMidIndex>();
midEQ.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
1000.0f, // center frequency
1.0f, // Q
juce::Decibels::decibelsToGain(shared.midGainDbls->load())
);
// HIGH SHELF
auto& highEQ = processorChain.get<eqHighIndex>();
highEQ.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
10000.0f, // cutoff
0.707f, // Q
juce::Decibels::decibelsToGain(shared.highGainDbls->load())
);
// 3. Apply ADSR envelope to tempBlock
std::vector<float*> channelPtrs;
for (size_t ch = 0; ch < tempBlock.getNumChannels(); ++ch)
channelPtrs.push_back(tempBlock.getChannelPointer(ch));
juce::AudioBuffer<float> buffer(channelPtrs.data(),
static_cast<int>(tempBlock.getNumChannels()),
static_cast<int>(tempBlock.getNumSamples()));
adsr.applyEnvelopeToBuffer(buffer, 0, numSamples);
juce::dsp::AudioBlock<float>(outputBuffer)
.getSubBlock((size_t)startSample, (size_t)numSamples)
.add(tempBlock);
}
//==============================================================================
void NeuralSynthVoice::noteStarted()
{
auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<oscIndex>().setFrequency(freqHz, true);
auto& chorus = processorChain.get<chorusIndex>();
chorus.setCentreDelay(shared.chorusCentre->load());
chorus.setDepth(shared.chorusDepth->load());
chorus.setFeedback(shared.chorusFeedback->load());
chorus.setMix(shared.chorusMix->load());
chorus.setRate(shared.chorusRate->load());
processorChain.get<delayIndex>().setDelay(shared.delayTime->load());
juce::Reverb::Parameters rp;
rp.damping = shared.reverbDamping->load();
rp.dryLevel = shared.reverbDryLevel->load();
rp.freezeMode = shared.reverbFreezeMode->load();
rp.roomSize = shared.reverbRoomSize->load();
rp.wetLevel = shared.reverbWetLevel->load();
rp.width = shared.reverbWidth->load();
processorChain.get<reverbIndex>().setParameters(rp);
juce::ADSR::Parameters p;
p.attack = shared.adsrAttack->load();
p.decay = shared.adsrDecay->load();
p.sustain = shared.adsrSustain->load();
p.release = shared.adsrRelease->load();
adsr.setParameters(p);
adsr.noteOn();
}
//==============================================================================
void NeuralSynthVoice::notePitchbendChanged()
{
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<oscIndex>().setFrequency(freqHz, true);
}
//==============================================================================
void NeuralSynthVoice::noteStopped(bool allowTailOff)
{
adsr.noteOff(); //Triggers release phase
}
//==============================================================================
void NeuralSynthVoice::notePressureChanged() {}
void NeuralSynthVoice::noteTimbreChanged() {}
void NeuralSynthVoice::noteKeyStateChanged() {}
void NeuralSynthVoice::setWaveform(int waveformType)
{
auto& osc = processorChain.template get<oscIndex>();
switch (waveformType)
{
case 0:
osc.initialise([](float x) { return std::sin(x); });
break;
case 1:
osc.initialise([](float x) { return x / juce::MathConstants<float>::pi; }); // Saw
break;
case 2:
osc.initialise([](float x) { return x < 0.0f ? -1.0f : 1.0f; }); // Square
break;
case 3:
osc.initialise([](float x) {
return 2.0f * std::abs(2.0f * (x / juce::MathConstants<float>::twoPi) - 1.0f) - 1.0f;
}); // Triangle
break;
}
}
#include "SynthVoice.h"
#include <cmath>
std::shared_ptr<WT::Bank> NeuralSynthVoice::wtBank;
//==============================================================================
NeuralSynthVoice::NeuralSynthVoice (NeuralSharedParams& sp)
: shared (sp) {}
//==============================================================================
void NeuralSynthVoice::prepare (const juce::dsp::ProcessSpec& newSpec)
{
spec = newSpec;
// --- Oscillator
osc.prepare (spec.sampleRate);
setWaveform (0); // default to sine
// --- Wavetable bank (build once), then prepare osc ---
if (!wtBank)
{
wtBank = std::make_shared<WT::Bank>(2048, 16, 6); // N=2048, frames=16, levels=6
wtBank->generateDefaultMorph(); // Sine -> Saw -> Square -> Triangle
wtBank->buildMipmaps();
}
wtOsc.prepare(spec.sampleRate);
wtOsc.setBank(wtBank);
// --- Scratch buffer (IMPORTANT: allocate real memory)
tempBuffer.setSize ((int) spec.numChannels, (int) spec.maximumBlockSize,
false, false, true);
tempBlock = juce::dsp::AudioBlock<float> (tempBuffer);
// --- Prepare chain elements
chain.prepare (spec);
// Set maximum delay sizes BEFORE runtime changes
{
// Flanger: up to 20 ms
auto& flanger = chain.get<flangerIndex>();
const size_t maxFlangerDelay = (size_t) juce::jmax<size_t>(
1, (size_t) std::ceil (0.020 * spec.sampleRate));
flanger.setMaximumDelayInSamples (maxFlangerDelay);
flanger.reset();
}
{
// Simple delay: up to 2 s
auto& delay = chain.get<delayIndex>();
const size_t maxDelay = (size_t) juce::jmax<size_t>(
1, (size_t) std::ceil (2.0 * spec.sampleRate));
delay.setMaximumDelayInSamples (maxDelay);
delay.reset();
}
// Envelopes
adsr.setSampleRate (spec.sampleRate);
filterAdsr.setSampleRate (spec.sampleRate);
// Filter
svf.reset();
svf.prepare (spec);
// Initial filter type
const int type = (int) std::lround (juce::jlimit (0.0f, 2.0f,
shared.filterType ? shared.filterType->load() : 0.0f));
switch (type)
{
case 0: svf.setType (juce::dsp::StateVariableTPTFilterType::lowpass); break;
case 1: svf.setType (juce::dsp::StateVariableTPTFilterType::highpass); break;
case 2: svf.setType (juce::dsp::StateVariableTPTFilterType::bandpass); break;
default: break;
}
}
//==============================================================================
void NeuralSynthVoice::renderNextBlock (juce::AudioBuffer<float>& outputBuffer,
int startSample, int numSamples)
{
if (numSamples <= 0)
return;
//if (! adsr.isActive())
// clearCurrentNote();
// Apply pending waveform change (from GUI / processor thread)
const int wf = pendingWaveform.exchange (-1, std::memory_order_acq_rel);
if (wf != -1)
setWaveform (wf);
// --- Generate oscillator into temp buffer (WT or BLEP) ---
tempBuffer.clear();
const int numCh = juce::jmin ((int) spec.numChannels, tempBuffer.getNumChannels());
const bool useWT = (shared.wtOn && shared.wtOn->load() > 0.5f);
if (useWT && shared.wtMorph)
wtOsc.setMorph(shared.wtMorph->load()); // 0..15 continuous
for (int i = 0; i < numSamples; ++i)
{
const float s = useWT ? wtOsc.process() : osc.process();
for (int ch = 0; ch < numCh; ++ch)
tempBuffer.getWritePointer (ch)[i] = s;
}
auto block = tempBlock.getSubBlock (0, (size_t) numSamples);
// ================================================================
// Flanger (pre-filter) manual per-sample to set varying delay
// ================================================================
{
auto& flanger = chain.get<flangerIndex>();
const bool enabled = shared.flangerOn && shared.flangerOn->load() > 0.5f;
if (enabled)
{
const float rate = shared.flangerRate ? shared.flangerRate->load() : 0.0f;
float lfoPhase = shared.flangerPhase ? shared.flangerPhase->load() : 0.0f;
const float flangerDepth = shared.flangerDepth ? shared.flangerDepth->load() : 0.0f; // ms
const float mix = shared.flangerDryMix ? shared.flangerDryMix->load() : 0.0f;
const float feedback = shared.flangerFeedback ? shared.flangerFeedback->load() : 0.0f;
const float baseDelayMs = shared.flangerDelay ? shared.flangerDelay->load() : 0.25f;
for (int i = 0; i < numSamples; ++i)
{
const float in = tempBuffer.getReadPointer (0)[i];
const float lfo = std::sin (lfoPhase);
const float delayMs = baseDelayMs + 0.5f * (1.0f + lfo) * flangerDepth;
const float delaySamples = juce::jmax (0.0f, delayMs * 0.001f * (float) spec.sampleRate);
flanger.setDelay (delaySamples);
const float delayed = flanger.popSample (0);
flanger.pushSample (0, in + delayed * feedback);
const float out = in * (1.0f - mix) + delayed * mix;
for (int ch = 0; ch < numCh; ++ch)
tempBuffer.getWritePointer (ch)[i] = out;
lfoPhase += juce::MathConstants<float>::twoPi * rate / (float) spec.sampleRate;
if (lfoPhase > juce::MathConstants<float>::twoPi)
lfoPhase -= juce::MathConstants<float>::twoPi;
}
}
}
// ================================================================
// Filter with per-sample ADSR modulation (poly)
// ================================================================
{
const bool enabled = shared.filterOn && shared.filterOn->load() > 0.5f;
// Update filter type every block (cheap)
const int ftype = (int) std::lround (juce::jlimit (0.0f, 2.0f,
shared.filterType ? shared.filterType->load() : 0.0f));
switch (ftype)
{
case 0: svf.setType (juce::dsp::StateVariableTPTFilterType::lowpass); break;
case 1: svf.setType (juce::dsp::StateVariableTPTFilterType::highpass); break;
case 2: svf.setType (juce::dsp::StateVariableTPTFilterType::bandpass); break;
default: break;
}
const float qOrRes = juce::jlimit (0.1f, 10.0f,
shared.filterResonance ? shared.filterResonance->load() : 0.7f);
svf.setResonance (qOrRes);
const float baseCutoff = juce::jlimit (20.0f, 20000.0f,
shared.filterCutoff ? shared.filterCutoff->load() : 1000.0f);
const float envAmt = shared.fenvAmount ? shared.fenvAmount->load() : 0.0f;
for (int i = 0; i < numSamples; ++i)
{
const float envVal = filterAdsr.getNextSample();
const float cutoff = juce::jlimit (20.0f, 20000.0f,
baseCutoff * std::pow (2.0f, envAmt * envVal));
svf.setCutoffFrequency (cutoff);
if (enabled)
{
for (int ch = 0; ch < numCh; ++ch)
{
float x = tempBuffer.getSample (ch, i);
x = svf.processSample (ch, x);
tempBuffer.setSample (ch, i, x);
}
}
}
}
// ================================================================
// Chorus
// ================================================================
if (shared.chorusOn && shared.chorusOn->load() > 0.5f)
{
auto& chorus = chain.get<chorusIndex>();
if (shared.chorusCentre) chorus.setCentreDelay (shared.chorusCentre->load());
if (shared.chorusDepth) chorus.setDepth (shared.chorusDepth->load());
if (shared.chorusFeedback) chorus.setFeedback (shared.chorusFeedback->load());
if (shared.chorusMix) chorus.setMix (shared.chorusMix->load());
if (shared.chorusRate) chorus.setRate (shared.chorusRate->load());
chain.get<chorusIndex>().process (juce::dsp::ProcessContextReplacing<float> (block));
}
// ================================================================
// Simple Delay (per-voice)
// ================================================================
if (shared.delayOn && shared.delayOn->load() > 0.5f)
{
auto& delay = chain.get<delayIndex>();
const float time = shared.delayTime ? shared.delayTime->load() : 0.1f;
delay.setDelay (juce::jmax (0.0f, time * (float) spec.sampleRate));
delay.process (juce::dsp::ProcessContextReplacing<float> (block));
}
// ================================================================
// Reverb
// ================================================================
if (shared.reverbOn && shared.reverbOn->load() > 0.5f)
{
juce::Reverb::Parameters rp;
rp.damping = shared.reverbDamping ? shared.reverbDamping->load() : 0.0f;
rp.dryLevel = shared.reverbDryLevel ? shared.reverbDryLevel->load() : 0.0f;
rp.freezeMode = shared.reverbFreezeMode ? shared.reverbFreezeMode->load() : 0.0f;
rp.roomSize = shared.reverbRoomSize ? shared.reverbRoomSize->load() : 0.0f;
rp.wetLevel = shared.reverbWetLevel ? shared.reverbWetLevel->load() : 0.0f;
rp.width = shared.reverbWidth ? shared.reverbWidth->load() : 0.0f;
chain.get<reverbIndex>().setParameters (rp);
chain.get<reverbIndex>().process (juce::dsp::ProcessContextReplacing<float> (block));
}
// ================================================================
// Distortion + tone (post LPF/Peak)
// ================================================================
{
const float driveDb = shared.distortionDrive ? shared.distortionDrive->load() : 0.0f;
const float bias = juce::jlimit (-1.0f, 1.0f, shared.distortionBias ? shared.distortionBias->load() : 0.0f);
const float toneHz = juce::jlimit (100.0f, 8000.0f, shared.distortionTone ? shared.distortionTone->load() : 3000.0f);
const int shape = (int) std::lround (juce::jlimit (0.0f, 2.0f,
shared.distortionShape ? shared.distortionShape->load() : 0.0f));
const float mix = shared.distortionMix ? shared.distortionMix->load() : 0.0f;
auto& pre = chain.get<distortionPreGain>();
auto& sh = chain.get<distortionIndex>();
auto& tone = chain.get<distortionPostLPF>();
pre.setGainDecibels (driveDb);
// Explicit std::function target (works on MSVC)
if (shape == 0) sh.functionToUse = std::function<float(float)>{ [bias](float x) noexcept { return std::tanh (x + bias); } };
else if (shape == 1) sh.functionToUse = std::function<float(float)>{ [bias](float x) noexcept { return juce::jlimit (-1.0f, 1.0f, x + bias); } };
else sh.functionToUse = std::function<float(float)>{ [bias](float x) noexcept { return std::atan (x + bias) * (2.0f / juce::MathConstants<float>::pi); } };
tone.coefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter (
spec.sampleRate, toneHz, 0.707f,
juce::Decibels::decibelsToGain (shared.highGainDbls ? shared.highGainDbls->load() : 0.0f));
if (shared.distortionOn && shared.distortionOn->load() > 0.5f)
{
// Wet/dry blend around the shaper
juce::AudioBuffer<float> dryCopy (tempBuffer.getNumChannels(), numSamples);
for (int ch = 0; ch < numCh; ++ch)
dryCopy.copyFrom (ch, 0, tempBuffer, ch, 0, numSamples);
// pre -> shaper -> tone
pre.process (juce::dsp::ProcessContextReplacing<float> (block));
sh.process (juce::dsp::ProcessContextReplacing<float> (block));
tone.process (juce::dsp::ProcessContextReplacing<float> (block));
const float wet = mix, dry = 1.0f - mix;
for (int ch = 0; ch < numCh; ++ch)
{
auto* d = dryCopy.getReadPointer (ch);
auto* w = tempBuffer.getWritePointer (ch);
for (int i = 0; i < numSamples; ++i)
w[i] = dry * d[i] + wet * w[i];
}
}
}
// ================================================================
// EQ + Master + Limiter (EQ guarded by eqOn)
// ================================================================
{
const bool eqEnabled = shared.eqOn && shared.eqOn->load() > 0.5f;
auto& eqL = chain.get<eqLowIndex>();
auto& eqM = chain.get<eqMidIndex>();
auto& eqH = chain.get<eqHighIndex>();
if (eqEnabled)
{
eqL.coefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf (
spec.sampleRate, 100.0f, 0.707f,
juce::Decibels::decibelsToGain (shared.lowGainDbls ? shared.lowGainDbls->load() : 0.0f));
eqM.coefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter (
spec.sampleRate, 1000.0f, 1.0f,
juce::Decibels::decibelsToGain (shared.midGainDbls ? shared.midGainDbls->load() : 0.0f));
eqH.coefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter (
spec.sampleRate, 10000.0f, 0.707f,
juce::Decibels::decibelsToGain (shared.highGainDbls ? shared.highGainDbls->load() : 0.0f));
eqL.process (juce::dsp::ProcessContextReplacing<float> (block));
eqM.process (juce::dsp::ProcessContextReplacing<float> (block));
eqH.process (juce::dsp::ProcessContextReplacing<float> (block));
}
chain.get<masterIndex>().setGainDecibels (shared.masterDbls ? shared.masterDbls->load() : 0.0f);
chain.get<masterIndex>().process (juce::dsp::ProcessContextReplacing<float> (block));
chain.get<limiterIndex>().process (juce::dsp::ProcessContextReplacing<float> (block));
}
// ================================================================
// Apply AMP ADSR envelope
// ================================================================
{
juce::AudioBuffer<float> buf (tempBuffer.getArrayOfWritePointers(), numCh, numSamples);
adsr.applyEnvelopeToBuffer (buf, 0, numSamples);
}
// Mix into output
juce::dsp::AudioBlock<float> (outputBuffer)
.getSubBlock ((size_t) startSample, (size_t) numSamples)
.add (block);
}
//==============================================================================
void NeuralSynthVoice::noteStarted()
{
const float freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
// Oscillator frequency + phase
osc.setFrequency (freqHz);
osc.resetPhase (0.0f);
// Wavetable oscillator too
wtOsc.setFrequency(freqHz);
wtOsc.resetPhase(0.0f);
// Chorus snapshot
if (shared.chorusCentre) chain.get<chorusIndex>().setCentreDelay (shared.chorusCentre->load());
if (shared.chorusDepth) chain.get<chorusIndex>().setDepth (shared.chorusDepth->load());
if (shared.chorusFeedback) chain.get<chorusIndex>().setFeedback (shared.chorusFeedback->load());
if (shared.chorusMix) chain.get<chorusIndex>().setMix (shared.chorusMix->load());
if (shared.chorusRate) chain.get<chorusIndex>().setRate (shared.chorusRate->load());
// Delay time (in samples)
if (shared.delayTime)
chain.get<delayIndex>().setDelay (juce::jmax (0.0f, shared.delayTime->load() * (float) spec.sampleRate));
// Reverb snapshot
juce::Reverb::Parameters rp;
rp.damping = shared.reverbDamping ? shared.reverbDamping->load() : 0.0f;
rp.dryLevel = shared.reverbDryLevel ? shared.reverbDryLevel->load() : 0.0f;
rp.freezeMode = shared.reverbFreezeMode ? shared.reverbFreezeMode->load() : 0.0f;
rp.roomSize = shared.reverbRoomSize ? shared.reverbRoomSize->load() : 0.0f;
rp.wetLevel = shared.reverbWetLevel ? shared.reverbWetLevel->load() : 0.0f;
rp.width = shared.reverbWidth ? shared.reverbWidth->load() : 0.0f;
chain.get<reverbIndex>().setParameters (rp);
// Amp ADSR
juce::ADSR::Parameters ap;
ap.attack = shared.adsrAttack ? shared.adsrAttack->load() : 0.01f;
ap.decay = shared.adsrDecay ? shared.adsrDecay->load() : 0.10f;
ap.sustain = shared.adsrSustain ? shared.adsrSustain->load() : 0.80f;
ap.release = shared.adsrRelease ? shared.adsrRelease->load() : 0.40f;
adsr.setParameters (ap);
adsr.noteOn();
// Filter ADSR
juce::ADSR::Parameters fp;
fp.attack = shared.fenvAttack ? shared.fenvAttack->load() : 0.01f;
fp.decay = shared.fenvDecay ? shared.fenvDecay->load() : 0.10f;
fp.sustain = shared.fenvSustain ? shared.fenvSustain->load() : 0.80f;
fp.release = shared.fenvRelease ? shared.fenvRelease->load() : 0.40f;
filterAdsr.setParameters (fp);
filterAdsr.noteOn();
}
//==============================================================================
void NeuralSynthVoice::notePitchbendChanged()
{
const float freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
osc.setFrequency (freqHz);
wtOsc.setFrequency (freqHz);
}
//==============================================================================
void NeuralSynthVoice::noteStopped (bool allowTailOff)
{
juce::ignoreUnused (allowTailOff);
adsr.noteOff();
filterAdsr.noteOff();
}
//==============================================================================
void NeuralSynthVoice::setWaveform (int waveformType)
{
switch (juce::jlimit (0, 3, waveformType))
{
case 0: osc.setWave (BlepWave::Sine); break;
case 1: osc.setWave (BlepWave::Saw); break;
case 2: osc.setWave (BlepWave::Square); break;
case 3: osc.setWave (BlepWave::Triangle); break;
default: osc.setWave (BlepWave::Sine); break;
}
}