mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
Add experimental code for biquad and low-pass filters.
This commit is contained in:
@@ -0,0 +1,237 @@
|
|||||||
|
#ifndef ma_lpf_h
|
||||||
|
#define ma_lpf_h
|
||||||
|
|
||||||
|
/*
|
||||||
|
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).
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_format format;
|
||||||
|
ma_uint32 channels;
|
||||||
|
float a0;
|
||||||
|
float a1;
|
||||||
|
float a2;
|
||||||
|
float b0;
|
||||||
|
float b1;
|
||||||
|
float b2;
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_biquad_config config;
|
||||||
|
ma_bool32 isPassthrough;
|
||||||
|
ma_uint32 prevFrameCount;
|
||||||
|
float x1[MA_MAX_CHANNELS]; /* x[n-1] */
|
||||||
|
float x2[MA_MAX_CHANNELS]; /* x[n-2] */
|
||||||
|
float y1[MA_MAX_CHANNELS]; /* y[n-1] */
|
||||||
|
float y2[MA_MAX_CHANNELS]; /* y[n-2] */
|
||||||
|
} ma_biquad;
|
||||||
|
|
||||||
|
ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ);
|
||||||
|
ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_format format;
|
||||||
|
ma_uint32 channels;
|
||||||
|
ma_uint32 sampleRate;
|
||||||
|
ma_uint32 cutoffFrequency;
|
||||||
|
} ma_lpf_config;
|
||||||
|
|
||||||
|
ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 cutoffFrequency);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_biquad bq; /* The low-pass filter is implemented as a biquad filter. */
|
||||||
|
ma_lpf_config config;
|
||||||
|
} ma_lpf;
|
||||||
|
|
||||||
|
ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF);
|
||||||
|
ma_result ma_lpf_process(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
|
||||||
|
|
||||||
|
#endif /* ma_lpf_h */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#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 config;
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(&config);
|
||||||
|
config.format = format;
|
||||||
|
config.channels = channels;
|
||||||
|
config.a0 = a0;
|
||||||
|
config.a1 = a1;
|
||||||
|
config.a2 = a2;
|
||||||
|
config.b0 = b0;
|
||||||
|
config.b1 = b1;
|
||||||
|
config.b2 = b2;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ)
|
||||||
|
{
|
||||||
|
if (pBQ == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(pBQ);
|
||||||
|
|
||||||
|
if (pConfig == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pConfig->a0 == 0) {
|
||||||
|
return MA_INVALID_ARGS; /* Division by zero. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Currently only supporting f32, but support for other formats will be added later. */
|
||||||
|
if (pConfig->format != ma_format_f32) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pBQ->config = *pConfig;
|
||||||
|
|
||||||
|
if (pConfig->a0 == 1 && pConfig->a1 == 0 && pConfig->a2 == 0 &&
|
||||||
|
pConfig->b0 == 1 && pConfig->b1 == 0 && pConfig->b2 == 0) {
|
||||||
|
pBQ->isPassthrough = MA_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normalize. */
|
||||||
|
pBQ->config.a1 /= pBQ->config.a0;
|
||||||
|
pBQ->config.a2 /= pBQ->config.a0;
|
||||||
|
pBQ->config.b0 /= pBQ->config.a0;
|
||||||
|
pBQ->config.b1 /= pBQ->config.a0;
|
||||||
|
pBQ->config.b2 /= pBQ->config.a0;
|
||||||
|
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_biquad_process(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (pBQ == NULL || pFramesOut == NULL || pFramesIn == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fast path for passthrough. */
|
||||||
|
if (pBQ->isPassthrough) {
|
||||||
|
ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pBQ->config.format, pBQ->config.channels));
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Currently only supporting f32. */
|
||||||
|
if (pBQ->config.format == ma_format_f32) {
|
||||||
|
float* pY = ( float*)pFramesOut;
|
||||||
|
const float* pX = (const float*)pFramesIn;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
pY[n*pBQ->config.channels + c] = y0;
|
||||||
|
pBQ->x2[c] = x1;
|
||||||
|
pBQ->x1[c] = x0;
|
||||||
|
pBQ->y2[c] = y1;
|
||||||
|
pBQ->y1[c] = y0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return MA_INVALID_ARGS; /* Format not supported. Should never hit this because it's checked in ma_biquad_init(). */
|
||||||
|
}
|
||||||
|
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 cutoffFrequency)
|
||||||
|
{
|
||||||
|
ma_lpf_config config;
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(&config);
|
||||||
|
config.format = format;
|
||||||
|
config.channels = channels;
|
||||||
|
config.sampleRate = sampleRate;
|
||||||
|
config.cutoffFrequency = cutoffFrequency;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_biquad_config bqConfig;
|
||||||
|
double q;
|
||||||
|
double w;
|
||||||
|
double s;
|
||||||
|
double c;
|
||||||
|
double a;
|
||||||
|
|
||||||
|
if (pLPF == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(pLPF);
|
||||||
|
|
||||||
|
if (pConfig == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pLPF->config = *pConfig;
|
||||||
|
|
||||||
|
q = 1 / sqrt(2);
|
||||||
|
w = 2 * MA_PI_D * pConfig->cutoffFrequency / pConfig->sampleRate;
|
||||||
|
s = sin(w);
|
||||||
|
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.format = pConfig->format;
|
||||||
|
bqConfig.channels = pConfig->channels;
|
||||||
|
|
||||||
|
result = ma_biquad_init(&bqConfig, &pLPF->bq);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_result ma_lpf_process(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
|
||||||
|
{
|
||||||
|
if (pLPF == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ma_biquad_process(&pLPF->bq, pFramesOut, pFramesIn, frameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
+50
-41
@@ -3,49 +3,43 @@
|
|||||||
#ifndef ma_resampler_h
|
#ifndef ma_resampler_h
|
||||||
#define ma_resampler_h
|
#define ma_resampler_h
|
||||||
|
|
||||||
|
#include "ma_lpf.h"
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. */
|
ma_resample_algorithm_linear_lpf = 0, /* Linear with a biquad low pass filter. Default. */
|
||||||
ma_resample_algorithm_linear_lpf, /* Linear with a biquad low pass filter. */
|
ma_resample_algorithm_linear, /* Fastest, lowest quality. */
|
||||||
} ma_resample_algorithm;
|
} ma_resample_algorithm;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
ma_resample_algorithm algorithm;
|
ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */
|
||||||
|
ma_uint32 channels;
|
||||||
ma_uint32 sampleRateIn;
|
ma_uint32 sampleRateIn;
|
||||||
ma_uint32 sampleRateOut;
|
ma_uint32 sampleRateOut;
|
||||||
ma_uint32 channels;
|
ma_resample_algorithm algorithm;
|
||||||
ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
int _unused;
|
int _unused;
|
||||||
} linear;
|
} linear;
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
int _unused;
|
ma_uint32 cutoffFrequency;
|
||||||
} linearLPF;
|
} linearLPF;
|
||||||
} ma_resampler_config;
|
} ma_resampler_config;
|
||||||
|
|
||||||
|
ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
ma_resampler_config config;
|
ma_resampler_config config;
|
||||||
|
float timeX; /* Input time. */
|
||||||
|
float timeY; /* Output time. */
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
float timeX; /* Input time. */
|
ma_lpf lpf;
|
||||||
float timeY; /* Output time. */
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
float yprev1; /* y-1 */
|
|
||||||
float yprev2; /* y-2 */
|
|
||||||
float a0;
|
|
||||||
float a1;
|
|
||||||
float a2;
|
|
||||||
float b0;
|
|
||||||
float b1;
|
|
||||||
float b2;
|
|
||||||
} lpf;
|
|
||||||
} linear;
|
} linear;
|
||||||
} state;
|
} state;
|
||||||
} ma_resampler;
|
} ma_resampler;
|
||||||
@@ -75,7 +69,7 @@ It is an error for [pFramesOut] to be non-NULL and [pFrameCountOut] to be NULL.
|
|||||||
|
|
||||||
It is an error for both [pFrameCountOut] and [pFrameCountIn] to be NULL.
|
It is an error for both [pFrameCountOut] and [pFrameCountIn] to be NULL.
|
||||||
*/
|
*/
|
||||||
ma_result ma_resampler_process(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, void* pFramesIn);
|
ma_result ma_resampler_process(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, const void* pFramesIn);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -99,37 +93,52 @@ Implementation
|
|||||||
#define MA_RESAMPLER_MAX_RATIO 48.0
|
#define MA_RESAMPLER_MAX_RATIO 48.0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm)
|
||||||
|
{
|
||||||
|
ma_resampler_config config;
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(&config);
|
||||||
|
config.format = format;
|
||||||
|
config.channels = channels;
|
||||||
|
config.sampleRateIn = sampleRateIn;
|
||||||
|
config.sampleRateOut = sampleRateOut;
|
||||||
|
config.algorithm = algorithm;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler)
|
ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler)
|
||||||
{
|
{
|
||||||
|
ma_result result;
|
||||||
|
|
||||||
if (pConfig == NULL || pResampler == NULL) {
|
if (pConfig == NULL || pResampler == NULL) {
|
||||||
return MA_INVALID_ARGS;
|
return MA_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
MA_ZERO_OBJECT(pResampler);
|
MA_ZERO_OBJECT(pResampler);
|
||||||
pResampler->config = *pConfig;
|
pResampler->config = *pConfig;
|
||||||
|
pResampler->timeX = 0.0f;
|
||||||
|
pResampler->timeY = 0.0f;
|
||||||
|
|
||||||
switch (pConfig->algorithm)
|
switch (pConfig->algorithm)
|
||||||
{
|
{
|
||||||
case ma_resample_algorithm_linear:
|
case ma_resample_algorithm_linear:
|
||||||
{
|
{
|
||||||
pResampler->state.linear.timeX = 0.0f;
|
|
||||||
pResampler->state.linear.timeY = 0.0f;
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ma_resample_algorithm_linear_lpf:
|
case ma_resample_algorithm_linear_lpf:
|
||||||
{
|
{
|
||||||
pResampler->state.linear.timeX = 0.0f;
|
ma_lpf_config lpfConfig;
|
||||||
pResampler->state.linear.timeY = 0.0f;
|
|
||||||
pResampler->state.linear.lpf.yprev1 = 0.0f;
|
lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateOut, pConfig->linearLPF.cutoffFrequency);
|
||||||
pResampler->state.linear.lpf.yprev2 = 0.0f;
|
if (lpfConfig.cutoffFrequency == 0) {
|
||||||
|
lpfConfig.cutoffFrequency = ma_min(pConfig->sampleRateIn, pConfig->sampleRateOut) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: Biquad LPF filter coefficients. */
|
result = ma_lpf_init(&lpfConfig, &pResampler->state.linear.lpf);
|
||||||
pResampler->state.linear.lpf.a0 = 0.0f;
|
if (result != MA_SUCCESS) {
|
||||||
pResampler->state.linear.lpf.a1 = 0.0f;
|
return result;
|
||||||
pResampler->state.linear.lpf.a2 = 0.0f;
|
}
|
||||||
pResampler->state.linear.lpf.b0 = 0.0f;
|
|
||||||
pResampler->state.linear.lpf.b1 = 0.0f;
|
|
||||||
pResampler->state.linear.lpf.b2 = 0.0f;
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default: return MA_INVALID_ARGS;
|
default: return MA_INVALID_ARGS;
|
||||||
@@ -138,7 +147,7 @@ ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pR
|
|||||||
return MA_SUCCESS;
|
return MA_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
MA_ASSERT(pResampler != NULL);
|
MA_ASSERT(pResampler != NULL);
|
||||||
|
|
||||||
@@ -163,13 +172,13 @@ static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, ma
|
|||||||
return MA_SUCCESS;
|
return MA_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result ma_resampler_process__seek__linear_lpf(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__seek__linear_lpf(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
/* TODO: Proper linear LPF implementation. */
|
/* TODO: Proper linear LPF implementation. */
|
||||||
return ma_resampler_process__seek__linear(pResampler, pFrameCountOut, pFrameCountIn, pFramesIn);
|
return ma_resampler_process__seek__linear(pResampler, pFrameCountOut, pFrameCountIn, pFramesIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result ma_resampler_process__seek(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__seek(ma_resampler* pResampler, ma_uint64* pFrameCountOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
MA_ASSERT(pResampler != NULL);
|
MA_ASSERT(pResampler != NULL);
|
||||||
|
|
||||||
@@ -190,7 +199,7 @@ static ma_result ma_resampler_process__seek(ma_resampler* pResampler, ma_uint64*
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
MA_ASSERT(pResampler != NULL);
|
MA_ASSERT(pResampler != NULL);
|
||||||
MA_ASSERT(pFramesOut != NULL);
|
MA_ASSERT(pFramesOut != NULL);
|
||||||
@@ -205,13 +214,13 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma
|
|||||||
return MA_SUCCESS;
|
return MA_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__read__linear_lpf(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
/* TODO: Proper linear LPF implementation. */
|
/* TODO: Proper linear LPF implementation. */
|
||||||
return ma_resampler_process__read__linear(pResampler, pFrameCountOut, pFramesOut, pFrameCountIn, pFramesIn);
|
return ma_resampler_process__read__linear(pResampler, pFrameCountOut, pFramesOut, pFrameCountIn, pFramesIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result ma_resampler_process__read(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
static ma_result ma_resampler_process__read(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
MA_ASSERT(pResampler != NULL);
|
MA_ASSERT(pResampler != NULL);
|
||||||
MA_ASSERT(pFramesOut != NULL);
|
MA_ASSERT(pFramesOut != NULL);
|
||||||
@@ -237,13 +246,13 @@ static ma_result ma_resampler_process__read(ma_resampler* pResampler, ma_uint64*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ma_result ma_resampler_process(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, void* pFramesIn)
|
ma_result ma_resampler_process(ma_resampler* pResampler, ma_uint64* pFrameCountOut, void* pFramesOut, ma_uint64* pFrameCountIn, const void* pFramesIn)
|
||||||
{
|
{
|
||||||
if (pResampler == NULL) {
|
if (pResampler == NULL) {
|
||||||
return MA_INVALID_ARGS;
|
return MA_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pFrameCountOut != NULL && pFrameCountIn == NULL) {
|
if (pFrameCountOut == NULL && pFrameCountIn == NULL) {
|
||||||
return MA_INVALID_ARGS;
|
return MA_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#define DR_FLAC_IMPLEMENTATION
|
||||||
|
#include "../../extras/dr_flac.h" /* Enables FLAC decoding. */
|
||||||
|
#define DR_MP3_IMPLEMENTATION
|
||||||
|
#include "../../extras/dr_mp3.h" /* Enables MP3 decoding. */
|
||||||
|
#define DR_WAV_IMPLEMENTATION
|
||||||
|
#include "../../extras/dr_wav.h" /* Enables WAV decoding. */
|
||||||
|
|
||||||
|
|
||||||
|
#define MA_DEBUG_OUTPUT
|
||||||
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
|
#include "../../miniaudio.h"
|
||||||
|
#include "../ma_lpf.h"
|
||||||
|
|
||||||
|
ma_lpf g_lpf;
|
||||||
|
|
||||||
|
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||||
|
{
|
||||||
|
ma_uint32 framesProcessed = 0;
|
||||||
|
ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData;
|
||||||
|
if (pDecoder == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to read into a temporary buffer and then run it through the low pass filter. */
|
||||||
|
while (framesProcessed < frameCount) {
|
||||||
|
float tempBuffer[4096];
|
||||||
|
ma_uint32 framesToProcessThisIteration;
|
||||||
|
|
||||||
|
framesToProcessThisIteration = frameCount - framesProcessed;
|
||||||
|
if (framesToProcessThisIteration > ma_countof(tempBuffer)/pDecoder->internalChannels) {
|
||||||
|
framesToProcessThisIteration = ma_countof(tempBuffer)/pDecoder->internalChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
ma_decoder_read_pcm_frames(pDecoder, ma_offset_ptr(pOutput, framesProcessed * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels)), framesToProcessThisIteration);
|
||||||
|
#else
|
||||||
|
ma_decoder_read_pcm_frames(pDecoder, tempBuffer, framesToProcessThisIteration);
|
||||||
|
|
||||||
|
/* Out the results from the low pass filter straight into our output buffer. */
|
||||||
|
ma_lpf_process(&g_lpf, ma_offset_ptr(pOutput, framesProcessed * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels)), tempBuffer, framesToProcessThisIteration);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
framesProcessed += framesToProcessThisIteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)pInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_decoder_config decoderConfig;
|
||||||
|
ma_decoder decoder;
|
||||||
|
ma_lpf_config lpfConfig;
|
||||||
|
ma_device_config deviceConfig;
|
||||||
|
ma_device device;
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("No input file.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoderConfig = ma_decoder_config_init(ma_format_f32, 0, 0);
|
||||||
|
|
||||||
|
result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lpfConfig.format = decoderConfig.format;
|
||||||
|
lpfConfig.channels = decoder.internalChannels;
|
||||||
|
lpfConfig.sampleRate = decoder.internalSampleRate;
|
||||||
|
lpfConfig.cutoffFrequency = lpfConfig.sampleRate / 4;
|
||||||
|
|
||||||
|
result = ma_lpf_init(&lpfConfig, &g_lpf);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return -100;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
deviceConfig.playback.format = decoder.outputFormat;
|
||||||
|
deviceConfig.playback.channels = decoder.outputChannels;
|
||||||
|
deviceConfig.sampleRate = decoder.outputSampleRate;
|
||||||
|
deviceConfig.dataCallback = data_callback;
|
||||||
|
deviceConfig.pUserData = &decoder;
|
||||||
|
|
||||||
|
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
|
||||||
|
printf("Failed to open playback device.\n");
|
||||||
|
ma_decoder_uninit(&decoder);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||||
|
printf("Failed to start playback device.\n");
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
ma_decoder_uninit(&decoder);
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Press Enter to quit...");
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
ma_device_uninit(&device);
|
||||||
|
ma_decoder_uninit(&decoder);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -327,13 +327,14 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\examples\simple_playback_emscripten.c">
|
<ClCompile Include="..\examples\simple_playback_emscripten.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\research\tests\ma_lpf_test_0.c" />
|
||||||
<ClCompile Include="..\research\tests\ma_resampler_test_0.c">
|
<ClCompile Include="..\research\tests\ma_resampler_test_0.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
@@ -409,6 +410,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\miniaudio.h" />
|
<ClInclude Include="..\miniaudio.h" />
|
||||||
|
<ClInclude Include="..\research\ma_lpf.h" />
|
||||||
<ClInclude Include="..\research\ma_resampler.h" />
|
<ClInclude Include="..\research\ma_resampler.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
|||||||
@@ -69,6 +69,9 @@
|
|||||||
<ClCompile Include="..\examples\fixed_size_callback.c">
|
<ClCompile Include="..\examples\fixed_size_callback.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\research\tests\ma_lpf_test_0.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\miniaudio.h">
|
<ClInclude Include="..\miniaudio.h">
|
||||||
@@ -77,5 +80,8 @@
|
|||||||
<ClInclude Include="..\research\ma_resampler.h">
|
<ClInclude Include="..\research\ma_resampler.h">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\research\ma_lpf.h">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
Reference in New Issue
Block a user