41#include "../Core/AudioBuffer.h"
42#include "../Core/AudioSpec.h"
43#include "../Core/DenormalGuard.h"
44#include "../Core/DspMath.h"
45#include "../Core/Interpolation.h"
46#include "../Core/StateBlob.h"
87 if (!std::isfinite(bufferSeconds)) bufferSeconds = 4.0;
88 prepared_.store(
false, std::memory_order_relaxed);
90 mixMaxStep_ =
static_cast<T
>(1.0 / std::max(1.0, sampleRate_ * 0.02));
94 while (size <
static_cast<int>(sampleRate_ * std::clamp(bufferSeconds, 0.5, 16.0)))
97 for (
int ch = 0; ch < 2; ++ch)
98 ring_[ch].assign(
static_cast<size_t>(size), T(0));
100 window_.resize(kWindowSize);
101 for (
int i = 0; i < kWindowSize; ++i)
102 window_[
static_cast<size_t>(i)] =
static_cast<T
>(
103 0.5 - 0.5 * std::cos(2.0 * 3.14159265358979 * i / (kWindowSize - 1)));
105 prepared_.store(
true, std::memory_order_relaxed);
112 if (!prepared_.load(std::memory_order_relaxed))
return;
113 for (
auto& r : ring_) std::fill(r.begin(), r.end(), T(0));
114 for (
auto& g : grains_) g.active =
false;
118 currentMix_ = mix_.load(std::memory_order_relaxed);
127 if (!std::isfinite(ms))
return;
128 grainMs_.store(std::clamp(ms, T(10), T(500)), std::memory_order_relaxed);
135 if (!std::isfinite(perSecond))
return;
136 density_.store(std::clamp(perSecond, T(1), T(200)), std::memory_order_relaxed);
143 if (!std::isfinite(amount))
return;
144 jitter_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
151 if (!std::isfinite(semitones))
return;
152 pitchSt_.store(std::clamp(semitones, T(-24), T(24)), std::memory_order_relaxed);
159 if (!std::isfinite(semitones))
return;
160 pitchJitterSt_.store(std::clamp(semitones, T(0), T(12)), std::memory_order_relaxed);
167 if (!std::isfinite(amount))
return;
168 spread_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
174 freeze_.store(frozen, std::memory_order_relaxed);
181 if (!std::isfinite(mix))
return;
182 mix_.store(std::clamp(mix, T(0), T(1)), std::memory_order_relaxed);
185 [[nodiscard]] T
getGrainSize() const noexcept {
return grainMs_.load(std::memory_order_relaxed); }
186 [[nodiscard]] T
getDensity() const noexcept {
return density_.load(std::memory_order_relaxed); }
187 [[nodiscard]] T
getJitter() const noexcept {
return jitter_.load(std::memory_order_relaxed); }
188 [[nodiscard]] T
getPitch() const noexcept {
return pitchSt_.load(std::memory_order_relaxed); }
189 [[nodiscard]] T
getPitchJitter() const noexcept {
return pitchJitterSt_.load(std::memory_order_relaxed); }
190 [[nodiscard]] T
getSpread() const noexcept {
return spread_.load(std::memory_order_relaxed); }
191 [[nodiscard]]
bool getFreeze() const noexcept {
return freeze_.load(std::memory_order_relaxed); }
192 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
195 [[nodiscard]]
static constexpr int getLatency() noexcept {
return 0; }
198 [[nodiscard]] std::vector<uint8_t>
getState()
const
203 w.
write(
"grainMs",
static_cast<float>(grainMs_.load(std::memory_order_relaxed)));
204 w.
write(
"density",
static_cast<float>(density_.load(std::memory_order_relaxed)));
205 w.
write(
"jitter",
static_cast<float>(jitter_.load(std::memory_order_relaxed)));
206 w.
write(
"pitch",
static_cast<float>(pitchSt_.load(std::memory_order_relaxed)));
207 w.
write(
"pitchJitter",
static_cast<float>(pitchJitterSt_.load(std::memory_order_relaxed)));
208 w.
write(
"spread",
static_cast<float>(spread_.load(std::memory_order_relaxed)));
209 w.
write(
"freeze", freeze_.load(std::memory_order_relaxed));
210 w.
write(
"mix",
static_cast<float>(mix_.load(std::memory_order_relaxed)));
236 if (!prepared_.load(std::memory_order_relaxed))
return;
239 const int nCh = std::min(buffer.getNumChannels(), numChannels_);
240 const int nS = buffer.getNumSamples();
241 if (nCh == 0 || nS == 0)
return;
243 const bool frozen = freeze_.load(std::memory_order_relaxed);
249 const T mixTarget = mix_.load(std::memory_order_relaxed);
250 const T mixStart = currentMix_;
251 const double spawnPerSample =
252 static_cast<double>(density_.load(std::memory_order_relaxed)) / sampleRate_;
254 for (
int i = 0; i < nS; ++i)
259 ring_[0][
static_cast<size_t>(writePos_)] = buffer.getChannel(0)[i];
260 ring_[1][
static_cast<size_t>(writePos_)] =
261 buffer.getChannel(nCh > 1 ? 1 : 0)[i];
262 writePos_ = (writePos_ + 1) & ringMask_;
266 spawnAcc_ += spawnPerSample;
267 while (spawnAcc_ >= 1.0)
274 T outL = T(0), outR = T(0);
275 for (
auto& g : grains_)
277 if (!g.active)
continue;
279 const auto idx =
static_cast<int64_t
>(g.pos);
280 const double frac = g.pos -
static_cast<double>(idx);
282 const double wPos = g.phase * (kWindowSize - 1);
283 const auto wIdx =
static_cast<int>(wPos);
284 const T wFrac =
static_cast<T
>(wPos - wIdx);
285 const T w = window_[
static_cast<size_t>(wIdx)]
286 + (window_[
static_cast<size_t>(std::min(wIdx + 1, kWindowSize - 1))]
287 - window_[
static_cast<size_t>(wIdx)]) * wFrac;
289 T sL = T(0), sR = T(0);
290 readGrain(g, idx, frac, nCh > 1, sL, sR);
292 outL += sL * w * g.gainL;
293 outR += sR * w * g.gainR;
296 g.phase += g.phaseInc;
301 const T mixVal =
moveTowards(mixStart, mixTarget, mixMaxStep_ *
static_cast<T
>(i + 1));
302 const T dryL = buffer.getChannel(0)[i];
303 buffer.getChannel(0)[i] = dryL + (outL - dryL) * mixVal;
306 const T dryR = buffer.getChannel(1)[i];
307 buffer.getChannel(1)[i] = dryR + (outR - dryR) * mixVal;
310 currentMix_ =
moveTowards(mixStart, mixTarget, mixMaxStep_ *
static_cast<T
>(nS));
314 static constexpr int kWindowSize = 2048;
316 static constexpr double kReadMargin =
325 double phaseInc = 0.0;
327 T gainL = T(0), gainR = T(0);
331 void readGrain(
const Grain& g, int64_t idx,
double frac,
bool stereo,
332 T& sL, T& sR)
noexcept
334 const T* ringL = ring_[0].data();
335 const T* ringR = ring_[1].data();
338 sL = reader_.readRing(ringL, ringMask_, idx, frac);
339 sR = stereo ? reader_.readRing(ringR, ringMask_, idx, frac) : sL;
342 sL = stretchedReader_.readRing(ringL, ringMask_, idx, frac, g.step);
343 sR = stereo ? stretchedReader_.readRing(ringR, ringMask_, idx, frac, g.step) : sL;
346 [[nodiscard]]
double frand() noexcept
348 rng_ = rng_ * 1664525u + 1013904223u;
349 return static_cast<double>(rng_ >> 8) / 16777216.0;
352 void spawnGrain() noexcept
354 for (
auto& g : grains_)
356 if (g.active)
continue;
358 const double sizeMs =
static_cast<double>(grainMs_.load(std::memory_order_relaxed));
359 const double lenSamples = sizeMs * 0.001 * sampleRate_;
360 const double jit =
static_cast<double>(jitter_.load(std::memory_order_relaxed));
361 const double st =
static_cast<double>(pitchSt_.load(std::memory_order_relaxed))
362 + (frand() * 2.0 - 1.0)
363 *
static_cast<double>(pitchJitterSt_.load(std::memory_order_relaxed));
364 const double rate = std::exp2(st / 12.0);
369 const double maxSpan = lenSamples * std::max(rate, 1.0) + kReadMargin;
370 const double jitterSpan = jit * 0.5 *
static_cast<double>(ringMask_ + 1 - maxSpan - kReadMargin);
371 const double back = maxSpan + frand() * std::max(jitterSpan, 0.0);
372 g.pos =
static_cast<double>(writePos_) - back;
373 while (g.pos < 0.0) g.pos +=
static_cast<double>(ringMask_ + 1);
378 g.phaseInc = 1.0 / lenSamples;
380 const double spreadAmt =
static_cast<double>(spread_.load(std::memory_order_relaxed));
381 const double pan = 0.5 + (frand() - 0.5) * spreadAmt;
382 const double a = pan * 1.5707963267948966;
384 const double overlap = std::max(1.0,
385 static_cast<double>(density_.load(std::memory_order_relaxed))
387 const auto norm =
static_cast<T
>(1.0 / std::sqrt(overlap));
388 g.gainL =
static_cast<T
>(std::cos(a)) * norm;
389 g.gainR =
static_cast<T
>(std::sin(a)) * norm;
396 double sampleRate_ = 48000.0;
397 int numChannels_ = 0;
398 std::atomic<bool> prepared_ {
false };
400 std::array<std::vector<T>, 2> ring_;
404 std::vector<T> window_;
405 std::array<Grain, kMaxGrains> grains_;
406 SincInterpolator<T> reader_;
407 StretchedSincReader<T> stretchedReader_;
408 double spawnAcc_ = 0.0;
409 uint32_t rng_ = 0x9E3779B9u;
410 T currentMix_ = T(1);
411 T mixMaxStep_ = T(1.0 / 960.0);
413 std::atomic<T> grainMs_ { T(80) };
414 std::atomic<T> density_ { T(25) };
415 std::atomic<T> jitter_ { T(0.3) };
416 std::atomic<T> pitchSt_ { T(0) };
417 std::atomic<T> pitchJitterSt_ { T(0) };
418 std::atomic<T> spread_ { T(0.5) };
419 std::atomic<bool> freeze_ {
false };
420 std::atomic<T> mix_ { T(1) };
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Granular clouds and spectral-freeze textures from live input.
void setPitch(T semitones) noexcept
Per-grain pitch shift in semitones [-24, +24] (default 0). Non-finite values are ignored.
void prepare(const AudioSpec &spec, double bufferSeconds=4.0)
Allocates the capture ring and grain pool.
void setDensity(T perSecond) noexcept
Grains per second [1, 200] (default 25). Non-finite values are ignored.
T getSpread() const noexcept
void setGrainSize(T ms) noexcept
Grain duration in milliseconds [10, 500] (default 80). Non-finite values are ignored.
void setSpread(T amount) noexcept
Stereo spread of grain panning [0, 1] (default 0.5). Non-finite values are ignored.
T getJitter() const noexcept
void setFreeze(bool frozen) noexcept
Freezes capture: grains keep playing the held history.
void reset() noexcept
Kills all grains and clears the capture ring. RT-safe.
void setMix(T mix) noexcept
Dry/wet mix [0, 1] (default 1); smoothed linearly over one block. Non-finite values are ignored.
T getGrainSize() const noexcept
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a block in-place (1 or 2 channels). Pass-through until prepare() succeeds.
void setPitchJitter(T semitones) noexcept
Random pitch spread per grain in semitones [0, 12] (default 0). Non-finite values are ignored.
T getPitchJitter() const noexcept
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
static constexpr int kMaxGrains
T getMix() const noexcept
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
T getPitch() const noexcept
void setJitter(T amount) noexcept
Position jitter [0, 1]: how far back grains may start (default 0.3). Non-finite values are ignored.
bool getFreeze() const noexcept
static constexpr int getLatency() noexcept
Zero: the cloud is parallel to the dry path.
T getDensity() const noexcept
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
bool isValid() const noexcept
uint32_t processorId() const noexcept
Serializes key/value parameters into a versioned blob.
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
void write(const char *key, float value)
Writes a float parameter.
Band-limited fractional reader for read rates from 1 to 4.
static int stepFor(double rate) noexcept
The table for a read rate: the nearest tabulated rate at or above it. Costs a log2,...
Main namespace for the DSPark framework.
T moveTowards(T from, T to, T maxDelta) noexcept
Moves a value toward a target by at most a given distance.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
double sampleRate
Sample rate in Hz.