8-voice polyphonic granular synth (VST3/AU/LV2) with: - 128 grain pool per voice, Hann windowing, linear interpolation - Root note selector, sample rate correction, sustain pedal (CC64) - Scatter controls, direction modes (Fwd/Rev/PingPong), freeze - ADSR envelope, global filter (LP/HP/BP), reverb - Waveform display with grain visualization - Drag & drop sample loading, full state save/restore - CI/CD for Windows/macOS/Linux Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 sor
2.0 KiB
C++
79 sor
2.0 KiB
C++
#include "GrainVoice.h"
|
|
|
|
GrainVoice::GrainVoice() {}
|
|
|
|
void GrainVoice::prepare (double sampleRate)
|
|
{
|
|
currentSampleRate = sampleRate;
|
|
cloud.prepare (sampleRate);
|
|
adsr.setSampleRate (sampleRate);
|
|
}
|
|
|
|
void GrainVoice::noteOn (int midiNote, float velocity)
|
|
{
|
|
currentNote = midiNote;
|
|
velocityGain = velocity;
|
|
voiceActive = true;
|
|
|
|
// Pitch offset relative to sample's root note
|
|
cloud.midiPitchOffset = (float) (midiNote - rootNote.load());
|
|
|
|
// Update ADSR
|
|
adsrParams.attack = attackTime.load();
|
|
adsrParams.decay = decayTime.load();
|
|
adsrParams.sustain = sustainLevel.load();
|
|
adsrParams.release = releaseTime.load();
|
|
adsr.setParameters (adsrParams);
|
|
adsr.noteOn();
|
|
|
|
cloud.reset();
|
|
}
|
|
|
|
void GrainVoice::noteOff()
|
|
{
|
|
adsr.noteOff();
|
|
}
|
|
|
|
void GrainVoice::forceStop()
|
|
{
|
|
adsr.reset();
|
|
voiceActive = false;
|
|
currentNote = -1;
|
|
cloud.reset();
|
|
}
|
|
|
|
void GrainVoice::processBlock (juce::AudioBuffer<float>& output, int numSamples,
|
|
const juce::AudioBuffer<float>& sourceBuffer)
|
|
{
|
|
if (! voiceActive) return;
|
|
|
|
// Render cloud into temp buffer
|
|
juce::AudioBuffer<float> voiceBuffer (output.getNumChannels(), numSamples);
|
|
voiceBuffer.clear();
|
|
|
|
cloud.processBlock (voiceBuffer, numSamples, sourceBuffer);
|
|
|
|
// Compute ADSR envelope once per sample (not per channel!)
|
|
std::vector<float> envBuffer ((size_t) numSamples);
|
|
for (int i = 0; i < numSamples; ++i)
|
|
envBuffer[(size_t) i] = adsr.getNextSample() * velocityGain;
|
|
|
|
// Apply envelope and mix into output
|
|
for (int ch = 0; ch < output.getNumChannels(); ++ch)
|
|
{
|
|
const float* voiceData = voiceBuffer.getReadPointer (ch);
|
|
float* outData = output.getWritePointer (ch);
|
|
|
|
for (int i = 0; i < numSamples; ++i)
|
|
outData[i] += voiceData[i] * envBuffer[(size_t) i];
|
|
}
|
|
|
|
// Check if ADSR has finished
|
|
if (! adsr.isActive())
|
|
{
|
|
voiceActive = false;
|
|
currentNote = -1;
|
|
cloud.reset();
|
|
}
|
|
}
|