diff --git a/research/ma_resampler.h b/research/ma_resampler.h index 9f71781e..41794f57 100644 --- a/research/ma_resampler.h +++ b/research/ma_resampler.h @@ -1,3 +1,200 @@ +/* Resampling research. Public domain. */ + +#ifndef ma_resampler_h +#define ma_resampler_h + +typedef enum +{ + ma_resample_algorithm_linear = 0, /* Default. Fastest. */ + ma_resample_algorithm_sinc /* Slower. */ +} ma_resample_algorithm; + +/* +Simple high-level API for resampling 32-bit floating point samples. + +Use ma_calculate_frame_count_after_src() to determine the required output buffer size. +*/ +ma_result ma_resample_f32(ma_resample_algorithm algorithm, ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn); + +#endif /* ma_resampler_h */ + +/* +Implementation +*/ +#ifdef MINIAUDIO_IMPLEMENTATION + +#ifndef MA_RESAMPLER_MIN_RATIO +#define MA_RESAMPLER_MIN_RATIO 0.02083333 +#endif +#ifndef MA_RESAMPLER_MAX_RATIO +#define MA_RESAMPLER_MAX_RATIO 48.0 +#endif + +ma_result ma_resample_f32__linear(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn) +{ + double ratio = (double)sampleRateIn / (double)sampleRateOut; + double timeIn = 0; + double timeOut = 0; + + /* Fast path if the sample rates are the same. */ + if (sampleRateOut == sampleRateIn) { + MA_COPY_MEMORY(pSamplesOut, pSamplesIn, (size_t)ma_min(sampleCountOut, sampleCountIn) * sizeof(float)); + return MA_SUCCESS; + } + + /* Do nothing if there's no input. */ + if (sampleCountOut == 0 || sampleCountIn == 0) { + return MA_SUCCESS; + } + + + /* The first output sample should always be the same as the input sample. */ + pSamplesOut[0] = pSamplesIn[0]; + timeIn += ratio; + timeOut += 1; + + for (;;) { + ma_uint64 iTimeIn; + ma_uint64 iTimeOut; + + iTimeIn = (ma_uint64)timeIn; + if (iTimeIn >= sampleCountIn) { + break; + } + + iTimeOut = (ma_uint64)timeOut; + if (iTimeOut >= sampleCountOut) { + break; + } + + /* To linearly interpolate we need the previous and next input samples. */ + { + ma_uint64 iTimeInPrev = iTimeIn; + ma_uint64 iTimeInNext = (ma_uint64)ceil(timeIn); + + if (iTimeInNext >= sampleCountIn) { + iTimeInNext = iTimeInPrev; /* <-- We could instead terminate here which would make the output a few samples shorter. */ + } + + pSamplesOut[iTimeOut] = ma_mix_f32_fast(pSamplesIn[iTimeInPrev], pSamplesIn[iTimeInNext], (float)(timeIn - iTimeIn)); + + /* Try some kind of low-pass filter. */ + #if 1 + { + double cutoff = ma_min(sampleRateIn, sampleRateOut) * 0.5; + double RC = 1.0/(cutoff*MA_TAU_D); + double dt = 1.0/sampleRateOut; + float alpha = (float)(dt/(RC+dt)); + pSamplesOut[iTimeOut] = pSamplesOut[iTimeOut-1] + (alpha*(pSamplesOut[iTimeOut] - pSamplesOut[iTimeOut-1])); + } + #endif + } + + timeIn += ratio; + timeOut += 1; + } + + return MA_INVALID_ARGS; +} + + +double ma_sinc_hann(double n, int N) +{ + double s = sin(MA_PI_D*n/N); + return s*s; +} + +ma_result ma_resample_f32__sinc(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn) +{ + double ratio = (double)sampleRateIn / (double)sampleRateOut; + double timeIn = 0; + double timeOut = 0; + double samplingPeriodIn = 1.0/sampleRateIn; + int windowWidth = 32; + + /* Fast path if the sample rates are the same. */ + if (sampleRateOut == sampleRateIn) { + MA_COPY_MEMORY(pSamplesOut, pSamplesIn, (size_t)ma_min(sampleCountOut, sampleCountIn) * sizeof(float)); + return MA_SUCCESS; + } + + /* Do nothing if there's no input. */ + if (sampleCountOut == 0 || sampleCountIn == 0) { + return MA_SUCCESS; + } + + + /* The first output sample should always be the same as the input sample. */ + pSamplesOut[0] = pSamplesIn[0]; + timeIn += ratio; + timeOut += 1; + + for (;;) { + ma_uint64 iTimeIn; + ma_uint64 iTimeOut; + + iTimeIn = (ma_uint64)timeIn; + if (iTimeIn >= sampleCountIn) { + break; + } + + iTimeOut = (ma_uint64)timeOut; + if (iTimeOut >= sampleCountOut) { + break; + } + + /* To linearly interpolate we need the previous and next input samples. */ + { + ma_uint64 iTimeInPrev = iTimeIn; + ma_uint64 iTimeInNext = (ma_uint64)ceil(timeIn); + + if (iTimeInNext >= sampleCountIn) { + iTimeInNext = iTimeInPrev; /* <-- We could instead terminate here which would make the output a few samples shorter. */ + } + + + + #if 0 + pSamplesOut[iTimeOut] = ma_mix_f32_fast(pSamplesIn[iTimeInPrev], pSamplesIn[iTimeInNext], (float)(timeIn - iTimeIn)); + #else + pSamplesOut[iTimeOut] = 0; + for (int i = -windowWidth; i < windowWidth; i += 1) { + if ((i < 0 && -i < iTimeIn) || i > 0 && i < sampleCountIn) { +#if 0 + double t = (iTimeIn+n); + double s = ma_sinc(sampleRateIn*t); + pSamplesOut[iTimeOut] += pSamplesIn[(ma_uint64)(iTimeIn+n)] * s; +#endif + } + } + #endif + } + + timeIn += ratio; + timeOut += 1; + } + + return MA_INVALID_ARGS; +} + +ma_result ma_resample_f32(ma_resample_algorithm algorithm, ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 sampleCountOut, float* pSamplesOut, ma_uint64 sampleCountIn, float* pSamplesIn) +{ + if (pSamplesOut == NULL || pSamplesIn == NULL) { + return MA_INVALID_ARGS; + } + + switch (algorithm) + { + case ma_resample_algorithm_linear: return ma_resample_f32__linear(sampleRateOut, sampleRateIn, sampleCountOut, pSamplesOut, sampleCountIn, pSamplesIn); + case ma_resample_algorithm_sinc: return ma_resample_f32__sinc (sampleRateOut, sampleRateIn, sampleCountOut, pSamplesOut, sampleCountIn, pSamplesIn); + default: return MA_INVALID_ARGS; + } +} + +#endif /* MINIAUDIO_IMPLEMENTATION */ + + +#if 0 /* Consider this code public domain. @@ -1204,3 +1401,4 @@ ma_uint64 ma_resampler_seek__sinc(ma_resampler* pResampler, ma_uint64 frameCount } #endif +#endif /* 0 (Old Implementation)*/ \ No newline at end of file diff --git a/research/tests/ma_resampler_test_0.c b/research/tests/ma_resampler_test_0.c index d775dc48..a2e74746 100644 --- a/research/tests/ma_resampler_test_0.c +++ b/research/tests/ma_resampler_test_0.c @@ -1,11 +1,122 @@ +#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 "../../../../dr_libs/dr_wav.h" +#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 @@ -71,4 +182,5 @@ int main(int argc, char** argv) (void)argc; (void)argv; return 0; -} \ No newline at end of file +} +#endif diff --git a/tests/ma_test_0.vcxproj b/tests/ma_test_0.vcxproj index b667c6b3..34fe9def 100644 --- a/tests/ma_test_0.vcxproj +++ b/tests/ma_test_0.vcxproj @@ -319,12 +319,12 @@ true - false - false - false - false - false - false + true + true + true + true + true + true true @@ -335,12 +335,12 @@ true - true - true - true - true - true - true + false + false + false + false + false + false true