From 58a280b840fc33063c9ce80484b2d40b356bdd50 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 1 Dec 2018 07:37:27 +1000 Subject: [PATCH 1/7] Fix channel routing weights for back/left and back/right. --- mini_al.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mini_al.h b/mini_al.h index 7aff081c..13538d9d 100644 --- a/mini_al.h +++ b/mini_al.h @@ -24676,8 +24676,8 @@ float g_malChannelPlaneRatios[MAL_CHANNEL_POSITION_COUNT][6] = { { 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_RIGHT { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_CENTER { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_LFE - { 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f}, // MAL_CHANNEL_BACK_LEFT - { 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f}, // MAL_CHANNEL_BACK_RIGHT + { 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f}, // MAL_CHANNEL_BACK_LEFT + { 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f}, // MAL_CHANNEL_BACK_RIGHT { 0.25f, 0.0f, 0.75f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_LEFT_CENTER { 0.0f, 0.25f, 0.75f, 0.0f, 0.0f, 0.0f}, // MAL_CHANNEL_FRONT_RIGHT_CENTER { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}, // MAL_CHANNEL_BACK_CENTER From b9ad5113b6c4464422d98b0164c28ab6bba3d5ea Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 4 Dec 2018 18:33:09 +1000 Subject: [PATCH 2/7] Bump version to 0.8.13 --- mini_al.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mini_al.h b/mini_al.h index 13538d9d..3c0c5204 100644 --- a/mini_al.h +++ b/mini_al.h @@ -1,5 +1,5 @@ // Audio playback and capture library. Public domain. See "unlicense" statement at the end of this file. -// mini_al - v0.8.12 - 2018-11-27 +// mini_al - v0.8.13 - 2018-12-04 // // David Reid - davidreidsoftware@gmail.com @@ -28473,6 +28473,10 @@ mal_uint64 mal_sine_wave_read_ex(mal_sine_wave* pSineWave, mal_uint64 frameCount // REVISION HISTORY // ================ // +// v0.8.13 - 2018-12-04 +// - Core Audio: Fix a bug with channel mapping. +// - Fix a bug with channel routing where the back/left and back/right channels have the wrong weight. +// // v0.8.12 - 2018-11-27 // - Drop support for SDL 1.2. The Emscripten build now requires "-s USE_SDL=2". // - Fix a linking error with ALSA. From 4d58137e31e5ab6b0ef0fbf6e10a8ffd0809e227 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 8 Dec 2018 07:07:43 +1000 Subject: [PATCH 3/7] Miscellaneous work on the new resampler. The requirement for pointers to be aligned to MAL_SIMD_ALIGNMENT has been dropped because it is adding too many complications to the implementation. --- research/mal_resampler.h | 209 +++++++++++++++++++++++++++++++++++---- 1 file changed, 188 insertions(+), 21 deletions(-) diff --git a/research/mal_resampler.h b/research/mal_resampler.h index 9266854b..3cf68142 100644 --- a/research/mal_resampler.h +++ b/research/mal_resampler.h @@ -25,7 +25,6 @@ Requirements: - Must have different modes on how to handle the last of the input samples. Certain situations (streaming) requires the last input samples to be cached in the internal structure for the windowing algorithm. Other situations require all of the input samples to be consumed in order to output the correct total sample count. -- Pointers passed into the onRead() callback must be guaranteed to be aligned to MAL_SIMD_ALIGNMENT. Other Notes: @@ -55,9 +54,10 @@ typedef struct mal_resampler mal_resampler; typedef mal_uint32 (* mal_resampler_read_from_client_proc)(mal_resampler* pResampler, mal_uint32 frameCount, void** ppFrames); /* Backend functions. */ -typedef mal_result (* mal_resampler_init_proc)(mal_resampler* pResampler); -typedef mal_uint64 (* mal_resampler_read_proc)(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames); -typedef mal_uint64 (* mal_resampler_seek_proc)(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options); +typedef mal_result (* mal_resampler_init_proc) (mal_resampler* pResampler); +typedef mal_uint64 (* mal_resampler_read_f32_proc)(mal_resampler* pResampler, mal_uint64 frameCount, float** ppFrames); +typedef mal_uint64 (* mal_resampler_read_s16_proc)(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames); +typedef mal_uint64 (* mal_resampler_seek_proc) (mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options); typedef enum { @@ -97,7 +97,8 @@ struct mal_resampler double windowTime; /* By input rate. Relative to the start of the cache. */ mal_resampler_config config; mal_resampler_init_proc init; - mal_resampler_read_proc read; + mal_resampler_read_f32_proc readF32; + mal_resampler_read_s16_proc readS16; mal_resampler_seek_proc seek; }; @@ -202,23 +203,37 @@ mal_uint64 mal_resampler_get_expected_output_frame_count(mal_resampler* pResampl #ifdef MINI_AL_IMPLEMENTATION #ifndef MAL_RESAMPLER_MIN_RATIO -#define MAL_RESAMPLER_MIN_RATIO 0.001 +#define MAL_RESAMPLER_MIN_RATIO 0.0416 #endif #ifndef MAL_RESAMPLER_MAX_RATIO -#define MAL_RESAMPLER_MAX_RATIO 100.0 +#define MAL_RESAMPLER_MAX_RATIO 24.0 #endif -mal_uint64 mal_resampler_read__linear(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames); +mal_result mal_resampler_init__linear(mal_resampler* pResampler); +mal_uint64 mal_resampler_read_f32__linear(mal_resampler* pResampler, mal_uint64 frameCount, float** ppFrames); +mal_uint64 mal_resampler_read_s16__linear(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames); mal_uint64 mal_resampler_seek__linear(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options); mal_result mal_resampler_init__sinc(mal_resampler* pResampler); -mal_uint64 mal_resampler_read__sinc(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames); +mal_uint64 mal_resampler_read_f32__sinc(mal_resampler* pResampler, mal_uint64 frameCount, float** ppFrames); +mal_uint64 mal_resampler_read_s16__sinc(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames); mal_uint64 mal_resampler_seek__sinc(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options); /* TODO: Add this to mini_al.h */ #define MAL_ALIGN_INT(val, alignment) (((val) + ((alignment)-1)) & ~((alignment)-1)) #define MAL_ALIGN_PTR(ptr, alignment) (void*)MAL_ALIGN_INT(((mal_uintptr)(ptr)), (alignment)) +static MAL_INLINE float mal_fractional_part_f32(float x) +{ + return x - ((mal_int32)x); +} + +static MAL_INLINE double mal_fractional_part_f64(double x) +{ + return x - ((mal_int64)x); +} + +#if 0 /* This macro declares a set of variables on the stack of a given size in bytes. The variables it creates are: - mal_uint8 Unaligned[size + MAL_SIMD_ALIGNMENT]; @@ -237,7 +252,7 @@ This does not work for formats that do not have a clean mapping to a primitive C name[iChannel] = (type*)((mal_uint8*)MAL_ALIGN_PTR(name##Unaligned, MAL_SIMD_ALIGNMENT) + (iChannel*((size) & ~((MAL_SIMD_ALIGNMENT)-1)))); \ } \ } while (0) - +#endif mal_result mal_resampler_init(const mal_resampler_config* pConfig, mal_resampler* pResampler) { @@ -270,16 +285,18 @@ mal_result mal_resampler_init(const mal_resampler_config* pConfig, mal_resampler switch (pResampler->config.algorithm) { case mal_resampler_algorithm_linear: { - pResampler->init = NULL; - pResampler->read = mal_resampler_read__linear; - pResampler->seek = mal_resampler_seek__linear; + pResampler->init = mal_resampler_init__linear; + pResampler->readF32 = mal_resampler_read_f32__linear; + pResampler->readS16 = mal_resampler_read_s16__linear; + pResampler->seek = mal_resampler_seek__linear; } break; case mal_resampler_algorithm_sinc: { - pResampler->init = mal_resampler_init__sinc; - pResampler->read = mal_resampler_read__sinc; - pResampler->seek = mal_resampler_seek__sinc; + pResampler->init = mal_resampler_init__sinc; + pResampler->readF32 = mal_resampler_read_f32__sinc; + pResampler->readS16 = mal_resampler_read_s16__sinc; + pResampler->seek = mal_resampler_seek__sinc; } break; } @@ -335,12 +352,31 @@ mal_result mal_resampler_set_rate_ratio(mal_resampler* pResampler, double ratio) return MAL_SUCCESS; } +typedef union +{ + float* f32[MAL_MAX_CHANNELS]; + mal_int16* s16[MAL_MAX_CHANNELS]; +} mal_resampler_running_frames; + mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames) { - if (pResampler == NULL || pResampler->read == NULL) { + mal_uint64 framesRead; + mal_resampler_running_frames runningFramesOut; + + if (pResampler == NULL) { return 0; /* Invalid arguments. */ } + if (pResampler->config.format == mal_format_f32) { + if (pResampler->readF32 == NULL) { + return 0; /* Invalid arguments. No read callback. */ + } + } else { + if (pResampler->readS16 == NULL) { + return 0; /* Invalid arguments. No read callback. */ + } + } + if (frameCount == 0) { return 0; /* Nothing to do, so return early. */ } @@ -350,8 +386,99 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, return mal_resampler_seek(pResampler, frameCount, 0); } + /* Initialization of the running frame pointers. */ + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + runningFramesOut.f32[iChannel] = (float*)ppFrames[iChannel]; + } - return pResampler->read(pResampler, frameCount, ppFrames); + /* + The backend read callbacks are only called for ranges that can be read entirely from cache. This simplifies each backend + because they do not need to worry about cache reloading logic. Instead we do all of the cache reloading stuff from here. + */ + + framesRead = 0; + while (framesRead < frameCount) { + double cachedOutputTime; + mal_uint64 framesRemaining = frameCount - framesRead; + mal_uint64 framesToReadRightNow = framesRemaining; + + /* We need to make sure we don't read more than what's already in the buffer at a time. */ + cachedOutputTime = mal_resampler_get_cached_output_time(pResampler); + if (framesRemaining > cachedOutputTime) { + framesToReadRightNow = (mal_uint64)floor(cachedOutputTime); + } + + /* + At this point we should know how many frames can be read this iteration. We need an optimization for when the ratio=1 + and the current time is a whole number. In this case we need to do a direct copy without any processing. + */ + if (pResampler->config.ratio == 1 && mal_fractional_part_f64(pResampler->windowTime) == 0) { + /* No need to read from the backend - just copy the input straight over without any processing. */ + if (pResampler->config.format == mal_format_f32) { + /* TODO: Implement me. */ + } else { + /* TOOD: Implement me. */ + } + } else { + /* Need to read from the backend. */ + mal_uint64 framesJustRead; + if (pResampler->config.format == mal_format_f32) { + framesJustRead = pResampler->readF32(pResampler, framesToReadRightNow, runningFramesOut.f32); + } else { + framesJustRead = pResampler->readS16(pResampler, framesToReadRightNow, runningFramesOut.s16); + } + + if (framesJustRead != framesToReadRightNow) { + mal_assert(MAL_FALSE); + break; /* Should never hit this. */ + } + } + + /* Move time forward. */ + pResampler->windowTime += (framesToReadRightNow * pResampler->config.ratio); + + if (pResampler->config.format == mal_format_f32) { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + runningFramesOut.f32[iChannel] += framesToReadRightNow; + } + } else { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + runningFramesOut.s16[iChannel] += framesToReadRightNow; + } + } + + /* We don't want to reload the buffer if we've finished reading. */ + framesRead += framesToReadRightNow; + if (framesRead == frameCount) { + break; + } + + /* + If we get here it means we need to reload the buffer from the client and keep iterating. To reload the buffer we + need to move the remaining data down to the front of the buffer, adjust the window time, then read more from the + client. + */ + { + mal_int32 offset = (mal_int32)pResampler->windowTime; + mal_assert(offset <= pResampler->cacheLengthInFrames); + + pResampler->windowTime -= offset; + if (pResampler->config.format == mal_format_f32) { + for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames - offset; ++i) { + pResampler->cache.f32[i] = pResampler->cache.f32[i + offset]; + } + } else { + for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames - offset; ++i) { + pResampler->cache.s16[i] = pResampler->cache.s16[i + offset]; + } + } + + /* Here is where we need to read more data from the client. */ + + } + } + + return framesRead; } mal_uint64 mal_resampler_seek(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options) @@ -364,8 +491,10 @@ mal_uint64 mal_resampler_seek(mal_resampler* pResampler, mal_uint64 frameCount, return 0; /* Nothing to do, so return early. */ } + /* TODO: Do seeking. */ + (void)options; - return pResampler->seek(pResampler, frameCount, options); + return 0; } @@ -472,7 +601,31 @@ mal_uint64 mal_resampler_get_expected_output_frame_count(mal_resampler* pResampl /* Linear */ -mal_uint64 mal_resampler_read__linear(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames) +mal_result mal_resampler_init__linear(mal_resampler* pResampler) +{ + mal_assert(pResampler != NULL); + + /* The linear implementation always has a window length of 2. */ + pResampler->windowLength = 2; + + return MAL_SUCCESS; +} + +mal_uint64 mal_resampler_read_f32__linear(mal_resampler* pResampler, mal_uint64 frameCount, float** ppFrames) +{ + mal_assert(pResampler != NULL); + mal_assert(pResampler->config.onRead != NULL); + mal_assert(frameCount > 0); + mal_assert(ppFrames != NULL); + + /* TODO: Implement me. */ + (void)pResampler; + (void)frameCount; + (void)ppFrames; + return 0; +} + +mal_uint64 mal_resampler_read_s16__linear(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames) { mal_assert(pResampler != NULL); mal_assert(pResampler->config.onRead != NULL); @@ -511,7 +664,21 @@ mal_result mal_resampler_init__sinc(mal_resampler* pResampler) return MAL_SUCCESS; } -mal_uint64 mal_resampler_read__sinc(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames) +mal_uint64 mal_resampler_read_f32__sinc(mal_resampler* pResampler, mal_uint64 frameCount, float** ppFrames) +{ + mal_assert(pResampler != NULL); + mal_assert(pResampler->config.onRead != NULL); + mal_assert(frameCount > 0); + mal_assert(ppFrames != NULL); + + /* TODO: Implement me. */ + (void)pResampler; + (void)frameCount; + (void)ppFrames; + return 0; +} + +mal_uint64 mal_resampler_read_s16__sinc(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames) { mal_assert(pResampler != NULL); mal_assert(pResampler->config.onRead != NULL); From fa94ca033ca3c4d3a1fac35585a0472a57b8b90f Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 8 Dec 2018 09:38:09 +1000 Subject: [PATCH 4/7] Update VC++ project files. --- tests/mal_test_0.vcxproj | 25 ++++++++++++++++++++++++- tests/mal_test_0.vcxproj.filters | 6 ++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/mal_test_0.vcxproj b/tests/mal_test_0.vcxproj index 1420d766..6d877767 100644 --- a/tests/mal_test_0.vcxproj +++ b/tests/mal_test_0.vcxproj @@ -262,7 +262,30 @@ - + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + false + false + false + false + false + false + true true diff --git a/tests/mal_test_0.vcxproj.filters b/tests/mal_test_0.vcxproj.filters index 8e66fa8c..5d2b4ad3 100644 --- a/tests/mal_test_0.vcxproj.filters +++ b/tests/mal_test_0.vcxproj.filters @@ -39,6 +39,12 @@ Source Files + + Source Files + + + Source Files + From 24c059acaf21d345945826564f82ace53e5f38c4 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 8 Dec 2018 09:44:51 +1000 Subject: [PATCH 5/7] Finish initial untested work on mal_resampler_read(). This does not yet implement the filter backends. It only does the data retrieval and buffer management part of it. --- research/mal_resampler.h | 155 ++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 36 deletions(-) diff --git a/research/mal_resampler.h b/research/mal_resampler.h index 3cf68142..347adb9f 100644 --- a/research/mal_resampler.h +++ b/research/mal_resampler.h @@ -37,8 +37,9 @@ Other Notes: Random Notes: - You cannot change the algorithm after initialization. - It is recommended to keep the mal_resampler object aligned to MAL_SIMD_ALIGNMENT, though it is not necessary. -- Ratios need to be in the range of MAL_RESAMPLER_MIN_RATIO and MAL_RESAMPLER_MAX_RATIO. If you need extreme ratios - then you will need to chain resamplers together. +- Ratios need to be in the range of MAL_RESAMPLER_MIN_RATIO and MAL_RESAMPLER_MAX_RATIO. This is enough to convert + to and from 8000 and 384000, which is the smallest and largest standard rates supported by mini_al. If you need + extreme ratios then you will need to chain resamplers together. */ #ifndef mal_resampler_h #define mal_resampler_h @@ -91,8 +92,8 @@ struct mal_resampler float f32[MAL_RESAMPLER_CACHE_SIZE_IN_BYTES/sizeof(float)]; mal_int16 s16[MAL_RESAMPLER_CACHE_SIZE_IN_BYTES/sizeof(mal_int16)]; } cache; /* Do not use directly. Keep this as the first member of this structure for SIMD alignment purposes. */ - mal_uint16 firstCachedFrameOffset; - mal_uint16 cacheLengthInFrames; /* The number of valid frames sitting in the cache. May be less than the cache's capacity. */ + mal_uint32 cacheStrideInFrames; /* The number of the samples between channels in the cache. The first sample for channel 0 is cacheStrideInFrames*0. The first sample for channel 1 is cacheStrideInFrames*1, etc. */ + mal_uint16 cacheLengthInFrames; /* The number of valid frames sitting in the cache, including the filter window. May be less than the cache's capacity. */ mal_uint16 windowLength; double windowTime; /* By input rate. Relative to the start of the cache. */ mal_resampler_config config; @@ -160,6 +161,10 @@ of time in input rate making up the cached input. When the end of input mode is set to mal_resampler_end_of_input_mode_no_consume, the input frames currently sitting in the window are not included in the calculation. + +This can return a negative value if nothing has yet been loaded into the internal cache. This will happen if this is called +immediately after initialization, before the first read has been performed, and may also happen if only a few samples have +been read from the client. */ double mal_resampler_get_cached_input_time(mal_resampler* pResampler); @@ -169,6 +174,10 @@ of time in output rate making up the cached output. When the end of input mode is set to mal_resampler_end_of_input_mode_no_consume, the input frames currently sitting in the window are not included in the calculation. + +This can return a negative value if nothing has yet been loaded into the internal cache. This will happen if this is called +immediately after initialization, before the first read has been performed, and may also happen if only a few samples have +been read from the client. */ double mal_resampler_get_cached_output_time(mal_resampler* pResampler); @@ -203,10 +212,10 @@ mal_uint64 mal_resampler_get_expected_output_frame_count(mal_resampler* pResampl #ifdef MINI_AL_IMPLEMENTATION #ifndef MAL_RESAMPLER_MIN_RATIO -#define MAL_RESAMPLER_MIN_RATIO 0.0416 +#define MAL_RESAMPLER_MIN_RATIO 0.02083333 #endif #ifndef MAL_RESAMPLER_MAX_RATIO -#define MAL_RESAMPLER_MAX_RATIO 24.0 +#define MAL_RESAMPLER_MAX_RATIO 48.0 #endif mal_result mal_resampler_init__linear(mal_resampler* pResampler); @@ -219,10 +228,6 @@ mal_uint64 mal_resampler_read_f32__sinc(mal_resampler* pResampler, mal_uint64 fr mal_uint64 mal_resampler_read_s16__sinc(mal_resampler* pResampler, mal_uint64 frameCount, mal_int16** ppFrames); mal_uint64 mal_resampler_seek__sinc(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options); -/* TODO: Add this to mini_al.h */ -#define MAL_ALIGN_INT(val, alignment) (((val) + ((alignment)-1)) & ~((alignment)-1)) -#define MAL_ALIGN_PTR(ptr, alignment) (void*)MAL_ALIGN_INT(((mal_uintptr)(ptr)), (alignment)) - static MAL_INLINE float mal_fractional_part_f32(float x) { return x - ((mal_int32)x); @@ -234,6 +239,9 @@ static MAL_INLINE double mal_fractional_part_f64(double x) } #if 0 +#define MAL_ALIGN_INT(val, alignment) (((val) + ((alignment)-1)) & ~((alignment)-1)) +#define MAL_ALIGN_PTR(ptr, alignment) (void*)MAL_ALIGN_INT(((mal_uintptr)(ptr)), (alignment)) + /* This macro declares a set of variables on the stack of a given size in bytes. The variables it creates are: - mal_uint8 Unaligned[size + MAL_SIMD_ALIGNMENT]; @@ -254,6 +262,20 @@ This does not work for formats that do not have a clean mapping to a primitive C } while (0) #endif +#define mal_filter_window_length_left(length) ((length) >> 1) +#define mal_filter_window_length_right(length) ((length) - mal_filter_window_length_left(length)) + +static MAL_INLINE mal_uint16 mal_resampler_window_length_left(const mal_resampler* pResampler) +{ + return mal_filter_window_length_left(pResampler->windowLength); +} + +static MAL_INLINE mal_uint16 mal_resampler_window_length_right(const mal_resampler* pResampler) +{ + return mal_filter_window_length_right(pResampler->windowLength); +} + + mal_result mal_resampler_init(const mal_resampler_config* pConfig, mal_resampler* pResampler) { if (pResampler == NULL) { @@ -300,6 +322,12 @@ mal_result mal_resampler_init(const mal_resampler_config* pConfig, mal_resampler } break; } + if (pResampler->config.format == mal_format_f32) { + pResampler->cacheStrideInFrames = mal_countof(pResampler->cache.f32) / pResampler->config.channels; + } else { + pResampler->cacheStrideInFrames = mal_countof(pResampler->cache.s16) / pResampler->config.channels; + } + if (pResampler->init != NULL) { mal_result result = pResampler->init(pResampler); if (result != MAL_SUCCESS) { @@ -307,6 +335,12 @@ mal_result mal_resampler_init(const mal_resampler_config* pConfig, mal_resampler } } + /* + After initializing the backend, we'll need to pre-fill the filter with zeroes. This has already been half done via + the call to mal_zero_object() at the top of this function, but we need to increment the frame counter to complete it. + */ + pResampler->cacheLengthInFrames = mal_resampler_window_length_left(pResampler); + return MAL_SUCCESS; } @@ -356,27 +390,18 @@ typedef union { float* f32[MAL_MAX_CHANNELS]; mal_int16* s16[MAL_MAX_CHANNELS]; -} mal_resampler_running_frames; +} mal_resampler_deinterleaved_pointers; mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames) { mal_uint64 framesRead; - mal_resampler_running_frames runningFramesOut; + mal_resampler_deinterleaved_pointers runningFramesOut; + mal_bool32 atEnd = MAL_FALSE; if (pResampler == NULL) { return 0; /* Invalid arguments. */ } - if (pResampler->config.format == mal_format_f32) { - if (pResampler->readF32 == NULL) { - return 0; /* Invalid arguments. No read callback. */ - } - } else { - if (pResampler->readS16 == NULL) { - return 0; /* Invalid arguments. No read callback. */ - } - } - if (frameCount == 0) { return 0; /* Nothing to do, so return early. */ } @@ -386,6 +411,14 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, return mal_resampler_seek(pResampler, frameCount, 0); } + + if (pResampler->config.format == mal_format_f32) { + mal_assert(pResampler->readF32 != NULL); + } else { + mal_assert(pResampler->readS16 != NULL); + } + + /* Initialization of the running frame pointers. */ for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { runningFramesOut.f32[iChannel] = (float*)ppFrames[iChannel]; @@ -413,11 +446,23 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, and the current time is a whole number. In this case we need to do a direct copy without any processing. */ if (pResampler->config.ratio == 1 && mal_fractional_part_f64(pResampler->windowTime) == 0) { - /* No need to read from the backend - just copy the input straight over without any processing. */ + /* + No need to read from the backend - just copy the input straight over without any processing. We start reading from + the right side of the filter window. + */ + mal_uint16 iFirstSample = (mal_uint16)pResampler->windowTime + mal_resampler_window_length_left(pResampler); if (pResampler->config.format == mal_format_f32) { - /* TODO: Implement me. */ + for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { + runningFramesOut.f32[iChannel][iFrame] = pResampler->cache.f32[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; + } + } } else { - /* TOOD: Implement me. */ + for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { + runningFramesOut.s16[iChannel][iFrame] = pResampler->cache.s16[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; + } + } } } else { /* Need to read from the backend. */ @@ -453,28 +498,66 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, break; } + /* + We need to exit if we've reached the end of the input buffer. We do not want to attempt to read more data, nor + do we want to read in zeroes to fill out the requested frame count (frameCount). + */ + if (atEnd) { + break; + } + /* If we get here it means we need to reload the buffer from the client and keep iterating. To reload the buffer we need to move the remaining data down to the front of the buffer, adjust the window time, then read more from the - client. + client. If we have already reached the end of the client's data, we don't want to attempt to read more. */ { - mal_int32 offset = (mal_int32)pResampler->windowTime; - mal_assert(offset <= pResampler->cacheLengthInFrames); + mal_uint32 framesToReadFromClient; + mal_uint32 framesReadFromClient; + mal_uint16 framesToConsume; + + mal_assert(pResampler->windowTime < 65536); + mal_assert(pResampler->windowTime <= pResampler->cacheLengthInFrames); + + framesToConsume = (mal_uint16)pResampler->windowTime; + + pResampler->windowTime -= framesToConsume; + pResampler->cacheLengthInFrames -= framesToConsume; - pResampler->windowTime -= offset; if (pResampler->config.format == mal_format_f32) { - for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames - offset; ++i) { - pResampler->cache.f32[i] = pResampler->cache.f32[i + offset]; + for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames; ++i) { + pResampler->cache.f32[i] = pResampler->cache.f32[i + framesToConsume]; } + framesToReadFromClient = mal_countof(pResampler->cache.f32) - pResampler->cacheLengthInFrames; } else { - for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames - offset; ++i) { - pResampler->cache.s16[i] = pResampler->cache.s16[i + offset]; + for (mal_int32 i = 0; i < pResampler->cacheLengthInFrames; ++i) { + pResampler->cache.s16[i] = pResampler->cache.s16[i + framesToConsume]; } + framesToReadFromClient = mal_countof(pResampler->cache.s16) - pResampler->cacheLengthInFrames; } - /* Here is where we need to read more data from the client. */ + /* Here is where we need to read more data from the client. We need to construct some deinterleaved buffers first, though. */ + mal_resampler_deinterleaved_pointers clientDst; + if (pResampler->config.format == mal_format_f32) { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + clientDst.f32[iChannel] = pResampler->cache.f32 + (pResampler->cacheStrideInFrames*iChannel + pResampler->cacheLengthInFrames); + } + framesReadFromClient = pResampler->config.onRead(pResampler, framesToReadFromClient, clientDst.f32); + } else { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + clientDst.s16[iChannel] = pResampler->cache.s16 + (pResampler->cacheStrideInFrames*iChannel + pResampler->cacheLengthInFrames); + } + framesReadFromClient = pResampler->config.onRead(pResampler, framesToReadFromClient, clientDst.s16); + } + + mal_assert(framesReadFromClient <= framesToReadFromClient); + if (framesReadFromClient < framesToReadFromClient) { + /* We have reached the end of the input buffer. We do _not_ want to attempt to read any more data from the client in this case. */ + atEnd = MAL_TRUE; + } + mal_assert(framesReadFromClient <= 65535); + pResampler->cacheLengthInFrames += (mal_uint16)framesReadFromClient; } } @@ -517,10 +600,10 @@ double mal_resampler__calculate_cached_input_time(mal_resampler* pResampler) */ double cachedInputTime = pResampler->cacheLengthInFrames; if (pResampler->config.endOfInputMode == mal_resampler_end_of_input_mode_consume) { - cachedInputTime -= (pResampler->windowTime + (pResampler->windowLength >> 1)); + cachedInputTime -= (pResampler->windowTime + mal_resampler_window_length_left(pResampler)); } else { cachedInputTime -= (pResampler->windowTime + pResampler->windowLength); - } + } return cachedInputTime; } From 621b68a2a69731ce9f35809f74cd60a049f74886 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 8 Dec 2018 09:52:19 +1000 Subject: [PATCH 6/7] Inline a few APIs for the new resampler. --- research/mal_resampler.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/research/mal_resampler.h b/research/mal_resampler.h index 347adb9f..a22077e8 100644 --- a/research/mal_resampler.h +++ b/research/mal_resampler.h @@ -591,7 +591,7 @@ mal_uint64 mal_resampler_get_cached_output_frame_count(mal_resampler* pResampler return (mal_uint64)floor(mal_resampler_get_cached_output_time(pResampler)); } -double mal_resampler__calculate_cached_input_time(mal_resampler* pResampler) +static MAL_INLINE double mal_resampler__calculate_cached_input_time(mal_resampler* pResampler) { /* The cached input time depends on whether or not the end of the input is being consumed. If so, it's the difference between the @@ -617,7 +617,7 @@ double mal_resampler_get_cached_input_time(mal_resampler* pResampler) return mal_resampler__calculate_cached_input_time(pResampler); } -double mal_resampler__calculate_cached_output_time(mal_resampler* pResampler) +static MAL_INLINE double mal_resampler__calculate_cached_output_time(mal_resampler* pResampler) { return mal_resampler__calculate_cached_input_time(pResampler) / pResampler->config.ratio; } From 54ee4879725d8456c1e3a93936dd04de5ab2af54 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 8 Dec 2018 10:44:02 +1000 Subject: [PATCH 7/7] Minor tweaks to the resampler. --- research/mal_resampler.h | 118 ++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/research/mal_resampler.h b/research/mal_resampler.h index a22077e8..d094d3d9 100644 --- a/research/mal_resampler.h +++ b/research/mal_resampler.h @@ -47,7 +47,9 @@ Random Notes: #define MAL_RESAMPLER_SEEK_NO_CLIENT_READ (1 << 0) /* When set, does not read anything from the client when seeking. This does _not_ call onRead(). */ #define MAL_RESAMPLER_SEEK_INPUT_RATE (1 << 1) /* When set, treats the specified frame count based on the input sample rate rather than the output sample rate. */ +#ifndef MAL_RESAMPLER_CACHE_SIZE_IN_BYTES #define MAL_RESAMPLER_CACHE_SIZE_IN_BYTES 4096 +#endif typedef struct mal_resampler mal_resampler; @@ -91,7 +93,7 @@ struct mal_resampler { float f32[MAL_RESAMPLER_CACHE_SIZE_IN_BYTES/sizeof(float)]; mal_int16 s16[MAL_RESAMPLER_CACHE_SIZE_IN_BYTES/sizeof(mal_int16)]; - } cache; /* Do not use directly. Keep this as the first member of this structure for SIMD alignment purposes. */ + } cache; /* Keep this as the first member of this structure for SIMD alignment purposes. */ mal_uint32 cacheStrideInFrames; /* The number of the samples between channels in the cache. The first sample for channel 0 is cacheStrideInFrames*0. The first sample for channel 1 is cacheStrideInFrames*1, etc. */ mal_uint16 cacheLengthInFrames; /* The number of valid frames sitting in the cache, including the filter window. May be less than the cache's capacity. */ mal_uint16 windowLength; @@ -120,6 +122,8 @@ mal_result mal_resampler_set_rate(mal_resampler* pResampler, mal_uint32 sampleRa /* Dynamically adjusts the sample rate by a ratio. + +The ratio is in/out. */ mal_result mal_resampler_set_rate_ratio(mal_resampler* pResampler, double ratio); @@ -163,7 +167,7 @@ When the end of input mode is set to mal_resampler_end_of_input_mode_no_consume, window are not included in the calculation. This can return a negative value if nothing has yet been loaded into the internal cache. This will happen if this is called -immediately after initialization, before the first read has been performed, and may also happen if only a few samples have +immediately after initialization, before the first read has been performed. It may also happen if only a few samples have been read from the client. */ double mal_resampler_get_cached_input_time(mal_resampler* pResampler); @@ -175,9 +179,7 @@ of time in output rate making up the cached output. When the end of input mode is set to mal_resampler_end_of_input_mode_no_consume, the input frames currently sitting in the window are not included in the calculation. -This can return a negative value if nothing has yet been loaded into the internal cache. This will happen if this is called -immediately after initialization, before the first read has been performed, and may also happen if only a few samples have -been read from the client. +This can return a negative value. See mal_resampler_get_cached_input_time() for details. */ double mal_resampler_get_cached_output_time(mal_resampler* pResampler); @@ -394,7 +396,7 @@ typedef union mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, void** ppFrames) { - mal_uint64 framesRead; + mal_uint64 totalFramesRead; mal_resampler_deinterleaved_pointers runningFramesOut; mal_bool32 atEnd = MAL_FALSE; @@ -429,73 +431,75 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, because they do not need to worry about cache reloading logic. Instead we do all of the cache reloading stuff from here. */ - framesRead = 0; - while (framesRead < frameCount) { + totalFramesRead = 0; + while (totalFramesRead < frameCount) { double cachedOutputTime; - mal_uint64 framesRemaining = frameCount - framesRead; + mal_uint64 framesRemaining = frameCount - totalFramesRead; mal_uint64 framesToReadRightNow = framesRemaining; /* We need to make sure we don't read more than what's already in the buffer at a time. */ cachedOutputTime = mal_resampler_get_cached_output_time(pResampler); - if (framesRemaining > cachedOutputTime) { - framesToReadRightNow = (mal_uint64)floor(cachedOutputTime); - } + if (cachedOutputTime > 0) { + if (framesRemaining > cachedOutputTime) { + framesToReadRightNow = (mal_uint64)floor(cachedOutputTime); + } - /* - At this point we should know how many frames can be read this iteration. We need an optimization for when the ratio=1 - and the current time is a whole number. In this case we need to do a direct copy without any processing. - */ - if (pResampler->config.ratio == 1 && mal_fractional_part_f64(pResampler->windowTime) == 0) { - /* - No need to read from the backend - just copy the input straight over without any processing. We start reading from - the right side of the filter window. + /* + At this point we should know how many frames can be read this iteration. We need an optimization for when the ratio=1 + and the current time is a whole number. In this case we need to do a direct copy without any processing. */ - mal_uint16 iFirstSample = (mal_uint16)pResampler->windowTime + mal_resampler_window_length_left(pResampler); - if (pResampler->config.format == mal_format_f32) { - for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { - for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { - runningFramesOut.f32[iChannel][iFrame] = pResampler->cache.f32[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; + if (pResampler->config.ratio == 1 && mal_fractional_part_f64(pResampler->windowTime) == 0) { + /* + No need to read from the backend - just copy the input straight over without any processing. We start reading from + the right side of the filter window. + */ + mal_uint16 iFirstSample = (mal_uint16)pResampler->windowTime + mal_resampler_window_length_left(pResampler); + if (pResampler->config.format == mal_format_f32) { + for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { + runningFramesOut.f32[iChannel][iFrame] = pResampler->cache.f32[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; + } + } + } else { + for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { + runningFramesOut.s16[iChannel][iFrame] = pResampler->cache.s16[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; + } } } } else { - for (mal_uint16 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { - for (mal_uint16 iFrame = 0; iFrame < framesToReadRightNow; ++iFrame) { - runningFramesOut.s16[iChannel][iFrame] = pResampler->cache.s16[pResampler->cacheStrideInFrames*iChannel + iFirstSample + iFrame]; - } + /* Need to read from the backend. */ + mal_uint64 framesJustRead; + if (pResampler->config.format == mal_format_f32) { + framesJustRead = pResampler->readF32(pResampler, framesToReadRightNow, runningFramesOut.f32); + } else { + framesJustRead = pResampler->readS16(pResampler, framesToReadRightNow, runningFramesOut.s16); } - } - } else { - /* Need to read from the backend. */ - mal_uint64 framesJustRead; - if (pResampler->config.format == mal_format_f32) { - framesJustRead = pResampler->readF32(pResampler, framesToReadRightNow, runningFramesOut.f32); - } else { - framesJustRead = pResampler->readS16(pResampler, framesToReadRightNow, runningFramesOut.s16); - } - if (framesJustRead != framesToReadRightNow) { - mal_assert(MAL_FALSE); - break; /* Should never hit this. */ + if (framesJustRead != framesToReadRightNow) { + mal_assert(MAL_FALSE); + break; /* Should never hit this. */ + } } - } - /* Move time forward. */ - pResampler->windowTime += (framesToReadRightNow * pResampler->config.ratio); + /* Move time forward. */ + pResampler->windowTime += (framesToReadRightNow * pResampler->config.ratio); - if (pResampler->config.format == mal_format_f32) { - for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { - runningFramesOut.f32[iChannel] += framesToReadRightNow; + if (pResampler->config.format == mal_format_f32) { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + runningFramesOut.f32[iChannel] += framesToReadRightNow; + } + } else { + for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { + runningFramesOut.s16[iChannel] += framesToReadRightNow; + } } - } else { - for (mal_uint32 iChannel = 0; iChannel < pResampler->config.channels; ++iChannel) { - runningFramesOut.s16[iChannel] += framesToReadRightNow; - } - } - /* We don't want to reload the buffer if we've finished reading. */ - framesRead += framesToReadRightNow; - if (framesRead == frameCount) { - break; + /* We don't want to reload the buffer if we've finished reading. */ + totalFramesRead += framesToReadRightNow; + if (totalFramesRead == frameCount) { + break; + } } /* @@ -561,7 +565,7 @@ mal_uint64 mal_resampler_read(mal_resampler* pResampler, mal_uint64 frameCount, } } - return framesRead; + return totalFramesRead; } mal_uint64 mal_resampler_seek(mal_resampler* pResampler, mal_uint64 frameCount, mal_uint32 options)