Skip to content

Commit 89ed700

Browse files
committed
Implement lowpass filter when down sampling
1 parent c5b94a1 commit 89ed700

File tree

4 files changed

+90
-16
lines changed

4 files changed

+90
-16
lines changed

NeuralRack/engine/StreamingResampler.h

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,26 @@
1414
#include <cstring>
1515

1616

17+
/****************************************************************
18+
StreamingResampler - cubic hermite resampler with nyquist lowpass in down sampler
19+
*****************************************************************/
20+
1721
class StreamingResampler {
1822
public:
1923
StreamingResampler() = default;
2024

2125
void setup(uint32_t channels, uint32_t maxBlockSize, uint32_t fs_in, uint32_t fs_out) {
2226
chan = channels;
27+
28+
downSample = fs_in > fs_out;
29+
if (downSample) {
30+
lpf.resize(chan);
31+
float cutoff = 0.45f * fs_out;
32+
for (uint32_t ch = 0; ch < chan; ++ch) {
33+
lpf[ch].setup((float)fs_in, cutoff);
34+
}
35+
}
36+
2337
ratio = double(fs_in) / double(fs_out);
2438
bufferSize = maxBlockSize + 8;
2539
buffer.resize(bufferSize * chan, 0.0f);
@@ -30,10 +44,13 @@ class StreamingResampler {
3044
readPos = 0.0;
3145
buffered = 0;
3246
std::fill(buffer.begin(), buffer.end(), 0.0f);
47+
for (auto& f : lpf) f.reset();
3348
}
3449

3550
void setSampleRates(uint32_t fs_in, uint32_t fs_out) {
3651
ratio = double(fs_in) / double(fs_out);
52+
bool useLPF = (fs_in > fs_out);
53+
if (useLPF != downSample) setup(chan, bufferSize / chan, fs_in, fs_out);
3754
}
3855

3956
uint32_t getOutSize(uint32_t inFrames) const {
@@ -70,6 +87,32 @@ class StreamingResampler {
7087
}
7188

7289
private:
90+
struct LPF {
91+
float a0, a1, a2, b1, b2;
92+
float z1 = 0.0f, z2 = 0.0f;
93+
94+
void setup(float sampleRate, float cutoff) {
95+
float K = std::tan(M_PI * cutoff / sampleRate);
96+
float norm = 1.0f / (1.0f + std::sqrt(2.0f)*K + K*K);
97+
a0 = K*K * norm;
98+
a1 = 2.0f * a0;
99+
a2 = a0;
100+
b1 = 2.0f * (K*K - 1.0f) * norm;
101+
b2 = (1.0f - std::sqrt(2.0f)*K + K*K) * norm;
102+
}
103+
104+
inline float process(float x) {
105+
float y = a0*x + z1;
106+
z1 = a1*x - b1*y + z2;
107+
z2 = a2*x - b2*y;
108+
return y;
109+
}
110+
111+
void reset() { z1 = z2 = 0.0f; }
112+
};
113+
114+
std::vector<LPF> lpf;
115+
bool downSample = false;
73116
uint32_t chan = 0;
74117
double ratio = 1.0;
75118
std::vector<float> buffer;
@@ -80,8 +123,9 @@ class StreamingResampler {
80123
void append(const float* input, uint32_t frames) {
81124
for (uint32_t i = 0; i < frames; ++i) {
82125
for (uint32_t ch = 0; ch < chan; ++ch) {
83-
buffer[(buffered + i) * chan + ch] =
84-
input[i * chan + ch];
126+
float s = input[i * chan + ch];
127+
if (downSample) s = lpf[ch].process(s);
128+
buffer[(buffered + i) * chan + ch] = s;
85129
}
86130
}
87131
buffered += frames;

NeuralRack/gui/widgets.cc

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,29 @@ char* utf8crop(char* dst, const char* src, size_t sizeDest ) {
140140
return dst;
141141
}
142142

143+
void utf8crop_middle(char* dst, const char* src, size_t maxLen) {
144+
size_t len = strlen(src);
145+
if (len < maxLen) {
146+
strcpy(dst, src);
147+
return;
148+
}
149+
150+
if (maxLen < 5) {
151+
utf8crop(dst, src, maxLen);
152+
return;
153+
}
154+
155+
size_t left = (maxLen - 3) / 2;
156+
size_t right = maxLen - 3 - left;
157+
char tmp[256];
158+
utf8crop(tmp, src, left + 1);
159+
strcpy(dst, tmp);
160+
strcat(dst, "...");
161+
const char* tail = src + len - right;
162+
utf8crop(tmp, tail, right + 1);
163+
strcat(dst, tmp);
164+
}
165+
143166
// draw the EQ and the Noise Gate window
144167
void draw_eq_window(void *w_, void* user_data) {
145168
Widget_t *w = (Widget_t*)w_;
@@ -250,9 +273,8 @@ void draw_elem(void *w_, void* user_data) {
250273
cairo_set_font_size (w->crb, w->app->big_font-3);
251274
int slen = strlen(basename(m.filename));
252275

253-
if ((slen - 4) > 38) {
254-
utf8crop(label,basename(m.filename), 38);
255-
strcat(label,"...");
276+
if (slen > 38) {
277+
utf8crop_middle(label,basename(m.filename), 38);
256278
tooltip_set_text(m.filebutton,basename(m.filename));
257279
m.filebutton->flags |= HAS_TOOLTIP;
258280
} else {
@@ -323,9 +345,8 @@ void draw_ir_elem(void *w_, void* user_data) {
323345
cairo_set_font_size (w->crb, w->app->big_font-3);
324346
int slen = strlen(basename(ps->ir.filename));
325347

326-
if ((slen - 4) > 36) {
327-
utf8crop(label,basename(ps->ir.filename), 36);
328-
strcat(label,"...");
348+
if (slen > 36) {
349+
utf8crop_middle(label,basename(ps->ir.filename), 36);
329350
tooltip_set_text(ps->ir.filebutton,basename(ps->ir.filename));
330351
ps->ir.filebutton->flags |= HAS_TOOLTIP;
331352
} else {
@@ -346,9 +367,8 @@ void draw_ir_elem(void *w_, void* user_data) {
346367
cairo_set_font_size (w->crb, w->app->big_font-3);
347368
int slen = strlen(basename(ps->ir1.filename));
348369

349-
if ((slen - 4) > 36) {
350-
utf8crop(label,basename(ps->ir1.filename), 36);
351-
strcat(label,"...");
370+
if (slen > 36) {
371+
utf8crop_middle(label,basename(ps->ir1.filename), 36);
352372
tooltip_set_text(ps->ir1.filebutton,basename(ps->ir1.filename));
353373
ps->ir1.filebutton->flags |= HAS_TOOLTIP;
354374
} else {
@@ -1177,9 +1197,8 @@ void draw_my_combobox(void *w_, void* user_data) {
11771197
int slen = strlen(comboboxlist->list_names[vl]);
11781198
widget_set_scale(w);
11791199

1180-
if ((slen - 4) > 45) {
1181-
utf8crop(label,comboboxlist->list_names[vl], 45);
1182-
strcat(label,"...");
1200+
if (slen > 45) {
1201+
utf8crop_middle(label,comboboxlist->list_names[vl], 45);
11831202
tooltip_set_text(w,comboboxlist->list_names[vl]);
11841203
w->flags |= HAS_TOOLTIP;
11851204
} else {

NeuralRack/lv2/NeuralRack.ttl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ nrack:irfile1
9292
pg:group nrack:G6_IR_Right ;
9393
rdfs:range atom:Path .
9494

95+
nrack:eqpos
96+
a lv2:Parameter ;
97+
rdfs:label "EQ position" ;
98+
rdfs:range atom:Int ;
99+
lv2:default 1 ;
100+
lv2:minimum 0 ;
101+
lv2:maximum 2 .
102+
95103
<urn:brummer:neuralrack>
96104
a lv2:Plugin ,
97105
lv2:SimulatorPlugin ;
@@ -107,8 +115,8 @@ nrack:irfile1
107115
opts:supportedOption bufsz:maxBlockLength ;
108116
lv2:extensionData work:interface ,
109117
state:interface ;
110-
lv2:minorVersion 2 ;
111-
lv2:microVersion 0 ;
118+
lv2:minorVersion 3 ;
119+
lv2:microVersion 2 ;
112120

113121
guiext:ui <urn:brummer:neuralrack_ui> ;
114122

@@ -118,6 +126,8 @@ patch:writable nrack:Neural_Model1 ;
118126
patch:writable nrack:irfile ;
119127
patch:writable nrack:irfile1 ;
120128

129+
patch:writable nrack:eqpos;
130+
121131
rdfs:comment """
122132
Neuralrack is a Neural Model loader with support for *.nam, *.aidax and *.json based model files.
123133
Neuralrack allow to load up to two model files and run them serial.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ For Impulse Response File Convolution it use [FFTConvolver](https://github.com/H
1616
Resampling is done by [Libzita-resampler](https://kokkinizita.linuxaudio.org/linuxaudio/zita-resampler/resampler.html)
1717

1818
NeuralRack emulate a simple guitar effect chain with a pedal, a EQ a Amplifier and a Stereo Cabinet.
19+
The EQ could be moved by drag and drop to act as a pre or post EQ.
1920

2021
Optional, NeuralRack could run one Model, or the complete process in buffered mode to reduce the dsp load.
2122
The resulting latency will be reported to the host so that it could be compensated.

0 commit comments

Comments
 (0)