Files
InstaGrain/Source/PluginProcessor.cpp
hariel1985 55b5f89ac5 Initial release — InstaGrain granular synthesizer v1.0
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>
2026-03-26 17:26:06 +01:00

120 sor
5.1 KiB
C++

#include "PluginProcessor.h"
#include "PluginEditor.h"
InstaGrainProcessor::InstaGrainProcessor()
: AudioProcessor (BusesProperties()
.withOutput ("Output", juce::AudioChannelSet::stereo(), true))
{
}
InstaGrainProcessor::~InstaGrainProcessor() {}
void InstaGrainProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
engine.prepare (sampleRate, samplesPerBlock);
}
void InstaGrainProcessor::releaseResources() {}
bool InstaGrainProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;
return true;
}
void InstaGrainProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
if (bypass.load())
{
buffer.clear();
return;
}
engine.processBlock (buffer, midiMessages);
}
juce::AudioProcessorEditor* InstaGrainProcessor::createEditor()
{
return new InstaGrainEditor (*this);
}
void InstaGrainProcessor::getStateInformation (juce::MemoryBlock& destData)
{
auto xml = std::make_unique<juce::XmlElement> ("InstaGrainState");
xml->setAttribute ("samplePath", engine.getSamplePath());
xml->setAttribute ("rootNote", engine.rootNote.load());
xml->setAttribute ("position", (double) engine.position.load());
xml->setAttribute ("grainSize", (double) engine.grainSizeMs.load());
xml->setAttribute ("density", (double) engine.density.load());
xml->setAttribute ("pitch", (double) engine.pitchSemitones.load());
xml->setAttribute ("pan", (double) engine.pan.load());
xml->setAttribute ("posScatter", (double) engine.posScatter.load());
xml->setAttribute ("sizeScatter", (double) engine.sizeScatter.load());
xml->setAttribute ("pitchScatter", (double) engine.pitchScatter.load());
xml->setAttribute ("panScatter", (double) engine.panScatter.load());
xml->setAttribute ("direction", engine.direction.load());
xml->setAttribute ("freeze", engine.freeze.load());
xml->setAttribute ("attack", (double) engine.attackTime.load());
xml->setAttribute ("decay", (double) engine.decayTime.load());
xml->setAttribute ("sustain", (double) engine.sustainLevel.load());
xml->setAttribute ("release", (double) engine.releaseTime.load());
xml->setAttribute ("filterType", engine.filterType.load());
xml->setAttribute ("filterCutoff", (double) engine.filterCutoff.load());
xml->setAttribute ("filterReso", (double) engine.filterReso.load());
xml->setAttribute ("reverbSize", (double) engine.reverbSize.load());
xml->setAttribute ("reverbDecay", (double) engine.reverbDecay.load());
xml->setAttribute ("masterVolume", (double) engine.masterVolume.load());
xml->setAttribute ("bypass", bypass.load());
copyXmlToBinary (*xml, destData);
}
void InstaGrainProcessor::setStateInformation (const void* data, int sizeInBytes)
{
auto xml = getXmlFromBinary (data, sizeInBytes);
if (xml == nullptr || ! xml->hasTagName ("InstaGrainState"))
return;
// Load sample
juce::String samplePath = xml->getStringAttribute ("samplePath", "");
if (samplePath.isNotEmpty())
{
juce::File f (samplePath);
if (f.existsAsFile())
engine.loadSample (f);
}
engine.rootNote.store (xml->getIntAttribute ("rootNote", 60));
engine.position.store ((float) xml->getDoubleAttribute ("position", 0.5));
engine.grainSizeMs.store ((float) xml->getDoubleAttribute ("grainSize", 100.0));
engine.density.store ((float) xml->getDoubleAttribute ("density", 10.0));
engine.pitchSemitones.store ((float) xml->getDoubleAttribute ("pitch", 0.0));
engine.pan.store ((float) xml->getDoubleAttribute ("pan", 0.0));
engine.posScatter.store ((float) xml->getDoubleAttribute ("posScatter", 0.0));
engine.sizeScatter.store ((float) xml->getDoubleAttribute ("sizeScatter", 0.0));
engine.pitchScatter.store ((float) xml->getDoubleAttribute ("pitchScatter", 0.0));
engine.panScatter.store ((float) xml->getDoubleAttribute ("panScatter", 0.0));
engine.direction.store (xml->getIntAttribute ("direction", 0));
engine.freeze.store (xml->getBoolAttribute ("freeze", false));
engine.attackTime.store ((float) xml->getDoubleAttribute ("attack", 0.01));
engine.decayTime.store ((float) xml->getDoubleAttribute ("decay", 0.1));
engine.sustainLevel.store ((float) xml->getDoubleAttribute ("sustain", 1.0));
engine.releaseTime.store ((float) xml->getDoubleAttribute ("release", 0.3));
engine.filterType.store (xml->getIntAttribute ("filterType", 0));
engine.filterCutoff.store ((float) xml->getDoubleAttribute ("filterCutoff", 20000.0));
engine.filterReso.store ((float) xml->getDoubleAttribute ("filterReso", 0.707));
engine.reverbSize.store ((float) xml->getDoubleAttribute ("reverbSize", 0.0));
engine.reverbDecay.store ((float) xml->getDoubleAttribute ("reverbDecay", 0.0));
engine.masterVolume.store ((float) xml->getDoubleAttribute ("masterVolume", 1.0));
bypass.store (xml->getBoolAttribute ("bypass", false));
}
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new InstaGrainProcessor();
}