Skip to content

Commit fd07b44

Browse files
committed
Resample output in real-time when sample rate != 44100
Here I introduce a resampler algorithm created by @cpuimage, which is fast and reliable. MIT licensed. Resampler Principle: to make sure that resampled audio frames can just fill in the whole buffer, without any empty buffer frame.
1 parent 346fde3 commit fd07b44

File tree

6 files changed

+302
-2
lines changed

6 files changed

+302
-2
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ dpf_add_plugin (${PROJECT_NAME}
9898
plugin/MinatonPlugin.cpp
9999
plugin/MinatonProcess.cpp
100100
plugin/MinatonParamAccess.cpp
101+
vendor/resampler/resampler.cpp
101102
FILES_UI
102103
plugin/MinatonUI.cpp
103104
plugin/MinatonUIHelper.cpp
@@ -110,6 +111,7 @@ target_include_directories (${PROJECT_NAME} PUBLIC
110111
plugin/
111112
src/
112113
${GENERATED_WAVES_DIR}
114+
vendor/resampler
113115
)
114116

115117
add_dependencies (${PROJECT_NAME}-dsp generate_waves)

plugin/MinatonPlugin.cpp

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include "DistrhoPlugin.hpp"
33
#include "MinatonParams.h"
44

5+
#include "resampler.hpp"
6+
57
START_NAMESPACE_DISTRHO
68

79
MinatonPlugin::MinatonPlugin()
@@ -33,11 +35,16 @@ MinatonPlugin::MinatonPlugin()
3335
fSynthesizer->release_envelope2();
3436
fSynthesizer->envelope1.level = 0;
3537
fSynthesizer->envelope2.level = 0;
38+
39+
// Initialize resample buffer
40+
initResampler(getBufferSize());
3641
}
3742

3843
MinatonPlugin::~MinatonPlugin()
3944
{
4045
fSynthesizer->cleanup();
46+
47+
cleanupResampler();
4148
}
4249

4350
void MinatonPlugin::initParameter(uint32_t index, Parameter& parameter)
@@ -98,13 +105,91 @@ void MinatonPlugin::run(const float** inputs, float** outputs, uint32_t frames,
98105
return;
99106
}
100107

101-
for (unsigned int x = 0; x < frames; x++) {
102-
_processAudioFrame(outputs[0], outputs[1], x);
108+
// Generate and output audio frames.
109+
// - If sample rate == 44100.0f (default sample rate), output frames as-is.
110+
// - If not, perform a resample on final mix.
111+
if (fSampleRate == 44100.0f) {
112+
for (unsigned int x = 0; x < frames; x++) {
113+
_processAudioFrame(outputs[0], outputs[1], x);
114+
}
115+
} else {
116+
// Resample principle: Fill in the audio buffer with resampled frames.
117+
// For example, audio buffer's size is 512, and target sample rate is 96000 Hz.
118+
// 1. Find out an input sample count so that resampler can output just 512 samples.
119+
// Result is 235.2.
120+
// 2. Run _processAudioFrame(), and generate 235 samples (decimal is floored).
121+
// 3. Run Resample_f32() to resample two channels. It will output 512 samples.
122+
// 4. Put the resampled frames to audio buffer.
123+
// Based on @cpuimage's resample algorithm.
124+
125+
const double expect_input_size = getExpectedInputSize(44100.0f, fSampleRate, frames);
126+
127+
for (uint32_t x = 0; x < expect_input_size; x++) {
128+
_processAudioFrame(buffer_before_resample_l, buffer_before_resample_r, x);
129+
}
130+
131+
// Process each channel respectively.
132+
// Note: The resampler functions are originally designed for interleaved WAV files.
133+
const int channels = 1;
134+
Resample_f32(buffer_before_resample_l, buffer_after_resample_l, 44100, int(fSampleRate), expect_input_size / channels, channels);
135+
Resample_f32(buffer_before_resample_r, buffer_after_resample_r, 44100, int(fSampleRate), expect_input_size / channels, channels);
136+
137+
// NOTICE: Do not use memcpy(). Use for-loop.
138+
// Otherwise the output samples will not be continuous!
139+
for (uint32_t x = 0; x < frames; x++) {
140+
outputs[0][x] = buffer_after_resample_l[x];
141+
outputs[1][x] = buffer_after_resample_r[x];
142+
}
143+
}
144+
}
145+
146+
void MinatonPlugin::bufferSizeChanged(int newBufferSize)
147+
{
148+
if (fBufferSize != newBufferSize) {
149+
d_stderr("[DSP] Buffer size changed: from %d to %d", fBufferSize, newBufferSize);
150+
151+
fBufferSize = newBufferSize;
152+
reinitResampler(fBufferSize, fSampleRate);
153+
} else {
154+
d_stderr("[DSP] Buffer size changed: same as current value, %d", fBufferSize);
103155
}
104156
}
105157

106158
void MinatonPlugin::sampleRateChanged(double newSampleRate)
107159
{
160+
if (fSampleRate != newSampleRate) {
161+
d_stderr("[DSP] Sample rate changed: from %f to %f", fSampleRate, newSampleRate);
162+
163+
fSampleRate = newSampleRate;
164+
reinitResampler(fBufferSize, fSampleRate);
165+
} else {
166+
d_stderr("[DSP] Sample rate changed: same as current value, %f", fSampleRate);
167+
}
168+
}
169+
170+
void MinatonPlugin::initResampler(uint32_t bufferSize)
171+
{
172+
buffer_before_resample_l = (float*)malloc(sizeof(float) * bufferSize);
173+
buffer_before_resample_r = (float*)malloc(sizeof(float) * bufferSize);
174+
buffer_after_resample_l = (float*)malloc(sizeof(float) * bufferSize);
175+
buffer_after_resample_r = (float*)malloc(sizeof(float) * bufferSize);
176+
}
177+
178+
void MinatonPlugin::reinitResampler(uint32_t bufferSize, uint32_t sampleRate)
179+
{
180+
buffer_before_resample_l = (float*)realloc(buffer_before_resample_l, sizeof(float) * bufferSize);
181+
buffer_before_resample_r = (float*)realloc(buffer_before_resample_r, sizeof(float) * bufferSize);
182+
183+
buffer_after_resample_l = (float*)realloc(buffer_after_resample_l, sizeof(float) * bufferSize);
184+
buffer_after_resample_r = (float*)realloc(buffer_after_resample_r, sizeof(float) * bufferSize);
185+
}
186+
187+
void MinatonPlugin::cleanupResampler()
188+
{
189+
free(buffer_before_resample_l);
190+
free(buffer_before_resample_r);
191+
free(buffer_after_resample_l);
192+
free(buffer_after_resample_r);
108193
}
109194

110195
Plugin* createPlugin()

plugin/MinatonPlugin.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ START_NAMESPACE_DISTRHO
1111

1212
class MinatonPlugin : public Plugin {
1313
double fSampleRate = getSampleRate();
14+
double fBufferSize = getBufferSize();
1415
std::unique_ptr<minaton_synth_dpf> fSynthesizer = std::make_unique<minaton_synth_dpf>();
1516

1617
// Minaton note key data
@@ -21,6 +22,10 @@ class MinatonPlugin : public Plugin {
2122
int last_note;
2223
int control_delay;
2324

25+
// Resampler data
26+
float *buffer_before_resample_l, *buffer_before_resample_r;
27+
float *buffer_after_resample_l, *buffer_after_resample_r;
28+
2429
public:
2530
MinatonPlugin();
2631
~MinatonPlugin();
@@ -103,6 +108,7 @@ class MinatonPlugin : public Plugin {
103108
// ----------------------------------------------------------------------------------------------------------------
104109
// Callbacks (optional)
105110

111+
void bufferSizeChanged(int newBufferSize);
106112
void sampleRateChanged(double newSampleRate) override;
107113

108114
private:
@@ -112,6 +118,10 @@ class MinatonPlugin : public Plugin {
112118
float _obtainSynthParameter(MinatonParamId index) const;
113119
void _applySynthParameter(MinatonParamId index, float value);
114120

121+
void initResampler(uint32_t bufferSize);
122+
void reinitResampler(uint32_t bufferSize, uint32_t sampleRate);
123+
void cleanupResampler();
124+
115125
// ----------------------------------------------------------------------------------------------------------------
116126
// Processors
117127

vendor/resampler/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# resampler
2+
A Simple and Efficient Audio Resampler Implementation in C. Created by [@cpuimage](https://github.com/cpuimage).
3+
4+
**NOTICE:** This edition has AnClark's extensions dedicated to Minaton-XT:
5+
6+
- `getExpectedInputSize()`: Given a target output buffer size, calculate how many input frames shall we have.
7+
- `Resample_f32()` variant: Receive float-point value as `inputSize`, because `getExpectedInputSize()` usually returns a float-point value as well.
8+
9+
Example
10+
=======
11+
12+
```C
13+
uint64_t Resample_f32(const float *input, float *output, int inSampleRate, int outSampleRate, uint64_t inputSize,
14+
uint32_t channels
15+
) {
16+
if (input == NULL)
17+
return 0;
18+
uint64_t outputSize = (uint64_t) (inputSize * (double) outSampleRate / (double) inSampleRate);
19+
outputSize -= outputSize % channels;
20+
if (output == NULL)
21+
return outputSize;
22+
double stepDist = ((double) inSampleRate / (double) outSampleRate);
23+
const uint64_t fixedFraction = (1LL << 32);
24+
const double normFixed = (1.0 / (1LL << 32));
25+
uint64_t step = ((uint64_t) (stepDist * fixedFraction + 0.5));
26+
uint64_t curOffset = 0;
27+
for (uint32_t i = 0; i < outputSize; i += 1) {
28+
for (uint32_t c = 0; c < channels; c += 1) {
29+
*output++ = (float) (input[c] + (input[c + channels] - input[c]) * (
30+
(double) (curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)
31+
)
32+
);
33+
}
34+
curOffset += step;
35+
input += (curOffset >> 32) * channels;
36+
curOffset &= (fixedFraction - 1);
37+
}
38+
return outputSize;
39+
}
40+
41+
42+
uint64_t Resample_s16(const int16_t *input, int16_t *output, int inSampleRate, int outSampleRate, uint64_t inputSize,
43+
uint32_t channels
44+
) {
45+
if (input == NULL)
46+
return 0;
47+
uint64_t outputSize = (uint64_t) (inputSize * (double) outSampleRate / (double) inSampleRate);
48+
outputSize -= outputSize % channels;
49+
if (output == NULL)
50+
return outputSize;
51+
double stepDist = ((double) inSampleRate / (double) outSampleRate);
52+
const uint64_t fixedFraction = (1LL << 32);
53+
const double normFixed = (1.0 / (1LL << 32));
54+
uint64_t step = ((uint64_t) (stepDist * fixedFraction + 0.5));
55+
uint64_t curOffset = 0;
56+
for (uint32_t i = 0; i < outputSize; i += 1) {
57+
for (uint32_t c = 0; c < channels; c += 1) {
58+
*output++ = (int16_t) (input[c] + (input[c + channels] - input[c]) * (
59+
(double) (curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)
60+
)
61+
);
62+
}
63+
curOffset += step;
64+
input += (curOffset >> 32) * channels;
65+
curOffset &= (fixedFraction - 1);
66+
}
67+
return outputSize;
68+
}
69+
```
70+
71+
72+
73+
74+
# Donating
75+
76+
If you found this project useful, consider buying me (@cpuimage) a coffee:
77+
78+
<a href="https://www.buymeacoffee.com/gaozhihan" target="_blank"><img src="https://img2018.cnblogs.com/blog/824862/201809/824862-20180930223603138-1708589189.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>

vendor/resampler/resampler.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Simple Resampler
3+
* https://github.com/cpuimage/resampler.git
4+
*
5+
* License: MIT
6+
*/
7+
8+
#include "resampler.hpp"
9+
10+
#include <stdio.h>
11+
12+
uint64_t Resample_f32(const float* input, float* output, int inSampleRate, int outSampleRate, uint64_t inputSize,
13+
uint32_t channels)
14+
{
15+
if (input == NULL)
16+
return 0;
17+
uint64_t outputSize = (uint64_t)(inputSize * (double)outSampleRate / (double)inSampleRate);
18+
outputSize -= outputSize % channels;
19+
if (output == NULL)
20+
return outputSize;
21+
double stepDist = ((double)inSampleRate / (double)outSampleRate);
22+
const uint64_t fixedFraction = (1LL << 32);
23+
const double normFixed = (1.0 / (1LL << 32));
24+
uint64_t step = ((uint64_t)(stepDist * fixedFraction + 0.5));
25+
uint64_t curOffset = 0;
26+
for (uint32_t i = 0; i < outputSize; i += 1) {
27+
for (uint32_t c = 0; c < channels; c += 1) {
28+
*output++ = (float)(input[c] + (input[c + channels] - input[c]) * ((double)(curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)));
29+
}
30+
curOffset += step;
31+
input += (curOffset >> 32) * channels;
32+
curOffset &= (fixedFraction - 1);
33+
}
34+
return outputSize;
35+
}
36+
37+
uint64_t Resample_f32(const float* input, float* output, int inSampleRate, int outSampleRate, double inputSize,
38+
uint32_t channels)
39+
{
40+
if (input == NULL)
41+
return 0;
42+
43+
/**
44+
* POLYFILL:
45+
* C/C++ has float decision issues.
46+
* For example, (44100.0f / 96000.0f * 512) should equal 235.2, but in C/C++ it equals 235.199997.
47+
*
48+
* Since audio sample rates are canonical values, calculating input/output buffer size will get
49+
* a number with only 1 decimal place. So simply round it.
50+
*
51+
* NOTICE: inputSize should always be a value calculated by getExpectedInputSize(),
52+
* and make sure you have enough space for output buffer,
53+
* otherwise you may encounter unexpected behaviors (out-of-range, stitches, etc.)!
54+
*/
55+
uint64_t outputSize = (uint64_t)round(inputSize * (double)outSampleRate / (double)inSampleRate);
56+
outputSize -= outputSize % channels;
57+
if (output == NULL)
58+
return outputSize;
59+
60+
double stepDist = ((double)inSampleRate / (double)outSampleRate);
61+
const uint64_t fixedFraction = (1LL << 32);
62+
const double normFixed = (1.0 / (1LL << 32));
63+
uint64_t step = ((uint64_t)(stepDist * fixedFraction + 0.5));
64+
uint64_t curOffset = 0;
65+
for (uint32_t i = 0; i < outputSize; i += 1) {
66+
for (uint32_t c = 0; c < channels; c += 1) {
67+
*output++ = (float)(input[c] + (input[c + channels] - input[c]) * ((double)(curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)));
68+
}
69+
curOffset += step;
70+
input += (curOffset >> 32) * channels;
71+
curOffset &= (fixedFraction - 1);
72+
}
73+
return outputSize;
74+
}
75+
76+
uint64_t Resample_s16(const int16_t* input, int16_t* output, int inSampleRate, int outSampleRate, uint64_t inputSize,
77+
uint32_t channels)
78+
{
79+
if (input == NULL)
80+
return 0;
81+
uint64_t outputSize = (uint64_t)(inputSize * (double)outSampleRate / (double)inSampleRate);
82+
outputSize -= outputSize % channels;
83+
if (output == NULL)
84+
return outputSize;
85+
double stepDist = ((double)inSampleRate / (double)outSampleRate);
86+
const uint64_t fixedFraction = (1LL << 32);
87+
const double normFixed = (1.0 / (1LL << 32));
88+
uint64_t step = ((uint64_t)(stepDist * fixedFraction + 0.5));
89+
uint64_t curOffset = 0;
90+
for (uint32_t i = 0; i < outputSize; i += 1) {
91+
for (uint32_t c = 0; c < channels; c += 1) {
92+
*output++ = (int16_t)(input[c] + (input[c + channels] - input[c]) * ((double)(curOffset >> 32) + ((curOffset & (fixedFraction - 1)) * normFixed)));
93+
}
94+
curOffset += step;
95+
input += (curOffset >> 32) * channels;
96+
curOffset &= (fixedFraction - 1);
97+
}
98+
return outputSize;
99+
}

vendor/resampler/resampler.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Simple Resampler
3+
* https://github.com/cpuimage/resampler.git
4+
*
5+
* License: MIT
6+
*/
7+
8+
#pragma once
9+
10+
#include <cmath>
11+
#include <cstddef>
12+
#include <cstdint>
13+
14+
constexpr double getExpectedInputSize(float inSampleRate, float outSampleRate, uint32_t targetOutSize)
15+
{
16+
return (inSampleRate / outSampleRate) * targetOutSize;
17+
}
18+
19+
uint64_t Resample_f32(const float* input, float* output, int inSampleRate, int outSampleRate, uint64_t inputSize,
20+
uint32_t channels);
21+
22+
uint64_t Resample_f32(const float* input, float* output, int inSampleRate, int outSampleRate, double inputSize,
23+
uint32_t channels);
24+
25+
uint64_t Resample_s16(const int16_t* input, int16_t* output, int inSampleRate, int outSampleRate, uint64_t inputSize,
26+
uint32_t channels);

0 commit comments

Comments
 (0)