Commit 7d377894 authored by David Reid's avatar David Reid

Resampling and LPF work.

parent df2903a0
...@@ -5,21 +5,45 @@ ...@@ -5,21 +5,45 @@
TODO: TODO:
- Document passthrough behaviour of the biquad filter and how it doesn't update previous inputs and outputs. - Document passthrough behaviour of the biquad filter and how it doesn't update previous inputs and outputs.
- Document how changing biquad constants requires reinitialization of the filter (due to issue above). - Document how changing biquad constants requires reinitialization of the filter (due to issue above).
- Document how ma_biquad_process() and ma_lpf_process() supports in-place filtering by passing in the same buffer for both the input and output.
*/ */
typedef struct typedef struct
{ {
ma_format format; ma_format format;
ma_uint32 channels; ma_uint32 channels;
float a0; double a0;
float a1; double a1;
float a2; double a2;
float b0; double b0;
float b1; double b1;
float b2; double b2;
#if 0
union
{
struct
{
double a0;
double a1;
double a2;
double b0;
double b1;
double b2;
} f32;
struct
{
ma_int32 a0;
ma_int32 a1;
ma_int32 a2;
ma_int32 b0;
ma_int32 b1;
ma_int32 b2;
} s16;
} constants;
#endif
} ma_biquad_config; } ma_biquad_config;
ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channels, float a0, float a1, float a2, float b0, float b1, float b2); ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channels, double a0, double a1, double a2, double b0, double b1, double b2);
typedef struct typedef struct
{ {
...@@ -62,7 +86,7 @@ ma_result ma_lpf_process(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ...@@ -62,7 +86,7 @@ ma_result ma_lpf_process(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn,
#if defined(MINIAUDIO_IMPLEMENTATION) #if defined(MINIAUDIO_IMPLEMENTATION)
ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channels, float a0, float a1, float a2, float b0, float b1, float b2) ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channels, double a0, double a1, double a2, double b0, double b1, double b2)
{ {
ma_biquad_config config; ma_biquad_config config;
...@@ -121,11 +145,11 @@ ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFrame ...@@ -121,11 +145,11 @@ ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFrame
{ {
ma_uint32 n; ma_uint32 n;
ma_uint32 c; ma_uint32 c;
float a1 = pBQ->config.a1; double a1 = pBQ->config.a1;
float a2 = pBQ->config.a2; double a2 = pBQ->config.a2;
float b0 = pBQ->config.b0; double b0 = pBQ->config.b0;
float b1 = pBQ->config.b1; double b1 = pBQ->config.b1;
float b2 = pBQ->config.b2; double b2 = pBQ->config.b2;
if (pBQ == NULL || pFramesOut == NULL || pFramesIn == NULL) { if (pBQ == NULL || pFramesOut == NULL || pFramesIn == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
...@@ -149,18 +173,18 @@ ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFrame ...@@ -149,18 +173,18 @@ ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFrame
for (n = 0; n < frameCount; n += 1) { for (n = 0; n < frameCount; n += 1) {
for (c = 0; c < pBQ->config.channels; c += 1) { for (c = 0; c < pBQ->config.channels; c += 1) {
float x2 = pBQ->x2[c]; double x2 = pBQ->x2[c];
float x1 = pBQ->x1[c]; double x1 = pBQ->x1[c];
float x0 = pX[n*pBQ->config.channels + c]; double x0 = pX[n*pBQ->config.channels + c];
float y2 = pBQ->y2[c]; double y2 = pBQ->y2[c];
float y1 = pBQ->y1[c]; double y1 = pBQ->y1[c];
float y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2; double y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2;
pY[n*pBQ->config.channels + c] = y0; pY[n*pBQ->config.channels + c] = (float)y0;
pBQ->x2[c] = x1; pBQ->x2[c] = (float)x1;
pBQ->x1[c] = x0; pBQ->x1[c] = (float)x0;
pBQ->y2[c] = y1; pBQ->y2[c] = (float)y1;
pBQ->y1[c] = y0; pBQ->y1[c] = (float)y0;
} }
} }
} else { } else {
...@@ -212,12 +236,12 @@ ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) ...@@ -212,12 +236,12 @@ ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF)
c = cos(w); c = cos(w);
a = s / (2*q); a = s / (2*q);
bqConfig.a0 = (float)( 1 + a); bqConfig.a0 = (double)( 1 + a);
bqConfig.a1 = (float)(-2 * c); bqConfig.a1 = (double)(-2 * c);
bqConfig.a2 = (float)( 1 - a); bqConfig.a2 = (double)( 1 - a);
bqConfig.b0 = (float)((1 - c) / 2); bqConfig.b0 = (double)((1 - c) / 2);
bqConfig.b1 = (float)( 1 - c); bqConfig.b1 = (double)( 1 - c);
bqConfig.b2 = (float)((1 - c) / 2); bqConfig.b2 = (double)((1 - c) / 2);
bqConfig.format = pConfig->format; bqConfig.format = pConfig->format;
bqConfig.channels = pConfig->channels; bqConfig.channels = pConfig->channels;
......
...@@ -203,88 +203,104 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma ...@@ -203,88 +203,104 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma
{ {
ma_uint64 frameCountOut; ma_uint64 frameCountOut;
ma_uint64 frameCountIn; ma_uint64 frameCountIn;
ma_uint64 iFrameOut;
ma_uint64 iFrameIn;
ma_uint64 iChannel;
float ratioInOut; float ratioInOut;
MA_ASSERT(pResampler != NULL); MA_ASSERT(pResampler != NULL);
MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesOut != NULL);
MA_ASSERT(pFrameCountOut != NULL); MA_ASSERT(pFrameCountOut != NULL);
MA_ASSERT(pFramesIn != NULL);
MA_ASSERT(pFrameCountIn != NULL); MA_ASSERT(pFrameCountIn != NULL);
frameCountOut = *pFrameCountOut; frameCountOut = *pFrameCountOut;
frameCountIn = *pFrameCountIn; frameCountIn = *pFrameCountIn;
ratioInOut = (float)pResampler->config.sampleRateIn / (float)pResampler->config.sampleRateOut; if (frameCountOut == 0 || frameCountIn == 0) {
return MA_INVALID_ARGS; /* Nothing to do. */
if (pFramesIn != NULL) { }
/* Pass in data from the input buffer. */
ma_uint64 iFrameOut;
ma_uint64 iFrameIn;
ma_uint64 iChannel;
iFrameOut = 0; ratioInOut = (float)pResampler->config.sampleRateIn / (float)pResampler->config.sampleRateOut;
iFrameIn = 0;
if (pResampler->config.format == ma_format_f32) { iFrameOut = 0;
float* pY = ( float*)pFramesOut; iFrameIn = 0;
const float* pX = (const float*)pFramesIn;
/* if (pResampler->config.format == ma_format_f32) {
We need to do an initial load of input data so that the first output frame is the same as the input frame. We can know whether or not to do this by float* pY = ( float*)pFramesOut;
checking whether or not the current time is < 0 (it will be initialized to -1). const float* pX = (const float*)pFramesIn;
*/
if (pResampler->state.linear.t < 0) {
if (frameCountIn > 0) {
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
pResampler->state.linear.x1[iChannel] = pX[iChannel];
}
iFrameIn += 1;
pResampler->state.linear.t = 1; /* Important that we set this to 1. This will cause the logic below to load the _second_ frame so we can do correct interpolation. */ /*
We need to do an initial load of input data so that the first output frame is the same as the input frame. We can know whether or not to do this by
checking whether or not the current time is < 0 (it will be initialized to -1).
*/
if (pResampler->state.linear.t < 0) {
if (frameCountIn > 0) {
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
pResampler->state.linear.x1[iChannel] = pX[iChannel];
} }
iFrameIn += 1;
pResampler->state.linear.t = 1; /* Important that we set this to 1. This will cause the logic below to load the _second_ frame so we can do correct interpolation. */
} }
}
for (;;) { for (;;) {
float t0; if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
float t1; break;
float y; }
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) { /* We can't interpolate if our interpolation factor (time relative to x0) is greater than 1. */
break; if (pResampler->state.linear.t > 1) {
} /* Need to load the next input frame. */
iFrameIn += (ma_uint64)pResampler->state.linear.t;
if (iFrameIn < frameCountIn) {
/* We have enough input frames remaining to bring the time down to 0..1. */
MA_ASSERT(iFrameIn > 0);
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
y = ma_mix_f32_fast(pResampler->state.linear.x0[iChannel], pResampler->state.linear.x1[iChannel], pResampler->state.linear.t); pResampler->state.linear.x0[iChannel] = pX[(iFrameIn-1)*pResampler->config.channels + iChannel];
} pResampler->state.linear.x1[iChannel] = pX[(iFrameIn-0)*pResampler->config.channels + iChannel];
}
t0 = pResampler->state.linear.t;
t1 = t0 + ratioInOut;
if (t1 >= 1) { /* The time should always be relative to x0, and should not be greater than 1. */
/* Need to load the next input frame. */ pResampler->state.linear.t -= floorf(pResampler->state.linear.t);
iFrameIn += (ma_uint64)t1; MA_ASSERT(pResampler->state.linear.t >= 0 && pResampler->state.linear.t <= 1);
if (iFrameIn > 0) { } else {
/* Ran out of input frames. Make sure we consume the rest of the input frames by adjusting our input time appropriately. */
if (frameCountIn > 1) {
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
pResampler->state.linear.x0[iChannel] = pX[(iFrameIn-1)*pResampler->config.channels + iChannel]; pResampler->state.linear.x0[iChannel] = pX[(frameCountIn-2)*pResampler->config.channels + iChannel];
pResampler->state.linear.x1[iChannel] = pX[(iFrameIn-0)*pResampler->config.channels + iChannel]; pResampler->state.linear.x1[iChannel] = pX[(frameCountIn-1)*pResampler->config.channels + iChannel];
} }
} else { } else {
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
pResampler->state.linear.x0[iChannel] = pResampler->state.linear.x1[iChannel]; pResampler->state.linear.x0[iChannel] = pResampler->state.linear.x1[iChannel];
pResampler->state.linear.x1[iChannel] = pX[iFrameIn*pResampler->config.channels + iChannel]; pResampler->state.linear.x1[iChannel] = pX[(frameCountIn-1)*pResampler->config.channels + iChannel];
} }
} }
}
pResampler->state.linear.t = t1 - floorf(t1); /* The time should always be relative to x0, and should not be greater than 1. */ pResampler->state.linear.t -= (iFrameIn - frameCountIn) + 1;
iFrameIn = frameCountIn;
iFrameOut += 1; break;
}
} }
} else {
/* Format not supported. */ for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
return MA_INVALID_OPERATION; pY[iFrameOut*pResampler->config.channels + iChannel] = ma_mix_f32_fast(pResampler->state.linear.x0[iChannel], pResampler->state.linear.x1[iChannel], pResampler->state.linear.t);
}
/* Move time forward. */
pResampler->state.linear.t += ratioInOut;
iFrameOut += 1;
} }
/* Here is where we set the number of frames that were consumed. */
*pFrameCountOut = iFrameOut;
*pFrameCountIn = iFrameIn;
} else { } else {
/* Pass in zeroes. */ /* Format not supported. */
return MA_INVALID_OPERATION; return MA_INVALID_OPERATION;
} }
...@@ -299,6 +315,7 @@ static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler ...@@ -299,6 +315,7 @@ static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler
MA_ASSERT(pResampler != NULL); MA_ASSERT(pResampler != NULL);
MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesOut != NULL);
MA_ASSERT(pFrameCountOut != NULL); MA_ASSERT(pFrameCountOut != NULL);
MA_ASSERT(pFramesIn != NULL);
MA_ASSERT(pFrameCountIn != NULL); MA_ASSERT(pFrameCountIn != NULL);
result = ma_resampler_process__read__linear(pResampler, pFrameCountOut, pFramesOut, pFrameCountIn, pFramesIn); result = ma_resampler_process__read__linear(pResampler, pFrameCountOut, pFramesOut, pFrameCountIn, pFramesIn);
...@@ -319,11 +336,16 @@ static ma_result ma_resampler_process__read(ma_resampler* pResampler, ma_uint64* ...@@ -319,11 +336,16 @@ static ma_result ma_resampler_process__read(ma_resampler* pResampler, ma_uint64*
MA_ASSERT(pResampler != NULL); MA_ASSERT(pResampler != NULL);
MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesOut != NULL);
/* ppFramesOut is not NULL, which means we must have a capacity. */ /* pFramesOut is not NULL, which means we must have a capacity. */
if (pFrameCountOut == NULL) { if (pFrameCountOut == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
/* It doesn't make sense to not have any input frames to process. */
if (pFrameCountIn == NULL || pFramesIn == NULL) {
return MA_INVALID_ARGS;
}
switch (pResampler->config.algorithm) switch (pResampler->config.algorithm)
{ {
case ma_resample_algorithm_linear: case ma_resample_algorithm_linear:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment