mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-21 15:56:58 +02:00
Remove old research code.
This code has now been integrated into miniaudio.h.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,457 +0,0 @@
|
||||
#ifndef ma_lpf_h
|
||||
#define ma_lpf_h
|
||||
|
||||
/**************************************************************************************************************************************************************
|
||||
|
||||
Biquad Filter
|
||||
=============
|
||||
Biquad filtering is achieved with the `ma_biquad` API. Example:
|
||||
|
||||
```c
|
||||
ma_biquad_config config = ma_biquad_config_init(ma_format_f32, channels, a0, a1, a2, b0, b1, b2);
|
||||
ma_result result = ma_biquad_init(&config, &biquad);
|
||||
if (result != MA_SUCCESS) {
|
||||
// Error.
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ma_biquad_process_pcm_frames(&biquad, pFramesOut, pFramesIn, frameCount);
|
||||
```
|
||||
|
||||
Biquad filtering is implemented using transposed direct form 2. The denominator coefficients are a0, a1 and a2, and the numerator coefficients are b0, b1 and
|
||||
b2. The a0 coefficient is required and coefficients must not be pre-normalized.
|
||||
|
||||
Supported formats are ma_format_s16 and ma_format_f32. If you need to use a different format you need to convert it yourself beforehand. When using
|
||||
ma_format_s16 the biquad filter will use fixed point arithmetic. When using ma_format_f32, floating point arithmetic will be used.
|
||||
|
||||
Input and output frames are always interleaved.
|
||||
|
||||
Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so:
|
||||
|
||||
```c
|
||||
ma_biquad_process_pcm_frames(&biquad, pMyData, pMyData, frameCount);
|
||||
```
|
||||
|
||||
If you need to change the values of the coefficients, but maintain the values in the registers you can do so with `ma_biquad_reinit()`. This is useful if you
|
||||
need to change the properties of the filter while keeping the values of registers valid to avoid glitching or whatnot. Do not use `ma_biquad_init()` for this
|
||||
as it will do a full initialization which involves clearing the registers to 0. Note that changing the format or channel count after initialization is invalid
|
||||
and will result in an error.
|
||||
|
||||
**************************************************************************************************************************************************************/
|
||||
|
||||
typedef union
|
||||
{
|
||||
float f32;
|
||||
ma_int32 s32;
|
||||
} ma_biquad_coefficient;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
double a0;
|
||||
double a1;
|
||||
double a2;
|
||||
double b0;
|
||||
double b1;
|
||||
double b2;
|
||||
} ma_biquad_config;
|
||||
|
||||
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
|
||||
{
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
ma_biquad_coefficient a1;
|
||||
ma_biquad_coefficient a2;
|
||||
ma_biquad_coefficient b0;
|
||||
ma_biquad_coefficient b1;
|
||||
ma_biquad_coefficient b2;
|
||||
ma_biquad_coefficient r1[MA_MAX_CHANNELS];
|
||||
ma_biquad_coefficient r2[MA_MAX_CHANNELS];
|
||||
} ma_biquad;
|
||||
|
||||
ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ);
|
||||
ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ);
|
||||
ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
|
||||
ma_uint32 ma_biquad_get_latency(ma_biquad* pBQ);
|
||||
|
||||
|
||||
/**************************************************************************************************************************************************************
|
||||
|
||||
Low-Pass Filter
|
||||
===============
|
||||
Low-pass filtering is achieved with the `ma_lpf` API. Example:
|
||||
|
||||
```c
|
||||
ma_lpf_config config = ma_lpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency);
|
||||
ma_result result = ma_lpf_init(&config, &lpf);
|
||||
if (result != MA_SUCCESS) {
|
||||
// Error.
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
ma_lpf_process_pcm_frames(&lpf, pFramesOut, pFramesIn, frameCount);
|
||||
```
|
||||
|
||||
Supported formats are ma_format_s16 and ma_format_f32. If you need to use a different format you need to convert it yourself beforehand. Input and output
|
||||
frames are always interleaved.
|
||||
|
||||
Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so:
|
||||
|
||||
```c
|
||||
ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount);
|
||||
```
|
||||
|
||||
The low-pass filter is implemented as a biquad filter. If you need increase the filter order, simply chain multiple low-pass filters together.
|
||||
|
||||
```c
|
||||
for (iFilter = 0; iFilter < filterCount; iFilter += 1) {
|
||||
ma_lpf_process_pcm_frames(&lpf[iFilter], pMyData, pMyData, frameCount);
|
||||
}
|
||||
```
|
||||
|
||||
If you need to change the configuration of the filter, but need to maintain the state of internal registers you can do so with `ma_lpf_reinit()`. This may be
|
||||
useful if you need to change the sample rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the format or channel
|
||||
count after initialization is invalid and will result in an error.
|
||||
|
||||
**************************************************************************************************************************************************************/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_format format;
|
||||
ma_uint32 channels;
|
||||
ma_uint32 sampleRate;
|
||||
double cutoffFrequency;
|
||||
} ma_lpf_config;
|
||||
|
||||
ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ma_biquad bq; /* The low-pass filter is implemented as a biquad filter. */
|
||||
} ma_lpf;
|
||||
|
||||
ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF);
|
||||
ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF);
|
||||
ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
|
||||
ma_uint32 ma_lpf_get_latency(ma_lpf* pLPF);
|
||||
|
||||
#endif /* ma_lpf_h */
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(MINIAUDIO_IMPLEMENTATION)
|
||||
|
||||
#ifndef MA_BIQUAD_FIXED_POINT_SHIFT
|
||||
#define MA_BIQUAD_FIXED_POINT_SHIFT 14
|
||||
#endif
|
||||
|
||||
static ma_int32 ma_biquad_float_to_fp(double x)
|
||||
{
|
||||
return (ma_int32)(x * (1 << MA_BIQUAD_FIXED_POINT_SHIFT));
|
||||
}
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
return ma_biquad_reinit(pConfig, pBQ);
|
||||
}
|
||||
|
||||
ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ)
|
||||
{
|
||||
if (pBQ == NULL || pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (pConfig->a0 == 0) {
|
||||
return MA_INVALID_ARGS; /* Division by zero. */
|
||||
}
|
||||
|
||||
/* Only supporting f32 and s16. */
|
||||
if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* The format cannot be changed after initialization. */
|
||||
if (pBQ->format != pConfig->format) {
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
/* The channel count cannot be changed after initialization. */
|
||||
if (pBQ->channels != pConfig->channels) {
|
||||
return MA_INVALID_OPERATION;
|
||||
}
|
||||
|
||||
|
||||
pBQ->format = pConfig->format;
|
||||
pBQ->channels = pConfig->channels;
|
||||
|
||||
/* Normalize. */
|
||||
if (pConfig->format == ma_format_f32) {
|
||||
pBQ->a1.f32 = (float)(pConfig->a1 / pConfig->a0);
|
||||
pBQ->a2.f32 = (float)(pConfig->a2 / pConfig->a0);
|
||||
pBQ->b0.f32 = (float)(pConfig->b0 / pConfig->a0);
|
||||
pBQ->b1.f32 = (float)(pConfig->b1 / pConfig->a0);
|
||||
pBQ->b2.f32 = (float)(pConfig->b2 / pConfig->a0);
|
||||
} else {
|
||||
pBQ->a1.s32 = ma_biquad_float_to_fp(pConfig->a1 / pConfig->a0);
|
||||
pBQ->a2.s32 = ma_biquad_float_to_fp(pConfig->a2 / pConfig->a0);
|
||||
pBQ->b0.s32 = ma_biquad_float_to_fp(pConfig->b0 / pConfig->a0);
|
||||
pBQ->b1.s32 = ma_biquad_float_to_fp(pConfig->b1 / pConfig->a0);
|
||||
pBQ->b2.s32 = ma_biquad_float_to_fp(pConfig->b2 / pConfig->a0);
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(ma_biquad* pBQ, float* pY, const float* pX)
|
||||
{
|
||||
ma_uint32 c;
|
||||
const float a1 = pBQ->a1.f32;
|
||||
const float a2 = pBQ->a2.f32;
|
||||
const float b0 = pBQ->b0.f32;
|
||||
const float b1 = pBQ->b1.f32;
|
||||
const float b2 = pBQ->b2.f32;
|
||||
|
||||
for (c = 0; c < pBQ->channels; c += 1) {
|
||||
float r1 = pBQ->r1[c].f32;
|
||||
float r2 = pBQ->r2[c].f32;
|
||||
float x = pX[c];
|
||||
float y;
|
||||
|
||||
y = b0*x + r1;
|
||||
r1 = b1*x - a1*y + r2;
|
||||
r2 = b2*x - a2*y;
|
||||
|
||||
pY[c] = y;
|
||||
pBQ->r1[c].f32 = r1;
|
||||
pBQ->r2[c].f32 = r2;
|
||||
}
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_biquad_process_pcm_frame_f32(ma_biquad* pBQ, float* pY, const float* pX)
|
||||
{
|
||||
ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(pBQ, pY, pX);
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed(ma_biquad* pBQ, ma_int16* pY, const ma_int16* pX)
|
||||
{
|
||||
ma_uint32 c;
|
||||
const ma_int32 a1 = pBQ->a1.s32;
|
||||
const ma_int32 a2 = pBQ->a2.s32;
|
||||
const ma_int32 b0 = pBQ->b0.s32;
|
||||
const ma_int32 b1 = pBQ->b1.s32;
|
||||
const ma_int32 b2 = pBQ->b2.s32;
|
||||
|
||||
for (c = 0; c < pBQ->channels; c += 1) {
|
||||
ma_int32 r1 = pBQ->r1[c].s32;
|
||||
ma_int32 r2 = pBQ->r2[c].s32;
|
||||
ma_int32 x = pX[c];
|
||||
ma_int32 y;
|
||||
|
||||
y = (b0*x + r1) >> MA_BIQUAD_FIXED_POINT_SHIFT;
|
||||
r1 = (b1*x - a1*y + r2);
|
||||
r2 = (b2*x - a2*y);
|
||||
|
||||
pY[c] = (ma_int16)ma_clamp(y, -32768, 32767);
|
||||
pBQ->r1[c].s32 = r1;
|
||||
pBQ->r2[c].s32 = r2;
|
||||
}
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_biquad_process_pcm_frame_s16(ma_biquad* pBQ, ma_int16* pY, const ma_int16* pX)
|
||||
{
|
||||
ma_biquad_process_pcm_frame_s16__direct_form_2_transposed(pBQ, pY, pX);
|
||||
}
|
||||
|
||||
ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
|
||||
{
|
||||
ma_uint32 n;
|
||||
|
||||
if (pBQ == NULL || pFramesOut == NULL || pFramesIn == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* Note that the logic below needs to support in-place filtering. That is, it must support the case where pFramesOut and pFramesIn are the same. */
|
||||
|
||||
if (pBQ->format == ma_format_f32) {
|
||||
/* */ float* pY = ( float*)pFramesOut;
|
||||
const float* pX = (const float*)pFramesIn;
|
||||
|
||||
for (n = 0; n < frameCount; n += 1) {
|
||||
ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(pBQ, pY, pX);
|
||||
pY += pBQ->channels;
|
||||
pX += pBQ->channels;
|
||||
}
|
||||
} else if (pBQ->format == ma_format_s16) {
|
||||
/* */ ma_int16* pY = ( ma_int16*)pFramesOut;
|
||||
const ma_int16* pX = (const ma_int16*)pFramesIn;
|
||||
|
||||
for (n = 0; n < frameCount; n += 1) {
|
||||
ma_biquad_process_pcm_frame_s16__direct_form_2_transposed(pBQ, pY, pX);
|
||||
pY += pBQ->channels;
|
||||
pX += pBQ->channels;
|
||||
}
|
||||
} else {
|
||||
MA_ASSERT(MA_FALSE);
|
||||
return MA_INVALID_ARGS; /* Format not supported. Should never hit this because it's checked in ma_biquad_init() and ma_biquad_reinit(). */
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
ma_uint32 ma_biquad_get_latency(ma_biquad* pBQ)
|
||||
{
|
||||
if (pBQ == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency)
|
||||
{
|
||||
ma_lpf_config config;
|
||||
|
||||
MA_ZERO_OBJECT(&config);
|
||||
config.format = format;
|
||||
config.channels = channels;
|
||||
config.sampleRate = sampleRate;
|
||||
config.cutoffFrequency = cutoffFrequency;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static MA_INLINE ma_biquad_config ma_lpf__get_biquad_config(const ma_lpf_config* pConfig)
|
||||
{
|
||||
ma_biquad_config bqConfig;
|
||||
double q;
|
||||
double w;
|
||||
double s;
|
||||
double c;
|
||||
double a;
|
||||
|
||||
MA_ASSERT(pConfig != NULL);
|
||||
|
||||
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 = 1 + a;
|
||||
bqConfig.a1 = -2 * c;
|
||||
bqConfig.a2 = 1 - a;
|
||||
bqConfig.b0 = (1 - c) / 2;
|
||||
bqConfig.b1 = 1 - c;
|
||||
bqConfig.b2 = (1 - c) / 2;
|
||||
|
||||
bqConfig.format = pConfig->format;
|
||||
bqConfig.channels = pConfig->channels;
|
||||
|
||||
return bqConfig;
|
||||
}
|
||||
|
||||
ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF)
|
||||
{
|
||||
ma_result result;
|
||||
ma_biquad_config bqConfig;
|
||||
|
||||
if (pLPF == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
MA_ZERO_OBJECT(pLPF);
|
||||
|
||||
if (pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
bqConfig = ma_lpf__get_biquad_config(pConfig);
|
||||
result = ma_biquad_init(&bqConfig, &pLPF->bq);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF)
|
||||
{
|
||||
ma_result result;
|
||||
ma_biquad_config bqConfig;
|
||||
|
||||
if (pLPF == NULL || pConfig == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
bqConfig = ma_lpf__get_biquad_config(pConfig);
|
||||
result = ma_biquad_reinit(&bqConfig, &pLPF->bq);
|
||||
if (result != MA_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_lpf_process_pcm_frame_s16(ma_lpf* pLPF, ma_int16* pFrameOut, const ma_int16* pFrameIn)
|
||||
{
|
||||
ma_biquad_process_pcm_frame_s16(&pLPF->bq, pFrameOut, pFrameIn);
|
||||
}
|
||||
|
||||
static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pFrameOut, const float* pFrameIn)
|
||||
{
|
||||
ma_biquad_process_pcm_frame_f32(&pLPF->bq, pFrameOut, pFrameIn);
|
||||
}
|
||||
|
||||
ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
|
||||
{
|
||||
if (pLPF == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
return ma_biquad_process_pcm_frames(&pLPF->bq, pFramesOut, pFramesIn, frameCount);
|
||||
}
|
||||
|
||||
ma_uint32 ma_lpf_get_latency(ma_lpf* pLPF)
|
||||
{
|
||||
if (pLPF == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ma_biquad_get_latency(&pLPF->bq);
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,109 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
#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_resampler.h"
|
||||
|
||||
#define USE_NEW_RESAMPLER 1
|
||||
|
||||
ma_uint64 g_outputFrameCount;
|
||||
void* g_pRunningFrameData;
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||
{
|
||||
ma_uint32 framesToCopy;
|
||||
|
||||
framesToCopy = frameCount;
|
||||
if (framesToCopy > (ma_uint32)g_outputFrameCount) {
|
||||
framesToCopy = (ma_uint32)g_outputFrameCount;
|
||||
}
|
||||
|
||||
MA_COPY_MEMORY(pOutput, g_pRunningFrameData, framesToCopy * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
|
||||
|
||||
g_pRunningFrameData = ma_offset_ptr(g_pRunningFrameData, framesToCopy * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
|
||||
g_outputFrameCount -= framesToCopy;
|
||||
|
||||
(void)pInput;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_decoder_config decoderConfig;
|
||||
ma_uint64 inputFrameCount;
|
||||
void* pInputFrameData;
|
||||
ma_uint64 outputFrameCount = 0;
|
||||
void* pOutputFrameData = NULL;
|
||||
ma_device_config deviceConfig;
|
||||
ma_device device;
|
||||
ma_backend backend;
|
||||
|
||||
/* This example just resamples the input file to an exclusive device's native sample rate. */
|
||||
if (argc < 2) {
|
||||
printf("No input file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
decoderConfig = ma_decoder_config_init(ma_format_f32, 1, 0);
|
||||
|
||||
result = ma_decode_file(argv[1], &decoderConfig, &inputFrameCount, &pInputFrameData);
|
||||
if (result != MA_SUCCESS) {
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
backend = ma_backend_wasapi;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
#if USE_NEW_RESAMPLER
|
||||
deviceConfig.playback.shareMode = ma_share_mode_exclusive; /* <-- We need to use exclusive mode to ensure there's no resampling going on by the OS. */
|
||||
deviceConfig.sampleRate = 0; /* <-- Always use the device's native sample rate. */
|
||||
#else
|
||||
deviceConfig.playback.shareMode = ma_share_mode_shared; /* <-- We need to use exclusive mode to ensure there's no resampling going on by the OS. */
|
||||
deviceConfig.sampleRate = decoderConfig.sampleRate;
|
||||
#endif
|
||||
deviceConfig.playback.format = decoderConfig.format;
|
||||
deviceConfig.playback.channels = decoderConfig.channels;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = NULL;
|
||||
|
||||
if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
|
||||
printf("Failed to open playback device.\n");
|
||||
ma_free(pInputFrameData);
|
||||
return -3;
|
||||
}
|
||||
|
||||
#if USE_NEW_RESAMPLER
|
||||
/* Resample. */
|
||||
outputFrameCount = ma_calculate_frame_count_after_src(device.sampleRate, decoderConfig.sampleRate, inputFrameCount);
|
||||
pOutputFrameData = ma_malloc((size_t)(outputFrameCount * ma_get_bytes_per_frame(device.playback.format, device.playback.channels)));
|
||||
if (pOutputFrameData == NULL) {
|
||||
printf("Out of memory.\n");
|
||||
ma_free(pInputFrameData);
|
||||
ma_device_uninit(&device);
|
||||
}
|
||||
|
||||
ma_resample_f32(ma_resample_algorithm_sinc, device.playback.internalSampleRate, decoderConfig.sampleRate, outputFrameCount, pOutputFrameData, inputFrameCount, pInputFrameData);
|
||||
|
||||
g_pRunningFrameData = pOutputFrameData;
|
||||
g_outputFrameCount = outputFrameCount;
|
||||
#else
|
||||
g_pRunningFrameData = pInputFrameData;
|
||||
g_outputFrameCount = inputFrameCount;
|
||||
#endif
|
||||
|
||||
if (ma_device_start(&device) != MA_SUCCESS) {
|
||||
printf("Failed to start playback device.\n");
|
||||
ma_device_uninit(&device);
|
||||
ma_free(pInputFrameData);
|
||||
ma_free(pOutputFrameData);
|
||||
return -4;
|
||||
}
|
||||
|
||||
printf("Press Enter to quit...");
|
||||
getchar();
|
||||
|
||||
ma_device_uninit(&device);
|
||||
ma_free(pInputFrameData);
|
||||
ma_free(pOutputFrameData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#define SAMPLE_RATE_IN 44100
|
||||
#define SAMPLE_RATE_OUT 44100
|
||||
#define CHANNELS 1
|
||||
#define OUTPUT_FILE "output.wav"
|
||||
|
||||
ma_sine_wave sineWave;
|
||||
|
||||
ma_uint32 on_read(ma_resampler* pResampler, ma_uint32 frameCount, void** ppFramesOut)
|
||||
{
|
||||
ma_assert(pResampler->config.format == ma_format_f32);
|
||||
|
||||
return (ma_uint32)ma_sine_wave_read_f32_ex(&sineWave, frameCount, pResampler->config.channels, pResampler->config.layout, (float**)ppFramesOut);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ma_result result;
|
||||
ma_resampler_config resamplerConfig;
|
||||
ma_resampler resampler;
|
||||
|
||||
ma_zero_object(&resamplerConfig);
|
||||
resamplerConfig.format = ma_format_f32;
|
||||
resamplerConfig.channels = CHANNELS;
|
||||
resamplerConfig.sampleRateIn = SAMPLE_RATE_IN;
|
||||
resamplerConfig.sampleRateOut = SAMPLE_RATE_OUT;
|
||||
resamplerConfig.algorithm = ma_resampler_algorithm_linear;
|
||||
resamplerConfig.endOfInputMode = ma_resampler_end_of_input_mode_consume;
|
||||
resamplerConfig.layout = ma_stream_layout_interleaved;
|
||||
resamplerConfig.onRead = on_read;
|
||||
resamplerConfig.pUserData = NULL;
|
||||
|
||||
result = ma_resampler_init(&resamplerConfig, &resampler);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to initialize resampler.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ma_sine_wave_init(0.5, 400, resamplerConfig.sampleRateIn, &sineWave);
|
||||
|
||||
|
||||
// Write to a WAV file. We'll do about 10 seconds worth, making sure we read in chunks to make sure everything is seamless.
|
||||
drwav_data_format format;
|
||||
format.container = drwav_container_riff;
|
||||
format.format = DR_WAVE_FORMAT_IEEE_FLOAT;
|
||||
format.channels = resampler.config.channels;
|
||||
format.sampleRate = resampler.config.sampleRateOut;
|
||||
format.bitsPerSample = 32;
|
||||
drwav* pWavWriter = drwav_open_file_write(OUTPUT_FILE, &format);
|
||||
|
||||
float buffer[SAMPLE_RATE_IN*CHANNELS];
|
||||
float* pBuffer = buffer;
|
||||
ma_uint32 iterations = 10;
|
||||
ma_uint32 framesToReadPerIteration = ma_countof(buffer)/CHANNELS;
|
||||
for (ma_uint32 i = 0; i < iterations; ++i) {
|
||||
ma_uint32 framesRead = (ma_uint32)ma_resampler_read(&resampler, framesToReadPerIteration, &pBuffer);
|
||||
drwav_write(pWavWriter, framesRead*CHANNELS, buffer);
|
||||
}
|
||||
|
||||
//float** test = &buffer;
|
||||
|
||||
drwav_close(pWavWriter);
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,317 +0,0 @@
|
||||
|
||||
#define MINIAUDIO_SPEEX_RESAMPLER_IMPLEMENTATION
|
||||
#include "../../extras/speex_resampler/ma_speex_resampler.h"
|
||||
|
||||
#define MA_DEBUG_OUTPUT
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "../../miniaudio.h"
|
||||
#include "../ma_resampler.h"
|
||||
|
||||
int init_resampler(ma_uint32 rateIn, ma_uint32 rateOut, ma_resample_algorithm algorithm, ma_resampler* pResampler)
|
||||
{
|
||||
ma_result result;
|
||||
ma_resampler_config config;
|
||||
|
||||
config = ma_resampler_config_init(ma_format_s16, 1, rateIn, rateOut, algorithm);
|
||||
result = ma_resampler_init(&config, pResampler);
|
||||
if (result != MA_SUCCESS) {
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_count_query_test__required_input__fixed_interval(ma_resampler* pResampler, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result = 0;
|
||||
ma_int16 input[4096];
|
||||
ma_int16 i;
|
||||
|
||||
MA_ASSERT(frameCountPerIteration < ma_countof(input));
|
||||
|
||||
/* Fill the input buffer with sequential numbers so we can get an idea on the state of things. Useful for inspecting the linear backend in particular. */
|
||||
for (i = 0; i < ma_countof(input); i += 1) {
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
for (i = 0; i < ma_countof(input); i += (ma_int16)frameCountPerIteration) {
|
||||
ma_int16 output[4096];
|
||||
ma_uint64 outputFrameCount;
|
||||
ma_uint64 inputFrameCount;
|
||||
ma_uint64 requiredInputFrameCount;
|
||||
|
||||
/* We retrieve the required number of input frames for the specified number of output frames, and then compare with what we actually get when reading. */
|
||||
requiredInputFrameCount = ma_resampler_get_required_input_frame_count(pResampler, frameCountPerIteration);
|
||||
|
||||
outputFrameCount = frameCountPerIteration;
|
||||
inputFrameCount = ma_countof(input);
|
||||
result = ma_resampler_process_pcm_frames(pResampler, input, &inputFrameCount, output, &outputFrameCount);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to process frames.");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (inputFrameCount != requiredInputFrameCount) {
|
||||
printf("ERROR: Predicted vs actual input count mismatch: predicted=%d, actual=%d\n", (int)requiredInputFrameCount, (int)inputFrameCount);
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
printf("FAILED\n");
|
||||
} else {
|
||||
printf("PASSED\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_test__required_input__by_algorithm_and_rate__fixed_interval(ma_resample_algorithm algorithm, ma_uint32 rateIn, ma_uint32 rateOut, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result;
|
||||
ma_resampler resampler;
|
||||
|
||||
result = init_resampler(rateIn, rateOut, algorithm, &resampler);
|
||||
if (result != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = do_count_query_test__required_input__fixed_interval(&resampler, frameCountPerIteration);
|
||||
|
||||
ma_resampler_uninit(&resampler);
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_test__required_input__by_algorithm__fixed_interval(ma_resample_algorithm algorithm, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm_and_rate__fixed_interval(algorithm, 44100, 48000, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm_and_rate__fixed_interval(algorithm, 48000, 44100, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm_and_rate__fixed_interval(algorithm, 44100, 192000, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm_and_rate__fixed_interval(algorithm, 192000, 44100, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_tests__required_input__by_algorithm(ma_resample_algorithm algorithm)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm__fixed_interval(algorithm, 1);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm__fixed_interval(algorithm, 16);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__required_input__by_algorithm__fixed_interval(algorithm, 127);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_count_query_tests__required_input()
|
||||
{
|
||||
int result;
|
||||
|
||||
printf("Linear\n");
|
||||
result = do_count_query_tests__required_input__by_algorithm(ma_resample_algorithm_linear);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
printf("Speex\n");
|
||||
result = do_count_query_tests__required_input__by_algorithm(ma_resample_algorithm_speex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int do_count_query_test__expected_output__fixed_interval(ma_resampler* pResampler, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result = 0;
|
||||
ma_int16 input[4096];
|
||||
ma_int16 i;
|
||||
|
||||
MA_ASSERT(frameCountPerIteration < ma_countof(input));
|
||||
|
||||
/* Fill the input buffer with sequential numbers so we can get an idea on the state of things. Useful for inspecting the linear backend in particular. */
|
||||
for (i = 0; i < ma_countof(input); i += 1) {
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
for (i = 0; i < ma_countof(input); i += (ma_int16)frameCountPerIteration) {
|
||||
ma_int16 output[4096];
|
||||
ma_uint64 outputFrameCount;
|
||||
ma_uint64 inputFrameCount;
|
||||
ma_uint64 expectedOutputFrameCount;
|
||||
|
||||
/* We retrieve the required number of input frames for the specified number of output frames, and then compare with what we actually get when reading. */
|
||||
expectedOutputFrameCount = ma_resampler_get_expected_output_frame_count(pResampler, frameCountPerIteration);
|
||||
|
||||
outputFrameCount = ma_countof(output);
|
||||
inputFrameCount = frameCountPerIteration;
|
||||
result = ma_resampler_process_pcm_frames(pResampler, input, &inputFrameCount, output, &outputFrameCount);
|
||||
if (result != MA_SUCCESS) {
|
||||
printf("Failed to process frames.");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (outputFrameCount != expectedOutputFrameCount) {
|
||||
printf("ERROR: Predicted vs actual output count mismatch: predicted=%d, actual=%d\n", (int)expectedOutputFrameCount, (int)outputFrameCount);
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
printf("FAILED\n");
|
||||
} else {
|
||||
printf("PASSED\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_test__expected_output__by_algorithm_and_rate__fixed_interval(ma_resample_algorithm algorithm, ma_uint32 rateIn, ma_uint32 rateOut, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result;
|
||||
ma_resampler resampler;
|
||||
|
||||
result = init_resampler(rateIn, rateOut, algorithm, &resampler);
|
||||
if (result != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
result = do_count_query_test__expected_output__fixed_interval(&resampler, frameCountPerIteration);
|
||||
|
||||
ma_resampler_uninit(&resampler);
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_test__expected_output__by_algorithm__fixed_interval(ma_resample_algorithm algorithm, ma_uint64 frameCountPerIteration)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm_and_rate__fixed_interval(algorithm, 44100, 48000, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm_and_rate__fixed_interval(algorithm, 48000, 44100, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm_and_rate__fixed_interval(algorithm, 44100, 192000, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm_and_rate__fixed_interval(algorithm, 192000, 44100, frameCountPerIteration);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int do_count_query_tests__expected_output__by_algorithm(ma_resample_algorithm algorithm)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm__fixed_interval(algorithm, 1);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm__fixed_interval(algorithm, 16);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_test__expected_output__by_algorithm__fixed_interval(algorithm, 127);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_count_query_tests__expected_output()
|
||||
{
|
||||
int result;
|
||||
|
||||
printf("Linear\n");
|
||||
result = do_count_query_tests__expected_output__by_algorithm(ma_resample_algorithm_linear);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
printf("Speex\n");
|
||||
result = do_count_query_tests__expected_output__by_algorithm(ma_resample_algorithm_speex);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int do_count_query_tests()
|
||||
{
|
||||
int result;
|
||||
|
||||
result = do_count_query_tests__expected_output();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = do_count_query_tests__required_input();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int result;
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
result = do_count_query_tests();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user