API CHANGE: Add onProp callback to ma_data_source_vtable.

A new callback called `onProp` has been added to
`ma_data_source_vtable`. This replaces the following callbacks:

    onGetDataFormat
    onGetCursor
    onGetLength
    onSetLooping

This new callback is for retrieving and setting various properties
relating to the data source. It takes a `prop` parameter which is an ID
for the property being handled, and a `void*` pointer for
property-specific data.

Typically onProp implementations would discriminate on the property type
using a switch. The example below shows how to handle the old callbacks:

    switch (prop)
    {
        // Replaces onGetDataFormat (format/channels/rate).
        case MA_DATA_SOURCE_GET_DATA_SOURCE:
        {
            ma_data_source_data_format* pDataFormat =
                (ma_data_source_data_format*)pData;
            pDataFormat->format     = pCustomDataSource->format;
            pDataFormat->channels   = pCustomDataSource->channels;
            pDataFormat->sampleRate = pCustomDataSource->sampleRate;

            return MA_SUCCESS;
        }

        // Replaces onGetDataFormat (channel map)
        case MA_DATA_SOURCE_GET_CHANNEL_MAP:
        {
            ma_channel_map_init_standard(
                ma_standard_channel_map_default,
                (ma_channel*)pData,
                MA_MAX_CHANNELS,
                pCustomDataSource->channels);

            return MA_SUCCESS;
        }

        // Replaces onGetCursor
        case MA_DATA_SOURCE_GET_CURSOR:
        {
            *((ma_uint64*)pData) = pCustomDataSource->cursor;
            return MA_SUCCESS;
        }

        // Replaces onGetLength
        case MA_DATA_SOURCE_GET_LENGTH:
        {
            *((ma_uint64*)pData) = pCustomDataSource->length;
            return MA_SUCCESS;
        }

        // Replaces onSetLooping
        case MA_DATA_SOURCE_SET_LOOPING:
        {
            pCustomDataSource->isLooping = *((ma_bool32*)pData);
            return MA_SUCCESS;
        }

        // Mandatory when MA_DATA_SOURCE_SET_LOOPING is implemented.
        case MA_DATA_SOURCE_GET_LOOPING:
        {
            *((ma_bool32*)pData) = pCustomDataSource->isLooping;
            return MA_SUCCESS;
        }

        // Return MA_NOT_IMPLEMENTED for any ignored properties.
        default: return MA_NOT_IMPLEMENTED;
    }

Note how the format/channels/rate and channel map properties have been
split across two separate properties, `MA_DATA_SOURCE_GET_DATA_SOURCE`
and `MA_DATA_SOURCE_GET_CHANNEL_MAP`. Along with this change, the
channel map parameters have been removed from
`ma_data_source_get_data_format()` and a new function called
`ma_data_source_get_channel_map()` has been added.

New properties have also been added for handling ranges and loop points.
This allows the data source implementation itself to handle it rather
than miniaudio doing it at a higher level. Where this is useful is if
your data source is a wrapper around another data source and you want to
route ranges and loop points to the internal data source.

The reason for this change is that it allows for properties to be added
without having to break the build due to yet another callback being
added. It also hides away the more niche properties that the majority of
data sources do not care about. For example, rarely does a data source
need to handle the `onSetLooping` callback, yet every data source needed
to add a `NULL` entry to their vtables just for this one extremely niche
property.

See documentation for further details.

Tag: release-notes
Tag: api-change
This commit is contained in:
David Reid
2026-05-02 15:31:09 +10:00
parent afa3a0b855
commit 15da9bd7ff
7 changed files with 1555 additions and 396 deletions
+3
View File
@@ -924,6 +924,9 @@ if(MINIAUDIO_BUILD_TESTS)
endif() endif()
add_test(NAME miniaudio_deviceio COMMAND miniaudio_deviceio --auto) add_test(NAME miniaudio_deviceio COMMAND miniaudio_deviceio --auto)
add_miniaudio_test(miniaudio_testbench testbench/testbench.c)
add_test(NAME miniaudio_testbench COMMAND miniaudio_testbench)
add_miniaudio_test(miniaudio_conversion conversion/conversion.c) add_miniaudio_test(miniaudio_conversion conversion/conversion.c)
add_test(NAME miniaudio_conversion COMMAND miniaudio_conversion) add_test(NAME miniaudio_conversion COMMAND miniaudio_conversion)
+1 -1
View File
@@ -94,7 +94,7 @@ int main(int argc, char** argv)
/* Initialize the device. */ /* Initialize the device. */
result = ma_data_source_get_data_format(&decoder, &format, &channels, &sampleRate, NULL, 0); result = ma_data_source_get_data_format(&decoder, &format, &channels, &sampleRate);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
printf("Failed to retrieve decoder data format."); printf("Failed to retrieve decoder data format.");
ma_decoder_uninit(&decoder); ma_decoder_uninit(&decoder);
+2 -2
View File
@@ -80,7 +80,7 @@ MA_API ma_result ma_data_source_read_pcm_frames_f32(ma_data_source* pDataSource,
ma_format format; ma_format format;
ma_uint32 channels; ma_uint32 channels;
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */ return result; /* Failed to retrieve the data format of the data source. */
} }
@@ -103,7 +103,7 @@ MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDat
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the data format of the data source. */ return result; /* Failed to retrieve the data format of the data source. */
} }
+30 -14
View File
@@ -30,19 +30,38 @@ static ma_result ma_libopus_ds_seek(ma_data_source* pDataSource, ma_uint64 frame
return ma_libopus_seek_to_pcm_frame((ma_libopus*)pDataSource, frameIndex); return ma_libopus_seek_to_pcm_frame((ma_libopus*)pDataSource, frameIndex);
} }
static ma_result ma_libopus_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) static ma_result ma_libopus_ds_prop(ma_data_source* pDataSource, int prop, void* pData)
{ {
return ma_libopus_get_data_format((ma_libopus*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); ma_libopus* pOpus = (ma_libopus*)pDataSource;
}
static ma_result ma_libopus_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) switch (prop)
{ {
return ma_libopus_get_cursor_in_pcm_frames((ma_libopus*)pDataSource, pCursor); case MA_DATA_SOURCE_GET_DATA_FORMAT:
} {
ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData;
static ma_result ma_libopus_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) return ma_libopus_get_data_format(pOpus, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0);
{ }
return ma_libopus_get_length_in_pcm_frames((ma_libopus*)pDataSource, pLength);
case MA_DATA_SOURCE_GET_CHANNEL_MAP:
{
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS);
}
case MA_DATA_SOURCE_GET_CURSOR:
{
return ma_libopus_get_cursor_in_pcm_frames(pOpus, (ma_uint64*)pData);
}
case MA_DATA_SOURCE_GET_LENGTH:
{
return ma_libopus_get_length_in_pcm_frames(pOpus, (ma_uint64*)pData);
}
default: break;
}
return MA_NOT_IMPLEMENTED;
} }
static ma_data_source_vtable ma_gDataSourceVTable_libopus = static ma_data_source_vtable ma_gDataSourceVTable_libopus =
@@ -52,10 +71,7 @@ static ma_data_source_vtable ma_gDataSourceVTable_libopus =
NULL, /* onCopy. Copying is not supported. */ NULL, /* onCopy. Copying is not supported. */
ma_libopus_ds_read, ma_libopus_ds_read,
ma_libopus_ds_seek, ma_libopus_ds_seek,
ma_libopus_ds_get_data_format, ma_libopus_ds_prop
ma_libopus_ds_get_cursor,
ma_libopus_ds_get_length,
NULL /* onSetLooping */
}; };
+30 -14
View File
@@ -33,19 +33,38 @@ static ma_result ma_libvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 fra
return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis*)pDataSource, frameIndex); return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis*)pDataSource, frameIndex);
} }
static ma_result ma_libvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) static ma_result ma_libvorbis_ds_prop(ma_data_source* pDataSource, int prop, void* pData)
{ {
return ma_libvorbis_get_data_format((ma_libvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); ma_libvorbis* pVorbis = (ma_libvorbis*)pDataSource;
}
static ma_result ma_libvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) switch (prop)
{ {
return ma_libvorbis_get_cursor_in_pcm_frames((ma_libvorbis*)pDataSource, pCursor); case MA_DATA_SOURCE_GET_DATA_FORMAT:
} {
ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData;
static ma_result ma_libvorbis_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) return ma_libvorbis_get_data_format(pVorbis, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0);
{ }
return ma_libvorbis_get_length_in_pcm_frames((ma_libvorbis*)pDataSource, pLength);
case MA_DATA_SOURCE_GET_CHANNEL_MAP:
{
return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS);
}
case MA_DATA_SOURCE_GET_CURSOR:
{
return ma_libvorbis_get_cursor_in_pcm_frames(pVorbis, (ma_uint64*)pData);
}
case MA_DATA_SOURCE_GET_LENGTH:
{
return ma_libvorbis_get_length_in_pcm_frames(pVorbis, (ma_uint64*)pData);
}
default: break;
}
return MA_NOT_IMPLEMENTED;
} }
static ma_data_source_vtable ma_gDataSourceVTable_libvorbis = static ma_data_source_vtable ma_gDataSourceVTable_libvorbis =
@@ -55,10 +74,7 @@ static ma_data_source_vtable ma_gDataSourceVTable_libvorbis =
NULL, /* onCopy. Copying is not supported. */ NULL, /* onCopy. Copying is not supported. */
ma_libvorbis_ds_read, ma_libvorbis_ds_read,
ma_libvorbis_ds_seek, ma_libvorbis_ds_seek,
ma_libvorbis_ds_get_data_format, ma_libvorbis_ds_prop
ma_libvorbis_ds_get_cursor,
ma_libvorbis_ds_get_length,
NULL /* onSetLooping */
}; };
+857 -364
View File
File diff suppressed because it is too large Load Diff
+631
View File
@@ -0,0 +1,631 @@
#include "../../miniaudio.c"
#include "../../external/fs/fs.c"
#include <stdio.h>
/* BEG ma_test.c */
typedef struct ma_test ma_test;
typedef int (* ma_test_proc)(ma_test* pUserData);
struct ma_test
{
const char* name;
ma_test_proc proc;
void* pUserData;
int result;
ma_test* pFirstChild;
ma_test* pNextSibling;
};
void ma_test_init(ma_test* pTest, const char* name, ma_test_proc proc, void* pUserData, ma_test* pParent)
{
if (pTest == NULL) {
return;
}
memset(pTest, 0, sizeof(ma_test));
pTest->name = name;
pTest->proc = proc;
pTest->pUserData = pUserData;
pTest->result = MA_SUCCESS;
pTest->pFirstChild = NULL;
pTest->pNextSibling = NULL;
if (pParent != NULL) {
if (pParent->pFirstChild == NULL) {
pParent->pFirstChild = pTest;
} else {
ma_test* pSibling = pParent->pFirstChild;
while (pSibling->pNextSibling != NULL) {
pSibling = pSibling->pNextSibling;
}
pSibling->pNextSibling = pTest;
}
}
}
void ma_test_count(ma_test* pTest, int* pCount, int* pPassed)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
*pCount += 1;
if (pTest->result == MA_SUCCESS) {
*pPassed += 1;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_count(pChild, pCount, pPassed);
pChild = pChild->pNextSibling;
}
}
int ma_test_run(ma_test* pTest)
{
/* Start our counts at -1 to exclude the root test. */
int testCount = -1;
int passedCount = -1;
if (pTest == NULL) {
return MA_ERROR;
}
if (pTest->name != NULL && pTest->proc != NULL) {
printf("Running Test: %s\n", pTest->name);
}
if (pTest->proc != NULL) {
pTest->result = pTest->proc(pTest);
if (pTest->result != MA_SUCCESS) {
return pTest->result;
}
}
/* Now we need to recursively execute children. If any child test fails, the parent test needs to be marked as failed as well. */
{
ma_test* pChild = pTest->pFirstChild;
while (pChild != NULL) {
int result = ma_test_run(pChild);
if (result != MA_SUCCESS) {
pTest->result = result;
}
pChild = pChild->pNextSibling;
}
}
/* Now count the number of failed tests and report success or failure depending on the result. */
ma_test_count(pTest, &testCount, &passedCount);
return (testCount == passedCount) ? MA_SUCCESS : MA_ERROR;
}
void ma_test_print_local_result(ma_test* pTest, int level)
{
if (pTest == NULL) {
return;
}
printf("[%s] %*s%s\n", pTest->result == MA_SUCCESS ? "PASS" : "FAIL", level * 2, "", pTest->name);
}
void ma_test_print_child_results(ma_test* pTest, int level)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_print_local_result(pChild, level);
ma_test_print_child_results(pChild, level + 1);
pChild = pChild->pNextSibling;
}
}
void ma_test_print_result(ma_test* pTest, int level)
{
ma_test* pChild;
if (pTest == NULL) {
return;
}
if (pTest->name != NULL) {
printf("[%s] %*s%s\n", pTest->result == MA_SUCCESS ? "PASS" : "FAIL", level * 2, "", pTest->name);
level += 1;
}
pChild = pTest->pFirstChild;
while (pChild != NULL) {
ma_test_print_result(pChild, level);
pChild = pChild->pNextSibling;
}
}
void ma_test_print_summary(ma_test* pTest)
{
/* Start our counts at -1 to exclude the root test. */
int testCount = -1;
int passedCount = -1;
if (pTest == NULL) {
return;
}
/* This should only be called on a root test. */
assert(pTest->name == NULL);
printf("=== Test Summary ===\n");
ma_test_print_result(pTest, 0);
/* We need to count how many tests failed. */
ma_test_count(pTest, &testCount, &passedCount);
printf("---\n%s%d / %d tests passed.\n", (testCount == passedCount) ? "[PASS]: " : "[FAIL]: ", passedCount, testCount);
}
/* END ma_test.c */
/* BEG ma_test_data_source.c */
typedef struct ma_test_data_source
{
ma_data_source_base base;
ma_test* pTest;
ma_format format;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint64 cursor; /* Relative to the beginning of the current range. */
ma_uint64 length; /* The full length of the data source, not including the range. */
ma_bool32 isInitialized;
ma_bool32 isLooping;
ma_uint64 rangeBegInFrames;
ma_uint64 rangeEndInFrames;
ma_uint64 loopBegInFrames;
ma_uint64 loopEndInFrames;
} ma_test_data_source;
static size_t ma_test_data_source_sizeof(void)
{
return sizeof(ma_test_data_source);
}
static void ma_test_data_source_uninit(ma_data_source* pDataSource)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
pTestDataSource->isInitialized = MA_FALSE;
}
static ma_result ma_test_data_source_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_test_data_source* pNewTestDataSource = (ma_test_data_source*)pNewDataSource;
pNewTestDataSource->format = pTestDataSource->format;
pNewTestDataSource->channels = pTestDataSource->channels;
pNewTestDataSource->sampleRate = pTestDataSource->sampleRate;
pNewTestDataSource->cursor = pTestDataSource->cursor;
pNewTestDataSource->length = pTestDataSource->length;
pNewTestDataSource->isInitialized = pTestDataSource->isInitialized;
pNewTestDataSource->isLooping = pTestDataSource->isLooping;
pNewTestDataSource->rangeBegInFrames = pTestDataSource->rangeBegInFrames;
pNewTestDataSource->rangeEndInFrames = pTestDataSource->rangeEndInFrames;
pNewTestDataSource->loopBegInFrames = pTestDataSource->loopBegInFrames;
pNewTestDataSource->loopEndInFrames = pTestDataSource->loopEndInFrames;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_read(ma_data_source* pDataSource, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_result result;
ma_uint64 length;
ma_uint64 framesRemaining;
/* miniaudio should always be giving us a valid pointer for pFramesRead. */
if (pFramesRead == NULL) {
printf("%s: pFramesRead is null.\n", pTestDataSource->pTest->name);
return MA_INVALID_ARGS;
}
result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve the length of the data source when reading.\n", pTestDataSource->pTest->name);
return result;
}
framesRemaining = length - pTestDataSource->cursor;
if (frameCount > framesRemaining) {
frameCount = framesRemaining;
}
MA_ZERO_MEMORY(pFrames, frameCount * ma_get_bytes_per_frame(pTestDataSource->format, pTestDataSource->channels));
pTestDataSource->cursor += frameCount;
if (pTestDataSource->cursor > length) {
MA_ASSERT(!"cursor > length. Test is incorrect.");
}
*pFramesRead = frameCount;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
ma_result result;
ma_uint64 length;
result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve the length of the data source when reading.\n", pTestDataSource->pTest->name);
return result;
}
if (frameIndex > length) {
return MA_BAD_SEEK;
}
pTestDataSource->cursor = frameIndex;
return MA_SUCCESS;
}
static ma_result ma_test_data_source_prop(ma_data_source* pDataSource, int prop, void* pData)
{
ma_test_data_source* pTestDataSource = (ma_test_data_source*)pDataSource;
switch (prop)
{
case MA_DATA_SOURCE_GET_DATA_FORMAT:
{
ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData;
pDataFormat->format = pTestDataSource->format;
pDataFormat->channels = pTestDataSource->channels;
pDataFormat->sampleRate = pTestDataSource->sampleRate;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_CURSOR:
{
*((ma_uint64*)pData) = pTestDataSource->cursor;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LENGTH:
{
*((ma_uint64*)pData) = pTestDataSource->length;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_LOOPING:
{
pTestDataSource->isLooping = *((ma_bool32*)pData);
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LOOPING:
{
*((ma_bool32*)pData) = pTestDataSource->isLooping;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_RANGE:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
if (pRange->begInFrames > pTestDataSource->length || pRange->endInFrames > pTestDataSource->length) {
return MA_INVALID_ARGS;
}
pTestDataSource->rangeBegInFrames = pRange->begInFrames;
pTestDataSource->rangeEndInFrames = pRange->endInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_RANGE:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
pRange->begInFrames = pTestDataSource->rangeBegInFrames;
pRange->endInFrames = pTestDataSource->rangeEndInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_SET_LOOP_POINT:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
if (pRange->begInFrames > pTestDataSource->length || pRange->endInFrames > pTestDataSource->length) {
return MA_INVALID_ARGS;
}
pTestDataSource->loopBegInFrames = pRange->begInFrames;
pTestDataSource->loopEndInFrames = pRange->endInFrames;
return MA_SUCCESS;
}
case MA_DATA_SOURCE_GET_LOOP_POINT:
{
ma_pcm_range* pRange = (ma_pcm_range*)pData;
pRange->begInFrames = pTestDataSource->loopBegInFrames;
pRange->endInFrames = pTestDataSource->loopEndInFrames;
return MA_SUCCESS;
}
default:
{
return MA_NOT_IMPLEMENTED;
}
}
}
static ma_data_source_vtable ma_gDataSourceVTable_Test =
{
ma_test_data_source_sizeof,
ma_test_data_source_uninit,
ma_test_data_source_copy,
ma_test_data_source_read,
ma_test_data_source_seek,
ma_test_data_source_prop
};
ma_result ma_test_data_source_init(ma_test* pTest, ma_test_data_source* pTestDataSource)
{
ma_result result;
ma_data_source_config baseConfig;
if (pTestDataSource == NULL) {
MA_ASSERT(!"No input data source. Fix the tests!");
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pTestDataSource);
pTestDataSource->pTest = pTest;
pTestDataSource->format = ma_format_s16;
pTestDataSource->channels = 2;
pTestDataSource->sampleRate = 48000;
pTestDataSource->cursor = 0;
pTestDataSource->length = 1024;
pTestDataSource->isInitialized = MA_FALSE;
pTestDataSource->isLooping = MA_FALSE;
pTestDataSource->rangeBegInFrames = 0;
pTestDataSource->rangeEndInFrames = ~((ma_uint64)0);
pTestDataSource->loopBegInFrames = 0;
pTestDataSource->loopEndInFrames = ~((ma_uint64)0);
baseConfig = ma_data_source_config_init();
baseConfig.pVTable = &ma_gDataSourceVTable_Test;
result = ma_data_source_base_init(&baseConfig, &pTestDataSource->base);
if (result != MA_SUCCESS) {
printf("%s: Failed to initialize test data source.\n", pTest->name);
return result;
}
return MA_SUCCESS;
}
int ma_test_data_source_properties(ma_test* pTest)
{
/* This tests that properties are properly intercepted by data sources. To do this we need a special test data source. */
ma_result result;
ma_test_data_source testDataSource;
result = ma_test_data_source_init(pTest, &testDataSource);
if (result != MA_SUCCESS) {
printf("%s: Failed to initialize test data source.\n", pTest->name);
return 1;
}
/* Basic property tests first. */
{
ma_data_source_data_format dataFormat;
result = ma_data_source_get_data_format(&testDataSource, &dataFormat.format, &dataFormat.channels, &dataFormat.sampleRate);
if (result != MA_SUCCESS) {
printf("%s: Failed to retrieve data format.\n", pTest->name);
return 1;
}
if (dataFormat.format != testDataSource.format) {
printf("%s: Sample format does not match.\n", pTest->name);
return 1;
}
if (dataFormat.channels != testDataSource.channels) {
printf("%s: Channel counts do not match.\n", pTest->name);
return 1;
}
if (dataFormat.sampleRate != testDataSource.sampleRate) {
printf("%s: Sample rates do not match.\n", pTest->name);
return 1;
}
}
/* Looping. */
{
ma_bool32 isLooping = MA_TRUE;
result = ma_data_source_set_looping(&testDataSource, isLooping);
if (result != MA_SUCCESS) {
printf("%s: Failed to set the looping state of the data source.\n", pTest->name);
return 1;
}
if (testDataSource.isLooping != isLooping) {
printf("%s: SET_LOOPING was not handled.\n", pTest->name);
return 1;
}
if (ma_data_source_is_looping(&testDataSource) != isLooping) {
printf("%s: is_looping() did not return expected value of %u.\n", pTest->name, isLooping);
return 1;
}
}
/* Loop Points. */
{
ma_uint64 loopPointBeg = 256;
ma_uint64 loopPointEnd = loopPointBeg + 128;
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, loopPointBeg, loopPointEnd);
if (result != MA_SUCCESS) {
printf("%s: Failed to set loop points.\n", pTest->name);
return 1;
}
if (testDataSource.loopBegInFrames != loopPointBeg || testDataSource.loopEndInFrames != loopPointEnd) {
printf("%s: SET_LOOP_POINT was not handled.\n", pTest->name);
return 1;
}
{
ma_uint64 queriedLoopPointBeg;
ma_uint64 queriedLoopPointEnd;
ma_data_source_get_loop_point_in_pcm_frames(&testDataSource, &queriedLoopPointBeg, &queriedLoopPointEnd);
if (queriedLoopPointBeg != loopPointBeg || queriedLoopPointEnd != loopPointEnd) {
printf("%s: get_loop_point_in_pcm_frames() returned enexpected values.\n", pTest->name);
return 1;
}
}
/* Erroneous Loop Points. */
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, 99999, 999999); /* Exceeds the length of the data source.*/
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_loop_point_in_pcm_frames().\n", pTest->name);
return 1;
}
result = ma_data_source_set_loop_point_in_pcm_frames(&testDataSource, 256, 0); /* Beg > End */
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_loop_point_in_pcm_frames().\n", pTest->name);
return 1;
}
}
/* Ranges. */
{
ma_uint64 rangeBeg = 128;
ma_uint64 rangeEnd = rangeBeg + 512;
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, rangeBeg, rangeEnd);
if (result != MA_SUCCESS) {
printf("%s: Failed to set range.\n", pTest->name);
return 1;
}
if (testDataSource.rangeBegInFrames != rangeBeg || testDataSource.rangeEndInFrames != rangeEnd) {
printf("%s: SET_RANGE was not handled.\n", pTest->name);
return 1;
}
{
ma_uint64 queriedRangeBeg;
ma_uint64 queriedRangeEnd;
ma_data_source_get_range_in_pcm_frames(&testDataSource, &queriedRangeBeg, &queriedRangeEnd);
if (queriedRangeBeg != rangeBeg || queriedRangeEnd != rangeEnd) {
printf("%s: get_range_in_pcm_frames() returned enexpected values.\n", pTest->name);
return 1;
}
}
/* Erroneous Ranges. */
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, 99999, 999999); /* Exceeds the length of the data source.*/
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_range_in_pcm_frames().\n", pTest->name);
return 1;
}
result = ma_data_source_set_range_in_pcm_frames(&testDataSource, 256, 0); /* Beg > End */
if (result == MA_SUCCESS) {
printf("%s: Expecting error from get_range_in_pcm_frames().\n", pTest->name);
return 1;
}
}
/* Length. */
{
/*
The length from ma_data_source_get_length_in_pcm_frames() needs to be clamped to the range, but the
length returned by MA_DATA_SOURCE_GET_LENGTH needs to be unclamped.
*/
ma_uint64 clampedLength = 512;
ma_uint64 rangeBeg = 0;
ma_uint64 rangeEnd = rangeBeg + clampedLength;
ma_uint64 queriedLengthClamped;
ma_uint64 queriedLengthFull;
MA_ASSERT(clampedLength != testDataSource.length); /* The clamped length cannot equal the full length for this test to work. */
ma_data_source_set_range_in_pcm_frames(&testDataSource, rangeBeg, rangeEnd);
ma_data_source_get_length_in_pcm_frames(&testDataSource, &queriedLengthClamped);
if (queriedLengthClamped != clampedLength) {
printf("%s: get_length_in_pcm_frames() returned an unexpected value.\n", pTest->name);
return 1;
}
ma_data_source_prop(&testDataSource, MA_DATA_SOURCE_GET_LENGTH, &queriedLengthFull);
if (queriedLengthFull != testDataSource.length) {
printf("%s: MA_DATA_SOURCE_GET_LENGTH returned an unexpected value.\n", pTest->name);
return 1;
}
}
return FS_SUCCESS;
}
/* END ma_test_data_source.c */
int main(int argc, char** argv)
{
int result;
ma_test test_root;
ma_test test_data_source;
ma_test test_data_source_properties;
/* Root. Only used for execution. */
ma_test_init(&test_root, NULL, NULL, NULL, NULL);
/* Data source tests. */
ma_test_init(&test_data_source, "Data Source", NULL, NULL, &test_root);
ma_test_init(&test_data_source_properties, "Data Source Properties", ma_test_data_source_properties, NULL, &test_data_source);
result = ma_test_run(&test_root);
/* Print the test summary. */
printf("\n");
ma_test_print_summary(&test_root);
(void)argc;
(void)argv;
if (result == FS_SUCCESS) {
return 0;
} else {
return 1;
}
}