├── .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