#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& output, int numSamples, const juce::AudioBuffer& sourceBuffer) { if (! voiceActive) return; // Render cloud into temp buffer juce::AudioBuffer voiceBuffer (output.getNumChannels(), numSamples); voiceBuffer.clear(); cloud.processBlock (voiceBuffer, numSamples, sourceBuffer); // Compute ADSR envelope once per sample (not per channel!) std::vector 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(); } }