diff --git a/tests/mal_resampling.c b/tests/mal_resampling.c new file mode 100644 index 00000000..4f4d0b9f --- /dev/null +++ b/tests/mal_resampling.c @@ -0,0 +1,195 @@ +// We're using sigvis for visualizations. This will include mini_al for us, so no need to include mini_al in this file. +#define MAL_NO_SSE2 +#define MAL_NO_AVX2 +#define MINI_SIGVIS_IMPLEMENTATION +#include "../tools/mini_sigvis/mini_sigvis.h" // <-- Includes mini_al. + +// There is a usage pattern for resampling that mini_al does not properly support which is where the client continuously +// reads samples until mal_src_read() returns 0. The problem with this pattern is that is consumes the samples sitting +// in the window which are needed to compute the next samples in future calls to mal_src_read() (assuming the client +// has re-filled the resampler's input data). + +/* +for (;;) { + fill_src_input_data(&src, someData); + + float buffer[4096] + while ((framesRead = mal_src_read(&src, ...) != 0) { + do_something_with_resampled_data(buffer); + } +} +*/ + +// In the use case above, the very last samples that are read from mal_src_read() will not have future samples to draw +// from in order to calculate the correct interpolation factor which in turn results in crackling. + +mal_uint32 sampleRateIn = 0; +mal_uint32 sampleRateOut = 0; +mal_sine_wave sineWave; // <-- This is the source data. +mal_src src; +float srcInput[1024]; +mal_uint32 srcNextSampleIndex = mal_countof(srcInput); + +void reload_src_input() +{ + mal_sine_wave_read(&sineWave, mal_countof(srcInput), srcInput); + srcNextSampleIndex = 0; +} + +mal_uint32 on_src(mal_src* pSRC, mal_uint32 frameCount, void** ppSamplesOut, void* pUserData) +{ + mal_assert(pSRC != NULL); + mal_assert(pSRC->config.channels == 1); + + (void)pUserData; + + // Only read as much as is available in the input buffer. Do not reload the buffer here. + mal_uint32 framesAvailable = mal_countof(srcInput) - srcNextSampleIndex; + mal_uint32 framesToRead = frameCount; + if (framesToRead > framesAvailable) { + framesToRead = framesAvailable; + } + + mal_copy_memory(ppSamplesOut[0], srcInput + srcNextSampleIndex, sizeof(float)*framesToRead); + srcNextSampleIndex += framesToRead; + + return framesToRead; +} + +mal_uint32 on_send_to_device(mal_device* pDevice, mal_uint32 frameCount, void* pFrames) +{ + (void)pDevice; + mal_assert(pDevice->format == mal_format_f32); + mal_assert(pDevice->channels == 1); + + float* pFramesF32 = (float*)pFrames; + + // To reproduce the case we are needing to test, we need to read from the SRC in a very specific way. We keep looping + // until we've read the requested frame count, however we have an inner loop that keeps running until mal_src_read() + // returns 0, in which case we need to reload the SRC's input data and keep going. + mal_uint32 totalFramesRead = 0; + while (totalFramesRead < frameCount) { + mal_uint32 framesRemaining = frameCount - totalFramesRead; + + mal_uint32 maxFramesToRead = 128; + mal_uint32 framesToRead = framesRemaining; + if (framesToRead > maxFramesToRead) { + framesToRead = maxFramesToRead; + } + + mal_uint32 framesRead = (mal_uint32)mal_src_read_deinterleaved(&src, framesToRead, (void**)&pFramesF32, NULL); + if (framesRead == 0) { + reload_src_input(); + } + + totalFramesRead += framesRead; + pFramesF32 += framesRead; + } + + mal_assert(totalFramesRead == frameCount); + return frameCount; +} + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + + + mal_device_config config = mal_device_config_init_playback(mal_format_f32, 1, 0, on_send_to_device); + mal_device device; + mal_result result; + + // We first play the sound the way it's meant to be played. + result = mal_device_init(NULL, mal_device_type_playback, NULL, &config, NULL, &device); + if (result != MAL_SUCCESS) { + return -1; + } + + + // For this test, we need the sine wave to be a different format to the device. + sampleRateOut = device.sampleRate; + sampleRateIn = (sampleRateOut == 44100) ? 48000 : 44100; + mal_sine_wave_init(0.2, 400, sampleRateIn, &sineWave); + + mal_src_config srcConfig = mal_src_config_init(sampleRateIn, sampleRateOut, 1, on_src, NULL); + srcConfig.algorithm = mal_src_algorithm_sinc; + + result = mal_src_init(&srcConfig, &src); + if (result != MAL_SUCCESS) { + printf("Failed to create SRC.\n"); + return -1; + } + + + +#if 0 + msigvis_context sigvis; + result = msigvis_init(&sigvis); + if (result != MAL_SUCCESS) { + printf("Failed to initialize mini_sigvis context.\n"); + return -1; + } + + msigvis_screen screen; + result = msigvis_screen_init(&sigvis, 1280, 720, &screen); + if (result != MAL_SUCCESS) { + printf("Failed to initialize mini_sigvis screen.\n"); + return -2; + } + + msigvis_screen_show(&screen); + + + msigvis_channel channelSineWave; + result = msigvis_channel_init(&sigvis, mal_format_f32, sampleRateOut, &channelSineWave); + if (result != MAL_SUCCESS) { + printf("Failed to initialize mini_sigvis channel.\n"); + return -3; + } + + float testSamples[40960]; + float* pFramesF32 = testSamples; + + // To reproduce the case we are needing to test, we need to read from the SRC in a very specific way. We keep looping + // until we've read the requested frame count, however we have an inner loop that keeps running until mal_src_read() + // returns 0, in which case we need to reload the SRC's input data and keep going. + mal_uint32 totalFramesRead = 0; + while (totalFramesRead < mal_countof(testSamples)) { + mal_uint32 maxFramesToRead = 128; + mal_uint32 framesToRead = mal_countof(testSamples); + if (framesToRead > maxFramesToRead) { + framesToRead = maxFramesToRead; + } + + mal_uint32 framesRead = (mal_uint32)mal_src_read_deinterleaved(&src, framesToRead, (void**)&pFramesF32, NULL); + if (framesRead == 0) { + reload_src_input(); + } + + totalFramesRead += framesRead; + pFramesF32 += framesRead; + } + + msigvis_channel_push_samples(&channelSineWave, mal_countof(testSamples), testSamples); + msigvis_screen_add_channel(&screen, &channelSineWave); + + + int exitCode = msigvis_run(&sigvis); + + msigvis_screen_uninit(&screen); + msigvis_uninit(&sigvis); +#else + + result = mal_device_start(&device); + if (result != MAL_SUCCESS) { + return -2; + } + + printf("Press Enter to quit...\n"); + getchar(); + mal_device_uninit(&device); +#endif + + return 0; +} \ No newline at end of file diff --git a/tests/mal_test_0.vcxproj b/tests/mal_test_0.vcxproj index 4b039eb2..248c7fe7 100644 --- a/tests/mal_test_0.vcxproj +++ b/tests/mal_test_0.vcxproj @@ -278,14 +278,22 @@ true true - + false - false false + false false false false + + true + true + true + true + true + true + true true diff --git a/tests/mal_test_0.vcxproj.filters b/tests/mal_test_0.vcxproj.filters index 33c2259c..f373d82b 100644 --- a/tests/mal_test_0.vcxproj.filters +++ b/tests/mal_test_0.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + diff --git a/tools/mini_sigvis/mini_sigvis.h b/tools/mini_sigvis/mini_sigvis.h index f4459074..b04cae16 100644 --- a/tools/mini_sigvis/mini_sigvis.h +++ b/tools/mini_sigvis/mini_sigvis.h @@ -373,6 +373,8 @@ mal_result msigvis_screen_redraw(msigvis_screen* pScreen) /////////////////////////////////////////////////////////////////////////////// mal_result msigvis_channel_init(msigvis_context* pContext, mal_format format, mal_uint32 sampleRate, msigvis_channel* pChannel) { + (void)pContext; + if (pChannel == NULL) { return MAL_INVALID_ARGS; }