This commit is contained in:
esca111
2026-03-29 16:44:13 +13:00
commit 772da41d74
28 changed files with 4070 additions and 0 deletions

2
src/CloudSeedCore/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

42
src/CloudSeedCore/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
*.sdf
*.vsp
*.opensdf
*.o
[Bb]in
[Dd]ebug*/
test-results/
*.lib
*.sbr
obj/
intermediate*/
[Rr]elease*/
_ReSharper*/
packages*/
[Tt]est[Rr]esult*
Build/*
Releases/*
.vs/*
**/.vs/*
**/.DS_Store
!VC_redist.x64.exe
outputLeft.bin
outputRight.bin

View File

@@ -0,0 +1,163 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <vector>
#include "ModulatedAllpass.h"
#include "RandomBuffer.h"
namespace Cloudseed
{
class AllpassDiffuser
{
public:
static const int MaxStageCount = 12;
private:
int samplerate;
ModulatedAllpass filters[MaxStageCount];
int delay;
float modRate;
std::vector<float> seedValues;
int seed;
float crossSeed;
public:
int Stages;
AllpassDiffuser()
{
crossSeed = 0.0;
seed = 23456;
UpdateSeeds();
Stages = 1;
SetSamplerate(48000);
}
int GetSamplerate()
{
return samplerate;
}
void SetSamplerate(int samplerate)
{
this->samplerate = samplerate;
SetModRate(modRate);
}
void SetSeed(int seed)
{
this->seed = seed;
UpdateSeeds();
}
void SetCrossSeed(float crossSeed)
{
this->crossSeed = crossSeed;
UpdateSeeds();
}
bool GetModulationEnabled()
{
return filters[0].ModulationEnabled;
}
void SetModulationEnabled(bool value)
{
for (int i = 0; i < MaxStageCount; i++)
filters[i].ModulationEnabled = value;
}
void SetInterpolationEnabled(bool enabled)
{
for (int i = 0; i < MaxStageCount; i++)
filters[i].InterpolationEnabled = enabled;
}
void SetDelay(int delaySamples)
{
delay = delaySamples;
Update();
}
void SetFeedback(float feedback)
{
for (int i = 0; i < MaxStageCount; i++)
filters[i].Feedback = feedback;
}
void SetModAmount(float amount)
{
for (int i = 0; i < MaxStageCount; i++)
filters[i].ModAmount = amount * (0.85 + 0.3 * seedValues[MaxStageCount + i]);
}
void SetModRate(float rate)
{
modRate = rate;
for (int i = 0; i < MaxStageCount; i++)
filters[i].ModRate = rate * (0.85 + 0.3 * seedValues[MaxStageCount * 2 + i]) / samplerate;
}
void Process(float* input, float* output, int bufSize)
{
float tempBuffer[BUFFER_SIZE];
filters[0].Process(input, tempBuffer, bufSize);
for (int i = 1; i < Stages; i++)
filters[i].Process(tempBuffer, tempBuffer, bufSize);
Utils::Copy(output, tempBuffer, bufSize);
}
void ClearBuffers()
{
for (int i = 0; i < MaxStageCount; i++)
filters[i].ClearBuffers();
}
private:
void Update()
{
for (int i = 0; i < MaxStageCount; i++)
{
auto r = seedValues[i];
auto d = std::pow(10, r) * 0.1; // 0.1 ... 1.0
filters[i].SampleDelay = (int)(delay * d);
}
}
void UpdateSeeds()
{
this->seedValues = RandomBuffer::Generate(seed, MaxStageCount * 3, crossSeed);
Update();
}
};
}

View File

@@ -0,0 +1,247 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "Biquad.h"
#define _USE_MATH_DEFINES
#include "math.h"
namespace Cloudseed
{
Biquad::Biquad()
{
ClearBuffers();
}
Biquad::Biquad(FilterType filterType, float fs)
{
Type = filterType;
SetSamplerate(fs);
SetGainDb(0.0f);
Frequency = (float)(fs * 0.25f);
SetQ(0.5f);
ClearBuffers();
}
Biquad::~Biquad()
{
}
float Biquad::GetSamplerate()
{
return fs;
}
void Biquad::SetSamplerate(float fs)
{
this->fs = fs;
fsInv = 1.0f / fs;
Update();
}
float Biquad::GetGainDb()
{
return gainDB;
}
float Biquad::GetGain()
{
return gain;
}
void Biquad::SetGainDb (float value)
{
// Clamp value between -60 and 60
if (value < -60)
value = -60;
else if (value > 60)
value = 60;
gainDB = value;
gain = powf (10.0f, value / 20.0f);
}
void Biquad::SetGain (float value)
{
if (value < 0.001f)
value = 0.001f; // -60dB
else if (value > 1000.0f)
value = 1000.0f; // 60dB
gain = value;
gainDB = log10f (gain) * 20;
}
float Biquad::GetQ()
{
return q;
}
void Biquad::SetQ(float value)
{
if (value < 0.001f)
value = 0.001f;
q = value;
}
// this is the newer set of formulas from http://www.earlevel.com/main/2011/01/02/biquad-formulas/
// Note that for shelf and peak filters, I had to invert the if/else statements for boost and cut, as
// I was getting the inverse desired effect, very odd...
void Biquad::Update()
{
auto Fc = Frequency;
//auto Fs = fs;
auto V = powf(10, fabsf(gainDB) / 20.0f);
//auto K = tanf(M_PI * Fc / Fs);
auto K = tanf(M_PI * Fc * fsInv);
auto Q = q;
double norm = 1.0;
switch (Type)
{
case FilterType::LowPass6db:
a1 = -expf(-2.0 * M_PI * (Fc * fsInv));
b0 = 1.0 + a1;
b1 = b2 = a2 = 0;
break;
case FilterType::HighPass6db:
a1 = -expf(-2.0 * M_PI * (Fc * fsInv));
b0 = a1;
b1 = -a1;
b2 = a2 = 0;
break;
case FilterType::LowPass:
norm = 1 / (1 + K / Q + K * K);
b0 = K * K * norm;
b1 = 2 * b0;
b2 = b0;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - K / Q + K * K) * norm;
break;
case FilterType::HighPass:
norm = 1 / (1 + K / Q + K * K);
b0 = 1 * norm;
b1 = -2 * b0;
b2 = b0;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - K / Q + K * K) * norm;
break;
case FilterType::BandPass:
norm = 1 / (1 + K / Q + K * K);
b0 = K / Q * norm;
b1 = 0;
b2 = -b0;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - K / Q + K * K) * norm;
break;
case FilterType::Notch:
norm = 1 / (1 + K / Q + K * K);
b0 = (1 + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = b0;
a1 = b1;
a2 = (1 - K / Q + K * K) * norm;
break;
case FilterType::Peak:
if (gainDB >= 0)
{
norm = 1 / (1 + 1 / Q * K + K * K);
b0 = (1 + V / Q * K + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - V / Q * K + K * K) * norm;
a1 = b1;
a2 = (1 - 1 / Q * K + K * K) * norm;
}
else
{
norm = 1 / (1 + V / Q * K + K * K);
b0 = (1 + 1 / Q * K + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - 1 / Q * K + K * K) * norm;
a1 = b1;
a2 = (1 - V / Q * K + K * K) * norm;
}
break;
case FilterType::LowShelf:
if (gainDB >= 0)
{
norm = 1 / (1 + sqrtf(2) * K + K * K);
b0 = (1 + sqrtf(2 * V) * K + V * K * K) * norm;
b1 = 2 * (V * K * K - 1) * norm;
b2 = (1 - sqrtf(2 * V) * K + V * K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - sqrtf(2) * K + K * K) * norm;
}
else
{
norm = 1 / (1 + sqrtf(2 * V) * K + V * K * K);
b0 = (1 + sqrtf(2) * K + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - sqrtf(2) * K + K * K) * norm;
a1 = 2 * (V * K * K - 1) * norm;
a2 = (1 - sqrtf(2 * V) * K + V * K * K) * norm;
}
break;
case FilterType::HighShelf:
if (gainDB >= 0)
{
norm = 1 / (1 + sqrtf(2) * K + K * K);
b0 = (V + sqrtf(2 * V) * K + K * K) * norm;
b1 = 2 * (K * K - V) * norm;
b2 = (V - sqrtf(2 * V) * K + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - sqrtf(2) * K + K * K) * norm;
}
else
{
norm = 1 / (V + sqrtf(2 * V) * K + K * K);
b0 = (1 + sqrtf(2) * K + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - sqrtf(2) * K + K * K) * norm;
a1 = 2 * (K * K - V) * norm;
a2 = (V - sqrtf(2 * V) * K + K * K) * norm;
}
break;
}
}
double Biquad::GetResponse(float freq) const
{
double phi = powf((sinf(2 * M_PI * freq / (2.0 * fs))), 2);
double y = ((powf(b0 + b1 + b2, 2.0) - 4.0 * (b0 * b1 + 4.0 * b0 * b2 + b1 * b2) * phi + 16.0 * b0 * b2 * phi * phi) / (powf(1.0 + a1 + a2, 2.0) - 4.0 * (a1 + 4.0 * a2 + a1 * a2) * phi + 16.0 * a2 * phi * phi));
// y gives you power gain, not voltage gain, and this a 10 * log_10(g) formula instead of 20 * log_10(g)
// by taking the sqrt we get a value that's more suitable for signal processing, i.e. the voltage gain
return sqrtf(y);
}
void Biquad::ClearBuffers()
{
y = 0;
x2 = 0;
y2 = 0;
x1 = 0;
y1 = 0;
}
}

View File

@@ -0,0 +1,105 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
namespace Cloudseed
{
class Biquad
{
public:
enum class FilterType
{
LowPass6db = 0,
HighPass6db,
LowPass,
HighPass,
BandPass,
Notch,
Peak,
LowShelf,
HighShelf
};
private:
float fs;
float fsInv;
float gainDB;
float q;
float a0, a1, a2, b0, b1, b2;
float x1, x2, y, y1, y2;
float gain;
public:
FilterType Type;
float Output;
float Frequency;
Biquad();
Biquad(FilterType filterType, float fs);
~Biquad();
float GetSamplerate();
void SetSamplerate(float fs);
float GetGainDb();
void SetGainDb(float value);
float GetGain();
void SetGain(float value);
float GetQ();
void SetQ(float value);
void Update();
double GetResponse(float freq) const;
float inline Process(float x)
{
y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
y2 = y1;
x1 = x;
y1 = y;
Output = y;
return Output;
}
void inline Process(float* input, float* output, int len)
{
for (int i = 0; i < len; i++)
{
float x = input[i];
y = ((b0 * x) + (b1 * x1) + (b2 * x2)) - (a1 * y1) - (a2 * y2);
x2 = x1;
y2 = y1;
x1 = x;
y1 = y;
output[i] = y;
}
Output = y;
}
void ClearBuffers();
};
}

View File

@@ -0,0 +1,284 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include "Lp1.h"
#include "ModulatedDelay.h"
#include "AllpassDiffuser.h"
#include "Biquad.h"
namespace Cloudseed
{
template<unsigned int N>
class CircularBuffer
{
float buffer[N];
int idxRead;
int idxWrite;
int count;
public:
CircularBuffer()
{
Reset();
}
void Reset()
{
for (int i = 0; i < N; i++)
buffer[i] = 0.0f;
idxRead = 0;
idxWrite = 0;
count = 0;
}
int GetCount()
{
return count;
}
int PushZeros(float* data, int bufSize)
{
int countBefore = count;
for (int i = 0; i < bufSize; i++)
{
buffer[idxWrite] = 0.0f;
idxWrite = (idxWrite + 1) % N;
count++;
if (count >= N)
break; // overflow
}
return count - countBefore;
}
int Push(float* data, int bufSize)
{
int countBefore = count;
for (int i = 0; i < bufSize; i++)
{
buffer[idxWrite] = data[i];
idxWrite = (idxWrite + 1) % N;
count++;
if (count >= N)
break; // overflow
}
return count - countBefore;
}
int Pop(float* destination, int bufSize)
{
int countBefore = count;
for (int i = 0; i < bufSize; i++)
{
if (count > 0)
{
destination[i] = buffer[idxRead];
idxRead = (idxRead + 1) % N;
count--;
}
else
{
destination[i] = 0.0f;
}
}
return countBefore - count;
}
};
class DelayLine
{
private:
ModulatedDelay delay;
AllpassDiffuser diffuser;
Biquad lowShelf;
Biquad highShelf;
Lp1 lowPass;
CircularBuffer<2*BUFFER_SIZE> feedbackBuffer;
float feedback;
public:
bool DiffuserEnabled;
bool LowShelfEnabled;
bool HighShelfEnabled;
bool CutoffEnabled;
bool TapPostDiffuser;
DelayLine() :
lowShelf(Biquad::FilterType::LowShelf, 48000),
highShelf(Biquad::FilterType::HighShelf, 48000)
{
feedback = 0;
lowShelf.SetGainDb(-20);
lowShelf.Frequency = 20;
highShelf.SetGainDb(-20);
highShelf.Frequency = 19000;
lowPass.SetCutoffHz(1000);
lowShelf.Update();
highShelf.Update();
SetSamplerate(48000);
SetDiffuserSeed(1, 0.0);
}
void SetSamplerate(int samplerate)
{
diffuser.SetSamplerate(samplerate);
lowPass.SetSamplerate(samplerate);
lowShelf.SetSamplerate(samplerate);
highShelf.SetSamplerate(samplerate);
}
void SetDiffuserSeed(int seed, float crossSeed)
{
diffuser.SetSeed(seed);
diffuser.SetCrossSeed(crossSeed);
}
void SetDelay(int delaySamples)
{
delay.SampleDelay = delaySamples;
}
void SetFeedback(float feedb)
{
feedback = feedb;
}
void SetDiffuserDelay(int delaySamples)
{
diffuser.SetDelay(delaySamples);
}
void SetDiffuserFeedback(float feedb)
{
diffuser.SetFeedback(feedb);
}
void SetDiffuserStages(int stages)
{
diffuser.Stages = stages;
}
void SetLowShelfGain(float gainDb)
{
lowShelf.SetGainDb(gainDb);
lowShelf.Update();
}
void SetLowShelfFrequency(float frequency)
{
lowShelf.Frequency = frequency;
lowShelf.Update();
}
void SetHighShelfGain(float gainDb)
{
highShelf.SetGainDb(gainDb);
highShelf.Update();
}
void SetHighShelfFrequency(float frequency)
{
highShelf.Frequency = frequency;
highShelf.Update();
}
void SetCutoffFrequency(float frequency)
{
lowPass.SetCutoffHz(frequency);
}
void SetLineModAmount(float amount)
{
delay.ModAmount = amount;
}
void SetLineModRate(float rate)
{
delay.ModRate = rate;
}
void SetDiffuserModAmount(float amount)
{
diffuser.SetModulationEnabled(amount > 0.0);
diffuser.SetModAmount(amount);
}
void SetDiffuserModRate(float rate)
{
diffuser.SetModRate(rate);
}
void SetInterpolationEnabled(bool value)
{
diffuser.SetInterpolationEnabled(value);
}
void Process(float* input, float* output, int bufSize)
{
float tempBuffer[BUFFER_SIZE];
feedbackBuffer.Pop(tempBuffer, bufSize);
for (int i = 0; i < bufSize; i++)
tempBuffer[i] = input[i] + tempBuffer[i] * feedback;
delay.Process(tempBuffer, tempBuffer, bufSize);
if (!TapPostDiffuser)
Utils::Copy(output, tempBuffer, bufSize);
if (DiffuserEnabled)
diffuser.Process(tempBuffer, tempBuffer, bufSize);
if (LowShelfEnabled)
lowShelf.Process(tempBuffer, tempBuffer, bufSize);
if (HighShelfEnabled)
highShelf.Process(tempBuffer, tempBuffer, bufSize);
if (CutoffEnabled)
lowPass.Process(tempBuffer, tempBuffer, bufSize);
feedbackBuffer.Push(tempBuffer, bufSize);
if (TapPostDiffuser)
Utils::Copy(output, tempBuffer, bufSize);
}
void ClearDiffuserBuffer()
{
diffuser.ClearBuffers();
}
void ClearBuffers()
{
delay.ClearBuffers();
diffuser.ClearBuffers();
lowShelf.ClearBuffers();
highShelf.ClearBuffers();
lowPass.Output = 0;
feedbackBuffer.Reset();
}
};
}

112
src/CloudSeedCore/DSP/Hp1.h Normal file
View File

@@ -0,0 +1,112 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#define _USE_MATH_DEFINES
#include <cmath>
namespace Cloudseed
{
class Hp1
{
private:
float fs;
float b0, a1;
float lpOut;
float cutoffHz;
public:
float Output;
Hp1()
{
fs = 48000;
b0 = 1;
a1 = 0;
lpOut = 0.0;
cutoffHz = 100;
}
float GetSamplerate()
{
return fs;
}
void SetSamplerate(float samplerate)
{
fs = samplerate;
}
float GetCutoffHz()
{
return cutoffHz;
}
void SetCutoffHz(float hz)
{
cutoffHz = hz;
Update();
}
void ClearBuffers()
{
lpOut = 0;
Output = 0;
}
void Update()
{
// Prevent going over the Nyquist frequency
if (cutoffHz >= fs * 0.5f)
cutoffHz = fs * 0.499f;
auto x = 2.0f * M_PI * cutoffHz / fs;
auto nn = (2.0f - cosf(x));
auto alpha = nn - sqrtf(nn * nn - 1);
a1 = alpha;
b0 = 1 - alpha;
}
float Process(float input)
{
if (input == 0 && lpOut < 0.000001f)
{
Output = 0;
}
else
{
lpOut = b0 * input + a1 * lpOut;
Output = input - lpOut;
}
return Output;
}
void Process(float* input, float* output, int len)
{
for (int i = 0; i < len; i++)
output[i] = Process(input[i]);
}
};
}

View File

@@ -0,0 +1,97 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <stdint.h>
namespace Cloudseed
{
class LcgRandom
{
private:
uint64_t x;
uint64_t a;
uint64_t c;
double doubleInv;
float floatUintInv;
float floatIntInv;
public:
inline LcgRandom(uint64_t seed = 0)
{
x = seed;
a = 22695477;
c = 1;
doubleInv = 1.0 / (double)UINT32_MAX;
floatUintInv = 1.0 / (float)UINT32_MAX;
floatIntInv = 1.0 / (float)INT32_MAX;
}
inline void SetSeed(uint64_t seed)
{
x = seed;
}
inline uint32_t NextUInt()
{
uint64_t axc = a * x + c;
//x = axc % m;
x = axc & 0xFFFFFFFF;
return (uint32_t)x;
}
inline int32_t NextInt()
{
int64_t axc = a * x + c;
//x = axc % m;
x = axc & 0x7FFFFFFF;
return (int32_t)x;
}
inline double NextDouble()
{
auto n = NextUInt();
return n * doubleInv;
}
inline float NextFloat()
{
auto n = NextInt();
return n * floatIntInv;
}
inline void GetFloats(float* buffer, int len)
{
for (int i = 0; i < len; i++)
buffer[i] = NextFloat();
}
inline void GetFloatsBipolar(float* buffer, int len)
{
for (int i = 0; i < len; i++)
buffer[i] = NextFloat() * 2 - 1;
}
};
}

107
src/CloudSeedCore/DSP/Lp1.h Normal file
View File

@@ -0,0 +1,107 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#define _USE_MATH_DEFINES
#include <cmath>
namespace Cloudseed
{
class Lp1
{
private:
float fs;
float b0, a1;
float cutoffHz;
public:
float Output;
Lp1()
{
fs = 48000;
b0 = 1;
a1 = 0;
cutoffHz = 1000;
}
float GetSamplerate()
{
return fs;
}
void SetSamplerate(float samplerate)
{
fs = samplerate;
}
float GetCutoffHz()
{
return cutoffHz;
}
void SetCutoffHz(float hz)
{
cutoffHz = hz;
Update();
}
void ClearBuffers()
{
Output = 0;
}
void Update()
{
// Prevent going over the Nyquist frequency
if (cutoffHz >= fs * 0.5f)
cutoffHz = fs * 0.499f;
auto x = 2.0f * M_PI * cutoffHz / fs;
auto nn = (2.0f - cosf(x));
auto alpha = nn - sqrtf(nn * nn - 1);
a1 = alpha;
b0 = 1 - alpha;
}
float Process(float input)
{
if (input == 0 && Output < 0.0000001f)
{
Output = 0;
}
else
{
Output = b0 * input + a1 * Output;
}
return Output;
}
void Process(float* input, float* output, int len)
{
for (int i = 0; i < len; i++)
output[i] = Process(input[i]);
}
};
}

View File

@@ -0,0 +1,187 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include "ModulatedAllpass.h"
#include "Utils.h"
#include <cmath>
namespace Cloudseed
{
class ModulatedAllpass
{
public:
static const int DelayBufferSize = 19200; // 100ms at 192Khz
static const int ModulationUpdateRate = 8;
private:
float delayBuffer[DelayBufferSize] = { 0 };
int index;
uint64_t samplesProcessed;
float modPhase;
int delayA;
int delayB;
float gainA;
float gainB;
public:
int SampleDelay;
float Feedback;
float ModAmount;
float ModRate;
bool InterpolationEnabled;
bool ModulationEnabled;
ModulatedAllpass()
{
index = DelayBufferSize - 1;
samplesProcessed = 0;
modPhase = 0.01 + 0.98 * rand() / (float)RAND_MAX;
delayA = 0;
delayB = 0;
gainA = 0;
gainB = 0;
SampleDelay = 100;
Feedback = 0.5;
ModAmount = 0.0;
ModRate = 0.0;
InterpolationEnabled = true;
ModulationEnabled = true;
Update();
}
void ClearBuffers()
{
Utils::ZeroBuffer(delayBuffer, DelayBufferSize);
}
void Process(float* input, float* output, int sampleCount)
{
if (ModulationEnabled)
ProcessWithMod(input, output, sampleCount);
else
ProcessNoMod(input, output, sampleCount);
}
private:
void ProcessNoMod(float* input, float* output, int sampleCount)
{
auto delayedIndex = index - SampleDelay;
if (delayedIndex < 0) delayedIndex += DelayBufferSize;
for (int i = 0; i < sampleCount; i++)
{
auto bufOut = delayBuffer[delayedIndex];
auto inVal = input[i] + bufOut * Feedback;
delayBuffer[index] = inVal;
output[i] = bufOut - inVal * Feedback;
index++;
delayedIndex++;
if (index >= DelayBufferSize) index -= DelayBufferSize;
if (delayedIndex >= DelayBufferSize) delayedIndex -= DelayBufferSize;
samplesProcessed++;
}
}
void ProcessWithMod(float* input, float* output, int sampleCount)
{
for (int i = 0; i < sampleCount; i++)
{
if (samplesProcessed >= ModulationUpdateRate)
{
Update();
samplesProcessed = 0;
}
float bufOut;
if (InterpolationEnabled)
{
int idxA = index - delayA;
int idxB = index - delayB;
idxA += DelayBufferSize * (idxA < 0); // modulo
idxB += DelayBufferSize * (idxB < 0); // modulo
bufOut = delayBuffer[idxA] * gainA + delayBuffer[idxB] * gainB;
}
else
{
int idxA = index - delayA;
idxA += DelayBufferSize * (idxA < 0); // modulo
bufOut = delayBuffer[idxA];
}
auto inVal = input[i] + bufOut * Feedback;
delayBuffer[index] = inVal;
output[i] = bufOut - inVal * Feedback;
index++;
if (index >= DelayBufferSize) index -= DelayBufferSize;
samplesProcessed++;
}
}
inline float Get(int delay)
{
int idx = index - delay;
if (idx < 0)
idx += DelayBufferSize;
return delayBuffer[idx];
}
void Update()
{
modPhase += ModRate * ModulationUpdateRate;
if (modPhase > 1)
modPhase = std::fmod(modPhase, 1.0);
auto mod = std::sinf(modPhase * 2 * M_PI);
if (ModAmount >= SampleDelay) // don't modulate to negative value
ModAmount = SampleDelay - 1;
auto totalDelay = SampleDelay + ModAmount * mod;
if (totalDelay <= 0) // should no longer be required
totalDelay = 1;
delayA = (int)totalDelay;
delayB = (int)totalDelay + 1;
auto partial = totalDelay - delayA;
gainA = 1 - partial;
gainB = partial;
}
};
}

View File

@@ -0,0 +1,125 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include "ModulatedDelay.h"
#include "Utils.h"
#include <stdint.h>
namespace Cloudseed
{
class ModulatedDelay
{
private:
static const int ModulationUpdateRate = 8;
static const int DelayBufferSize = 192000 * 2;
float delayBuffer[DelayBufferSize] = { 0 };
int writeIndex;
int readIndexA;
int readIndexB;
uint64_t samplesProcessed;
float modPhase;
float gainA;
float gainB;
public:
int SampleDelay;
float ModAmount;
float ModRate;
ModulatedDelay()
{
writeIndex = 0;
readIndexA = 0;
readIndexB = 0;
samplesProcessed = 0;
modPhase = 0.01 + 0.98 * (rand() / (float)RAND_MAX);
gainA = 0;
gainB = 0;
SampleDelay = 100;
ModAmount = 0.0;
ModRate = 0.0;
Update();
}
void Process(float* input, float* output, int bufSize)
{
for (int i = 0; i < bufSize; i++)
{
if (samplesProcessed >= ModulationUpdateRate)
{
Update();
samplesProcessed = 0;
}
delayBuffer[writeIndex] = input[i];
output[i] = delayBuffer[readIndexA] * gainA + delayBuffer[readIndexB] * gainB;
writeIndex++;
readIndexA++;
readIndexB++;
if (writeIndex >= DelayBufferSize) writeIndex -= DelayBufferSize;
if (readIndexA >= DelayBufferSize) readIndexA -= DelayBufferSize;
if (readIndexB >= DelayBufferSize) readIndexB -= DelayBufferSize;
samplesProcessed++;
}
}
void ClearBuffers()
{
Utils::ZeroBuffer(delayBuffer, DelayBufferSize);
}
private:
void Update()
{
modPhase += ModRate * ModulationUpdateRate;
if (modPhase > 1)
modPhase = std::fmod(modPhase, 1.0);
auto mod = std::sinf(modPhase * 2 * M_PI);
auto totalDelay = SampleDelay + ModAmount * mod;
auto delayA = (int)totalDelay;
auto delayB = (int)totalDelay + 1;
auto partial = totalDelay - delayA;
gainA = 1 - partial;
gainB = partial;
readIndexA = writeIndex - delayA;
readIndexB = writeIndex - delayB;
if (readIndexA < 0) readIndexA += DelayBufferSize;
if (readIndexB < 0) readIndexB += DelayBufferSize;
}
};
}

View File

@@ -0,0 +1,150 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <vector>
#include <memory>
#include <array>
#include <cmath>
#include "Utils.h"
#include "RandomBuffer.h"
namespace Cloudseed
{
class MultitapDelay
{
public:
static const int MaxTaps = 256;
static const int DelayBufferSize = 192000 * 2;
private:
float delayBuffer[DelayBufferSize] = { 0 };
float tapGains[MaxTaps] = { 0 };
float tapPosition[MaxTaps] = { 0 };
std::vector<float> seedValues;
int writeIdx;
int seed;
float crossSeed;
int count;
float lengthSamples;
float decay;
public:
MultitapDelay()
{
writeIdx = 0;
seed = 0;
crossSeed = 0.0;
count = 1;
lengthSamples = 1000;
decay = 1.0;
UpdateSeeds();
}
void SetSeed(int seed)
{
this->seed = seed;
UpdateSeeds();
}
void SetCrossSeed(float crossSeed)
{
this->crossSeed = crossSeed;
UpdateSeeds();
}
void SetTapCount(int tapCount)
{
if (tapCount < 1) tapCount = 1;
count = tapCount;
Update();
}
void SetTapLength(int tapLengthSamples)
{
if (tapLengthSamples < 10) tapLengthSamples = 10;
lengthSamples = tapLengthSamples;
Update();
}
void SetTapDecay(float tapDecay)
{
decay = tapDecay;
}
void Process(float* input, float* output, int bufSize)
{
float lengthScaler = lengthSamples / (float)count;
float totalGain = 3.0 / std::sqrtf(1 + count);
totalGain *= (1 + decay * 2);
for (int i = 0; i < bufSize; i++)
{
delayBuffer[writeIdx] = input[i];
output[i] = 0;
for (int j = 0; j < count; j++)
{
float offset = tapPosition[j] * lengthScaler;
float decayEffective = std::expf(-offset / lengthSamples * 3.3) * decay + (1-decay);
int readIdx = writeIdx - (int)offset;
if (readIdx < 0) readIdx += DelayBufferSize;
output[i] += delayBuffer[readIdx] * tapGains[j] * decayEffective * totalGain;
}
writeIdx = (writeIdx + 1) % DelayBufferSize;
}
}
void ClearBuffers()
{
Utils::ZeroBuffer(delayBuffer, DelayBufferSize);
}
private:
void Update()
{
int s = 0;
auto rand = [&]() {return seedValues[s++]; };
for (int i = 0; i < MaxTaps; i++)
{
float phase = rand() < 0.5 ? 1 : -1;
tapGains[i] = Utils::DB2Gainf(-20 + rand() * 20) * phase;
tapPosition[i] = i + rand();
}
}
void UpdateSeeds()
{
this->seedValues = RandomBuffer::Generate(seed, MaxTaps * 3, crossSeed);
Update();
}
};
}

View File

@@ -0,0 +1,57 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <climits>
#include "RandomBuffer.h"
#include "LcgRandom.h"
namespace Cloudseed
{
std::vector<float> RandomBuffer::Generate(uint64_t seed, int count)
{
LcgRandom rand(seed);
std::vector<float> output;
for (int i = 0; i < count; i++)
{
unsigned int val = rand.NextUInt();
float fVal = val / (float)UINT_MAX;
output.push_back(fVal);
}
return output;
}
std::vector<float> RandomBuffer::Generate(uint64_t seed, int count, float crossSeed)
{
auto seedA = seed;
auto seedB = ~seed;
auto seriesA = Generate(seedA, count);
auto seriesB = Generate(seedB, count);
std::vector<float> output;
for (int i = 0; i < count; i++)
output.push_back(seriesA[i] * (1 - crossSeed) + seriesB[i] * crossSeed);
return output;
}
}

View File

@@ -0,0 +1,36 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <vector>
#include <stdint.h>
namespace Cloudseed
{
class RandomBuffer
{
public:
static std::vector<float> Generate(uint64_t seed, int count);
static std::vector<float> Generate(uint64_t seed, int count, float crossSeed);
};
}

View File

@@ -0,0 +1,428 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <map>
#include <memory>
#include "../Parameters.h"
#include "ModulatedDelay.h"
#include "MultitapDelay.h"
#include "RandomBuffer.h"
#include "Lp1.h"
#include "Hp1.h"
#include "DelayLine.h"
#include "AllpassDiffuser.h"
#include <cmath>
#include "ReverbChannel.h"
#include "Utils.h"
namespace Cloudseed
{
enum class ChannelLR
{
Left,
Right
};
class ReverbChannel
{
private:
static const int TotalLineCount = 12;
double paramsScaled[Parameter::COUNT] = { 0.0 };
int samplerate;
ModulatedDelay preDelay;
MultitapDelay multitap;
AllpassDiffuser diffuser;
DelayLine lines[TotalLineCount];
RandomBuffer rand;
Hp1 highPass;
Lp1 lowPass;
int delayLineSeed;
int postDiffusionSeed;
// Used the the main process loop
int lineCount;
bool lowCutEnabled;
bool highCutEnabled;
bool multitapEnabled;
bool diffuserEnabled;
float inputMix;
float dryOut;
float earlyOut;
float lineOut;
float crossSeed;
ChannelLR channelLr;
public:
ReverbChannel(int samplerate, ChannelLR leftOrRight)
{
this->channelLr = leftOrRight;
crossSeed = 0.0;
lineCount = 8;
diffuser.SetInterpolationEnabled(true);
highPass.SetCutoffHz(20);
lowPass.SetCutoffHz(20000);
SetSamplerate(samplerate);
}
int GetSamplerate()
{
return samplerate;
}
void SetSamplerate(int samplerate)
{
this->samplerate = samplerate;
highPass.SetSamplerate(samplerate);
lowPass.SetSamplerate(samplerate);
diffuser.SetSamplerate(samplerate);
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetSamplerate(samplerate);
ReapplyAllParams();
ClearBuffers();
UpdateLines();
}
void ReapplyAllParams()
{
for (int i = 0; i < Parameter::COUNT; i++)
SetParameter(i, paramsScaled[i]);
}
void SetParameter(int para, double scaledValue)
{
paramsScaled[para] = scaledValue;
switch (para)
{
case Parameter::Interpolation:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetInterpolationEnabled(scaledValue >= 0.5);
break;
case Parameter::LowCutEnabled:
lowCutEnabled = scaledValue >= 0.5;
if (lowCutEnabled)
highPass.ClearBuffers();
break;
case Parameter::HighCutEnabled:
highCutEnabled = scaledValue >= 0.5;
if (highCutEnabled)
lowPass.ClearBuffers();
break;
case Parameter::InputMix:
inputMix = scaledValue;
break;
case Parameter::LowCut:
highPass.SetCutoffHz(scaledValue);
break;
case Parameter::HighCut:
lowPass.SetCutoffHz(scaledValue);
break;
case Parameter::DryOut:
dryOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue);
break;
case Parameter::EarlyOut:
earlyOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue);
break;
case Parameter::LateOut:
lineOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue);
break;
case Parameter::TapEnabled:
{
auto newVal = scaledValue >= 0.5;
if (newVal != multitapEnabled)
multitap.ClearBuffers();
multitapEnabled = newVal;
break;
}
case Parameter::TapCount:
multitap.SetTapCount((int)scaledValue);
break;
case Parameter::TapDecay:
multitap.SetTapDecay(scaledValue);
break;
case Parameter::TapPredelay:
preDelay.SampleDelay = (int)Ms2Samples(scaledValue);
break;
case Parameter::TapLength:
multitap.SetTapLength((int)Ms2Samples(scaledValue));
break;
case Parameter::EarlyDiffuseEnabled:
{
auto newVal = scaledValue >= 0.5;
if (newVal != diffuserEnabled)
diffuser.ClearBuffers();
diffuserEnabled = newVal;
break;
}
case Parameter::EarlyDiffuseCount:
diffuser.Stages = (int)scaledValue;
break;
case Parameter::EarlyDiffuseDelay:
diffuser.SetDelay((int)Ms2Samples(scaledValue));
break;
case Parameter::EarlyDiffuseModAmount:
diffuser.SetModulationEnabled(scaledValue > 0.5);
diffuser.SetModAmount(Ms2Samples(scaledValue));
break;
case Parameter::EarlyDiffuseFeedback:
diffuser.SetFeedback(scaledValue);
break;
case Parameter::EarlyDiffuseModRate:
diffuser.SetModRate(scaledValue);
break;
case Parameter::LateMode:
for (int i = 0; i < TotalLineCount; i++)
lines[i].TapPostDiffuser = scaledValue >= 0.5;
break;
case Parameter::LateLineCount:
lineCount = (int)scaledValue;
break;
case Parameter::LateDiffuseEnabled:
for (int i = 0; i < TotalLineCount; i++)
{
auto newVal = scaledValue >= 0.5;
if (newVal != lines[i].DiffuserEnabled)
lines[i].ClearDiffuserBuffer();
lines[i].DiffuserEnabled = newVal;
}
break;
case Parameter::LateDiffuseCount:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetDiffuserStages((int)scaledValue);
break;
case Parameter::LateLineSize:
UpdateLines();
break;
case Parameter::LateLineModAmount:
UpdateLines();
break;
case Parameter::LateDiffuseDelay:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetDiffuserDelay((int)Ms2Samples(scaledValue));
break;
case Parameter::LateDiffuseModAmount:
UpdateLines();
break;
case Parameter::LateLineDecay:
UpdateLines();
break;
case Parameter::LateLineModRate:
UpdateLines();
break;
case Parameter::LateDiffuseFeedback:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetDiffuserFeedback(scaledValue);
break;
case Parameter::LateDiffuseModRate:
UpdateLines();
break;
case Parameter::EqLowShelfEnabled:
for (int i = 0; i < TotalLineCount; i++)
lines[i].LowShelfEnabled = scaledValue >= 0.5;
break;
case Parameter::EqHighShelfEnabled:
for (int i = 0; i < TotalLineCount; i++)
lines[i].HighShelfEnabled = scaledValue >= 0.5;
break;
case Parameter::EqLowpassEnabled:
for (int i = 0; i < TotalLineCount; i++)
lines[i].CutoffEnabled = scaledValue >= 0.5;
break;
case Parameter::EqLowFreq:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetLowShelfFrequency(scaledValue);
break;
case Parameter::EqHighFreq:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetHighShelfFrequency(scaledValue);
break;
case Parameter::EqCutoff:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetCutoffFrequency(scaledValue);
break;
case Parameter::EqLowGain:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetLowShelfGain(scaledValue);
break;
case Parameter::EqHighGain:
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetHighShelfGain(scaledValue);
break;
case Parameter::EqCrossSeed:
crossSeed = channelLr == ChannelLR::Right ? 0.5 * scaledValue : 1 - 0.5 * scaledValue;
multitap.SetCrossSeed(crossSeed);
diffuser.SetCrossSeed(crossSeed);
UpdateLines();
UpdatePostDiffusion();
break;
case Parameter::SeedTap:
multitap.SetSeed((int)scaledValue);
break;
case Parameter::SeedDiffusion:
diffuser.SetSeed((int)scaledValue);
break;
case Parameter::SeedDelay:
delayLineSeed = (int)scaledValue;
UpdateLines();
break;
case Parameter::SeedPostDiffusion:
postDiffusionSeed = (int)scaledValue;
UpdatePostDiffusion();
break;
}
}
void Process(float* input, float* output, int bufSize)
{
float tempBuffer[BUFFER_SIZE];
float earlyOutBuffer[BUFFER_SIZE];
float lineOutBuffer[BUFFER_SIZE];
float lineSumBuffer[BUFFER_SIZE];
Utils::Copy(tempBuffer, input, bufSize);
if (lowCutEnabled)
highPass.Process(tempBuffer, tempBuffer, bufSize);
if (highCutEnabled)
lowPass.Process(tempBuffer, tempBuffer, bufSize);
// completely zero if no input present
// Previously, the very small values were causing some really strange CPU spikes
for (int i = 0; i < bufSize; i++)
{
auto n = tempBuffer[i];
if (n * n < 0.000000001)
tempBuffer[i] = 0;
}
preDelay.Process(tempBuffer, tempBuffer, bufSize);
if (multitapEnabled)
multitap.Process(tempBuffer, tempBuffer, bufSize);
if (diffuserEnabled)
diffuser.Process(tempBuffer, tempBuffer, bufSize);
Utils::Copy(earlyOutBuffer, tempBuffer, bufSize);
Utils::ZeroBuffer(lineSumBuffer, bufSize);
for (int i = 0; i < lineCount; i++)
{
lines[i].Process(tempBuffer, lineOutBuffer, bufSize);
Utils::Mix(lineSumBuffer, lineOutBuffer, 1.0f, bufSize);
}
auto perLineGain = GetPerLineGain();
Utils::Gain(lineSumBuffer, perLineGain, bufSize);
for (int i = 0; i < bufSize; i++)
{
output[i] = dryOut * input[i]
+ earlyOut * earlyOutBuffer[i]
+ lineOut * lineSumBuffer[i];
}
}
void ClearBuffers()
{
lowPass.ClearBuffers();
highPass.ClearBuffers();
preDelay.ClearBuffers();
multitap.ClearBuffers();
diffuser.ClearBuffers();
for (int i = 0; i < TotalLineCount; i++)
lines[i].ClearBuffers();
}
private:
float GetPerLineGain()
{
return 1.0 / std::sqrt(lineCount);
}
void UpdateLines()
{
auto lineDelaySamples = (int)Ms2Samples(paramsScaled[Parameter::LateLineSize]);
auto lineDecayMillis = paramsScaled[Parameter::LateLineDecay] * 1000;
auto lineDecaySamples = Ms2Samples(lineDecayMillis);
auto lineModAmount = Ms2Samples(paramsScaled[Parameter::LateLineModAmount]);
auto lineModRate = paramsScaled[Parameter::LateLineModRate];
auto lateDiffusionModAmount = Ms2Samples(paramsScaled[Parameter::LateDiffuseModAmount]);
auto lateDiffusionModRate = paramsScaled[Parameter::LateDiffuseModRate];
auto delayLineSeeds = RandomBuffer::Generate(delayLineSeed, TotalLineCount * 3, crossSeed);
for (int i = 0; i < TotalLineCount; i++)
{
auto modAmount = lineModAmount * (0.7 + 0.3 * delayLineSeeds[i]);
auto modRate = lineModRate * (0.7 + 0.3 * delayLineSeeds[TotalLineCount + i]) / samplerate;
auto delaySamples = (0.5 + 1.0 * delayLineSeeds[TotalLineCount * 2 + i]) * lineDelaySamples;
if (delaySamples < modAmount + 2) // when the delay is set really short, and the modulation is very high
delaySamples = modAmount + 2; // the mod could actually take the delay time negative, prevent that! -- provide 2 extra sample as margin of safety
auto dbAfter1Iteration = delaySamples / lineDecaySamples * (-60); // lineDecay is the time it takes to reach T60
auto gainAfter1Iteration = Utils::DB2Gainf(dbAfter1Iteration);
lines[i].SetDelay((int)delaySamples);
lines[i].SetFeedback(gainAfter1Iteration);
lines[i].SetLineModAmount(modAmount);
lines[i].SetLineModRate(modRate);
lines[i].SetDiffuserModAmount(lateDiffusionModAmount);
lines[i].SetDiffuserModRate(lateDiffusionModRate);
}
}
void UpdatePostDiffusion()
{
for (int i = 0; i < TotalLineCount; i++)
lines[i].SetDiffuserSeed((postDiffusionSeed) * (i + 1), crossSeed);
}
float Ms2Samples(float value)
{
return value / 1000.0f * samplerate;
}
};
}

View File

@@ -0,0 +1,126 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <vector>
#include "../Parameters.h"
#include "ReverbChannel.h"
#include "AllpassDiffuser.h"
#include "MultitapDelay.h"
#include "Utils.h"
namespace Cloudseed
{
class ReverbController
{
private:
int samplerate;
ReverbChannel channelL;
ReverbChannel channelR;
double parameters[(int)Parameter::COUNT] = {0};
public:
ReverbController(int samplerate) :
channelL(samplerate, ChannelLR::Left),
channelR(samplerate, ChannelLR::Right)
{
this->samplerate = samplerate;
}
int GetSamplerate()
{
return samplerate;
}
void SetSamplerate(int samplerate)
{
this->samplerate = samplerate;
channelL.SetSamplerate(samplerate);
channelR.SetSamplerate(samplerate);
}
int GetParameterCount()
{
return Parameter::COUNT;
}
double* GetAllParameters()
{
return parameters;
}
void SetParameter(int paramId, double value)
{
parameters[paramId] = value;
auto scaled = ScaleParam(value, paramId);
channelL.SetParameter(paramId, scaled);
channelR.SetParameter(paramId, scaled);
}
void ClearBuffers()
{
channelL.ClearBuffers();
channelR.ClearBuffers();
}
void Process(float* inL, float* inR, float* outL, float* outR, int bufSize)
{
float outLTemp[BUFFER_SIZE];
float outRTemp[BUFFER_SIZE];
while (bufSize > 0)
{
int subBufSize = bufSize > BUFFER_SIZE ? BUFFER_SIZE : bufSize;
ProcessChunk(inL, inR, outLTemp, outRTemp, subBufSize);
Utils::Copy(outL, outLTemp, subBufSize);
Utils::Copy(outR, outRTemp, subBufSize);
inL = &inL[subBufSize];
inR = &inR[subBufSize];
outL = &outL[subBufSize];
outR = &outR[subBufSize];
bufSize -= subBufSize;
}
}
private:
void ProcessChunk(float* inL, float* inR, float* outL, float* outR, int bufSize)
{
float leftChannelIn[BUFFER_SIZE];
float rightChannelIn[BUFFER_SIZE];
float inputMix = ScaleParam(parameters[Parameter::InputMix], Parameter::InputMix);
float cm = inputMix * 0.5;
float cmi = (1 - cm);
for (int i = 0; i < bufSize; i++)
{
leftChannelIn[i] = inL[i] * cmi + inR[i] * cm;
rightChannelIn[i] = inR[i] * cmi + inL[i] * cm;
}
channelL.Process(leftChannelIn, outL, bufSize);
channelR.Process(rightChannelIn, outR, bufSize);
}
};
}

View File

@@ -0,0 +1,112 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#define _USE_MATH_DEFINES 1
#include <math.h>
#include <stdint.h>
namespace Cloudseed
{
namespace Utils
{
template<typename T>
inline void ZeroBuffer(T* buffer, int len)
{
for (int i = 0; i < len; i++)
buffer[i] = 0;
}
template<typename T>
inline void Copy(T* dest, T* source, int len)
{
memcpy(dest, source, len * sizeof(T));
}
template<typename T>
inline void Gain(T* buffer, T gain, int len)
{
for (int i = 0; i < len; i++)
{
buffer[i] *= gain;
}
}
template<typename T>
inline void Mix(T* target, T* source, T gain, int len)
{
for (int i = 0; i < len; i++)
target[i] += source[i] * gain;
}
inline float DB2Gainf(float input)
{
//return std::pow(10.0f, input / 20.0f);
return powf(10, input * 0.05f);
}
template<typename T>
inline double DB2Gain(T input)
{
return pow10f(input / 20.0);
}
template<typename T>
inline double Gain2DB(T input)
{
//if (input < 0.0000001)
// return -100000;
return 20.0f * log10f(input);
}
const float dec1Mult = (10 / 9.0) * 0.1;
const float dec2Mult = (100 / 99.0) * 0.01;
const float dec3Mult = (1000 / 999.0) * 0.001;
const float dec4Mult = (10000 / 9999.0) * 0.0001;
const float oct1Mult = (2 / 1.0) * 0.5;
const float oct2Mult = (4 / 3.0) * 0.25;
const float oct3Mult = (8 / 7.0) * 0.125;
const float oct4Mult = (16 / 15.0) * 0.0625;
const float oct5Mult = (32 / 31.0) * 0.03125;
const float oct6Mult = (64 / 63.0) * 0.015625;
const float oct7Mult = (128 / 127.0) * 0.0078125;
const float oct8Mult = (256 / 255.0) * 0.00390625;
inline float Resp1dec(float x) { return (powf(10, x) - 1) * dec1Mult; }
inline float Resp2dec(float x) { return (powf(10, 2 * x) - 1) * dec2Mult; }
inline float Resp3dec(float x) { return (powf(10, 3 * x) - 1) * dec3Mult; }
inline float Resp4dec(float x) { return (powf(10, 4 * x) - 1) * dec4Mult; }
inline float Resp1oct(float x) { return (powf(2, x) - 1) * oct1Mult; }
inline float Resp2oct(float x) { return (powf(2, 2 * x) - 1) * oct2Mult; }
inline float Resp3oct(float x) { return (powf(2, 3 * x) - 1) * oct3Mult; }
inline float Resp4oct(float x) { return (powf(2, 4 * x) - 1) * oct4Mult; }
inline float Resp5oct(float x) { return (powf(2, 5 * x) - 1) * oct5Mult; }
inline float Resp6oct(float x) { return (powf(2, 6 * x) - 1) * oct6Mult; }
inline float Resp7oct(float x) { return (powf(2, 7 * x) - 1) * oct7Mult; }
inline float Resp8oct(float x) { return (powf(2, 8 * x) - 1) * oct8Mult; }
}
}

21
src/CloudSeedCore/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Ghost Note Audio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,80 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "Parameters.h"
namespace Cloudseed
{
const char* ParameterLabel[Parameter::COUNT] = {
"Interpolation",
"High Cut",
"Low Cut",
"Input Mix",
"High Cut",
"Low Cut",
"Dry",
"Early",
"Late",
"Multitap Delay",
"Count",
"Decay",
"Pre-delay",
"Length",
"Diffusion",
"Diffusion Stages",
"Delay",
"Mod Amt",
"Feedback",
"Mod Rate",
"Mode",
"Line Count",
"Diffusion",
"Diffusion Stages",
"Size",
"Mod Amt",
"Delay",
"Mod Amt",
"Decay",
"Mod Rate",
"Feedback",
"Mod Rate",
"Low Shelf",
"High Shelf",
"Lowpass",
"Low Freq",
"High Freq",
"Cutoff",
"Low Gain",
"High Gain",
"Cross Seed",
"Tap Seed",
"Diffusion Seed",
"Delay Seed",
"Late Diffusion Seed",
};
}

View File

@@ -0,0 +1,286 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "DSP/Utils.h"
#include "limits.h"
#include <cstring>
namespace Cloudseed
{
namespace Parameter
{
const int Interpolation = 0;
const int LowCutEnabled = 1;
const int HighCutEnabled = 2;
const int InputMix = 3;
const int LowCut = 4;
const int HighCut = 5;
const int DryOut = 6;
const int EarlyOut = 7;
const int LateOut = 8;
const int TapEnabled = 9;
const int TapCount = 10;
const int TapDecay = 11;
const int TapPredelay = 12;
const int TapLength = 13;
const int EarlyDiffuseEnabled = 14;
const int EarlyDiffuseCount = 15;
const int EarlyDiffuseDelay = 16;
const int EarlyDiffuseModAmount = 17;
const int EarlyDiffuseFeedback = 18;
const int EarlyDiffuseModRate = 19;
const int LateMode = 20;
const int LateLineCount = 21;
const int LateDiffuseEnabled = 22;
const int LateDiffuseCount = 23;
const int LateLineSize = 24;
const int LateLineModAmount = 25;
const int LateDiffuseDelay = 26;
const int LateDiffuseModAmount = 27;
const int LateLineDecay = 28;
const int LateLineModRate = 29;
const int LateDiffuseFeedback = 30;
const int LateDiffuseModRate = 31;
const int EqLowShelfEnabled = 32;
const int EqHighShelfEnabled = 33;
const int EqLowpassEnabled = 34;
const int EqLowFreq = 35;
const int EqHighFreq = 36;
const int EqCutoff = 37;
const int EqLowGain = 38;
const int EqHighGain = 39;
const int EqCrossSeed = 40;
const int SeedTap = 41;
const int SeedDiffusion = 42;
const int SeedDelay = 43;
const int SeedPostDiffusion = 44;
const int COUNT = 45;
};
extern const char* ParameterLabel[Parameter::COUNT];
inline double ScaleParam(double val, int index)
{
switch (index)
{
case Parameter::Interpolation:
case Parameter::LowCutEnabled:
case Parameter::HighCutEnabled:
case Parameter::TapEnabled:
case Parameter::LateDiffuseEnabled:
case Parameter::EqLowShelfEnabled:
case Parameter::EqHighShelfEnabled:
case Parameter::EqLowpassEnabled:
case Parameter::EarlyDiffuseEnabled:
return val < 0.5 ? 0.0 : 1.0;
case Parameter::InputMix:
case Parameter::EarlyDiffuseFeedback:
case Parameter::TapDecay:
case Parameter::LateDiffuseFeedback:
case Parameter::EqCrossSeed:
return val;
case Parameter::SeedTap:
case Parameter::SeedDiffusion:
case Parameter::SeedDelay:
case Parameter::SeedPostDiffusion:
return (int)floor(val * 999.999);
case Parameter::LowCut:
return 20 + Utils::Resp4oct(val) * 980;
case Parameter::HighCut:
return 400 + Utils::Resp4oct(val) * 19600;
case Parameter::DryOut:
case Parameter::EarlyOut:
case Parameter::LateOut:
return -30 + val * 30;
case Parameter::TapCount:
return (int)(1 + val * 255);
case Parameter::TapPredelay:
return Utils::Resp1dec(val) * 500;
case Parameter::TapLength:
return 10 + val * 990;
case Parameter::EarlyDiffuseCount:
return (int)(1 + val * 11.999);
case Parameter::EarlyDiffuseDelay:
return 10 + val * 90;
case Parameter::EarlyDiffuseModAmount:
return val * 2.5;
case Parameter::EarlyDiffuseModRate:
return Utils::Resp2dec(val) * 5;
case Parameter::LateMode:
return val < 0.5 ? 0.0 : 1.0;
case Parameter::LateLineCount:
return (int)(1 + val * 11.999);
case Parameter::LateDiffuseCount:
return (int)(1 + val * 7.999);
case Parameter::LateLineSize:
return 20 + Utils::Resp2dec(val) * 980;
case Parameter::LateLineModAmount:
return val * 2.5;
case Parameter::LateDiffuseDelay:
return 10 + val * 90;
case Parameter::LateDiffuseModAmount:
return val * 2.5;
case Parameter::LateLineDecay:
return 0.05 + Utils::Resp3dec(val) * 59.95;
case Parameter::LateLineModRate:
return Utils::Resp2dec(val) * 5;
case Parameter::LateDiffuseModRate:
return Utils::Resp2dec(val) * 5;
case Parameter::EqLowFreq:
return 20 + Utils::Resp3oct(val) * 980;
case Parameter::EqHighFreq:
return 400 + Utils::Resp4oct(val) * 19600;
case Parameter::EqCutoff:
return 400 + Utils::Resp4oct(val) * 19600;
case Parameter::EqLowGain:
return -20 + val * 20;
case Parameter::EqHighGain:
return -20 + val * 20;
}
return 0;
}
inline void FormatParameter(float val, int maxLen, int paramId, char* buffer)
{
double s = ScaleParam(val, paramId);
switch (paramId)
{
case Parameter::Interpolation:
case Parameter::HighCutEnabled:
case Parameter::LowCutEnabled:
case Parameter::TapEnabled:
case Parameter::LateDiffuseEnabled:
case Parameter::EqLowShelfEnabled:
case Parameter::EqHighShelfEnabled:
case Parameter::EqLowpassEnabled:
case Parameter::EarlyDiffuseEnabled:
if (ScaleParam(val, paramId) == 1)
strcpy(buffer, "ENABLED");
else
strcpy(buffer, "DISABLED");
break;
case Parameter::InputMix:
case Parameter::EarlyDiffuseFeedback:
case Parameter::TapDecay:
case Parameter::LateDiffuseFeedback:
case Parameter::EqCrossSeed:
snprintf(buffer, MAX_STR_SIZE, "%d%%", (int)(s * 100));
break;
case Parameter::SeedTap:
case Parameter::SeedDiffusion:
case Parameter::SeedDelay:
case Parameter::SeedPostDiffusion:
snprintf(buffer, MAX_STR_SIZE, "%03d", (int)s);
break;
case Parameter::LowCut:
case Parameter::HighCut:
case Parameter::EqLowFreq:
case Parameter::EqHighFreq:
case Parameter::EqCutoff:
snprintf(buffer, MAX_STR_SIZE, "%d Hz", (int)s);
break;
case Parameter::DryOut:
case Parameter::EarlyOut:
case Parameter::LateOut:
if (s <= -30)
strcpy(buffer, "MUTED");
else
snprintf(buffer, MAX_STR_SIZE, "%.1f dB", s);
break;
case Parameter::TapCount:
case Parameter::EarlyDiffuseCount:
case Parameter::LateLineCount:
case Parameter::LateDiffuseCount:
snprintf(buffer, MAX_STR_SIZE, "%d", (int)s);
break;
case Parameter::TapPredelay:
case Parameter::TapLength:
case Parameter::EarlyDiffuseDelay:
case Parameter::LateLineSize:
case Parameter::LateDiffuseDelay:
snprintf(buffer, MAX_STR_SIZE, "%d ms", (int)s);
break;
case Parameter::LateLineDecay:
if (s < 1)
snprintf(buffer, MAX_STR_SIZE, "%d ms", (int)(s * 1000));
else if (s < 10)
snprintf(buffer, MAX_STR_SIZE, "%.2f sec", s);
else
snprintf(buffer, MAX_STR_SIZE, "%.1f sec", s);
break;
case Parameter::LateMode:
if (s == 1)
strcpy(buffer, "POST");
else
strcpy(buffer, "PRE");
break;
case Parameter::EarlyDiffuseModAmount:
case Parameter::LateLineModAmount:
case Parameter::LateDiffuseModAmount:
snprintf(buffer, MAX_STR_SIZE, "%d%%", (int)(s * 100));
break;
case Parameter::EarlyDiffuseModRate:
case Parameter::LateLineModRate:
case Parameter::LateDiffuseModRate:
snprintf(buffer, MAX_STR_SIZE, "%.2f Hz", s);
break;
case Parameter::EqLowGain:
case Parameter::EqHighGain:
snprintf(buffer, MAX_STR_SIZE, "%.1f dB", s);
break;
default:
snprintf(buffer, MAX_STR_SIZE, "%.2f", s);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
Copyright (c) 2024 Ghost Note Engineering Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include "Parameters.h"
namespace Cloudseed
{
float ProgramDarkPlate[Parameter::COUNT];
void initPrograms()
{
ProgramDarkPlate[Parameter::DryOut] = 0.8705999851226807;
ProgramDarkPlate[Parameter::EarlyDiffuseCount] = 0.2960000038146973;
ProgramDarkPlate[Parameter::EarlyDiffuseDelay] = 0.3066999912261963;
ProgramDarkPlate[Parameter::EarlyDiffuseEnabled] = 0.0;
ProgramDarkPlate[Parameter::EarlyDiffuseFeedback] = 0.7706999778747559;
ProgramDarkPlate[Parameter::EarlyDiffuseModAmount] = 0.143899992108345;
ProgramDarkPlate[Parameter::EarlyDiffuseModRate] = 0.2466999888420105;
ProgramDarkPlate[Parameter::EarlyOut] = 0.0;
ProgramDarkPlate[Parameter::EqCrossSeed] = 0.0;
ProgramDarkPlate[Parameter::EqCutoff] = 0.9759999513626099;
ProgramDarkPlate[Parameter::EqHighFreq] = 0.5133999586105347;
ProgramDarkPlate[Parameter::EqHighGain] = 0.7680000066757202;
ProgramDarkPlate[Parameter::EqHighShelfEnabled] = 1.0;
ProgramDarkPlate[Parameter::EqLowFreq] = 0.3879999816417694;
ProgramDarkPlate[Parameter::EqLowGain] = 0.5559999942779541;
ProgramDarkPlate[Parameter::EqLowShelfEnabled] = 0.0;
ProgramDarkPlate[Parameter::EqLowpassEnabled] = 0.0;
ProgramDarkPlate[Parameter::HighCut] = 0.2933000028133392;
ProgramDarkPlate[Parameter::HighCutEnabled] = 0.0;
ProgramDarkPlate[Parameter::InputMix] = 0.2346999943256378;
ProgramDarkPlate[Parameter::Interpolation] = 1.0;
ProgramDarkPlate[Parameter::LateDiffuseCount] = 0.4879999756813049;
ProgramDarkPlate[Parameter::LateDiffuseDelay] = 0.239999994635582;
ProgramDarkPlate[Parameter::LateDiffuseEnabled] = 1.0;
ProgramDarkPlate[Parameter::LateDiffuseFeedback] = 0.8506999611854553;
ProgramDarkPlate[Parameter::LateDiffuseModAmount] = 0.1467999964952469;
ProgramDarkPlate[Parameter::LateDiffuseModRate] = 0.1666999906301498;
ProgramDarkPlate[Parameter::LateLineCount] = 1.0;
ProgramDarkPlate[Parameter::LateLineDecay] = 0.6345999836921692;
ProgramDarkPlate[Parameter::LateLineModAmount] = 0.2719999849796295;
ProgramDarkPlate[Parameter::LateLineModRate] = 0.2292999923229218;
ProgramDarkPlate[Parameter::LateLineSize] = 0.4693999886512756;
ProgramDarkPlate[Parameter::LateMode] = 1.0;
ProgramDarkPlate[Parameter::LateOut] = 0.6613999605178833;
ProgramDarkPlate[Parameter::LowCut] = 0.6399999856948853;
ProgramDarkPlate[Parameter::LowCutEnabled] = 1.0;
ProgramDarkPlate[Parameter::SeedDelay] = 0.2180999964475632;
ProgramDarkPlate[Parameter::SeedDiffusion] = 0.1850000023841858;
ProgramDarkPlate[Parameter::SeedPostDiffusion] = 0.3652999997138977;
ProgramDarkPlate[Parameter::SeedTap] = 0.3339999914169312;
ProgramDarkPlate[Parameter::TapDecay] = 1.0;
ProgramDarkPlate[Parameter::TapLength] = 0.9866999983787537;
ProgramDarkPlate[Parameter::TapPredelay] = 0.0;
ProgramDarkPlate[Parameter::TapCount] = 0.1959999948740005;
ProgramDarkPlate[Parameter::TapEnabled] = 0.0;
}
}

View File

@@ -0,0 +1,40 @@
# Cloud Seed Core
This is the core DSP code of Cloud Seed 2.0
**You can download and purchase the plugin here:**
**https://ghostnoteaudio.uk/products/cloudseed**
The plugin itself is not open source, but the core reverb algorithm is.
This repository contains all the necessary code to integrate the Cloud Seed algorithm into your application.
# License
This code it MIT Licensed. In short:
* You can use this plugin for both free and commercial purposes.
* Please credit Ghost Note Audio as the original author of this code.
* You *can* use the name "Cloud Seed" in your marketing material, but please do so respectfully, and in a way that would not confuse potential customers. It should be clear that your product is NOT created by Ghost Note Audio, even if it does use our algorithm.
* I do reserve the right to ask you to please stop using the Cloud Seed name, should you not adhere to this.
* If you can, please drop us a line at support@ghostnoteaudio.uk - we love seeing what people are using our algorithms for, and we're usually happy to offer suggestions and advice!
# Build Instructions
This is a bare-bones console C++ program. It requires C++14 to compile.
The demo program creates an impulse and feeds it through the reverb. It's only meant to show how the API works, and how to call the necessary functions and pass in the right argument to get it working.
The output is written to a raw binary file, which you can open using Audacity:
* File -> Import -> Raw Data
* Encoding: 32-bit float
* Byte order: default endianness
* Channels: 1 Channel (mono)
* Sample Rate: 48000 Hz
## Preprocessor Definitions
BUFFER_SIZE=1024 (or whatever you want the maximum supported buffer size to be)
MAX_STR_SIZE=32 (maximum length of strings being formatted and returned)

314
src/cloudPedal.cpp Normal file
View File

@@ -0,0 +1,314 @@
#include <lv2.h>
#include "CloudSeedCore/DSP/ReverbController.h"
#include "CloudSeedCore/Parameters.h"
#include <cmath>
using namespace Cloudseed;
enum parameters{
SIZE,
BRIGHT,
DECAY,
DIFFUSION,
MIX
};
class cloudPedal
{
private:
// PORTS
float* audio_in_l_ptr;
float* audio_in_r_ptr;
float* audio_out_l_ptr;
float* audio_out_r_ptr;
float* size_ptr;
float* bright_ptr;
float* decay_ptr;
float* diffusion_ptr;
float* mix_ptr;
float params[5];
//REVERBSHIT
Cloudseed::ReverbController reverb;
float reverbProgram[Parameter::COUNT];
double samplerate;
void start();
void setParams();
public:
explicit cloudPedal(double samplerate);
~cloudPedal();
void connectPort(const uint32_t port, void* data_location);
void activate();
void run(const uint32_t sample_count);
void deactivate();
};
void cloudPedal::setParams(){
params[SIZE] = *size_ptr;
params[BRIGHT] = *bright_ptr;
params[DECAY] = *decay_ptr;
params[DIFFUSION] = *diffusion_ptr;
params[MIX] = *mix_ptr;
//constants
reverbProgram[Parameter::EarlyDiffuseEnabled] = 1.0;
reverbProgram[Parameter::LateDiffuseEnabled] = 1.0;
reverbProgram[Parameter::LateMode] = 1.0;
reverbProgram[Parameter::LateLineCount] = 1.0;
reverbProgram[Parameter::Interpolation] = 1.0;
reverbProgram[Parameter::TapEnabled] = 1.0;
reverbProgram[Parameter::LowCutEnabled] = 1.0;
reverbProgram[Parameter::HighCutEnabled] = 1;
reverbProgram[Parameter::HighCut] = 0.3;
reverbProgram[Parameter::EqLowShelfEnabled] = 0.0;
reverbProgram[Parameter::EqLowFreq] = 0.4;
reverbProgram[Parameter::EqLowGain] = 0.5;
reverbProgram[Parameter::EqCrossSeed] = 1.0;
reverbProgram[Parameter::SeedTap] = 0.33;
reverbProgram[Parameter::TapDecay] = 1.0;
reverbProgram[Parameter::TapLength] = 0.98;
reverbProgram[Parameter::TapPredelay] = 0.0;
// SIZE-------------------------------------------
float reverbSize = params[SIZE]/2;
float earlyScale = 0.5+(2-params[SIZE])/4;
float lateScale = 0.5+params[SIZE]/4;
//early
reverbProgram[Parameter::EarlyDiffuseDelay] = 0.1*(1-reverbSize);
reverbProgram[Parameter::EarlyDiffuseModAmount] = reverbSize/2;
reverbProgram[Parameter::EarlyDiffuseModRate] = 1*(1-reverbSize);
//late
reverbProgram[Parameter::LateDiffuseDelay] = 0.1*(1-reverbSize);
reverbProgram[Parameter::LateDiffuseModAmount] = reverbSize/2;
reverbProgram[Parameter::LateDiffuseModRate] = (1-reverbSize);
reverbProgram[Parameter::LateLineModAmount] = reverbSize/2;
reverbProgram[Parameter::LateLineModRate] = (1-reverbSize);
reverbProgram[Parameter::LateLineSize] = reverbSize;
//seed?
reverbProgram[Parameter::SeedDelay] = 0.5*(1-reverbSize);
//BRIGHT--------------------------------------
float low = params[BRIGHT]-1 < 0 ? 0 : params[BRIGHT]-1;
float high = params[BRIGHT] - 1 < 0 ? 1-params[BRIGHT] : 0;
reverbProgram[Parameter::EqHighShelfEnabled] = high==0 ? 0 : 1;
reverbProgram[Parameter::EqHighFreq] = high/2;
reverbProgram[Parameter::EqHighGain] = 3*high/8;
reverbProgram[Parameter::LowCut] = 0.3+high/4;
reverbProgram[Parameter::EqLowpassEnabled] = low==0 ? 1: 0;
reverbProgram[Parameter::EqCutoff] = low==0 ? 1 : 1-low;
//DIFFUSION---------------------------------
reverbProgram[Parameter::EarlyDiffuseCount] = params[DIFFUSION]/2;
reverbProgram[Parameter::LateDiffuseCount] = params[DIFFUSION]/2;
reverbProgram[Parameter::SeedDiffusion] = params[DIFFUSION]/4;
reverbProgram[Parameter::SeedPostDiffusion] = params[DIFFUSION]/3;
reverbProgram[Parameter::TapCount] = params[DIFFUSION]/5;
//DECAY----------------------------------------
reverbProgram[Parameter::EarlyDiffuseFeedback] = params[DECAY]/2;
reverbProgram[Parameter::LateDiffuseFeedback] = params[DECAY]/2;
reverbProgram[Parameter::LateLineDecay] = 3*params[DECAY]/8;
//MIX--------------------------------------------
float mix = 1-params[MIX]/2;
reverbProgram[Parameter::DryOut] = sqrt(mix);
reverbProgram[Parameter::EarlyOut] = sqrt(1-mix)*earlyScale/2;
reverbProgram[Parameter::LateOut] = sqrt(1-mix)*lateScale/2;
// -------------------------------------------------
for (int i = 0; i < Parameter::COUNT; i++)
{
reverb.SetParameter(i, reverbProgram[i]);
}
}
void cloudPedal::start()
{
int sr = (int) samplerate;
reverb.SetSamplerate(sr);
setParams();
reverb.ClearBuffers();
}
cloudPedal::cloudPedal(double samplerate_):
audio_in_l_ptr(nullptr),
audio_in_r_ptr(nullptr),
audio_out_l_ptr(nullptr),
audio_out_r_ptr(nullptr),
size_ptr(nullptr),
bright_ptr(nullptr),
diffusion_ptr(nullptr),
decay_ptr(nullptr),
mix_ptr(nullptr),
reverb(Cloudseed::ReverbController((int)samplerate_)),
samplerate(samplerate_)
{
}
cloudPedal::~cloudPedal()
{
}
void cloudPedal::connectPort(const uint32_t port, void* data_location)
{
switch(port)
{
case 0:
audio_in_l_ptr = (float*) data_location;
break;
case 1:
audio_in_r_ptr = (float*) data_location;
break;
case 2:
audio_out_l_ptr = (float*) data_location;
break;
case 3:
audio_out_r_ptr = (float*) data_location;
break;
case 4:
size_ptr = (float*) data_location;
break;
case 5:
bright_ptr = (float*) data_location;
break;
case 6:
decay_ptr = (float*) data_location;
break;
case 7:
diffusion_ptr = (float*) data_location;
break;
case 8:
mix_ptr = (float*) data_location;
break;
default:
break;
}
}
void cloudPedal::activate()
{
start();
}
void cloudPedal::deactivate()
{
}
void cloudPedal::run(const uint32_t sample_count)
{
if(
(params[0]!=*size_ptr)||
(params[1]!=*bright_ptr) ||
(params[2]!=*decay_ptr)||
(params[3]!=*diffusion_ptr)||
(params[4]!=*mix_ptr)
){
setParams();
}
reverb.Process(audio_in_l_ptr, audio_in_r_ptr, audio_out_l_ptr, audio_out_r_ptr, sample_count);
}
/* internal core methods */
static LV2_Handle instantiate (const struct LV2_Descriptor *descriptor, double sample_rate, const char *bundle_path, const LV2_Feature *const *features)
{
cloudPedal* m = new cloudPedal(sample_rate);
return m;
}
static void connect_port (LV2_Handle instance, uint32_t port, void *data_location)
{
cloudPedal* m = (cloudPedal*) instance;
if (m) m->connectPort(port, data_location);
}
static void activate (LV2_Handle instance)
{
cloudPedal* m = (cloudPedal*) instance;
if (m) m->activate();
}
static void run (LV2_Handle instance, uint32_t sample_count)
{
cloudPedal* m = (cloudPedal*) instance;
if (m) m->run(sample_count);
}
static void deactivate (LV2_Handle instance)
{
cloudPedal* m = (cloudPedal*) instance;
if (m) m->deactivate();
}
static void cleanup (LV2_Handle instance)
{
cloudPedal* m = (cloudPedal*) instance;
if (m) delete m;
}
static const void* extension_data (const char *uri)
{
return NULL;
}
/* descriptor */
static LV2_Descriptor const descriptor =
{
"https://git.esca111.xyz/ESCA111/cloudPedal",
instantiate,
connect_port,
activate /* or NULL */,
run,
deactivate /* or NULL */,
cleanup,
extension_data /* or NULL */
};
/* interface */
LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index)
{
if (index == 0) return &descriptor;
else return NULL;
}