Skip to content

Commit c5b94a1

Browse files
committed
Fix issue #33, replace zita-resampler with cubic hermite streaming resampler
1 parent 35db14f commit c5b94a1

3 files changed

Lines changed: 124 additions & 27 deletions

File tree

NeuralRack/engine/NeuralModelLoader.cpp

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
namespace neuralrack {
1414

1515
NeuralModelLoader::NeuralModelLoader(std::condition_variable *Sync)
16-
: model(nullptr), smp(), SyncWait(Sync) {
16+
: model(nullptr), SyncWait(Sync) {
1717
NeuralAudio::NeuralModel::SetDefaultMaxAudioBufferSize(4096);
1818
loudness = 0.0;
1919
nGain = 1.0;
@@ -86,28 +86,12 @@ void NeuralModelLoader::compute(uint32_t count, float *input0, float *output0) {
8686
memcpy(buf, output0, count*sizeof(float));
8787

8888
if (needResample ) {
89-
uint32_t ReCounta = count;
90-
if (needResample == 1) {
91-
ReCounta = smp.max_out_count(count);
92-
} else if (needResample == 2) {
93-
ReCounta = static_cast<int>(ceil((count*static_cast<double>(modelSampleRate))/fSampleRate));
94-
}
89+
int ReCounta = toModel.getOutSize(count);
9590
float buf1[ReCounta];
9691
memset(buf1, 0, ReCounta*sizeof(float));
97-
if (needResample == 1) {
98-
ReCounta = smp.up(count, buf, buf1);
99-
} else if (needResample == 2) {
100-
smp.down(buf, buf1);
101-
} else {
102-
memcpy(buf1, buf, ReCounta * sizeof(float));
103-
}
92+
ReCounta = toModel.resample(buf, buf1, count);
10493
model->Process(buf1, buf1, ReCounta);
105-
106-
if (needResample == 1) {
107-
smp.down(buf1, buf);
108-
} else if (needResample == 2) {
109-
smp.up(ReCounta, buf1, buf);
110-
}
94+
toStream.resample(buf1, buf, ReCounta);
11195
} else {
11296
model->Process(buf, buf, count);
11397
}
@@ -174,12 +158,10 @@ bool NeuralModelLoader::loadModel() {
174158
}
175159
modelSampleRate = static_cast<int>(model->GetSampleRate());
176160
if (modelSampleRate <= 0) modelSampleRate = 48000;
177-
if (modelSampleRate > fSampleRate) {
178-
smp.setup(fSampleRate, modelSampleRate);
161+
if (modelSampleRate != fSampleRate) {
162+
toModel.setup(1, 8196, fSampleRate, modelSampleRate);
163+
toStream.setup(1, 8196, modelSampleRate, fSampleRate);
179164
needResample = 1;
180-
} else if (modelSampleRate < fSampleRate) {
181-
smp.setup(modelSampleRate, fSampleRate);
182-
needResample = 2;
183165
}
184166
float* buffer = new float[warmUpSize];
185167
memset(buffer, 0, warmUpSize * sizeof(float));

NeuralRack/engine/NeuralModelLoader.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
#include "NeuralModel.h"
2222

23-
#include "gx_resampler.h"
23+
#include "StreamingResampler.h"
2424

2525
#pragma once
2626

@@ -33,7 +33,8 @@ namespace neuralrack {
3333
class NeuralModelLoader {
3434
private:
3535
NeuralAudio::NeuralModel* model;
36-
gx_resample::FixedRateResampler smp;
36+
StreamingResampler toModel;
37+
StreamingResampler toStream;
3738

3839
std::atomic<bool> ready;
3940
std::atomic<bool> do_ramp;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* StreamingResampler.h
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*
6+
* Copyright (C) 2026 brummer <brummer@web.de>
7+
*/
8+
9+
#pragma once
10+
11+
#include <cstdint>
12+
#include <vector>
13+
#include <cmath>
14+
#include <cstring>
15+
16+
17+
class StreamingResampler {
18+
public:
19+
StreamingResampler() = default;
20+
21+
void setup(uint32_t channels, uint32_t maxBlockSize, uint32_t fs_in, uint32_t fs_out) {
22+
chan = channels;
23+
ratio = double(fs_in) / double(fs_out);
24+
bufferSize = maxBlockSize + 8;
25+
buffer.resize(bufferSize * chan, 0.0f);
26+
reset();
27+
}
28+
29+
void reset() {
30+
readPos = 0.0;
31+
buffered = 0;
32+
std::fill(buffer.begin(), buffer.end(), 0.0f);
33+
}
34+
35+
void setSampleRates(uint32_t fs_in, uint32_t fs_out) {
36+
ratio = double(fs_in) / double(fs_out);
37+
}
38+
39+
uint32_t getOutSize(uint32_t inFrames) const {
40+
return (uint32_t)std::ceil(inFrames / ratio);
41+
}
42+
43+
uint32_t resample(const float* input, float* output, uint32_t inFrames) {
44+
append(input, inFrames);
45+
uint32_t outFrames = 0;
46+
while (true) {
47+
int ip = (int)readPos;
48+
if (ip + 2 >= (int)buffered) break;
49+
float t = float(readPos - ip);
50+
51+
for (uint32_t ch = 0; ch < chan; ++ch) {
52+
float x0 = sample(ip - 1, ch);
53+
float x1 = sample(ip, ch);
54+
float x2 = sample(ip + 1, ch);
55+
float x3 = sample(ip + 2, ch);
56+
output[outFrames * chan + ch] = hermite(x0, x1, x2, x3, t);
57+
}
58+
readPos += ratio;
59+
++outFrames;
60+
}
61+
62+
int consumed = (int)readPos;
63+
64+
if (consumed > 0) {
65+
shiftBuffer(consumed);
66+
readPos -= consumed;
67+
}
68+
69+
return outFrames;
70+
}
71+
72+
private:
73+
uint32_t chan = 0;
74+
double ratio = 1.0;
75+
std::vector<float> buffer;
76+
uint32_t bufferSize = 0;
77+
uint32_t buffered = 0;
78+
double readPos = 0.0;
79+
80+
void append(const float* input, uint32_t frames) {
81+
for (uint32_t i = 0; i < frames; ++i) {
82+
for (uint32_t ch = 0; ch < chan; ++ch) {
83+
buffer[(buffered + i) * chan + ch] =
84+
input[i * chan + ch];
85+
}
86+
}
87+
buffered += frames;
88+
}
89+
90+
void shiftBuffer(uint32_t frames) {
91+
uint32_t remaining = buffered - frames;
92+
93+
std::memmove(buffer.data(), buffer.data() + frames * chan, remaining * chan * sizeof(float));
94+
95+
buffered = remaining;
96+
}
97+
98+
inline float sample(int idx, uint32_t ch) const {
99+
if (idx < 0) return buffer[ch];
100+
101+
if ((uint32_t)idx >= buffered)
102+
return buffer[(buffered - 1) * chan + ch];
103+
104+
return buffer[idx * chan + ch];
105+
}
106+
107+
static inline float hermite(float x0, float x1, float x2, float x3, float t) {
108+
float c0 = x1;
109+
float c1 = 0.5f * (x2 - x0);
110+
float c2 = x0 - 2.5f * x1 + 2.0f * x2 - 0.5f * x3;
111+
float c3 = 0.5f * (x3 - x0) + 1.5f * (x1 - x2);
112+
return ((c3*t + c2)*t + c1)*t + c0;
113+
}
114+
};

0 commit comments

Comments
 (0)