60#include "../Core/Convolver.h"
61#include "../Core/DryWetMixer.h"
62#include "../Core/RingBuffer.h"
63#include "../Core/AudioSpec.h"
64#include "../Core/AudioBuffer.h"
65#include "../Core/DspMath.h"
66#include "../Core/Resampler.h"
67#include "../Core/StateBlob.h"
68#ifndef DSPARK_NO_FILE_IO
69#include "../IO/WavFile.h"
120 const int blockSize = std::clamp(spec.
maxBlockSize, 1, 1 << 20);
122 while (fftBlock < blockSize) fftBlock <<= 1;
132 int maxDelaySamples =
static_cast<int>(spec.
sampleRate * 0.5) + 1;
135 rb.prepare(maxDelaySamples);
169 if (!bank || bank->convolvers.empty())
return;
171 const int nCh = std::min(buffer.getNumChannels(),
172 static_cast<int>(bank->convolvers.size()));
173 const int nS = buffer.getNumSamples();
178 T mixVal =
mix_.load(std::memory_order_relaxed);
180 for (
int ch = 0; ch < nCh; ++ch)
182 T* data = buffer.getChannel(ch);
187 for (
int i = 0; i < nS; ++i)
190 data[i] = ring.read(preDelSamp);
194 bank->convolvers[
static_cast<size_t>(ch)].processInPlace(data, nS);
197 mixer_.mixWet(buffer, mixVal);
212 for (
auto& conv : bank->convolvers)
221#ifndef DSPARK_NO_FILE_IO
240 if (info.numSamples <= 0 || info.numChannels <= 0
241 || info.numSamples > (
static_cast<int64_t
>(1) << 30)
242 || !(info.sampleRate > 0))
249 irBuf.
resize(info.numChannels,
static_cast<int>(info.numSamples));
255 int irLen =
static_cast<int>(info.numSamples);
261 T* dst =
irStorage_.data() +
static_cast<size_t>(ch) *
static_cast<size_t>(irLen);
262 std::copy_n(src, irLen, dst);
281 if (!std::isfinite(dryWet))
return;
282 mix_.store(std::clamp(dryWet, T(0), T(1)), std::memory_order_relaxed);
295 bool loadIR(
const T* data,
int length,
double irSampleRate)
297 if (data ==
nullptr || length <= 0
298 || !std::isfinite(irSampleRate) || !(irSampleRate > 0.0))
325 if (!std::isfinite(ms))
return;
326 preDelayMs_.store(std::clamp(ms, T(0), T(500)), std::memory_order_relaxed);
355 if (!std::isfinite(scale))
return;
356 decayScale_.store(std::clamp(scale, T(0.25), T(2)),
357 std::memory_order_relaxed);
378 if (!std::isfinite(ratio))
return;
379 stretch_.store(std::clamp(ratio, T(0.5), T(2)),
380 std::memory_order_relaxed);
407 if (!bank || bank->convolvers.empty())
409 const int n =
static_cast<int>(bank->convolvers.size());
410 channel = std::clamp(channel, 0, n - 1);
411 return bank->convolvers[
static_cast<size_t>(channel)];
420 return static_cast<bool>(
loadBank());
424 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
440 return (bank && !bank->convolvers.empty())
441 ? bank->convolvers.front().getLatency() : 0;
447 [[nodiscard]] std::vector<uint8_t>
getState()
const
450 w.
write(
"mix",
mix_.load(std::memory_order_relaxed));
453 w.
write(
"stretch",
stretch_.load(std::memory_order_relaxed));
467 T ds =
static_cast<T
>(r.
read(
"decayScale", 1.0f));
468 T st =
static_cast<T
>(r.
read(
"stretch", 1.0f));
469 if (!std::isfinite(ds)) ds =
decayScale_.load(std::memory_order_relaxed);
470 if (!std::isfinite(st)) st =
stretch_.load(std::memory_order_relaxed);
471 ds = std::clamp(ds, T(0.25), T(2));
472 st = std::clamp(st, T(0.5), T(2));
473 const bool shapeChanged =
475 || st !=
stretch_.load(std::memory_order_relaxed);
477 stretch_.store(st, std::memory_order_relaxed);
492 *
preDelayMs_.load(std::memory_order_relaxed) / T(1000));
493 preDelaySamples_.store(std::clamp(samp, 0, maxSamp), std::memory_order_relaxed);
505 auto newBank = std::make_shared<ConvolverBank>();
506 newBank->convolvers.resize(
static_cast<size_t>(numCh));
511 const double dScale =
512 static_cast<double>(
decayScale_.load(std::memory_order_relaxed));
513 const double stretch =
514 static_cast<double>(
stretch_.load(std::memory_order_relaxed));
515 const bool doShape = std::abs(dScale - 1.0) > 1e-6;
516 const double effIrRate =
irSampleRate_ / std::max(stretch, 0.01);
518 std::vector<T> shaped;
521 for (
int ch = 0; ch < numCh; ++ch)
526 +
static_cast<size_t>(irCh) *
static_cast<size_t>(
irLength_);
531 if (shapedCh != irCh)
538 irData = shaped.data();
539 irLen =
static_cast<int>(shaped.size());
543 auto& conv = newBank->convolvers[
static_cast<size_t>(ch)];
553 std::vector<T> resampled(
581 [[nodiscard]] std::vector<T>
shapeDecay(
const T* ir,
int len,
584 if (!ir || len < 64)
return {};
588 double peakMag = 0.0;
590 for (
int n = 0; n < len; ++n)
592 const double v =
static_cast<double>(ir[n]);
594 const double m = std::abs(v);
595 if (m > peakMag) { peakMag = m; peak = n; }
597 if (total <= 1e-30 || peakMag <= 0.0)
return {};
600 constexpr double r5 = 0.31622776601683794;
601 constexpr double r25 = 0.0031622776601683794;
602 int t5 = -1, t25 = -1;
604 for (
int n = 0; n < len; ++n)
606 const double ratio = tail / total;
607 if (t5 < 0 && ratio <= r5 && n > peak) t5 = n;
608 if (ratio <= r25 && n > peak) { t25 = n;
break; }
609 const double v =
static_cast<double>(ir[n]);
612 if (t5 < 0 || t25 < 0 || t25 - t5 < 32)
return {};
616 const double beta = 2.302585092994046 /
static_cast<double>(t25 - t5);
617 const double k = beta * (1.0 / factor - 1.0);
619 std::vector<T> out(
static_cast<size_t>(len));
620 const double gStep = std::exp(-k);
622 for (
int n = 0; n < len; ++n)
624 out[
static_cast<size_t>(n)] = (n <= peak)
626 :
static_cast<T
>(
static_cast<double>(ir[n]) * g);
627 if (n >= peak) g *= gStep;
636 for (
const T v : out) sTotal +=
static_cast<double>(v) * v;
639 double sTail = sTotal;
641 for (
int n = 0; n < len; ++n)
643 if (sTail / sTotal <= 1e-10) { cut = n;
break; }
644 const double v =
static_cast<double>(out[
static_cast<size_t>(n)]);
647 cut = std::max(cut, 64);
648 if (cut < len) out.resize(
static_cast<size_t>(cut));
651 else if (factor > 1.0)
656 const int fade = std::min(len / 8, 960);
657 const int start = len - fade;
658 for (
int i = 0; i < fade; ++i)
660 const double w = 0.5 * (1.0 + std::cos(3.141592653589793
661 *
static_cast<double>(i + 1)
662 /
static_cast<double>(fade)));
663 const size_t idx =
static_cast<size_t>(start + i);
664 out[idx] =
static_cast<T
>(
static_cast<double>(out[idx]) * w);
680 std::atomic<T>
mix_ { T(0.3) };
708 [[nodiscard]] std::shared_ptr<ConvolverBank>
loadBank() const noexcept
710 while (
bankLock_.test_and_set(std::memory_order_acquire)) {}
712 bankLock_.clear(std::memory_order_release);
715 void storeBank(std::shared_ptr<ConvolverBank> next)
noexcept
717 while (
bankLock_.test_and_set(std::memory_order_acquire)) {}
719 bankLock_.clear(std::memory_order_release);
Non-owning view over audio channel data.
Owning audio buffer with contiguous, 32-byte aligned storage.
AudioBufferView< T, MaxChannels > toView() noexcept
Returns a non-owning mutable view of this buffer. The view's channel capacity is propagated from MaxC...
T * getChannel(int ch) noexcept
Returns a pointer to the sample data.
void resize(int numChannels, int numSamples)
Allocates the buffer for the given dimensions.
Real-time partitioned convolution using overlap-save with FFT.
Pre-allocated, SIMD-friendly dry/wet blender for real-time audio.
Windowed-sinc sample rate converter optimized for real-time DSP.
int processBlock(const T *input, int inputLength, T *output) noexcept
Resamples a block of audio in single-channel streaming mode.
void prepare(double sourceRate, double targetRate, Quality quality=Quality::Normal)
Prepares the resampler for a given rate conversion.
int getMaxOutputSamples(int inputLength) const noexcept
Returns the maximum number of output samples for a given input length.
Convolution reverb with IR loading, dry/wet, and pre-delay.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes audio through the reverb.
DryWetMixer< T > & getMixer()
Direct access to the DryWetMixer.
T getMix() const noexcept
Returns the current mix value.
std::atomic< int > preDelaySamples_
bool isLoaded() const noexcept
Returns true if an IR has been loaded and applied.
std::vector< T > irStorage_
std::vector< T > shapeDecay(const T *ir, int len, double factor) const
Returns a decay-scaled copy of one IR channel (see setDecayScale).
void storeBank(std::shared_ptr< ConvolverBank > next) noexcept
void updatePreDelay() noexcept
std::atomic< T > stretch_
int fftBlockSize_
Convolver partition size = engine latency (set in prepare()).
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
T getPreDelay() const noexcept
Returns the current pre-delay in ms.
std::atomic< T > decayScale_
bool loadIR(const char *wavFilePath)
Loads an impulse response from a WAV file.
std::atomic< T > preDelayMs_
int getLatency() const noexcept
Returns the convolution latency in samples.
bool loadIR(const T *data, int length, double irSampleRate)
Loads an IR from raw sample data.
std::shared_ptr< ConvolverBank > loadBank() const noexcept
Convolver< T > fallbackConvolver_
Inert engine for getConvolver() with no bank.
void setStretch(T ratio)
Stretches the loaded IR in time (tape-speed style).
void prepare(const AudioSpec &spec)
Prepares the reverb for processing.
void setMix(T dryWet) noexcept
Sets the dry/wet mix.
std::vector< RingBuffer< T > > preDelayBuffers_
std::atomic_flag bankLock_
std::vector< uint8_t > getState() const
Serializes the parameter state. The impulse response itself is content (load it with loadIR),...
Convolver< T > & getConvolver(int channel=0)
Direct access to a channel's Convolver (GUI thread only).
std::shared_ptr< ConvolverBank > bankPtr_
void reset() noexcept
Resets the DSP state (convolver tails, pre-delay, mixer). RT-Safe.
T getDecayScale() const noexcept
Returns the current IR decay scale.
void setDecayScale(T scale)
Scales the decay time (T60) of the loaded IR.
T getStretch() const noexcept
Returns the current IR stretch ratio.
void setPreDelay(T ms) noexcept
Sets the pre-delay time in milliseconds.
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.
Complete WAV file reader and writer in pure C++20.
AudioFileInfo getInfo() const override
Retrieves metadata of the currently opened file.
bool readSamples(AudioBufferView< float > dest) override
Reads samples from the start of the file into the destination view.
bool openRead(const std::filesystem::path &path) override
Opens a WAV file for reading.
void close() override
Finalizes file headers and releases system handles.
Main namespace for the DSPark framework.
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).
int maxBlockSize
Maximum number of samples per processing block.
double sampleRate
Sample rate in Hz.
std::vector< Convolver< T > > convolvers