#pragma once #include #include "Grain.h" class GrainCloud { public: static constexpr int maxGrains = 128; enum class Direction { Forward, Reverse, PingPong }; GrainCloud(); void prepare (double sampleRate); void processBlock (juce::AudioBuffer& output, int numSamples, const juce::AudioBuffer& sourceBuffer); void reset(); // Parameters (atomic — GUI writes, audio reads) std::atomic position { 0.5f }; // 0-1 std::atomic grainSizeMs { 100.0f }; // 10-500 std::atomic density { 10.0f }; // 1-100 grains/sec std::atomic pitchSemitones { 0.0f }; // -24..+24 std::atomic pan { 0.0f }; // -1..+1 std::atomic posScatter { 0.0f }; // 0-1 std::atomic sizeScatter { 0.0f }; // 0-1 std::atomic pitchScatter { 0.0f }; // 0-1 std::atomic panScatter { 0.0f }; // 0-1 std::atomic direction { 0 }; // 0=Fwd, 1=Rev, 2=PingPong std::atomic freeze { false }; // Extra pitch offset from MIDI note float midiPitchOffset = 0.0f; // Sample rate correction: sourceSampleRate / dawSampleRate float sampleRateRatio = 1.0f; // For visualization — snapshot of active grains struct GrainInfo { int startSample; int lengthSamples; float progress; }; std::array getActiveGrainInfo() const; int getActiveGrainCount() const; private: double currentSampleRate = 44100.0; std::array grains; GrainWindow window; int samplesUntilNextGrain = 0; juce::Random rng; void spawnGrain (const juce::AudioBuffer& sourceBuffer); float readSampleInterpolated (const juce::AudioBuffer& buffer, double position) const; };