Upload files to "Source"

This commit is contained in:
ed
2025-10-25 17:57:05 +00:00
parent 430ee53b98
commit 0785f6fedd
3 changed files with 952 additions and 37 deletions

View File

@@ -14,7 +14,24 @@ void NeuralSynthVoice::prepare (const juce::dsp::ProcessSpec& newSpec)
// --- Oscillator
osc.prepare (spec.sampleRate);
setWaveform (0); // default to sine
osc.setWave (BlepWave::Sine);
// --- Wavetable oscillator factory banks ---
wtOsc.prepare (spec.sampleRate);
morphLfo.prepare (spec.sampleRate);
currentWtBankIndex = -1;
wtOsc2.prepare (spec.sampleRate);
morphLfo2.prepare (spec.sampleRate);
currentWtBankIndex2 = -1;
const auto& library = WT::FactoryLibrary::get();
if (! library.empty())
{
wtOsc.setBank (library.front().bank);
currentWtBankIndex = 0;
wtOsc2.setBank (library.front().bank);
currentWtBankIndex2 = 0;
}
// --- Scratch buffer (IMPORTANT: allocate real memory)
tempBuffer.setSize ((int) spec.numChannels, (int) spec.maximumBlockSize,
@@ -23,6 +40,10 @@ void NeuralSynthVoice::prepare (const juce::dsp::ProcessSpec& newSpec)
// --- Prepare chain elements
chain.prepare (spec);
chain.get<masterIndex>().setRampDurationSeconds (0.02f);
chain.get<limiterIndex>().setThreshold (-1.0f);
chain.get<limiterIndex>().setRelease (0.05f);
chain.get<limiterIndex>().reset();
// Set maximum delay sizes BEFORE runtime changes
{
@@ -73,20 +94,109 @@ void NeuralSynthVoice::renderNextBlock (juce::AudioBuffer<float>& outputBuffer,
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
// --- Generate oscillator into temp buffer (BLEP or Wavetable)
tempBuffer.clear();
const int numCh = juce::jmin ((int) spec.numChannels, tempBuffer.getNumChannels());
const auto& library = WT::FactoryLibrary::get();
const int librarySize = (int) library.size();
if (librarySize > 0 && shared.wtBank)
{
const int targetBank = juce::jlimit (0, librarySize - 1,
(int) std::lround (shared.wtBank->load()));
if (targetBank != currentWtBankIndex)
{
wtOsc.setBank (library[(size_t) targetBank].bank);
currentWtBankIndex = targetBank;
}
}
if (librarySize > 0 && shared.wt2Bank)
{
const int targetBank2 = juce::jlimit (0, librarySize - 1,
(int) std::lround (shared.wt2Bank->load()));
if (targetBank2 != currentWtBankIndex2)
{
wtOsc2.setBank (library[(size_t) targetBank2].bank);
currentWtBankIndex2 = targetBank2;
}
}
const bool useWTLayerA = (shared.wtOn && shared.wtOn->load() > 0.5f)
&& wtOsc.getFrameCount() > 0;
const bool useWTLayerB = (shared.wt2On && shared.wt2On->load() > 0.5f)
&& wtOsc2.getFrameCount() > 0;
const float morphMaxA = wtOsc.getMaxMorph();
const float morphBaseA = shared.wtMorph
? juce::jlimit (0.0f, morphMaxA, shared.wtMorph->load())
: 0.0f;
const float lfoDepthA = shared.wtLfoDepth ? shared.wtLfoDepth->load() : 0.0f;
const float lfoRateA = shared.wtLfoRate ? shared.wtLfoRate->load() : 1.0f;
const int lfoShapeA = shared.wtLfoShape ? (int) std::lround (shared.wtLfoShape->load()) : 0;
morphLfo.setRate (lfoRateA);
morphLfo.setShape (lfoShapeA);
const float depthFramesA = juce::jlimit (0.0f, morphMaxA, lfoDepthA);
const float morphMaxB = wtOsc2.getMaxMorph();
const float morphBaseB = shared.wt2Morph
? juce::jlimit (0.0f, morphMaxB, shared.wt2Morph->load())
: 0.0f;
const float lfoDepthB = shared.wt2LfoDepth ? shared.wt2LfoDepth->load() : 0.0f;
const float lfoRateB = shared.wt2LfoRate ? shared.wt2LfoRate->load() : 0.3f;
const int lfoShapeB = shared.wt2LfoShape ? (int) std::lround (shared.wt2LfoShape->load()) : 0;
morphLfo2.setRate (lfoRateB);
morphLfo2.setShape (lfoShapeB);
const float depthFramesB = juce::jlimit (0.0f, morphMaxB, lfoDepthB);
const float levelA = shared.wtLevel ? juce::jlimit (0.0f, 1.0f, shared.wtLevel->load()) : 0.0f;
const float levelB = shared.wt2Level ? juce::jlimit (0.0f, 1.0f, shared.wt2Level->load()) : 0.0f;
const float safeLevelSum = juce::jlimit (0.5f, 2.0f, levelA + levelB + 0.0001f);
const float mixGain = 0.45f / safeLevelSum;
for (int i = 0; i < numSamples; ++i)
{
const float s = osc.process();
float sampleA = useWTLayerA ? 0.0f : osc.process();
if (useWTLayerA)
{
const float lfoValueA = morphLfo.process();
const float headroomNegA = juce::jmin (depthFramesA, morphBaseA);
const float headroomPosA = juce::jmin (depthFramesA, morphMaxA - morphBaseA);
const float offsetA = (lfoValueA >= 0.0f ? lfoValueA * headroomPosA
: lfoValueA * headroomNegA);
const float morphValueA = juce::jlimit (0.0f, morphMaxA, morphBaseA + offsetA);
sampleA = wtOsc.process (morphValueA);
}
else
{
morphLfo.process(); // advance for consistency
}
float sampleB = 0.0f;
if (useWTLayerB)
{
const float lfoValueB = morphLfo2.process();
const float headroomNegB = juce::jmin (depthFramesB, morphBaseB);
const float headroomPosB = juce::jmin (depthFramesB, morphMaxB - morphBaseB);
const float offsetB = (lfoValueB >= 0.0f ? lfoValueB * headroomPosB
: lfoValueB * headroomNegB);
const float morphValueB = juce::jlimit (0.0f, morphMaxB, morphBaseB + offsetB);
sampleB = wtOsc2.process (morphValueB);
}
else
{
morphLfo2.process();
}
const float combined = mixGain * ((sampleA * levelA) + (sampleB * levelB));
for (int ch = 0; ch < numCh; ++ch)
tempBuffer.getWritePointer (ch)[i] = s;
tempBuffer.getWritePointer (ch)[i] = combined;
}
auto block = tempBlock.getSubBlock (0, (size_t) numSamples);
@@ -321,10 +431,22 @@ void NeuralSynthVoice::renderNextBlock (juce::AudioBuffer<float>& outputBuffer,
void NeuralSynthVoice::noteStarted()
{
const float freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
const float initPhase = shared.wtPhase
? juce::jlimit (0.0f, 1.0f, shared.wtPhase->load())
: 0.0f;
// Oscillator frequency and phase retrigger
// Oscillator frequency and phase retrigger (BLEP + WT)
osc.setFrequency (freqHz);
osc.resetPhase (0.0f);
osc.resetPhase (initPhase);
wtOsc.setFrequency (freqHz);
wtOsc.resetPhase (initPhase);
morphLfo.reset();
const float initPhaseB = shared.wt2Phase
? juce::jlimit (0.0f, 1.0f, shared.wt2Phase->load())
: initPhase;
wtOsc2.setFrequency (freqHz);
wtOsc2.resetPhase (initPhaseB);
morphLfo2.reset();
// Chorus snapshot
if (shared.chorusCentre) chain.get<chorusIndex>().setCentreDelay (shared.chorusCentre->load());
@@ -372,6 +494,7 @@ void NeuralSynthVoice::notePitchbendChanged()
{
const float freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();
osc.setFrequency (freqHz);
wtOsc.setFrequency (freqHz);
}
//==============================================================================
@@ -384,15 +507,3 @@ void NeuralSynthVoice::noteStopped (bool allowTailOff)
}
//==============================================================================
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;
}
}