Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library with Decimator and Interpolator classes #677

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/Audio/order.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"filterbanks",
"FFT-phase-vocoder",
"sample-streamer",
"sample-streamer-multi"
"sample-streamer-multi",
"resample"
]
100 changes: 100 additions & 0 deletions examples/Audio/resample/render.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
____ _____ _ _
| __ )| ____| | / \
| _ \| _| | | / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/ \_\
http://bela.io
*/
/**
\example Audio/resample/render.cpp

Downsample a signal to process it at a lower sample rate.
=========================================================

This project downsamples a source signal, does some processing at the a lower
sample rate and then upsamples the processed signal again such that it can be
send to the output channel.
The source signal is a simple sine tone. The original signal and the downsampled
signal are send to the scope and can be compared there. Note that the resampling
introduces a small delay due to the anti-aliasing filter that is applied both
during decimation (downsampling) and interpolation (upsampling).
By changing the global variable gDecimationFactor, you can decide the ratio between
the original samplerate and the samplerate after downsampling.
*/

#include <Bela.h>
#include <libraries/AudioFile/AudioFile.h>
#include <libraries/Resample/Resample.h>
#include <libraries/Scope/Scope.h>

const float gFrequency = 440.0;
const unsigned int gDecimationFactor = 4;
const unsigned int gOutputChannel = 0;

Scope scope;
Decimator decimator;
Interpolator interpolator;

unsigned int gBlockSizeResampled;
float gPhase;
float gInverseSampleRate;

float sinetone() {
float out = 0.8f * sinf(gPhase);
gPhase += 2.0f * (float)M_PI * gFrequency * gInverseSampleRate;
if (gPhase > M_PI)
gPhase -= 2.0f * (float)M_PI;
return out;
}

bool setup(BelaContext* context, void* userData) {
gBlockSizeResampled = context->audioFrames / gDecimationFactor;
scope.setup(2, context->audioSampleRate);

if (decimator.setup(gDecimationFactor, context->audioFrames, ResampleBase::fir_quality::high, ResampleBase::fir_phase::linear)) {
return false;
};
if (interpolator.setup(gDecimationFactor, gBlockSizeResampled, ResampleBase::fir_quality::high, ResampleBase::fir_phase::linear)) {
return false;
};

gInverseSampleRate = 1.0 / context->audioSampleRate;
gPhase = 0.0;

return true;
}

void render(BelaContext* context, void* userData) {

// source is a sine tone
float inOutBuf[context->audioFrames];
for (unsigned int n = 0; n < context->audioFrames; ++n) {
inOutBuf[n] = sinetone();
}

// downsample
float downsampledBuf[gBlockSizeResampled];
decimator.process(downsampledBuf, inOutBuf);

// do some processing at a lower sampling rate
for (unsigned int n = 0; n < gBlockSizeResampled; n++) {
downsampledBuf[n] *= 1; // obviously this doen't do anything
}

// inspect source and resampled signal in scope
for (unsigned int n = 0; n < context->audioFrames; n++) {
scope.log(inOutBuf[n], downsampledBuf[n / gDecimationFactor]);
}

// upsample before output
interpolator.process(inOutBuf, downsampledBuf);

// play at output channel
for (unsigned int n = 0; n < context->audioFrames; ++n) {
audioWrite(context, n, gOutputChannel, inOutBuf[n]);
}
}

void cleanup(BelaContext* context, void* userData) {
}
205 changes: 205 additions & 0 deletions libraries/Resample/Resample.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include <iostream>
#include <math.h> // ceil
#include <string.h> // memcpy

#define ENABLE_NE10_FIR_DECIMATE_FLOAT_NEON // Defines needed for Ne10 library
#define ENABLE_NE10_FIR_INTERPOLATE_FLOAT_NEON
#include <libraries/ne10/NE10.h> // neon library, must be included before `Resample.h` (or anything else that #includes NE10.h)

#include "Resample.h"
#include "aa_fir.h"

std::vector<float> ResampleBase::get_fir(unsigned int factor, fir_quality quality, fir_phase phase) {
if (factor == 2 && quality == high && phase == minimum)
return fir_2_high_minphase;
else if (factor == 2 && quality == high && phase == linear)
return fir_2_high_linear;
else if (factor == 2 && quality == low && phase == minimum)
return fir_2_low_minphase;
else if (factor == 2 && quality == low && phase == linear)
return fir_2_low_linear;
else if (factor == 4 && quality == high && phase == minimum)
return fir_4_high_minphase;
else if (factor == 4 && quality == high && phase == linear)
return fir_4_high_linear;
else if (factor == 4 && quality == low && phase == minimum)
return fir_4_low_minphase;
else if (factor == 4 && quality == low && phase == linear)
return fir_4_low_linear;
else if (factor == 8 && quality == high && phase == minimum)
return fir_8_high_minphase;
else if (factor == 8 && quality == high && phase == linear)
return fir_8_high_linear;
else if (factor == 8 && quality == low && phase == minimum)
return fir_8_low_minphase;
else if (factor == 8 && quality == low && phase == linear)
return fir_8_low_linear;
else if (factor == 16 && quality == high && phase == minimum)
return fir_16_high_minphase;
else if (factor == 16 && quality == high && phase == linear)
return fir_16_high_linear;
else if (factor == 16 && quality == low && phase == minimum)
return fir_16_low_minphase;
else if (factor == 16 && quality == low && phase == linear)
return fir_16_low_linear;
else {
std::cerr << "get_fir: invalid combination " << factor << quality << phase << std::endl;
return std::vector<float>();
}
}

void ResampleBase::cleanup() {
NE10_FREE(pCoeff);
NE10_FREE(pState);
pCoeff = nullptr;
pState = nullptr;
}

int Decimator::setup(unsigned int decimationFactor, unsigned int blockSize, fir_quality quality, fir_phase phase) {
cleanup();
this->blockSizeIn = blockSize;
this->factor = decimationFactor;
if (decimationFactor == 1) {
// Don't do anything
return 0;
}
std::vector<float> fir = get_fir(decimationFactor, quality, phase);
uint numTaps = fir.size();

pCoeff = (ne10_float32_t*)NE10_MALLOC(numTaps * sizeof(ne10_float32_t));
pState = (ne10_float32_t*)NE10_MALLOC(
(numTaps + blockSize - 1) * sizeof(ne10_float32_t));
if (!pState || !pCoeff)
return -1;

for (unsigned int n = 0; n < numTaps; ++n) {
// reverse filter coefficients
pCoeff[n] = fir[numTaps - 1 - n];
}
int err = ne10_fir_decimate_init_float(&decimator, numTaps, decimationFactor, pCoeff, pState, blockSize);
if (err) {
std::cerr << "ERR: blockSize must be whole multiple of decimationFactor" << std::endl;
return -1;
}

return 0;
}

void Decimator::process(ne10_float32_t* outBlock, const ne10_float32_t* inBlock) {
if (factor == 1 && outBlock != inBlock) {
memcpy(outBlock, inBlock, blockSizeIn * sizeof(*outBlock));
return;
}
ne10_fir_decimate_float_neon(&decimator, (float*)inBlock, outBlock, blockSizeIn);
}

int Interpolator::setup(unsigned int L, unsigned int blockSize, fir_quality quality, fir_phase phase) {
cleanup();
this->blockSizeIn = blockSize / L;
this->factor = L;
if (L == 1) {
// Don't do anything
return 0;
}
std::vector<float> fir = get_fir(L, quality, phase);
uint filtSize = fir.size();
// numTaps must be a multiple of the interpolation factor
uint numTaps = ceil((float)filtSize / L) * L;
uint phaseLength = numTaps / L;
pCoeff = (ne10_float32_t*)NE10_MALLOC(numTaps * sizeof(ne10_float32_t));
pState = (ne10_float32_t*)NE10_MALLOC(
(blockSizeIn + phaseLength - 1) * sizeof(ne10_float32_t));
if (!pState || !pCoeff) {
return -1;
}

for (unsigned int n = 0; n < filtSize; ++n) {
// reverse fir coefficients
pCoeff[numTaps - filtSize + n] = fir[filtSize - 1 - n];
}
for (uint n = 0; n < numTaps - filtSize; ++n) {
// rest are are zeros
pCoeff[n] = 0;
}

int err = ne10_fir_interpolate_init_float(&interpolator, L, numTaps, pCoeff, pState, blockSizeIn);
if (err) {
std::cerr << "Error: couldn't init interpolator" << std::endl;
return -1;
}

return 0;
}

void Interpolator::process(ne10_float32_t* outBlock, const ne10_float32_t* inBlock) {
if (factor == 1 && outBlock != inBlock) {
memcpy(outBlock, inBlock, blockSizeIn * sizeof(*outBlock));
return;
}
ne10_fir_interpolate_float_neon(&interpolator, (float*)inBlock, outBlock, blockSizeIn);
}

#if 0
#undef NDEBUG
#include <assert.h>
bool DecimatorTest()
{
std::vector<std::vector<float>> irs(2);
const unsigned int len = 1000;
for(unsigned int n = 0; n < len; ++n) {
float val = (len - n) / (float)len;
irs[0].push_back(val);
irs[1].push_back(-val);
}
Decimator c;
Decimator d;
const unsigned int blockSize = 16;
c.setup(irs, blockSize);
d.setup(irs, blockSize);
const unsigned int outChannels = 3;
for(int inChannels = 1; inChannels < 5; ++inChannels)
{
const unsigned int numFrames = 3 * len;
// interleaved buffers
std::vector<float> outs(numFrames * outChannels);
std::vector<float> douts(numFrames * outChannels);
std::vector<float> ins(numFrames * inChannels);
// ins contains impulses
for(unsigned int n = 0; n < inChannels; ++n)
ins[n] = n + 1;
for(unsigned int n = 0; n < numFrames - blockSize; n += blockSize) {
unsigned int outOffset = n * outChannels;
unsigned int inOffset = n * inChannels;
c.processInterleaved(outs.data() + outOffset, ins.data() + inOffset,
blockSize, outChannels, inChannels);
for(unsigned int i = 0; i < inChannels && i < d.getChannels(); ++i)
{
float out[blockSize];
float in[blockSize];
for(unsigned int k = 0; k < blockSize; ++k)
in[k] = ins[numFrames + k * inChannels + i];
d.process(out, in, blockSize, i);
for(unsigned int k = 0; k < blockSize; ++k)
outs[numFrames + k * inChannels + i] = out[k];
}
}
for(unsigned int n = 0; n < numFrames; ++n)
{
for(unsigned int i = 0; i < outChannels; ++i) {
float expected;
if(i < irs.size() && n < irs[i].size() && i < inChannels)
expected = irs[i][n] * (i + 1);
else
expected = 0;
float out = outs[n * outChannels + i];
float dout = outs[n * outChannels + i];
if(out != expected || dout != expected) {
fprintf(stderr, "error at n: %d, ch: %d. inChannels: %d, inVal: %f, expected: %f, got: %f, dgot: %f\n", n, i, inChannels, ins[n * inChannels +i], expected, out, dout);
assert(false);
}
}
}
}
return true;
}
#endif
48 changes: 48 additions & 0 deletions libraries/Resample/Resample.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <libraries/ne10/NE10.h>
#include <vector>

// Abstract base class for decimator and intepolator
class ResampleBase {
public:
enum fir_quality { high,
low };
enum fir_phase { minimum,
linear };
~ResampleBase() { cleanup(); };
virtual int setup(unsigned int factor, unsigned int blockSize, fir_quality quality, fir_phase phase) = 0;
virtual void process(float* outBlock, const float* inBlock) = 0;

protected:
std::vector<float> get_fir(unsigned int factor, fir_quality quality, fir_phase phase);
void cleanup();
unsigned int blockSizeIn;
unsigned int factor;
ne10_float32_t* pCoeff = nullptr;
ne10_float32_t* pState = nullptr;
};

class Decimator : public ResampleBase {
public:
Decimator(){};
Decimator(unsigned int factor, unsigned int blockSize, fir_quality quality, fir_phase phase) {
setup(factor, blockSize, quality, phase);
};
int setup(unsigned int decimationFactor, unsigned int blockSize, fir_quality quality = high, fir_phase phase = linear);
void process(float* outBlock, const float* inBlock);

private:
ne10_fir_decimate_instance_f32_t decimator;
};

class Interpolator : public ResampleBase {
public:
Interpolator(){};
Interpolator(unsigned int factor, unsigned int blockSize, fir_quality quality, fir_phase phase) {
setup(factor, blockSize, quality, phase);
};
int setup(unsigned int interpolationFactor, unsigned int blockSize, fir_quality quality = high, fir_phase phase = linear);
void process(float* outBlock, const float* inBlock);

private:
ne10_fir_interpolate_instance_f32_t interpolator;
};
Loading