diff --git a/research/ma_lpf.h b/research/ma_lpf.h
new file mode 100644
index 00000000..dd93f60c
--- /dev/null
+++ b/research/ma_lpf.h
@@ -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
\ No newline at end of file
diff --git a/research/ma_resampler.h b/research/ma_resampler.h
index 8d941db8..d2b7c9fc 100644
--- a/research/ma_resampler.h
+++ b/research/ma_resampler.h
@@ -3,49 +3,43 @@
#ifndef ma_resampler_h
#define ma_resampler_h
+#include "ma_lpf.h"
+
typedef enum
{
- ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. */
- ma_resample_algorithm_linear_lpf, /* Linear with a biquad low pass filter. */
+ ma_resample_algorithm_linear_lpf = 0, /* Linear with a biquad low pass filter. Default. */
+ ma_resample_algorithm_linear, /* Fastest, lowest quality. */
} ma_resample_algorithm;
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 sampleRateOut;
- ma_uint32 channels;
- ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */
+ ma_resample_algorithm algorithm;
struct
{
int _unused;
} linear;
struct
{
- int _unused;
+ ma_uint32 cutoffFrequency;
} linearLPF;
} 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
{
ma_resampler_config config;
+ float timeX; /* Input time. */
+ float timeY; /* Output time. */
union
{
struct
{
- float timeX; /* Input time. */
- 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;
+ ma_lpf lpf;
} linear;
} state;
} 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.
*/
-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
#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 result;
+
if (pConfig == NULL || pResampler == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pResampler);
pResampler->config = *pConfig;
+ pResampler->timeX = 0.0f;
+ pResampler->timeY = 0.0f;
switch (pConfig->algorithm)
{
case ma_resample_algorithm_linear:
{
- pResampler->state.linear.timeX = 0.0f;
- pResampler->state.linear.timeY = 0.0f;
} break;
case ma_resample_algorithm_linear_lpf:
{
- pResampler->state.linear.timeX = 0.0f;
- pResampler->state.linear.timeY = 0.0f;
- pResampler->state.linear.lpf.yprev1 = 0.0f;
- pResampler->state.linear.lpf.yprev2 = 0.0f;
+ ma_lpf_config lpfConfig;
+
+ lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateOut, pConfig->linearLPF.cutoffFrequency);
+ if (lpfConfig.cutoffFrequency == 0) {
+ lpfConfig.cutoffFrequency = ma_min(pConfig->sampleRateIn, pConfig->sampleRateOut) / 2;
+ }
- /* TODO: Biquad LPF filter coefficients. */
- pResampler->state.linear.lpf.a0 = 0.0f;
- pResampler->state.linear.lpf.a1 = 0.0f;
- 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;
+ result = ma_lpf_init(&lpfConfig, &pResampler->state.linear.lpf);
+ if (result != MA_SUCCESS) {
+ return result;
+ }
} break;
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;
}
-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);
@@ -163,13 +172,13 @@ static ma_result ma_resampler_process__seek__linear(ma_resampler* pResampler, ma
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. */
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);
@@ -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(pFramesOut != NULL);
@@ -205,13 +214,13 @@ static ma_result ma_resampler_process__read__linear(ma_resampler* pResampler, ma
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. */
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(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) {
return MA_INVALID_ARGS;
}
- if (pFrameCountOut != NULL && pFrameCountIn == NULL) {
+ if (pFrameCountOut == NULL && pFrameCountIn == NULL) {
return MA_INVALID_ARGS;
}
diff --git a/research/tests/ma_lpf_test_0.c b/research/tests/ma_lpf_test_0.c
new file mode 100644
index 00000000..a1136ead
--- /dev/null
+++ b/research/tests/ma_lpf_test_0.c
@@ -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;
+}
diff --git a/tests/ma_test_0.vcxproj b/tests/ma_test_0.vcxproj
index 357f3fc6..de27b068 100644
--- a/tests/ma_test_0.vcxproj
+++ b/tests/ma_test_0.vcxproj
@@ -327,13 +327,14 @@
true
- false
- false
- false
- false
- false
- false
+ true
+ true
+ true
+ true
+ true
+ true
+
true
true
@@ -409,6 +410,7 @@
+
diff --git a/tests/ma_test_0.vcxproj.filters b/tests/ma_test_0.vcxproj.filters
index 6b18d42e..442e827b 100644
--- a/tests/ma_test_0.vcxproj.filters
+++ b/tests/ma_test_0.vcxproj.filters
@@ -69,6 +69,9 @@
Source Files
+
+ Source Files
+
@@ -77,5 +80,8 @@
Source Files
+
+ Source Files
+
\ No newline at end of file