├── .gitattributes ├── .gitignore ├── CloudSeedCore.sln ├── CloudSeedCore.vcxproj ├── CloudSeedCore.vcxproj.filters ├── DSP ├── AllpassDiffuser.h ├── Biquad.cpp ├── Biquad.h ├── DelayLine.h ├── Hp1.h ├── LcgRandom.h ├── Lp1.h ├── ModulatedAllpass.h ├── ModulatedDelay.h ├── MultitapDelay.h ├── RandomBuffer.cpp ├── RandomBuffer.h ├── ReverbChannel.h ├── ReverbController.h └── Utils.h ├── LICENSE ├── Parameters.cpp ├── Parameters.h ├── PluginProcessor.cpp ├── Programs.h └── Readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | *.obj 3 | *.exe 4 | *.pdb 5 | *.user 6 | *.aps 7 | *.pch 8 | *.vspscc 9 | *_i.c 10 | *_p.c 11 | *.ncb 12 | *.suo 13 | *.sln.docstates 14 | *.tlb 15 | *.tlh 16 | *.bak 17 | *.cache 18 | *.ilk 19 | *.log 20 | *.sdf 21 | *.vsp 22 | *.opensdf 23 | *.o 24 | [Bb]in 25 | [Dd]ebug*/ 26 | test-results/ 27 | *.lib 28 | *.sbr 29 | obj/ 30 | intermediate*/ 31 | [Rr]elease*/ 32 | _ReSharper*/ 33 | packages*/ 34 | [Tt]est[Rr]esult* 35 | Build/* 36 | Releases/* 37 | .vs/* 38 | **/.vs/* 39 | **/.DS_Store 40 | !VC_redist.x64.exe 41 | outputLeft.bin 42 | outputRight.bin -------------------------------------------------------------------------------- /CloudSeedCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34221.43 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloudSeedCore", "CloudSeedCore.vcxproj", "{7EC3FD43-1E45-4046-BEDF-33861EF49015}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Debug|x64.ActiveCfg = Debug|x64 17 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Debug|x64.Build.0 = Debug|x64 18 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Debug|x86.ActiveCfg = Debug|Win32 19 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Debug|x86.Build.0 = Debug|Win32 20 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Release|x64.ActiveCfg = Release|x64 21 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Release|x64.Build.0 = Release|x64 22 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Release|x86.ActiveCfg = Release|Win32 23 | {7EC3FD43-1E45-4046-BEDF-33861EF49015}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {984BFEEF-D2E4-4A1A-968E-CD3044280064} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CloudSeedCore.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {7ec3fd43-1e45-4046-bedf-33861ef49015} 25 | CloudSeedCore 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | 80 | 81 | Console 82 | true 83 | 84 | 85 | 86 | 87 | Level3 88 | true 89 | true 90 | true 91 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | 94 | 95 | Console 96 | true 97 | true 98 | true 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | _DEBUG;_CONSOLE;BUFFER_SIZE=1024;MAX_STR_SIZE=32;%(PreprocessorDefinitions) 106 | true 107 | 108 | 109 | Console 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | true 118 | true 119 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | true 121 | 122 | 123 | Console 124 | true 125 | true 126 | true 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /CloudSeedCore.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {d1a62df7-fbd4-4696-b69e-0dfb810054a1} 18 | 19 | 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files\DSP 29 | 30 | 31 | Source Files\DSP 32 | 33 | 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files\DSP 40 | 41 | 42 | Source Files\DSP 43 | 44 | 45 | Source Files\DSP 46 | 47 | 48 | Source Files\DSP 49 | 50 | 51 | Source Files\DSP 52 | 53 | 54 | Source Files\DSP 55 | 56 | 57 | Source Files\DSP 58 | 59 | 60 | Source Files\DSP 61 | 62 | 63 | Source Files\DSP 64 | 65 | 66 | Source Files\DSP 67 | 68 | 69 | Source Files\DSP 70 | 71 | 72 | Source Files\DSP 73 | 74 | 75 | Source Files\DSP 76 | 77 | 78 | Source Files 79 | 80 | 81 | -------------------------------------------------------------------------------- /DSP/AllpassDiffuser.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include "ModulatedAllpass.h" 27 | #include "RandomBuffer.h" 28 | 29 | namespace Cloudseed 30 | { 31 | class AllpassDiffuser 32 | { 33 | public: 34 | static const int MaxStageCount = 12; 35 | 36 | private: 37 | int samplerate; 38 | 39 | ModulatedAllpass filters[MaxStageCount]; 40 | int delay; 41 | float modRate; 42 | std::vector seedValues; 43 | int seed; 44 | float crossSeed; 45 | 46 | public: 47 | int Stages; 48 | 49 | AllpassDiffuser() 50 | { 51 | crossSeed = 0.0; 52 | seed = 23456; 53 | UpdateSeeds(); 54 | Stages = 1; 55 | 56 | SetSamplerate(48000); 57 | } 58 | 59 | int GetSamplerate() 60 | { 61 | return samplerate; 62 | } 63 | 64 | void SetSamplerate(int samplerate) 65 | { 66 | this->samplerate = samplerate; 67 | SetModRate(modRate); 68 | } 69 | 70 | void SetSeed(int seed) 71 | { 72 | this->seed = seed; 73 | UpdateSeeds(); 74 | } 75 | 76 | void SetCrossSeed(float crossSeed) 77 | { 78 | this->crossSeed = crossSeed; 79 | UpdateSeeds(); 80 | } 81 | 82 | 83 | bool GetModulationEnabled() 84 | { 85 | return filters[0].ModulationEnabled; 86 | } 87 | 88 | void SetModulationEnabled(bool value) 89 | { 90 | for (int i = 0; i < MaxStageCount; i++) 91 | filters[i].ModulationEnabled = value; 92 | 93 | } 94 | 95 | void SetInterpolationEnabled(bool enabled) 96 | { 97 | for (int i = 0; i < MaxStageCount; i++) 98 | filters[i].InterpolationEnabled = enabled; 99 | } 100 | 101 | void SetDelay(int delaySamples) 102 | { 103 | delay = delaySamples; 104 | Update(); 105 | } 106 | 107 | void SetFeedback(float feedback) 108 | { 109 | for (int i = 0; i < MaxStageCount; i++) 110 | filters[i].Feedback = feedback; 111 | } 112 | 113 | void SetModAmount(float amount) 114 | { 115 | for (int i = 0; i < MaxStageCount; i++) 116 | filters[i].ModAmount = amount * (0.85 + 0.3 * seedValues[MaxStageCount + i]); 117 | } 118 | 119 | void SetModRate(float rate) 120 | { 121 | modRate = rate; 122 | 123 | for (int i = 0; i < MaxStageCount; i++) 124 | filters[i].ModRate = rate * (0.85 + 0.3 * seedValues[MaxStageCount * 2 + i]) / samplerate; 125 | } 126 | 127 | void Process(float* input, float* output, int bufSize) 128 | { 129 | float tempBuffer[BUFFER_SIZE]; 130 | 131 | filters[0].Process(input, tempBuffer, bufSize); 132 | 133 | for (int i = 1; i < Stages; i++) 134 | filters[i].Process(tempBuffer, tempBuffer, bufSize); 135 | 136 | Utils::Copy(output, tempBuffer, bufSize); 137 | } 138 | 139 | void ClearBuffers() 140 | { 141 | for (int i = 0; i < MaxStageCount; i++) 142 | filters[i].ClearBuffers(); 143 | } 144 | 145 | private: 146 | void Update() 147 | { 148 | for (int i = 0; i < MaxStageCount; i++) 149 | { 150 | auto r = seedValues[i]; 151 | auto d = std::pow(10, r) * 0.1; // 0.1 ... 1.0 152 | filters[i].SampleDelay = (int)(delay * d); 153 | } 154 | } 155 | 156 | void UpdateSeeds() 157 | { 158 | this->seedValues = RandomBuffer::Generate(seed, MaxStageCount * 3, crossSeed); 159 | Update(); 160 | } 161 | 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /DSP/Biquad.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include "Biquad.h" 24 | #define _USE_MATH_DEFINES 25 | #include "math.h" 26 | 27 | namespace Cloudseed 28 | { 29 | Biquad::Biquad() 30 | { 31 | ClearBuffers(); 32 | } 33 | 34 | Biquad::Biquad(FilterType filterType, float fs) 35 | { 36 | Type = filterType; 37 | SetSamplerate(fs); 38 | 39 | SetGainDb(0.0f); 40 | Frequency = (float)(fs * 0.25f); 41 | SetQ(0.5f); 42 | ClearBuffers(); 43 | } 44 | 45 | Biquad::~Biquad() 46 | { 47 | 48 | } 49 | 50 | 51 | float Biquad::GetSamplerate() 52 | { 53 | return fs; 54 | } 55 | 56 | void Biquad::SetSamplerate(float fs) 57 | { 58 | this->fs = fs; 59 | fsInv = 1.0f / fs; 60 | Update(); 61 | } 62 | 63 | float Biquad::GetGainDb() 64 | { 65 | return gainDB; 66 | } 67 | 68 | float Biquad::GetGain() 69 | { 70 | return gain; 71 | } 72 | 73 | void Biquad::SetGainDb (float value) 74 | { 75 | // Clamp value between -60 and 60 76 | if (value < -60) 77 | value = -60; 78 | else if (value > 60) 79 | value = 60; 80 | 81 | gainDB = value; 82 | gain = powf (10.0f, value / 20.0f); 83 | } 84 | 85 | void Biquad::SetGain (float value) 86 | { 87 | if (value < 0.001f) 88 | value = 0.001f; // -60dB 89 | else if (value > 1000.0f) 90 | value = 1000.0f; // 60dB 91 | 92 | gain = value; 93 | gainDB = log10f (gain) * 20; 94 | } 95 | 96 | float Biquad::GetQ() 97 | { 98 | return q; 99 | } 100 | 101 | void Biquad::SetQ(float value) 102 | { 103 | if (value < 0.001f) 104 | value = 0.001f; 105 | q = value; 106 | } 107 | 108 | // this is the newer set of formulas from http://www.earlevel.com/main/2011/01/02/biquad-formulas/ 109 | // Note that for shelf and peak filters, I had to invert the if/else statements for boost and cut, as 110 | // I was getting the inverse desired effect, very odd... 111 | void Biquad::Update() 112 | { 113 | auto Fc = Frequency; 114 | //auto Fs = fs; 115 | 116 | auto V = powf(10, fabsf(gainDB) / 20.0f); 117 | //auto K = tanf(M_PI * Fc / Fs); 118 | auto K = tanf(M_PI * Fc * fsInv); 119 | auto Q = q; 120 | double norm = 1.0; 121 | 122 | switch (Type) 123 | { 124 | case FilterType::LowPass6db: 125 | a1 = -expf(-2.0 * M_PI * (Fc * fsInv)); 126 | b0 = 1.0 + a1; 127 | b1 = b2 = a2 = 0; 128 | break; 129 | case FilterType::HighPass6db: 130 | a1 = -expf(-2.0 * M_PI * (Fc * fsInv)); 131 | b0 = a1; 132 | b1 = -a1; 133 | b2 = a2 = 0; 134 | break; 135 | case FilterType::LowPass: 136 | norm = 1 / (1 + K / Q + K * K); 137 | b0 = K * K * norm; 138 | b1 = 2 * b0; 139 | b2 = b0; 140 | a1 = 2 * (K * K - 1) * norm; 141 | a2 = (1 - K / Q + K * K) * norm; 142 | break; 143 | case FilterType::HighPass: 144 | norm = 1 / (1 + K / Q + K * K); 145 | b0 = 1 * norm; 146 | b1 = -2 * b0; 147 | b2 = b0; 148 | a1 = 2 * (K * K - 1) * norm; 149 | a2 = (1 - K / Q + K * K) * norm; 150 | break; 151 | case FilterType::BandPass: 152 | norm = 1 / (1 + K / Q + K * K); 153 | b0 = K / Q * norm; 154 | b1 = 0; 155 | b2 = -b0; 156 | a1 = 2 * (K * K - 1) * norm; 157 | a2 = (1 - K / Q + K * K) * norm; 158 | break; 159 | case FilterType::Notch: 160 | norm = 1 / (1 + K / Q + K * K); 161 | b0 = (1 + K * K) * norm; 162 | b1 = 2 * (K * K - 1) * norm; 163 | b2 = b0; 164 | a1 = b1; 165 | a2 = (1 - K / Q + K * K) * norm; 166 | break; 167 | case FilterType::Peak: 168 | if (gainDB >= 0) 169 | { 170 | norm = 1 / (1 + 1 / Q * K + K * K); 171 | b0 = (1 + V / Q * K + K * K) * norm; 172 | b1 = 2 * (K * K - 1) * norm; 173 | b2 = (1 - V / Q * K + K * K) * norm; 174 | a1 = b1; 175 | a2 = (1 - 1 / Q * K + K * K) * norm; 176 | } 177 | else 178 | { 179 | norm = 1 / (1 + V / Q * K + K * K); 180 | b0 = (1 + 1 / Q * K + K * K) * norm; 181 | b1 = 2 * (K * K - 1) * norm; 182 | b2 = (1 - 1 / Q * K + K * K) * norm; 183 | a1 = b1; 184 | a2 = (1 - V / Q * K + K * K) * norm; 185 | } 186 | break; 187 | case FilterType::LowShelf: 188 | if (gainDB >= 0) 189 | { 190 | norm = 1 / (1 + sqrtf(2) * K + K * K); 191 | b0 = (1 + sqrtf(2 * V) * K + V * K * K) * norm; 192 | b1 = 2 * (V * K * K - 1) * norm; 193 | b2 = (1 - sqrtf(2 * V) * K + V * K * K) * norm; 194 | a1 = 2 * (K * K - 1) * norm; 195 | a2 = (1 - sqrtf(2) * K + K * K) * norm; 196 | } 197 | else 198 | { 199 | norm = 1 / (1 + sqrtf(2 * V) * K + V * K * K); 200 | b0 = (1 + sqrtf(2) * K + K * K) * norm; 201 | b1 = 2 * (K * K - 1) * norm; 202 | b2 = (1 - sqrtf(2) * K + K * K) * norm; 203 | a1 = 2 * (V * K * K - 1) * norm; 204 | a2 = (1 - sqrtf(2 * V) * K + V * K * K) * norm; 205 | } 206 | break; 207 | case FilterType::HighShelf: 208 | if (gainDB >= 0) 209 | { 210 | norm = 1 / (1 + sqrtf(2) * K + K * K); 211 | b0 = (V + sqrtf(2 * V) * K + K * K) * norm; 212 | b1 = 2 * (K * K - V) * norm; 213 | b2 = (V - sqrtf(2 * V) * K + K * K) * norm; 214 | a1 = 2 * (K * K - 1) * norm; 215 | a2 = (1 - sqrtf(2) * K + K * K) * norm; 216 | } 217 | else 218 | { 219 | norm = 1 / (V + sqrtf(2 * V) * K + K * K); 220 | b0 = (1 + sqrtf(2) * K + K * K) * norm; 221 | b1 = 2 * (K * K - 1) * norm; 222 | b2 = (1 - sqrtf(2) * K + K * K) * norm; 223 | a1 = 2 * (K * K - V) * norm; 224 | a2 = (V - sqrtf(2 * V) * K + K * K) * norm; 225 | } 226 | break; 227 | } 228 | } 229 | 230 | double Biquad::GetResponse(float freq) const 231 | { 232 | double phi = powf((sinf(2 * M_PI * freq / (2.0 * fs))), 2); 233 | 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)); 234 | // y gives you power gain, not voltage gain, and this a 10 * log_10(g) formula instead of 20 * log_10(g) 235 | // by taking the sqrt we get a value that's more suitable for signal processing, i.e. the voltage gain 236 | return sqrtf(y); 237 | } 238 | 239 | void Biquad::ClearBuffers() 240 | { 241 | y = 0; 242 | x2 = 0; 243 | y2 = 0; 244 | x1 = 0; 245 | y1 = 0; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /DSP/Biquad.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | namespace Cloudseed 26 | { 27 | class Biquad 28 | { 29 | public: 30 | enum class FilterType 31 | { 32 | LowPass6db = 0, 33 | HighPass6db, 34 | LowPass, 35 | HighPass, 36 | BandPass, 37 | Notch, 38 | Peak, 39 | LowShelf, 40 | HighShelf 41 | }; 42 | 43 | private: 44 | float fs; 45 | float fsInv; 46 | float gainDB; 47 | float q; 48 | float a0, a1, a2, b0, b1, b2; 49 | float x1, x2, y, y1, y2; 50 | float gain; 51 | 52 | public: 53 | FilterType Type; 54 | float Output; 55 | float Frequency; 56 | 57 | Biquad(); 58 | Biquad(FilterType filterType, float fs); 59 | ~Biquad(); 60 | 61 | float GetSamplerate(); 62 | void SetSamplerate(float fs); 63 | float GetGainDb(); 64 | void SetGainDb(float value); 65 | float GetGain(); 66 | void SetGain(float value); 67 | float GetQ(); 68 | void SetQ(float value); 69 | 70 | void Update(); 71 | 72 | double GetResponse(float freq) const; 73 | 74 | float inline Process(float x) 75 | { 76 | y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; 77 | x2 = x1; 78 | y2 = y1; 79 | x1 = x; 80 | y1 = y; 81 | 82 | Output = y; 83 | return Output; 84 | } 85 | 86 | void inline Process(float* input, float* output, int len) 87 | { 88 | for (int i = 0; i < len; i++) 89 | { 90 | float x = input[i]; 91 | y = ((b0 * x) + (b1 * x1) + (b2 * x2)) - (a1 * y1) - (a2 * y2); 92 | x2 = x1; 93 | y2 = y1; 94 | x1 = x; 95 | y1 = y; 96 | 97 | output[i] = y; 98 | } 99 | 100 | Output = y; 101 | } 102 | 103 | void ClearBuffers(); 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /DSP/DelayLine.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "Lp1.h" 26 | #include "ModulatedDelay.h" 27 | #include "AllpassDiffuser.h" 28 | #include "Biquad.h" 29 | 30 | namespace Cloudseed 31 | { 32 | template 33 | class CircularBuffer 34 | { 35 | float buffer[N]; 36 | int idxRead; 37 | int idxWrite; 38 | int count; 39 | public: 40 | CircularBuffer() 41 | { 42 | Reset(); 43 | } 44 | 45 | void Reset() 46 | { 47 | for (int i = 0; i < N; i++) 48 | buffer[i] = 0.0f; 49 | idxRead = 0; 50 | idxWrite = 0; 51 | count = 0; 52 | } 53 | 54 | int GetCount() 55 | { 56 | return count; 57 | } 58 | 59 | int PushZeros(float* data, int bufSize) 60 | { 61 | int countBefore = count; 62 | for (int i = 0; i < bufSize; i++) 63 | { 64 | buffer[idxWrite] = 0.0f; 65 | idxWrite = (idxWrite + 1) % N; 66 | count++; 67 | if (count >= N) 68 | break; // overflow 69 | } 70 | 71 | return count - countBefore; 72 | } 73 | 74 | int Push(float* data, int bufSize) 75 | { 76 | int countBefore = count; 77 | for (int i = 0; i < bufSize; i++) 78 | { 79 | buffer[idxWrite] = data[i]; 80 | idxWrite = (idxWrite + 1) % N; 81 | count++; 82 | if (count >= N) 83 | break; // overflow 84 | } 85 | 86 | return count - countBefore; 87 | } 88 | 89 | int Pop(float* destination, int bufSize) 90 | { 91 | int countBefore = count; 92 | for (int i = 0; i < bufSize; i++) 93 | { 94 | if (count > 0) 95 | { 96 | destination[i] = buffer[idxRead]; 97 | idxRead = (idxRead + 1) % N; 98 | count--; 99 | } 100 | else 101 | { 102 | destination[i] = 0.0f; 103 | } 104 | } 105 | 106 | return countBefore - count; 107 | } 108 | 109 | }; 110 | 111 | class DelayLine 112 | { 113 | private: 114 | ModulatedDelay delay; 115 | AllpassDiffuser diffuser; 116 | Biquad lowShelf; 117 | Biquad highShelf; 118 | Lp1 lowPass; 119 | CircularBuffer<2*BUFFER_SIZE> feedbackBuffer; 120 | float feedback; 121 | 122 | public: 123 | bool DiffuserEnabled; 124 | bool LowShelfEnabled; 125 | bool HighShelfEnabled; 126 | bool CutoffEnabled; 127 | bool TapPostDiffuser; 128 | 129 | DelayLine() : 130 | lowShelf(Biquad::FilterType::LowShelf, 48000), 131 | highShelf(Biquad::FilterType::HighShelf, 48000) 132 | { 133 | feedback = 0; 134 | 135 | lowShelf.SetGainDb(-20); 136 | lowShelf.Frequency = 20; 137 | 138 | highShelf.SetGainDb(-20); 139 | highShelf.Frequency = 19000; 140 | 141 | lowPass.SetCutoffHz(1000); 142 | lowShelf.Update(); 143 | highShelf.Update(); 144 | SetSamplerate(48000); 145 | SetDiffuserSeed(1, 0.0); 146 | } 147 | 148 | void SetSamplerate(int samplerate) 149 | { 150 | diffuser.SetSamplerate(samplerate); 151 | lowPass.SetSamplerate(samplerate); 152 | lowShelf.SetSamplerate(samplerate); 153 | highShelf.SetSamplerate(samplerate); 154 | } 155 | 156 | void SetDiffuserSeed(int seed, float crossSeed) 157 | { 158 | diffuser.SetSeed(seed); 159 | diffuser.SetCrossSeed(crossSeed); 160 | } 161 | 162 | void SetDelay(int delaySamples) 163 | { 164 | delay.SampleDelay = delaySamples; 165 | } 166 | 167 | void SetFeedback(float feedb) 168 | { 169 | feedback = feedb; 170 | } 171 | 172 | void SetDiffuserDelay(int delaySamples) 173 | { 174 | diffuser.SetDelay(delaySamples); 175 | } 176 | 177 | void SetDiffuserFeedback(float feedb) 178 | { 179 | diffuser.SetFeedback(feedb); 180 | } 181 | 182 | void SetDiffuserStages(int stages) 183 | { 184 | diffuser.Stages = stages; 185 | } 186 | 187 | void SetLowShelfGain(float gainDb) 188 | { 189 | lowShelf.SetGainDb(gainDb); 190 | lowShelf.Update(); 191 | } 192 | 193 | void SetLowShelfFrequency(float frequency) 194 | { 195 | lowShelf.Frequency = frequency; 196 | lowShelf.Update(); 197 | } 198 | 199 | void SetHighShelfGain(float gainDb) 200 | { 201 | highShelf.SetGainDb(gainDb); 202 | highShelf.Update(); 203 | } 204 | 205 | void SetHighShelfFrequency(float frequency) 206 | { 207 | highShelf.Frequency = frequency; 208 | highShelf.Update(); 209 | } 210 | 211 | void SetCutoffFrequency(float frequency) 212 | { 213 | lowPass.SetCutoffHz(frequency); 214 | } 215 | 216 | void SetLineModAmount(float amount) 217 | { 218 | delay.ModAmount = amount; 219 | } 220 | 221 | void SetLineModRate(float rate) 222 | { 223 | delay.ModRate = rate; 224 | } 225 | 226 | void SetDiffuserModAmount(float amount) 227 | { 228 | diffuser.SetModulationEnabled(amount > 0.0); 229 | diffuser.SetModAmount(amount); 230 | } 231 | 232 | void SetDiffuserModRate(float rate) 233 | { 234 | diffuser.SetModRate(rate); 235 | } 236 | 237 | void SetInterpolationEnabled(bool value) 238 | { 239 | diffuser.SetInterpolationEnabled(value); 240 | } 241 | 242 | void Process(float* input, float* output, int bufSize) 243 | { 244 | float tempBuffer[BUFFER_SIZE]; 245 | feedbackBuffer.Pop(tempBuffer, bufSize); 246 | 247 | for (int i = 0; i < bufSize; i++) 248 | tempBuffer[i] = input[i] + tempBuffer[i] * feedback; 249 | 250 | delay.Process(tempBuffer, tempBuffer, bufSize); 251 | 252 | if (!TapPostDiffuser) 253 | Utils::Copy(output, tempBuffer, bufSize); 254 | if (DiffuserEnabled) 255 | diffuser.Process(tempBuffer, tempBuffer, bufSize); 256 | if (LowShelfEnabled) 257 | lowShelf.Process(tempBuffer, tempBuffer, bufSize); 258 | if (HighShelfEnabled) 259 | highShelf.Process(tempBuffer, tempBuffer, bufSize); 260 | if (CutoffEnabled) 261 | lowPass.Process(tempBuffer, tempBuffer, bufSize); 262 | 263 | feedbackBuffer.Push(tempBuffer, bufSize); 264 | 265 | if (TapPostDiffuser) 266 | Utils::Copy(output, tempBuffer, bufSize); 267 | } 268 | 269 | void ClearDiffuserBuffer() 270 | { 271 | diffuser.ClearBuffers(); 272 | } 273 | 274 | void ClearBuffers() 275 | { 276 | delay.ClearBuffers(); 277 | diffuser.ClearBuffers(); 278 | lowShelf.ClearBuffers(); 279 | highShelf.ClearBuffers(); 280 | lowPass.Output = 0; 281 | feedbackBuffer.Reset(); 282 | } 283 | }; 284 | } 285 | -------------------------------------------------------------------------------- /DSP/Hp1.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #define _USE_MATH_DEFINES 26 | #include 27 | 28 | namespace Cloudseed 29 | { 30 | class Hp1 31 | { 32 | private: 33 | float fs; 34 | float b0, a1; 35 | float lpOut; 36 | float cutoffHz; 37 | 38 | public: 39 | float Output; 40 | 41 | Hp1() 42 | { 43 | fs = 48000; 44 | b0 = 1; 45 | a1 = 0; 46 | lpOut = 0.0; 47 | cutoffHz = 100; 48 | } 49 | 50 | float GetSamplerate() 51 | { 52 | return fs; 53 | } 54 | 55 | void SetSamplerate(float samplerate) 56 | { 57 | fs = samplerate; 58 | } 59 | 60 | float GetCutoffHz() 61 | { 62 | return cutoffHz; 63 | } 64 | 65 | void SetCutoffHz(float hz) 66 | { 67 | cutoffHz = hz; 68 | Update(); 69 | } 70 | 71 | void ClearBuffers() 72 | { 73 | lpOut = 0; 74 | Output = 0; 75 | } 76 | 77 | void Update() 78 | { 79 | // Prevent going over the Nyquist frequency 80 | if (cutoffHz >= fs * 0.5f) 81 | cutoffHz = fs * 0.499f; 82 | 83 | auto x = 2.0f * M_PI * cutoffHz / fs; 84 | auto nn = (2.0f - cosf(x)); 85 | auto alpha = nn - sqrtf(nn * nn - 1); 86 | 87 | a1 = alpha; 88 | b0 = 1 - alpha; 89 | } 90 | 91 | float Process(float input) 92 | { 93 | if (input == 0 && lpOut < 0.000001f) 94 | { 95 | Output = 0; 96 | } 97 | else 98 | { 99 | lpOut = b0 * input + a1 * lpOut; 100 | Output = input - lpOut; 101 | } 102 | 103 | return Output; 104 | } 105 | 106 | void Process(float* input, float* output, int len) 107 | { 108 | for (int i = 0; i < len; i++) 109 | output[i] = Process(input[i]); 110 | } 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /DSP/LcgRandom.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | namespace Cloudseed 28 | { 29 | class LcgRandom 30 | { 31 | private: 32 | uint64_t x; 33 | uint64_t a; 34 | uint64_t c; 35 | 36 | double doubleInv; 37 | float floatUintInv; 38 | float floatIntInv; 39 | 40 | public: 41 | inline LcgRandom(uint64_t seed = 0) 42 | { 43 | x = seed; 44 | a = 22695477; 45 | c = 1; 46 | 47 | doubleInv = 1.0 / (double)UINT32_MAX; 48 | floatUintInv = 1.0 / (float)UINT32_MAX; 49 | floatIntInv = 1.0 / (float)INT32_MAX; 50 | } 51 | 52 | inline void SetSeed(uint64_t seed) 53 | { 54 | x = seed; 55 | } 56 | 57 | inline uint32_t NextUInt() 58 | { 59 | uint64_t axc = a * x + c; 60 | //x = axc % m; 61 | x = axc & 0xFFFFFFFF; 62 | return (uint32_t)x; 63 | } 64 | 65 | inline int32_t NextInt() 66 | { 67 | int64_t axc = a * x + c; 68 | //x = axc % m; 69 | x = axc & 0x7FFFFFFF; 70 | return (int32_t)x; 71 | } 72 | 73 | inline double NextDouble() 74 | { 75 | auto n = NextUInt(); 76 | return n * doubleInv; 77 | } 78 | 79 | inline float NextFloat() 80 | { 81 | auto n = NextInt(); 82 | return n * floatIntInv; 83 | } 84 | 85 | inline void GetFloats(float* buffer, int len) 86 | { 87 | for (int i = 0; i < len; i++) 88 | buffer[i] = NextFloat(); 89 | } 90 | 91 | inline void GetFloatsBipolar(float* buffer, int len) 92 | { 93 | for (int i = 0; i < len; i++) 94 | buffer[i] = NextFloat() * 2 - 1; 95 | } 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /DSP/Lp1.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #define _USE_MATH_DEFINES 26 | #include 27 | 28 | namespace Cloudseed 29 | { 30 | class Lp1 31 | { 32 | private: 33 | float fs; 34 | float b0, a1; 35 | float cutoffHz; 36 | 37 | public: 38 | float Output; 39 | 40 | Lp1() 41 | { 42 | fs = 48000; 43 | b0 = 1; 44 | a1 = 0; 45 | cutoffHz = 1000; 46 | } 47 | 48 | float GetSamplerate() 49 | { 50 | return fs; 51 | } 52 | 53 | void SetSamplerate(float samplerate) 54 | { 55 | fs = samplerate; 56 | } 57 | 58 | float GetCutoffHz() 59 | { 60 | return cutoffHz; 61 | } 62 | 63 | void SetCutoffHz(float hz) 64 | { 65 | cutoffHz = hz; 66 | Update(); 67 | } 68 | 69 | void ClearBuffers() 70 | { 71 | Output = 0; 72 | } 73 | 74 | void Update() 75 | { 76 | // Prevent going over the Nyquist frequency 77 | if (cutoffHz >= fs * 0.5f) 78 | cutoffHz = fs * 0.499f; 79 | 80 | auto x = 2.0f * M_PI * cutoffHz / fs; 81 | auto nn = (2.0f - cosf(x)); 82 | auto alpha = nn - sqrtf(nn * nn - 1); 83 | 84 | a1 = alpha; 85 | b0 = 1 - alpha; 86 | } 87 | 88 | float Process(float input) 89 | { 90 | if (input == 0 && Output < 0.0000001f) 91 | { 92 | Output = 0; 93 | } 94 | else 95 | { 96 | Output = b0 * input + a1 * Output; 97 | } 98 | return Output; 99 | } 100 | 101 | void Process(float* input, float* output, int len) 102 | { 103 | for (int i = 0; i < len; i++) 104 | output[i] = Process(input[i]); 105 | } 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /DSP/ModulatedAllpass.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "ModulatedAllpass.h" 26 | #include "Utils.h" 27 | #include 28 | 29 | namespace Cloudseed 30 | { 31 | class ModulatedAllpass 32 | { 33 | public: 34 | static const int DelayBufferSize = 19200; // 100ms at 192Khz 35 | static const int ModulationUpdateRate = 8; 36 | 37 | private: 38 | float delayBuffer[DelayBufferSize] = { 0 }; 39 | int index; 40 | uint64_t samplesProcessed; 41 | 42 | float modPhase; 43 | int delayA; 44 | int delayB; 45 | float gainA; 46 | float gainB; 47 | 48 | public: 49 | 50 | int SampleDelay; 51 | float Feedback; 52 | float ModAmount; 53 | float ModRate; 54 | 55 | bool InterpolationEnabled; 56 | bool ModulationEnabled; 57 | 58 | ModulatedAllpass() 59 | { 60 | index = DelayBufferSize - 1; 61 | samplesProcessed = 0; 62 | 63 | modPhase = 0.01 + 0.98 * std::rand() / (float)RAND_MAX; 64 | delayA = 0; 65 | delayB = 0; 66 | gainA = 0; 67 | gainB = 0; 68 | 69 | SampleDelay = 100; 70 | Feedback = 0.5; 71 | ModAmount = 0.0; 72 | ModRate = 0.0; 73 | 74 | InterpolationEnabled = true; 75 | ModulationEnabled = true; 76 | Update(); 77 | } 78 | 79 | void ClearBuffers() 80 | { 81 | Utils::ZeroBuffer(delayBuffer, DelayBufferSize); 82 | } 83 | 84 | void Process(float* input, float* output, int sampleCount) 85 | { 86 | if (ModulationEnabled) 87 | ProcessWithMod(input, output, sampleCount); 88 | else 89 | ProcessNoMod(input, output, sampleCount); 90 | } 91 | 92 | private: 93 | void ProcessNoMod(float* input, float* output, int sampleCount) 94 | { 95 | auto delayedIndex = index - SampleDelay; 96 | if (delayedIndex < 0) delayedIndex += DelayBufferSize; 97 | 98 | for (int i = 0; i < sampleCount; i++) 99 | { 100 | auto bufOut = delayBuffer[delayedIndex]; 101 | auto inVal = input[i] + bufOut * Feedback; 102 | 103 | delayBuffer[index] = inVal; 104 | output[i] = bufOut - inVal * Feedback; 105 | 106 | index++; 107 | delayedIndex++; 108 | if (index >= DelayBufferSize) index -= DelayBufferSize; 109 | if (delayedIndex >= DelayBufferSize) delayedIndex -= DelayBufferSize; 110 | samplesProcessed++; 111 | } 112 | } 113 | 114 | void ProcessWithMod(float* input, float* output, int sampleCount) 115 | { 116 | for (int i = 0; i < sampleCount; i++) 117 | { 118 | if (samplesProcessed >= ModulationUpdateRate) 119 | { 120 | Update(); 121 | samplesProcessed = 0; 122 | } 123 | 124 | float bufOut; 125 | 126 | if (InterpolationEnabled) 127 | { 128 | int idxA = index - delayA; 129 | int idxB = index - delayB; 130 | idxA += DelayBufferSize * (idxA < 0); // modulo 131 | idxB += DelayBufferSize * (idxB < 0); // modulo 132 | 133 | bufOut = delayBuffer[idxA] * gainA + delayBuffer[idxB] * gainB; 134 | } 135 | else 136 | { 137 | int idxA = index - delayA; 138 | idxA += DelayBufferSize * (idxA < 0); // modulo 139 | bufOut = delayBuffer[idxA]; 140 | } 141 | 142 | auto inVal = input[i] + bufOut * Feedback; 143 | delayBuffer[index] = inVal; 144 | output[i] = bufOut - inVal * Feedback; 145 | 146 | index++; 147 | if (index >= DelayBufferSize) index -= DelayBufferSize; 148 | samplesProcessed++; 149 | } 150 | } 151 | 152 | inline float Get(int delay) 153 | { 154 | int idx = index - delay; 155 | if (idx < 0) 156 | idx += DelayBufferSize; 157 | 158 | return delayBuffer[idx]; 159 | } 160 | 161 | void Update() 162 | { 163 | modPhase += ModRate * ModulationUpdateRate; 164 | if (modPhase > 1) 165 | modPhase = std::fmod(modPhase, 1.0); 166 | 167 | auto mod = std::sinf(modPhase * 2 * M_PI); 168 | 169 | if (ModAmount >= SampleDelay) // don't modulate to negative value 170 | ModAmount = SampleDelay - 1; 171 | 172 | 173 | auto totalDelay = SampleDelay + ModAmount * mod; 174 | 175 | if (totalDelay <= 0) // should no longer be required 176 | totalDelay = 1; 177 | 178 | delayA = (int)totalDelay; 179 | delayB = (int)totalDelay + 1; 180 | 181 | auto partial = totalDelay - delayA; 182 | 183 | gainA = 1 - partial; 184 | gainB = partial; 185 | } 186 | }; 187 | } 188 | -------------------------------------------------------------------------------- /DSP/ModulatedDelay.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "ModulatedDelay.h" 26 | #include "Utils.h" 27 | #include 28 | 29 | namespace Cloudseed 30 | { 31 | class ModulatedDelay 32 | { 33 | private: 34 | 35 | static const int ModulationUpdateRate = 8; 36 | static const int DelayBufferSize = 192000 * 2; 37 | 38 | float delayBuffer[DelayBufferSize] = { 0 }; 39 | int writeIndex; 40 | int readIndexA; 41 | int readIndexB; 42 | uint64_t samplesProcessed; 43 | 44 | float modPhase; 45 | float gainA; 46 | float gainB; 47 | 48 | public: 49 | int SampleDelay; 50 | 51 | float ModAmount; 52 | float ModRate; 53 | 54 | ModulatedDelay() 55 | { 56 | writeIndex = 0; 57 | readIndexA = 0; 58 | readIndexB = 0; 59 | samplesProcessed = 0; 60 | 61 | modPhase = 0.01 + 0.98 * (std::rand() / (float)RAND_MAX); 62 | gainA = 0; 63 | gainB = 0; 64 | 65 | SampleDelay = 100; 66 | ModAmount = 0.0; 67 | ModRate = 0.0; 68 | 69 | Update(); 70 | } 71 | 72 | void Process(float* input, float* output, int bufSize) 73 | { 74 | for (int i = 0; i < bufSize; i++) 75 | { 76 | if (samplesProcessed >= ModulationUpdateRate) 77 | { 78 | Update(); 79 | samplesProcessed = 0; 80 | } 81 | 82 | delayBuffer[writeIndex] = input[i]; 83 | output[i] = delayBuffer[readIndexA] * gainA + delayBuffer[readIndexB] * gainB; 84 | 85 | writeIndex++; 86 | readIndexA++; 87 | readIndexB++; 88 | if (writeIndex >= DelayBufferSize) writeIndex -= DelayBufferSize; 89 | if (readIndexA >= DelayBufferSize) readIndexA -= DelayBufferSize; 90 | if (readIndexB >= DelayBufferSize) readIndexB -= DelayBufferSize; 91 | samplesProcessed++; 92 | } 93 | } 94 | 95 | void ClearBuffers() 96 | { 97 | Utils::ZeroBuffer(delayBuffer, DelayBufferSize); 98 | } 99 | 100 | 101 | private: 102 | void Update() 103 | { 104 | modPhase += ModRate * ModulationUpdateRate; 105 | if (modPhase > 1) 106 | modPhase = std::fmod(modPhase, 1.0); 107 | 108 | auto mod = std::sinf(modPhase * 2 * M_PI); 109 | auto totalDelay = SampleDelay + ModAmount * mod; 110 | 111 | auto delayA = (int)totalDelay; 112 | auto delayB = (int)totalDelay + 1; 113 | 114 | auto partial = totalDelay - delayA; 115 | 116 | gainA = 1 - partial; 117 | gainB = partial; 118 | 119 | readIndexA = writeIndex - delayA; 120 | readIndexB = writeIndex - delayB; 121 | if (readIndexA < 0) readIndexA += DelayBufferSize; 122 | if (readIndexB < 0) readIndexB += DelayBufferSize; 123 | } 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /DSP/MultitapDelay.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "Utils.h" 30 | #include "RandomBuffer.h" 31 | 32 | namespace Cloudseed 33 | { 34 | class MultitapDelay 35 | { 36 | public: 37 | static const int MaxTaps = 256; 38 | static const int DelayBufferSize = 192000 * 2; 39 | 40 | private: 41 | float delayBuffer[DelayBufferSize] = { 0 }; 42 | 43 | float tapGains[MaxTaps] = { 0 }; 44 | float tapPosition[MaxTaps] = { 0 }; 45 | 46 | std::vector seedValues; 47 | 48 | int writeIdx; 49 | int seed; 50 | float crossSeed; 51 | int count; 52 | float lengthSamples; 53 | float decay; 54 | 55 | public: 56 | MultitapDelay() 57 | { 58 | writeIdx = 0; 59 | seed = 0; 60 | crossSeed = 0.0; 61 | count = 1; 62 | lengthSamples = 1000; 63 | decay = 1.0; 64 | 65 | UpdateSeeds(); 66 | } 67 | 68 | void SetSeed(int seed) 69 | { 70 | this->seed = seed; 71 | UpdateSeeds(); 72 | } 73 | 74 | void SetCrossSeed(float crossSeed) 75 | { 76 | this->crossSeed = crossSeed; 77 | UpdateSeeds(); 78 | } 79 | 80 | void SetTapCount(int tapCount) 81 | { 82 | if (tapCount < 1) tapCount = 1; 83 | count = tapCount; 84 | Update(); 85 | } 86 | 87 | void SetTapLength(int tapLengthSamples) 88 | { 89 | if (tapLengthSamples < 10) tapLengthSamples = 10; 90 | lengthSamples = tapLengthSamples; 91 | Update(); 92 | } 93 | 94 | void SetTapDecay(float tapDecay) 95 | { 96 | decay = tapDecay; 97 | } 98 | 99 | void Process(float* input, float* output, int bufSize) 100 | { 101 | float lengthScaler = lengthSamples / (float)count; 102 | float totalGain = 3.0 / std::sqrtf(1 + count); 103 | totalGain *= (1 + decay * 2); 104 | 105 | for (int i = 0; i < bufSize; i++) 106 | { 107 | delayBuffer[writeIdx] = input[i]; 108 | output[i] = 0; 109 | 110 | for (int j = 0; j < count; j++) 111 | { 112 | float offset = tapPosition[j] * lengthScaler; 113 | float decayEffective = std::expf(-offset / lengthSamples * 3.3) * decay + (1-decay); 114 | int readIdx = writeIdx - (int)offset; 115 | if (readIdx < 0) readIdx += DelayBufferSize; 116 | 117 | output[i] += delayBuffer[readIdx] * tapGains[j] * decayEffective * totalGain; 118 | } 119 | 120 | writeIdx = (writeIdx + 1) % DelayBufferSize; 121 | } 122 | } 123 | 124 | void ClearBuffers() 125 | { 126 | Utils::ZeroBuffer(delayBuffer, DelayBufferSize); 127 | } 128 | 129 | 130 | private: 131 | void Update() 132 | { 133 | int s = 0; 134 | auto rand = [&]() {return seedValues[s++]; }; 135 | 136 | for (int i = 0; i < MaxTaps; i++) 137 | { 138 | float phase = rand() < 0.5 ? 1 : -1; 139 | tapGains[i] = Utils::DB2Gainf(-20 + rand() * 20) * phase; 140 | tapPosition[i] = i + rand(); 141 | } 142 | } 143 | 144 | void UpdateSeeds() 145 | { 146 | this->seedValues = RandomBuffer::Generate(seed, MaxTaps * 3, crossSeed); 147 | Update(); 148 | } 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /DSP/RandomBuffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include "RandomBuffer.h" 25 | #include "LcgRandom.h" 26 | 27 | namespace Cloudseed 28 | { 29 | std::vector RandomBuffer::Generate(uint64_t seed, int count) 30 | { 31 | LcgRandom rand(seed); 32 | std::vector output; 33 | 34 | for (int i = 0; i < count; i++) 35 | { 36 | unsigned int val = rand.NextUInt(); 37 | float fVal = val / (float)UINT_MAX; 38 | output.push_back(fVal); 39 | } 40 | 41 | return output; 42 | } 43 | 44 | std::vector RandomBuffer::Generate(uint64_t seed, int count, float crossSeed) 45 | { 46 | auto seedA = seed; 47 | auto seedB = ~seed; 48 | auto seriesA = Generate(seedA, count); 49 | auto seriesB = Generate(seedB, count); 50 | 51 | std::vector output; 52 | for (int i = 0; i < count; i++) 53 | output.push_back(seriesA[i] * (1 - crossSeed) + seriesB[i] * crossSeed); 54 | 55 | return output; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DSP/RandomBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | namespace Cloudseed 29 | { 30 | class RandomBuffer 31 | { 32 | public: 33 | static std::vector Generate(uint64_t seed, int count); 34 | static std::vector Generate(uint64_t seed, int count, float crossSeed); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /DSP/ReverbChannel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include "../Parameters.h" 28 | #include "ModulatedDelay.h" 29 | #include "MultitapDelay.h" 30 | #include "RandomBuffer.h" 31 | #include "Lp1.h" 32 | #include "Hp1.h" 33 | #include "DelayLine.h" 34 | #include "AllpassDiffuser.h" 35 | #include 36 | #include "ReverbChannel.h" 37 | #include "Utils.h" 38 | 39 | namespace Cloudseed 40 | { 41 | enum class ChannelLR 42 | { 43 | Left, 44 | Right 45 | }; 46 | 47 | class ReverbChannel 48 | { 49 | private: 50 | static const int TotalLineCount = 12; 51 | 52 | double paramsScaled[Parameter::COUNT] = { 0.0 }; 53 | int samplerate; 54 | 55 | ModulatedDelay preDelay; 56 | MultitapDelay multitap; 57 | AllpassDiffuser diffuser; 58 | DelayLine lines[TotalLineCount]; 59 | RandomBuffer rand; 60 | Hp1 highPass; 61 | Lp1 lowPass; 62 | 63 | int delayLineSeed; 64 | int postDiffusionSeed; 65 | 66 | // Used the the main process loop 67 | int lineCount; 68 | 69 | bool lowCutEnabled; 70 | bool highCutEnabled; 71 | bool multitapEnabled; 72 | bool diffuserEnabled; 73 | float inputMix; 74 | float dryOut; 75 | float earlyOut; 76 | float lineOut; 77 | float crossSeed; 78 | ChannelLR channelLr; 79 | 80 | public: 81 | 82 | ReverbChannel(int samplerate, ChannelLR leftOrRight) 83 | { 84 | this->channelLr = leftOrRight; 85 | crossSeed = 0.0; 86 | lineCount = 8; 87 | diffuser.SetInterpolationEnabled(true); 88 | highPass.SetCutoffHz(20); 89 | lowPass.SetCutoffHz(20000); 90 | SetSamplerate(samplerate); 91 | } 92 | 93 | int GetSamplerate() 94 | { 95 | return samplerate; 96 | } 97 | 98 | void SetSamplerate(int samplerate) 99 | { 100 | this->samplerate = samplerate; 101 | highPass.SetSamplerate(samplerate); 102 | lowPass.SetSamplerate(samplerate); 103 | diffuser.SetSamplerate(samplerate); 104 | 105 | for (int i = 0; i < TotalLineCount; i++) 106 | lines[i].SetSamplerate(samplerate); 107 | 108 | ReapplyAllParams(); 109 | ClearBuffers(); 110 | UpdateLines(); 111 | } 112 | 113 | void ReapplyAllParams() 114 | { 115 | for (int i = 0; i < Parameter::COUNT; i++) 116 | SetParameter(i, paramsScaled[i]); 117 | } 118 | 119 | void SetParameter(int para, double scaledValue) 120 | { 121 | paramsScaled[para] = scaledValue; 122 | 123 | switch (para) 124 | { 125 | case Parameter::Interpolation: 126 | for (int i = 0; i < TotalLineCount; i++) 127 | lines[i].SetInterpolationEnabled(scaledValue >= 0.5); 128 | break; 129 | case Parameter::LowCutEnabled: 130 | lowCutEnabled = scaledValue >= 0.5; 131 | if (lowCutEnabled) 132 | highPass.ClearBuffers(); 133 | break; 134 | case Parameter::HighCutEnabled: 135 | highCutEnabled = scaledValue >= 0.5; 136 | if (highCutEnabled) 137 | lowPass.ClearBuffers(); 138 | break; 139 | case Parameter::InputMix: 140 | inputMix = scaledValue; 141 | break; 142 | case Parameter::LowCut: 143 | highPass.SetCutoffHz(scaledValue); 144 | break; 145 | case Parameter::HighCut: 146 | lowPass.SetCutoffHz(scaledValue); 147 | break; 148 | case Parameter::DryOut: 149 | dryOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); 150 | break; 151 | case Parameter::EarlyOut: 152 | earlyOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); 153 | break; 154 | case Parameter::LateOut: 155 | lineOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); 156 | break; 157 | 158 | 159 | case Parameter::TapEnabled: 160 | { 161 | auto newVal = scaledValue >= 0.5; 162 | if (newVal != multitapEnabled) 163 | multitap.ClearBuffers(); 164 | multitapEnabled = newVal; 165 | break; 166 | } 167 | case Parameter::TapCount: 168 | multitap.SetTapCount((int)scaledValue); 169 | break; 170 | case Parameter::TapDecay: 171 | multitap.SetTapDecay(scaledValue); 172 | break; 173 | case Parameter::TapPredelay: 174 | preDelay.SampleDelay = (int)Ms2Samples(scaledValue); 175 | break; 176 | case Parameter::TapLength: 177 | multitap.SetTapLength((int)Ms2Samples(scaledValue)); 178 | break; 179 | 180 | 181 | case Parameter::EarlyDiffuseEnabled: 182 | { 183 | auto newVal = scaledValue >= 0.5; 184 | if (newVal != diffuserEnabled) 185 | diffuser.ClearBuffers(); 186 | diffuserEnabled = newVal; 187 | break; 188 | } 189 | case Parameter::EarlyDiffuseCount: 190 | diffuser.Stages = (int)scaledValue; 191 | break; 192 | case Parameter::EarlyDiffuseDelay: 193 | diffuser.SetDelay((int)Ms2Samples(scaledValue)); 194 | break; 195 | case Parameter::EarlyDiffuseModAmount: 196 | diffuser.SetModulationEnabled(scaledValue > 0.5); 197 | diffuser.SetModAmount(Ms2Samples(scaledValue)); 198 | break; 199 | case Parameter::EarlyDiffuseFeedback: 200 | diffuser.SetFeedback(scaledValue); 201 | break; 202 | case Parameter::EarlyDiffuseModRate: 203 | diffuser.SetModRate(scaledValue); 204 | break; 205 | 206 | 207 | case Parameter::LateMode: 208 | for (int i = 0; i < TotalLineCount; i++) 209 | lines[i].TapPostDiffuser = scaledValue >= 0.5; 210 | break; 211 | case Parameter::LateLineCount: 212 | lineCount = (int)scaledValue; 213 | break; 214 | case Parameter::LateDiffuseEnabled: 215 | for (int i = 0; i < TotalLineCount; i++) 216 | { 217 | auto newVal = scaledValue >= 0.5; 218 | if (newVal != lines[i].DiffuserEnabled) 219 | lines[i].ClearDiffuserBuffer(); 220 | lines[i].DiffuserEnabled = newVal; 221 | } 222 | break; 223 | case Parameter::LateDiffuseCount: 224 | for (int i = 0; i < TotalLineCount; i++) 225 | lines[i].SetDiffuserStages((int)scaledValue); 226 | break; 227 | case Parameter::LateLineSize: 228 | UpdateLines(); 229 | break; 230 | case Parameter::LateLineModAmount: 231 | UpdateLines(); 232 | break; 233 | case Parameter::LateDiffuseDelay: 234 | for (int i = 0; i < TotalLineCount; i++) 235 | lines[i].SetDiffuserDelay((int)Ms2Samples(scaledValue)); 236 | break; 237 | case Parameter::LateDiffuseModAmount: 238 | UpdateLines(); 239 | break; 240 | case Parameter::LateLineDecay: 241 | UpdateLines(); 242 | break; 243 | case Parameter::LateLineModRate: 244 | UpdateLines(); 245 | break; 246 | case Parameter::LateDiffuseFeedback: 247 | for (int i = 0; i < TotalLineCount; i++) 248 | lines[i].SetDiffuserFeedback(scaledValue); 249 | break; 250 | case Parameter::LateDiffuseModRate: 251 | UpdateLines(); 252 | break; 253 | 254 | 255 | case Parameter::EqLowShelfEnabled: 256 | for (int i = 0; i < TotalLineCount; i++) 257 | lines[i].LowShelfEnabled = scaledValue >= 0.5; 258 | break; 259 | case Parameter::EqHighShelfEnabled: 260 | for (int i = 0; i < TotalLineCount; i++) 261 | lines[i].HighShelfEnabled = scaledValue >= 0.5; 262 | break; 263 | case Parameter::EqLowpassEnabled: 264 | for (int i = 0; i < TotalLineCount; i++) 265 | lines[i].CutoffEnabled = scaledValue >= 0.5; 266 | break; 267 | case Parameter::EqLowFreq: 268 | for (int i = 0; i < TotalLineCount; i++) 269 | lines[i].SetLowShelfFrequency(scaledValue); 270 | break; 271 | case Parameter::EqHighFreq: 272 | for (int i = 0; i < TotalLineCount; i++) 273 | lines[i].SetHighShelfFrequency(scaledValue); 274 | break; 275 | case Parameter::EqCutoff: 276 | for (int i = 0; i < TotalLineCount; i++) 277 | lines[i].SetCutoffFrequency(scaledValue); 278 | break; 279 | case Parameter::EqLowGain: 280 | for (int i = 0; i < TotalLineCount; i++) 281 | lines[i].SetLowShelfGain(scaledValue); 282 | break; 283 | case Parameter::EqHighGain: 284 | for (int i = 0; i < TotalLineCount; i++) 285 | lines[i].SetHighShelfGain(scaledValue); 286 | break; 287 | 288 | 289 | case Parameter::EqCrossSeed: 290 | crossSeed = channelLr == ChannelLR::Right ? 0.5 * scaledValue : 1 - 0.5 * scaledValue; 291 | multitap.SetCrossSeed(crossSeed); 292 | diffuser.SetCrossSeed(crossSeed); 293 | UpdateLines(); 294 | UpdatePostDiffusion(); 295 | break; 296 | 297 | 298 | case Parameter::SeedTap: 299 | multitap.SetSeed((int)scaledValue); 300 | break; 301 | case Parameter::SeedDiffusion: 302 | diffuser.SetSeed((int)scaledValue); 303 | break; 304 | case Parameter::SeedDelay: 305 | delayLineSeed = (int)scaledValue; 306 | UpdateLines(); 307 | break; 308 | case Parameter::SeedPostDiffusion: 309 | postDiffusionSeed = (int)scaledValue; 310 | UpdatePostDiffusion(); 311 | break; 312 | } 313 | } 314 | 315 | void Process(float* input, float* output, int bufSize) 316 | { 317 | float tempBuffer[BUFFER_SIZE]; 318 | float earlyOutBuffer[BUFFER_SIZE]; 319 | float lineOutBuffer[BUFFER_SIZE]; 320 | float lineSumBuffer[BUFFER_SIZE]; 321 | 322 | Utils::Copy(tempBuffer, input, bufSize); 323 | 324 | if (lowCutEnabled) 325 | highPass.Process(tempBuffer, tempBuffer, bufSize); 326 | if (highCutEnabled) 327 | lowPass.Process(tempBuffer, tempBuffer, bufSize); 328 | 329 | // completely zero if no input present 330 | // Previously, the very small values were causing some really strange CPU spikes 331 | for (int i = 0; i < bufSize; i++) 332 | { 333 | auto n = tempBuffer[i]; 334 | if (n * n < 0.000000001) 335 | tempBuffer[i] = 0; 336 | } 337 | 338 | preDelay.Process(tempBuffer, tempBuffer, bufSize); 339 | if (multitapEnabled) 340 | multitap.Process(tempBuffer, tempBuffer, bufSize); 341 | if (diffuserEnabled) 342 | diffuser.Process(tempBuffer, tempBuffer, bufSize); 343 | 344 | Utils::Copy(earlyOutBuffer, tempBuffer, bufSize); 345 | Utils::ZeroBuffer(lineSumBuffer, bufSize); 346 | for (int i = 0; i < lineCount; i++) 347 | { 348 | lines[i].Process(tempBuffer, lineOutBuffer, bufSize); 349 | Utils::Mix(lineSumBuffer, lineOutBuffer, 1.0f, bufSize); 350 | } 351 | 352 | auto perLineGain = GetPerLineGain(); 353 | Utils::Gain(lineSumBuffer, perLineGain, bufSize); 354 | 355 | for (int i = 0; i < bufSize; i++) 356 | { 357 | output[i] = dryOut * input[i] 358 | + earlyOut * earlyOutBuffer[i] 359 | + lineOut * lineSumBuffer[i]; 360 | } 361 | } 362 | 363 | void ClearBuffers() 364 | { 365 | lowPass.ClearBuffers(); 366 | highPass.ClearBuffers(); 367 | preDelay.ClearBuffers(); 368 | multitap.ClearBuffers(); 369 | diffuser.ClearBuffers(); 370 | for (int i = 0; i < TotalLineCount; i++) 371 | lines[i].ClearBuffers(); 372 | } 373 | 374 | 375 | private: 376 | float GetPerLineGain() 377 | { 378 | return 1.0 / std::sqrt(lineCount); 379 | } 380 | 381 | void UpdateLines() 382 | { 383 | auto lineDelaySamples = (int)Ms2Samples(paramsScaled[Parameter::LateLineSize]); 384 | auto lineDecayMillis = paramsScaled[Parameter::LateLineDecay] * 1000; 385 | auto lineDecaySamples = Ms2Samples(lineDecayMillis); 386 | 387 | auto lineModAmount = Ms2Samples(paramsScaled[Parameter::LateLineModAmount]); 388 | auto lineModRate = paramsScaled[Parameter::LateLineModRate]; 389 | 390 | auto lateDiffusionModAmount = Ms2Samples(paramsScaled[Parameter::LateDiffuseModAmount]); 391 | auto lateDiffusionModRate = paramsScaled[Parameter::LateDiffuseModRate]; 392 | 393 | auto delayLineSeeds = RandomBuffer::Generate(delayLineSeed, TotalLineCount * 3, crossSeed); 394 | 395 | for (int i = 0; i < TotalLineCount; i++) 396 | { 397 | auto modAmount = lineModAmount * (0.7 + 0.3 * delayLineSeeds[i]); 398 | auto modRate = lineModRate * (0.7 + 0.3 * delayLineSeeds[TotalLineCount + i]) / samplerate; 399 | 400 | auto delaySamples = (0.5 + 1.0 * delayLineSeeds[TotalLineCount * 2 + i]) * lineDelaySamples; 401 | if (delaySamples < modAmount + 2) // when the delay is set really short, and the modulation is very high 402 | delaySamples = modAmount + 2; // the mod could actually take the delay time negative, prevent that! -- provide 2 extra sample as margin of safety 403 | 404 | auto dbAfter1Iteration = delaySamples / lineDecaySamples * (-60); // lineDecay is the time it takes to reach T60 405 | auto gainAfter1Iteration = Utils::DB2Gainf(dbAfter1Iteration); 406 | 407 | lines[i].SetDelay((int)delaySamples); 408 | lines[i].SetFeedback(gainAfter1Iteration); 409 | lines[i].SetLineModAmount(modAmount); 410 | lines[i].SetLineModRate(modRate); 411 | lines[i].SetDiffuserModAmount(lateDiffusionModAmount); 412 | lines[i].SetDiffuserModRate(lateDiffusionModRate); 413 | } 414 | } 415 | 416 | void UpdatePostDiffusion() 417 | { 418 | for (int i = 0; i < TotalLineCount; i++) 419 | lines[i].SetDiffuserSeed((postDiffusionSeed) * (i + 1), crossSeed); 420 | } 421 | 422 | float Ms2Samples(float value) 423 | { 424 | return value / 1000.0f * samplerate; 425 | } 426 | 427 | }; 428 | } 429 | -------------------------------------------------------------------------------- /DSP/ReverbController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include "../Parameters.h" 27 | #include "ReverbChannel.h" 28 | #include "AllpassDiffuser.h" 29 | #include "MultitapDelay.h" 30 | #include "Utils.h" 31 | 32 | namespace Cloudseed 33 | { 34 | class ReverbController 35 | { 36 | private: 37 | int samplerate; 38 | 39 | ReverbChannel channelL; 40 | ReverbChannel channelR; 41 | double parameters[(int)Parameter::COUNT] = {0}; 42 | 43 | public: 44 | ReverbController(int samplerate) : 45 | channelL(samplerate, ChannelLR::Left), 46 | channelR(samplerate, ChannelLR::Right) 47 | { 48 | this->samplerate = samplerate; 49 | } 50 | 51 | int GetSamplerate() 52 | { 53 | return samplerate; 54 | } 55 | 56 | void SetSamplerate(int samplerate) 57 | { 58 | this->samplerate = samplerate; 59 | channelL.SetSamplerate(samplerate); 60 | channelR.SetSamplerate(samplerate); 61 | } 62 | 63 | int GetParameterCount() 64 | { 65 | return Parameter::COUNT; 66 | } 67 | 68 | double* GetAllParameters() 69 | { 70 | return parameters; 71 | } 72 | 73 | void SetParameter(int paramId, double value) 74 | { 75 | parameters[paramId] = value; 76 | auto scaled = ScaleParam(value, paramId); 77 | channelL.SetParameter(paramId, scaled); 78 | channelR.SetParameter(paramId, scaled); 79 | } 80 | 81 | void ClearBuffers() 82 | { 83 | channelL.ClearBuffers(); 84 | channelR.ClearBuffers(); 85 | } 86 | 87 | void Process(float* inL, float* inR, float* outL, float* outR, int bufSize) 88 | { 89 | float outLTemp[BUFFER_SIZE]; 90 | float outRTemp[BUFFER_SIZE]; 91 | 92 | while (bufSize > 0) 93 | { 94 | int subBufSize = bufSize > BUFFER_SIZE ? BUFFER_SIZE : bufSize; 95 | ProcessChunk(inL, inR, outLTemp, outRTemp, subBufSize); 96 | Utils::Copy(outL, outLTemp, subBufSize); 97 | Utils::Copy(outR, outRTemp, subBufSize); 98 | inL = &inL[subBufSize]; 99 | inR = &inR[subBufSize]; 100 | outL = &outL[subBufSize]; 101 | outR = &outR[subBufSize]; 102 | bufSize -= subBufSize; 103 | } 104 | } 105 | 106 | private: 107 | void ProcessChunk(float* inL, float* inR, float* outL, float* outR, int bufSize) 108 | { 109 | float leftChannelIn[BUFFER_SIZE]; 110 | float rightChannelIn[BUFFER_SIZE]; 111 | 112 | float inputMix = ScaleParam(parameters[Parameter::InputMix], Parameter::InputMix); 113 | float cm = inputMix * 0.5; 114 | float cmi = (1 - cm); 115 | 116 | for (int i = 0; i < bufSize; i++) 117 | { 118 | leftChannelIn[i] = inL[i] * cmi + inR[i] * cm; 119 | rightChannelIn[i] = inR[i] * cmi + inL[i] * cm; 120 | } 121 | 122 | channelL.Process(leftChannelIn, outL, bufSize); 123 | channelR.Process(rightChannelIn, outR, bufSize); 124 | } 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /DSP/Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #define _USE_MATH_DEFINES 1 26 | #include 27 | #include 28 | 29 | namespace Cloudseed 30 | { 31 | namespace Utils 32 | { 33 | template 34 | inline void ZeroBuffer(T* buffer, int len) 35 | { 36 | for (int i = 0; i < len; i++) 37 | buffer[i] = 0; 38 | } 39 | 40 | template 41 | inline void Copy(T* dest, T* source, int len) 42 | { 43 | memcpy(dest, source, len * sizeof(T)); 44 | } 45 | 46 | template 47 | inline void Gain(T* buffer, T gain, int len) 48 | { 49 | for (int i = 0; i < len; i++) 50 | { 51 | buffer[i] *= gain; 52 | } 53 | } 54 | 55 | template 56 | inline void Mix(T* target, T* source, T gain, int len) 57 | { 58 | for (int i = 0; i < len; i++) 59 | target[i] += source[i] * gain; 60 | } 61 | 62 | inline float DB2Gainf(float input) 63 | { 64 | //return std::pow(10.0f, input / 20.0f); 65 | return powf(10, input * 0.05f); 66 | } 67 | 68 | template 69 | inline double DB2Gain(T input) 70 | { 71 | return pow10f(input / 20.0); 72 | } 73 | 74 | template 75 | inline double Gain2DB(T input) 76 | { 77 | //if (input < 0.0000001) 78 | // return -100000; 79 | 80 | return 20.0f * log10f(input); 81 | } 82 | 83 | const float dec1Mult = (10 / 9.0) * 0.1; 84 | const float dec2Mult = (100 / 99.0) * 0.01; 85 | const float dec3Mult = (1000 / 999.0) * 0.001; 86 | const float dec4Mult = (10000 / 9999.0) * 0.0001; 87 | 88 | const float oct1Mult = (2 / 1.0) * 0.5; 89 | const float oct2Mult = (4 / 3.0) * 0.25; 90 | const float oct3Mult = (8 / 7.0) * 0.125; 91 | const float oct4Mult = (16 / 15.0) * 0.0625; 92 | const float oct5Mult = (32 / 31.0) * 0.03125; 93 | const float oct6Mult = (64 / 63.0) * 0.015625; 94 | const float oct7Mult = (128 / 127.0) * 0.0078125; 95 | const float oct8Mult = (256 / 255.0) * 0.00390625; 96 | 97 | inline float Resp1dec(float x) { return (powf(10, x) - 1) * dec1Mult; } 98 | inline float Resp2dec(float x) { return (powf(10, 2 * x) - 1) * dec2Mult; } 99 | inline float Resp3dec(float x) { return (powf(10, 3 * x) - 1) * dec3Mult; } 100 | inline float Resp4dec(float x) { return (powf(10, 4 * x) - 1) * dec4Mult; } 101 | 102 | inline float Resp1oct(float x) { return (powf(2, x) - 1) * oct1Mult; } 103 | inline float Resp2oct(float x) { return (powf(2, 2 * x) - 1) * oct2Mult; } 104 | inline float Resp3oct(float x) { return (powf(2, 3 * x) - 1) * oct3Mult; } 105 | inline float Resp4oct(float x) { return (powf(2, 4 * x) - 1) * oct4Mult; } 106 | inline float Resp5oct(float x) { return (powf(2, 5 * x) - 1) * oct5Mult; } 107 | inline float Resp6oct(float x) { return (powf(2, 6 * x) - 1) * oct6Mult; } 108 | inline float Resp7oct(float x) { return (powf(2, 7 * x) - 1) * oct7Mult; } 109 | inline float Resp8oct(float x) { return (powf(2, 8 * x) - 1) * oct8Mult; } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ghost Note Audio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Parameters.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include "Parameters.h" 24 | 25 | namespace Cloudseed 26 | { 27 | const char* ParameterLabel[Parameter::COUNT] = { 28 | "Interpolation", 29 | "High Cut", 30 | "Low Cut", 31 | "Input Mix", 32 | "High Cut", 33 | "Low Cut", 34 | "Dry", 35 | "Early", 36 | "Late", 37 | 38 | "Multitap Delay", 39 | "Count", 40 | "Decay", 41 | "Pre-delay", 42 | "Length", 43 | 44 | "Diffusion", 45 | "Diffusion Stages", 46 | "Delay", 47 | "Mod Amt", 48 | "Feedback", 49 | "Mod Rate", 50 | 51 | "Mode", 52 | "Line Count", 53 | "Diffusion", 54 | "Diffusion Stages", 55 | "Size", 56 | "Mod Amt", 57 | "Delay", 58 | "Mod Amt", 59 | "Decay", 60 | "Mod Rate", 61 | "Feedback", 62 | "Mod Rate", 63 | 64 | "Low Shelf", 65 | "High Shelf", 66 | "Lowpass", 67 | "Low Freq", 68 | "High Freq", 69 | "Cutoff", 70 | "Low Gain", 71 | "High Gain", 72 | "Cross Seed", 73 | 74 | "Tap Seed", 75 | "Diffusion Seed", 76 | "Delay Seed", 77 | "Late Diffusion Seed", 78 | }; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Parameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include "DSP/Utils.h" 29 | 30 | namespace Cloudseed 31 | { 32 | namespace Parameter 33 | { 34 | const int Interpolation = 0; 35 | const int LowCutEnabled = 1; 36 | const int HighCutEnabled = 2; 37 | const int InputMix = 3; 38 | const int LowCut = 4; 39 | const int HighCut = 5; 40 | const int DryOut = 6; 41 | const int EarlyOut = 7; 42 | const int LateOut = 8; 43 | 44 | const int TapEnabled = 9; 45 | const int TapCount = 10; 46 | const int TapDecay = 11; 47 | const int TapPredelay = 12; 48 | const int TapLength = 13; 49 | 50 | const int EarlyDiffuseEnabled = 14; 51 | const int EarlyDiffuseCount = 15; 52 | const int EarlyDiffuseDelay = 16; 53 | const int EarlyDiffuseModAmount = 17; 54 | const int EarlyDiffuseFeedback = 18; 55 | const int EarlyDiffuseModRate = 19; 56 | 57 | const int LateMode = 20; 58 | const int LateLineCount = 21; 59 | const int LateDiffuseEnabled = 22; 60 | const int LateDiffuseCount = 23; 61 | const int LateLineSize = 24; 62 | const int LateLineModAmount = 25; 63 | const int LateDiffuseDelay = 26; 64 | const int LateDiffuseModAmount = 27; 65 | const int LateLineDecay = 28; 66 | const int LateLineModRate = 29; 67 | const int LateDiffuseFeedback = 30; 68 | const int LateDiffuseModRate = 31; 69 | 70 | const int EqLowShelfEnabled = 32; 71 | const int EqHighShelfEnabled = 33; 72 | const int EqLowpassEnabled = 34; 73 | const int EqLowFreq = 35; 74 | const int EqHighFreq = 36; 75 | const int EqCutoff = 37; 76 | const int EqLowGain = 38; 77 | const int EqHighGain = 39; 78 | const int EqCrossSeed = 40; 79 | 80 | const int SeedTap = 41; 81 | const int SeedDiffusion = 42; 82 | const int SeedDelay = 43; 83 | const int SeedPostDiffusion = 44; 84 | 85 | const int COUNT = 45; 86 | }; 87 | 88 | extern const char* ParameterLabel[Parameter::COUNT]; 89 | 90 | inline double ScaleParam(double val, int index) 91 | { 92 | switch (index) 93 | { 94 | case Parameter::Interpolation: 95 | case Parameter::LowCutEnabled: 96 | case Parameter::HighCutEnabled: 97 | case Parameter::TapEnabled: 98 | case Parameter::LateDiffuseEnabled: 99 | case Parameter::EqLowShelfEnabled: 100 | case Parameter::EqHighShelfEnabled: 101 | case Parameter::EqLowpassEnabled: 102 | case Parameter::EarlyDiffuseEnabled: 103 | return val < 0.5 ? 0.0 : 1.0; 104 | 105 | case Parameter::InputMix: 106 | case Parameter::EarlyDiffuseFeedback: 107 | case Parameter::TapDecay: 108 | case Parameter::LateDiffuseFeedback: 109 | case Parameter::EqCrossSeed: 110 | return val; 111 | 112 | case Parameter::SeedTap: 113 | case Parameter::SeedDiffusion: 114 | case Parameter::SeedDelay: 115 | case Parameter::SeedPostDiffusion: 116 | return (int)floor(val * 999.999); 117 | 118 | case Parameter::LowCut: 119 | return 20 + Utils::Resp4oct(val) * 980; 120 | case Parameter::HighCut: 121 | return 400 + Utils::Resp4oct(val) * 19600; 122 | 123 | case Parameter::DryOut: 124 | case Parameter::EarlyOut: 125 | case Parameter::LateOut: 126 | return -30 + val * 30; 127 | 128 | case Parameter::TapCount: 129 | return (int)(1 + val * 255); 130 | case Parameter::TapPredelay: 131 | return Utils::Resp1dec(val) * 500; 132 | case Parameter::TapLength: 133 | return 10 + val * 990; 134 | 135 | case Parameter::EarlyDiffuseCount: 136 | return (int)(1 + val * 11.999); 137 | case Parameter::EarlyDiffuseDelay: 138 | return 10 + val * 90; 139 | case Parameter::EarlyDiffuseModAmount: 140 | return val * 2.5; 141 | case Parameter::EarlyDiffuseModRate: 142 | return Utils::Resp2dec(val) * 5; 143 | 144 | case Parameter::LateMode: 145 | return val < 0.5 ? 0.0 : 1.0; 146 | case Parameter::LateLineCount: 147 | return (int)(1 + val * 11.999); 148 | case Parameter::LateDiffuseCount: 149 | return (int)(1 + val * 7.999); 150 | case Parameter::LateLineSize: 151 | return 20 + Utils::Resp2dec(val) * 980; 152 | case Parameter::LateLineModAmount: 153 | return val * 2.5; 154 | case Parameter::LateDiffuseDelay: 155 | return 10 + val * 90; 156 | case Parameter::LateDiffuseModAmount: 157 | return val * 2.5; 158 | case Parameter::LateLineDecay: 159 | return 0.05 + Utils::Resp3dec(val) * 59.95; 160 | case Parameter::LateLineModRate: 161 | return Utils::Resp2dec(val) * 5; 162 | case Parameter::LateDiffuseModRate: 163 | return Utils::Resp2dec(val) * 5; 164 | 165 | case Parameter::EqLowFreq: 166 | return 20 + Utils::Resp3oct(val) * 980; 167 | case Parameter::EqHighFreq: 168 | return 400 + Utils::Resp4oct(val) * 19600; 169 | case Parameter::EqCutoff: 170 | return 400 + Utils::Resp4oct(val) * 19600; 171 | case Parameter::EqLowGain: 172 | return -20 + val * 20; 173 | case Parameter::EqHighGain: 174 | return -20 + val * 20; 175 | } 176 | return 0; 177 | } 178 | 179 | inline void FormatParameter(float val, int maxLen, int paramId, char* buffer) 180 | { 181 | double s = ScaleParam(val, paramId); 182 | 183 | switch (paramId) 184 | { 185 | case Parameter::Interpolation: 186 | case Parameter::HighCutEnabled: 187 | case Parameter::LowCutEnabled: 188 | case Parameter::TapEnabled: 189 | case Parameter::LateDiffuseEnabled: 190 | case Parameter::EqLowShelfEnabled: 191 | case Parameter::EqHighShelfEnabled: 192 | case Parameter::EqLowpassEnabled: 193 | case Parameter::EarlyDiffuseEnabled: 194 | if (ScaleParam(val, paramId) == 1) 195 | strcpy_s(buffer, MAX_STR_SIZE, "ENABLED"); 196 | else 197 | strcpy_s(buffer, MAX_STR_SIZE, "DISABLED"); 198 | break; 199 | 200 | case Parameter::InputMix: 201 | case Parameter::EarlyDiffuseFeedback: 202 | case Parameter::TapDecay: 203 | case Parameter::LateDiffuseFeedback: 204 | case Parameter::EqCrossSeed: 205 | snprintf(buffer, MAX_STR_SIZE, "%d%%", (int)(s * 100)); 206 | break; 207 | 208 | case Parameter::SeedTap: 209 | case Parameter::SeedDiffusion: 210 | case Parameter::SeedDelay: 211 | case Parameter::SeedPostDiffusion: 212 | snprintf(buffer, MAX_STR_SIZE, "%03d", (int)s); 213 | break; 214 | 215 | case Parameter::LowCut: 216 | case Parameter::HighCut: 217 | case Parameter::EqLowFreq: 218 | case Parameter::EqHighFreq: 219 | case Parameter::EqCutoff: 220 | snprintf(buffer, MAX_STR_SIZE, "%d Hz", (int)s); 221 | break; 222 | 223 | case Parameter::DryOut: 224 | case Parameter::EarlyOut: 225 | case Parameter::LateOut: 226 | if (s <= -30) 227 | strcpy_s(buffer, MAX_STR_SIZE, "MUTED"); 228 | else 229 | snprintf(buffer, MAX_STR_SIZE, "%.1f dB", s); 230 | break; 231 | 232 | case Parameter::TapCount: 233 | case Parameter::EarlyDiffuseCount: 234 | case Parameter::LateLineCount: 235 | case Parameter::LateDiffuseCount: 236 | snprintf(buffer, MAX_STR_SIZE, "%d", (int)s); 237 | break; 238 | 239 | case Parameter::TapPredelay: 240 | case Parameter::TapLength: 241 | case Parameter::EarlyDiffuseDelay: 242 | case Parameter::LateLineSize: 243 | case Parameter::LateDiffuseDelay: 244 | snprintf(buffer, MAX_STR_SIZE, "%d ms", (int)s); 245 | break; 246 | 247 | case Parameter::LateLineDecay: 248 | if (s < 1) 249 | snprintf(buffer, MAX_STR_SIZE, "%d ms", (int)(s * 1000)); 250 | else if (s < 10) 251 | snprintf(buffer, MAX_STR_SIZE, "%.2f sec", s); 252 | else 253 | snprintf(buffer, MAX_STR_SIZE, "%.1f sec", s); 254 | break; 255 | 256 | case Parameter::LateMode: 257 | if (s == 1) 258 | strcpy_s(buffer, MAX_STR_SIZE, "POST"); 259 | else 260 | strcpy_s(buffer, MAX_STR_SIZE, "PRE"); 261 | break; 262 | 263 | case Parameter::EarlyDiffuseModAmount: 264 | case Parameter::LateLineModAmount: 265 | case Parameter::LateDiffuseModAmount: 266 | snprintf(buffer, MAX_STR_SIZE, "%d%%", (int)(s * 100)); 267 | break; 268 | 269 | case Parameter::EarlyDiffuseModRate: 270 | case Parameter::LateLineModRate: 271 | case Parameter::LateDiffuseModRate: 272 | snprintf(buffer, MAX_STR_SIZE, "%.2f Hz", s); 273 | break; 274 | 275 | case Parameter::EqLowGain: 276 | case Parameter::EqHighGain: 277 | snprintf(buffer, MAX_STR_SIZE, "%.1f dB", s); 278 | break; 279 | 280 | default: 281 | snprintf(buffer, MAX_STR_SIZE, "%.2f", s); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "DSP/ReverbController.h" 26 | #include "Programs.h" 27 | 28 | using namespace Cloudseed; 29 | 30 | Cloudseed::ReverbController reverb(48000); 31 | 32 | void start(float* programData, int samplerate) 33 | { 34 | for (int i = 0; i < Parameter::COUNT; i++) 35 | { 36 | reverb.SetParameter(i, programData[i]); 37 | } 38 | 39 | reverb.SetSamplerate(samplerate); 40 | reverb.ClearBuffers(); 41 | } 42 | 43 | void printSettings() 44 | { 45 | auto params = reverb.GetAllParameters(); 46 | char buffer[32]; 47 | for (int i = 0; i < Parameter::COUNT; i++) 48 | { 49 | FormatParameter(params[i], MAX_STR_SIZE, i, buffer); 50 | std::cout << ParameterLabel[i] << ": " << buffer << "\r\n"; 51 | } 52 | } 53 | 54 | void processBlock (float* inputL, float* inputR, float* outputL, float* outputR, int bufferSize) 55 | { 56 | reverb.Process(inputL, inputR, outputL, outputR, bufferSize); 57 | } 58 | 59 | int main() 60 | { 61 | initPrograms(); 62 | int samplerate = 48000; 63 | int sampleCount = samplerate * 5; 64 | 65 | // Apply the program parameters and set the samplerate 66 | start(ProgramDarkPlate, samplerate); 67 | 68 | // Print all the settings in human-readable form 69 | printSettings(); 70 | 71 | // Process an impulse response input and store the output 72 | auto inputL = new float[sampleCount] { 0.0f }; 73 | auto inputR = new float[sampleCount] { 0.0f }; 74 | auto outputL = new float[sampleCount]; 75 | auto outputR = new float[sampleCount]; 76 | inputL[10000] = 1.0f; 77 | inputR[10000] = 1.0f; 78 | processBlock(inputL, inputR, outputL, outputR, sampleCount); 79 | 80 | // Write outputs to file 81 | int totalOutputBytes = sampleCount * 4; 82 | std::ofstream fsL("outputLeft.bin", std::ios::out | std::ios::binary | std::ios::app); 83 | std::ofstream fsR("outputRight.bin", std::ios::out | std::ios::binary | std::ios::app); 84 | fsL.write((char*)outputL, totalOutputBytes); 85 | fsR.write((char*)outputR, totalOutputBytes); 86 | fsL.close(); 87 | fsR.close(); 88 | } -------------------------------------------------------------------------------- /Programs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024 Ghost Note Engineering Ltd 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "Parameters.h" 26 | 27 | namespace Cloudseed 28 | { 29 | float ProgramDarkPlate[Parameter::COUNT]; 30 | 31 | void initPrograms() 32 | { 33 | ProgramDarkPlate[Parameter::DryOut] = 0.8705999851226807; 34 | ProgramDarkPlate[Parameter::EarlyDiffuseCount] = 0.2960000038146973; 35 | ProgramDarkPlate[Parameter::EarlyDiffuseDelay] = 0.3066999912261963; 36 | ProgramDarkPlate[Parameter::EarlyDiffuseEnabled] = 0.0; 37 | ProgramDarkPlate[Parameter::EarlyDiffuseFeedback] = 0.7706999778747559; 38 | ProgramDarkPlate[Parameter::EarlyDiffuseModAmount] = 0.143899992108345; 39 | ProgramDarkPlate[Parameter::EarlyDiffuseModRate] = 0.2466999888420105; 40 | ProgramDarkPlate[Parameter::EarlyOut] = 0.0; 41 | ProgramDarkPlate[Parameter::EqCrossSeed] = 0.0; 42 | ProgramDarkPlate[Parameter::EqCutoff] = 0.9759999513626099; 43 | ProgramDarkPlate[Parameter::EqHighFreq] = 0.5133999586105347; 44 | ProgramDarkPlate[Parameter::EqHighGain] = 0.7680000066757202; 45 | ProgramDarkPlate[Parameter::EqHighShelfEnabled] = 1.0; 46 | ProgramDarkPlate[Parameter::EqLowFreq] = 0.3879999816417694; 47 | ProgramDarkPlate[Parameter::EqLowGain] = 0.5559999942779541; 48 | ProgramDarkPlate[Parameter::EqLowShelfEnabled] = 0.0; 49 | ProgramDarkPlate[Parameter::EqLowpassEnabled] = 0.0; 50 | ProgramDarkPlate[Parameter::HighCut] = 0.2933000028133392; 51 | ProgramDarkPlate[Parameter::HighCutEnabled] = 0.0; 52 | ProgramDarkPlate[Parameter::InputMix] = 0.2346999943256378; 53 | ProgramDarkPlate[Parameter::Interpolation] = 1.0; 54 | ProgramDarkPlate[Parameter::LateDiffuseCount] = 0.4879999756813049; 55 | ProgramDarkPlate[Parameter::LateDiffuseDelay] = 0.239999994635582; 56 | ProgramDarkPlate[Parameter::LateDiffuseEnabled] = 1.0; 57 | ProgramDarkPlate[Parameter::LateDiffuseFeedback] = 0.8506999611854553; 58 | ProgramDarkPlate[Parameter::LateDiffuseModAmount] = 0.1467999964952469; 59 | ProgramDarkPlate[Parameter::LateDiffuseModRate] = 0.1666999906301498; 60 | ProgramDarkPlate[Parameter::LateLineCount] = 1.0; 61 | ProgramDarkPlate[Parameter::LateLineDecay] = 0.6345999836921692; 62 | ProgramDarkPlate[Parameter::LateLineModAmount] = 0.2719999849796295; 63 | ProgramDarkPlate[Parameter::LateLineModRate] = 0.2292999923229218; 64 | ProgramDarkPlate[Parameter::LateLineSize] = 0.4693999886512756; 65 | ProgramDarkPlate[Parameter::LateMode] = 1.0; 66 | ProgramDarkPlate[Parameter::LateOut] = 0.6613999605178833; 67 | ProgramDarkPlate[Parameter::LowCut] = 0.6399999856948853; 68 | ProgramDarkPlate[Parameter::LowCutEnabled] = 1.0; 69 | ProgramDarkPlate[Parameter::SeedDelay] = 0.2180999964475632; 70 | ProgramDarkPlate[Parameter::SeedDiffusion] = 0.1850000023841858; 71 | ProgramDarkPlate[Parameter::SeedPostDiffusion] = 0.3652999997138977; 72 | ProgramDarkPlate[Parameter::SeedTap] = 0.3339999914169312; 73 | ProgramDarkPlate[Parameter::TapDecay] = 1.0; 74 | ProgramDarkPlate[Parameter::TapLength] = 0.9866999983787537; 75 | ProgramDarkPlate[Parameter::TapPredelay] = 0.0; 76 | ProgramDarkPlate[Parameter::TapCount] = 0.1959999948740005; 77 | ProgramDarkPlate[Parameter::TapEnabled] = 0.0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Cloud Seed Core 2 | 3 | This is the core DSP code of Cloud Seed 2.0 4 | 5 | **You can download and purchase the plugin here:** 6 | 7 | **https://ghostnoteaudio.uk/products/cloudseed** 8 | 9 | The plugin itself is not open source, but the core reverb algorithm is. 10 | 11 | This repository contains all the necessary code to integrate the Cloud Seed algorithm into your application. 12 | 13 | # License 14 | 15 | This code it MIT Licensed. In short: 16 | 17 | * You can use this plugin for both free and commercial purposes. 18 | * Please credit Ghost Note Audio as the original author of this code. 19 | * 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. 20 | * I do reserve the right to ask you to please stop using the Cloud Seed name, should you not adhere to this. 21 | * 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! 22 | 23 | # Build Instructions 24 | 25 | This is a bare-bones console C++ program. It requires C++14 to compile. 26 | 27 | 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. 28 | 29 | The output is written to a raw binary file, which you can open using Audacity: 30 | 31 | * File -> Import -> Raw Data 32 | * Encoding: 32-bit float 33 | * Byte order: default endianness 34 | * Channels: 1 Channel (mono) 35 | * Sample Rate: 48000 Hz 36 | 37 | ## Preprocessor Definitions 38 | 39 | BUFFER_SIZE=1024 (or whatever you want the maximum supported buffer size to be) 40 | MAX_STR_SIZE=32 (maximum length of strings being formatted and returned) 41 | --------------------------------------------------------------------------------