mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-24 09:14:04 +02:00
Resampling and LPF work.
This commit is contained in:
+54
-30
@@ -5,21 +5,45 @@
|
||||
TODO:
|
||||
- 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 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
|
||||
{
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
float a0;
|
||||
float a1;
|
||||
float a2;
|
||||
float b0;
|
||||
float b1;
|
||||
float b2;
|
||||
double a0;
|
||||
double a1;
|
||||
double a2;
|
||||
double b0;
|
||||
double b1;
|
||||
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_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
|
||||
{
|
||||
@@ -62,7 +86,7 @@ ma_result ma_lpf_process(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn,
|
||||
|
||||
#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;
|
||||
|
||||
@@ -121,11 +145,11 @@ ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFrame
|
||||
{
|
||||
ma_uint32 n;
|
||||
ma_uint32 c;
|
||||
float a1 = pBQ->config.a1;
|
||||
float a2 = pBQ->config.a2;
|
||||
float b0 = pBQ->config.b0;
|
||||
float b1 = pBQ->config.b1;
|
||||
float b2 = pBQ->config.b2;
|
||||
double a1 = pBQ->config.a1;
|
||||
double a2 = pBQ->config.a2;
|
||||
double b0 = pBQ->config.b0;
|
||||
double b1 = pBQ->config.b1;
|
||||
double b2 = pBQ->config.b2;
|
||||
|
||||
if (pBQ == NULL || pFramesOut == NULL || pFramesIn == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
@@ -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 (c = 0; c < pBQ->config.channels; c += 1) {
|
||||
float x2 = pBQ->x2[c];
|
||||
float x1 = pBQ->x1[c];
|
||||
float x0 = pX[n*pBQ->config.channels + c];
|
||||
float y2 = pBQ->y2[c];
|
||||
float y1 = pBQ->y1[c];
|
||||
float y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2;
|
||||
double x2 = pBQ->x2[c];
|
||||
double x1 = pBQ->x1[c];
|
||||
double x0 = pX[n*pBQ->config.channels + c];
|
||||
double y2 = pBQ->y2[c];
|
||||
double y1 = pBQ->y1[c];
|
||||
double y0 = b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2;
|
||||
|
||||
pY[n*pBQ->config.channels + c] = y0;
|
||||
pBQ->x2[c] = x1;
|
||||
pBQ->x1[c] = x0;
|
||||
pBQ->y2[c] = y1;
|
||||
pBQ->y1[c] = y0;
|
||||
pY[n*pBQ->config.channels + c] = (float)y0;
|
||||
pBQ->x2[c] = (float)x1;
|
||||
pBQ->x1[c] = (float)x0;
|
||||
pBQ->y2[c] = (float)y1;
|
||||
pBQ->y1[c] = (float)y0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -212,12 +236,12 @@ ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF)
|
||||
c = cos(w);
|
||||
a = s / (2*q);
|
||||
|
||||
bqConfig.a0 = (float)( 1 + a);
|
||||
bqConfig.a1 = (float)(-2 * c);
|
||||
bqConfig.a2 = (float)( 1 - a);
|
||||
bqConfig.b0 = (float)((1 - c) / 2);
|
||||
bqConfig.b1 = (float)( 1 - c);
|
||||
bqConfig.b2 = (float)((1 - c) / 2);
|
||||
bqConfig.a0 = (double)( 1 + a);
|
||||
bqConfig.a1 = (double)(-2 * c);
|
||||
bqConfig.a2 = (double)( 1 - a);
|
||||
bqConfig.b0 = (double)((1 - c) / 2);
|
||||
bqConfig.b1 = (double)( 1 - c);
|
||||
bqConfig.b2 = (double)((1 - c) / 2);
|
||||
|
||||
bqConfig.format = pConfig->format;
|
||||
bqConfig.channels = pConfig->channels;
|
||||
|
||||
+50
-28
@@ -203,23 +203,25 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma
|
||||
{
|
||||
ma_uint64 frameCountOut;
|
||||
ma_uint64 frameCountIn;
|
||||
ma_uint64 iFrameOut;
|
||||
ma_uint64 iFrameIn;
|
||||
ma_uint64 iChannel;
|
||||
float ratioInOut;
|
||||
|
||||
MA_ASSERT(pResampler != NULL);
|
||||
MA_ASSERT(pFramesOut != NULL);
|
||||
MA_ASSERT(pFrameCountOut != NULL);
|
||||
MA_ASSERT(pFramesIn != NULL);
|
||||
MA_ASSERT(pFrameCountIn != NULL);
|
||||
|
||||
frameCountOut = *pFrameCountOut;
|
||||
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;
|
||||
ratioInOut = (float)pResampler->config.sampleRateIn / (float)pResampler->config.sampleRateOut;
|
||||
|
||||
iFrameOut = 0;
|
||||
iFrameIn = 0;
|
||||
@@ -244,49 +246,63 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
float t0;
|
||||
float t1;
|
||||
float y;
|
||||
|
||||
if (iFrameOut >= frameCountOut || iFrameIn >= frameCountIn) {
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
t0 = pResampler->state.linear.t;
|
||||
t1 = t0 + ratioInOut;
|
||||
|
||||
if (t1 >= 1) {
|
||||
/* We can't interpolate if our interpolation factor (time relative to x0) is greater than 1. */
|
||||
if (pResampler->state.linear.t > 1) {
|
||||
/* Need to load the next input frame. */
|
||||
iFrameIn += (ma_uint64)t1;
|
||||
if (iFrameIn > 0) {
|
||||
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) {
|
||||
pResampler->state.linear.x0[iChannel] = pX[(iFrameIn-1)*pResampler->config.channels + iChannel];
|
||||
pResampler->state.linear.x1[iChannel] = pX[(iFrameIn-0)*pResampler->config.channels + iChannel];
|
||||
}
|
||||
|
||||
/* The time should always be relative to x0, and should not be greater than 1. */
|
||||
pResampler->state.linear.t -= floorf(pResampler->state.linear.t);
|
||||
MA_ASSERT(pResampler->state.linear.t >= 0 && pResampler->state.linear.t <= 1);
|
||||
} 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) {
|
||||
pResampler->state.linear.x0[iChannel] = pX[(frameCountIn-2)*pResampler->config.channels + iChannel];
|
||||
pResampler->state.linear.x1[iChannel] = pX[(frameCountIn-1)*pResampler->config.channels + iChannel];
|
||||
}
|
||||
} else {
|
||||
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
|
||||
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;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) {
|
||||
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 {
|
||||
/* Format not supported. */
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
} else {
|
||||
/* Pass in zeroes. */
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
@@ -299,6 +315,7 @@ static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler
|
||||
MA_ASSERT(pResampler != NULL);
|
||||
MA_ASSERT(pFramesOut != NULL);
|
||||
MA_ASSERT(pFrameCountOut != NULL);
|
||||
MA_ASSERT(pFramesIn != NULL);
|
||||
MA_ASSERT(pFrameCountIn != NULL);
|
||||
|
||||
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*
|
||||
MA_ASSERT(pResampler != 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) {
|
||||
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)
|
||||
{
|
||||
case ma_resample_algorithm_linear:
|
||||
|
||||
Reference in New Issue
Block a user