From 2671e07560780f61c38fa5f7fbf86522d1c80526 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 14 Aug 2021 18:41:26 +1000 Subject: [PATCH] Add examples for the high level API. --- examples/custom_decoder_engine.c | 230 ++++++++++++ examples/duplex_effect.c | 149 ++++++++ examples/engine_advanced.c | 247 +++++++++++++ examples/engine_hello_world.c | 34 ++ examples/resource_manager.c | 149 ++++++++ examples/resource_manager_advanced.c | 327 +++++++++++++++++ research/_examples/custom_decoder_engine.c | 255 +------------- research/_examples/duplex_effect.c | 150 +------- research/_examples/engine_hello_world.c | 35 +- research/_examples/resource_manager.c | 150 +------- .../_examples/resource_manager_advanced.c | 328 +----------------- 11 files changed, 1141 insertions(+), 913 deletions(-) create mode 100644 examples/custom_decoder_engine.c create mode 100644 examples/duplex_effect.c create mode 100644 examples/engine_advanced.c create mode 100644 examples/engine_hello_world.c create mode 100644 examples/resource_manager.c create mode 100644 examples/resource_manager_advanced.c diff --git a/examples/custom_decoder_engine.c b/examples/custom_decoder_engine.c new file mode 100644 index 00000000..cc97c5ca --- /dev/null +++ b/examples/custom_decoder_engine.c @@ -0,0 +1,230 @@ +/* +Demonstrates how to implement a custom decoder and use it with the high level API. + +This is the same as the custom_decoder example, only it's used with the high level engine API +rather than the low level decoding API. You can use this to add support for Opus to your games, for +example (via libopus). +*/ +#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */ +#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../../extras/miniaudio_libvorbis.h" +#include "../../extras/miniaudio_libopus.h" + +#include + +static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_result result; + ma_libvorbis* pVorbis; + + (void)pUserData; + + pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); + if (pVorbis == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis); + if (result != MA_SUCCESS) { + ma_free(pVorbis, pAllocationCallbacks); + return result; + } + + *ppBackend = pVorbis; + + return MA_SUCCESS; +} + +static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_result result; + ma_libvorbis* pVorbis; + + (void)pUserData; + + pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); + if (pVorbis == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis); + if (result != MA_SUCCESS) { + ma_free(pVorbis, pAllocationCallbacks); + return result; + } + + *ppBackend = pVorbis; + + return MA_SUCCESS; +} + +static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; + + (void)pUserData; + + ma_libvorbis_uninit(pVorbis, pAllocationCallbacks); + ma_free(pVorbis, pAllocationCallbacks); +} + +static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; + + (void)pUserData; + + return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); +} + +static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis = +{ + ma_decoding_backend_init__libvorbis, + ma_decoding_backend_init_file__libvorbis, + NULL, /* onInitFileW() */ + NULL, /* onInitMemory() */ + ma_decoding_backend_uninit__libvorbis +}; + + + +static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_result result; + ma_libopus* pOpus; + + (void)pUserData; + + pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks); + if (pOpus == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus); + if (result != MA_SUCCESS) { + ma_free(pOpus, pAllocationCallbacks); + return result; + } + + *ppBackend = pOpus; + + return MA_SUCCESS; +} + +static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_result result; + ma_libopus* pOpus; + + (void)pUserData; + + pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks); + if (pOpus == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus); + if (result != MA_SUCCESS) { + ma_free(pOpus, pAllocationCallbacks); + return result; + } + + *ppBackend = pOpus; + + return MA_SUCCESS; +} + +static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_libopus* pOpus = (ma_libopus*)pBackend; + + (void)pUserData; + + ma_libopus_uninit(pOpus, pAllocationCallbacks); + ma_free(pOpus, pAllocationCallbacks); +} + +static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_libopus* pOpus = (ma_libopus*)pBackend; + + (void)pUserData; + + return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap); +} + +static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus = +{ + ma_decoding_backend_init__libopus, + ma_decoding_backend_init_file__libopus, + NULL, /* onInitFileW() */ + NULL, /* onInitMemory() */ + ma_decoding_backend_uninit__libopus +}; + + + +int main(int argc, char** argv) +{ + ma_result result; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_engine_config engineConfig; + ma_engine engine; + + /* + Add your custom backend vtables here. The order in the array defines the order of priority. The + vtables will be passed in to the resource manager config. + */ + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + + if (argc < 2) { + printf("No input file.\n"); + return -1; + } + + + /* Using custom decoding backends requires a resource manager. */ + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; + resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + resourceManagerConfig.pCustomDecodingBackendUserData = NULL; /* <-- This will be passed in to the pUserData parameter of each function in the decoding backend vtables. */ + + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + printf("Failed to initialize resource manager."); + return -1; + } + + + /* Once we have a resource manager we can create the engine. */ + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &resourceManager; + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + printf("Failed to initialize engine."); + return -1; + } + + + /* Now we can play our sound. */ + result = ma_engine_play_sound(&engine, argv[1], NULL); + if (result != MA_SUCCESS) { + printf("Failed to play sound."); + return -1; + } + + + printf("Press Enter to quit..."); + getchar(); + + return 0; +} \ No newline at end of file diff --git a/examples/duplex_effect.c b/examples/duplex_effect.c new file mode 100644 index 00000000..b0f1d522 --- /dev/null +++ b/examples/duplex_effect.c @@ -0,0 +1,149 @@ +/* +Demonstrates how to apply an effect to a duplex stream using the node graph system. + +This example applies a vocoder effect to the input stream before outputting it. A custom node +called `ma_vocoder_node` is used to achieve the effect which can be found in the extras folder in +the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the +effect. +*/ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../miniaudio_engine.h" +#include "../_extras/nodes/ma_vocoder_node/ma_vocoder_node.c" + +#include + +#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */ +#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */ + +static ma_waveform g_sourceData; /* The underlying data source of the excite node. */ +static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the source node. */ +static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */ +static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */ +static ma_vocoder_node g_vocoderNode; /* The vocoder node. */ +static ma_node_graph g_nodeGraph; + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + MA_ASSERT(pDevice->capture.format == pDevice->playback.format); + MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels); + + /* + The node graph system is a pulling style of API. At the lowest level of the chain will be a + node acting as a data source for the purpose of delivering the initial audio data. In our case, + the data source is our `pInput` buffer. We need to update the underlying data source so that it + read data from `pInput`. + */ + ma_audio_buffer_ref_set_data(&g_exciteData, pInput, frameCount); + + /* With the source buffer configured we can now read directly from the node graph. */ + ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL); +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_device_config deviceConfig; + ma_device device; + ma_node_graph_config nodeGraphConfig; + ma_vocoder_node_config vocoderNodeConfig; + ma_data_source_node_config sourceNodeConfig; + ma_data_source_node_config exciteNodeConfig; + ma_waveform_config waveformConfig; + + deviceConfig = ma_device_config_init(ma_device_type_duplex); + deviceConfig.capture.pDeviceID = NULL; + deviceConfig.capture.format = DEVICE_FORMAT; + deviceConfig.capture.channels = DEVICE_CHANNELS; + deviceConfig.capture.shareMode = ma_share_mode_shared; + deviceConfig.playback.pDeviceID = NULL; + deviceConfig.playback.format = DEVICE_FORMAT; + deviceConfig.playback.channels = DEVICE_CHANNELS; + deviceConfig.dataCallback = data_callback; + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + return result; + } + + + /* Now we can setup our node graph. */ + nodeGraphConfig = ma_node_graph_config_init(device.capture.channels); + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph); + if (result != MA_SUCCESS) { + printf("Failed to initialize node graph."); + goto done0; + } + + + /* Vocoder. Attached straight to the endpoint. */ + vocoderNodeConfig = ma_vocoder_node_config_init(device.capture.channels, device.sampleRate); + + result = ma_vocoder_node_init(&g_nodeGraph, &vocoderNodeConfig, NULL, &g_vocoderNode); + if (result != MA_SUCCESS) { + printf("Failed to initialize vocoder node."); + goto done1; + } + + ma_node_attach_output_bus(&g_vocoderNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0); + + /* Amplify the volume of the vocoder output because in my testing it is a bit quiet. */ + ma_node_set_output_bus_volume(&g_vocoderNode, 0, 4); + + + /* Source/carrier. Attached to input bus 0 of the vocoder node. */ + waveformConfig = ma_waveform_config_init(device.capture.format, device.capture.channels, device.sampleRate, ma_waveform_type_sawtooth, 1.0, 50); + + result = ma_waveform_init(&waveformConfig, &g_sourceData); + if (result != MA_SUCCESS) { + printf("Failed to initialize waveform for excite node."); + goto done3; + } + + sourceNodeConfig = ma_data_source_node_config_init(&g_sourceData, MA_FALSE); + + result = ma_data_source_node_init(&g_nodeGraph, &sourceNodeConfig, NULL, &g_sourceNode); + if (result != MA_SUCCESS) { + printf("Failed to initialize excite node."); + goto done3; + } + + ma_node_attach_output_bus(&g_sourceNode, 0, &g_vocoderNode, 0); + + + /* Excite/modulator. Attached to input bus 1 of the vocoder node. */ + result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio buffer for source."); + goto done2; + } + + exciteNodeConfig = ma_data_source_node_config_init(&g_exciteData, MA_FALSE); + + result = ma_data_source_node_init(&g_nodeGraph, &exciteNodeConfig, NULL, &g_exciteNode); + if (result != MA_SUCCESS) { + printf("Failed to initialize source node."); + goto done2; + } + + ma_node_attach_output_bus(&g_exciteNode, 0, &g_vocoderNode, 1); + + + ma_device_start(&device); + + printf("Press Enter to quit...\n"); + getchar(); + + /* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */ + ma_device_stop(&device); + +/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL); +done3: ma_data_source_node_uninit(&g_sourceNode, NULL); +done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL); +done1: ma_node_graph_uninit(&g_nodeGraph, NULL); +done0: ma_device_uninit(&device); + + (void)argc; + (void)argv; + return 0; +} diff --git a/examples/engine_advanced.c b/examples/engine_advanced.c new file mode 100644 index 00000000..7464732a --- /dev/null +++ b/examples/engine_advanced.c @@ -0,0 +1,247 @@ +/* +This example demonstrates some of the advanced features of the high level engine API. + +The following features are demonstrated: + + * Initialization of the engine from a pre-initialized device. + * Self-managed resource managers. + * Multiple engines with a shared resource manager. + * Creation and management of `ma_sound` objects. + +This example will play the sound that's passed in the command line. + +If you were wanting to support multiple listeners, this example will show you how to do that. You achieve this by +initializing one `ma_engine` object for each listener, each of which share a single self-managed resource manager. +*/ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../miniaudio_engine.h" + +#define MAX_DEVICES 2 +#define MAX_SOUNDS 32 + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + (void)pInput; + + /* + Since we're managing the underlying device ourselves, we need to manually call the engine's data handler. To do + this we need access to the `ma_engine` object which we passed in to the user data. The advantage of this is that + you could do your own audio processing in addition to the engine's standard processing. + */ + ma_engine_read_pcm_frames((ma_engine*)pDevice->pUserData, pOutput, frameCount, NULL); +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_context context; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_engine engines[MAX_DEVICES]; + ma_device devices[MAX_DEVICES]; + ma_uint32 engineCount = 0; + ma_uint32 iEngine; + ma_device_info* pPlaybackDeviceInfos; + ma_uint32 playbackDeviceCount; + ma_uint32 iAvailableDevice; + ma_uint32 iChosenDevice; + ma_sound sounds[MAX_SOUNDS]; + ma_uint32 soundCount; + ma_uint32 iSound; + + if (argc < 2) { + printf("No input file."); + return -1; + } + + + /* + We are going to be initializing multiple engines. In order to save on memory usage we can use a self managed + resource manager so we can share a single resource manager across multiple engines. + */ + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.decodedFormat = ma_format_f32; /* ma_format_f32 should almost always be used as that's what the engine (and most everything else) uses for mixing. */ + resourceManagerConfig.decodedChannels = 0; /* Setting the channel count to 0 will cause sounds to use their native channel count. */ + resourceManagerConfig.decodedSampleRate = 48000; /* Using a consistent sample rate is useful for avoiding expensive resampling in the audio thread. This will result in resampling being performed by the loading thread(s). */ + + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + printf("Failed to initialize resource manager."); + return -1; + } + + + /* We're going to want a context so we can enumerate our playback devices. */ + result = ma_context_init(NULL, 0, NULL, &context); + if (result != MA_SUCCESS) { + printf("Failed to initialize context."); + return -1; + } + + /* + Now that we have a context we will want to enumerate over each device so we can display them to the user and give + them a chance to select the output devices they want to use. + */ + result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); + if (result != MA_SUCCESS) { + printf("Failed to enumerate playback devices."); + ma_context_uninit(&context); + return -1; + } + + + /* We have our devices, so now we want to get the user to select the devices they want to output to. */ + engineCount = 0; + + for (iChosenDevice = 0; iChosenDevice < MAX_DEVICES; iChosenDevice += 1) { + int c = 0; + for (;;) { + printf("Select playback device %d ([%d - %d], Q to quit):\n", iChosenDevice+1, 0, ma_min((int)playbackDeviceCount, 9)); + + for (iAvailableDevice = 0; iAvailableDevice < playbackDeviceCount; iAvailableDevice += 1) { + printf(" %d: %s\n", iAvailableDevice, pPlaybackDeviceInfos[iAvailableDevice].name); + } + + for (;;) { + c = getchar(); + if (c != '\n') { + break; + } + } + + if (c == 'q' || c == 'Q') { + return 0; /* User aborted. */ + } + + if (c >= '0' && c <= '9') { + c -= '0'; + + if (c < (int)playbackDeviceCount) { + ma_device_config deviceConfig; + ma_engine_config engineConfig; + + /* + Create the device first before the engine. We'll specify the device in the engine's config. This is optional. When a device is + not pre-initialized the engine will create one for you internally. The device does not need to be started here - the engine will + do that for us in `ma_engine_start()`. The device's format is derived from the resource manager, but can be whatever you want. + It's useful to keep the format consistent with the resource manager to avoid data conversions costs in the audio callback. In + this example we're using the resource manager's sample format and sample rate, but leaving the channel count set to the device's + native channels. You can use whatever format/channels/rate you like. + */ + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = &pPlaybackDeviceInfos[c].id; + deviceConfig.playback.format = resourceManager.config.decodedFormat; + deviceConfig.playback.channels = 0; + deviceConfig.sampleRate = resourceManager.config.decodedSampleRate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &engines[engineCount]; + + result = ma_device_init(&context, &deviceConfig, &devices[engineCount]); + if (result != MA_SUCCESS) { + printf("Failed to initialize device for %s.\n", pPlaybackDeviceInfos[c].name); + return -1; + } + + /* Now that we have the device we can initialize the engine. The device is passed into the engine's config. */ + engineConfig = ma_engine_config_init(); + engineConfig.pDevice = &devices[engineCount]; + engineConfig.pResourceManager = &resourceManager; + engineConfig.noAutoStart = MA_TRUE; /* Don't start the engine by default - we'll do that manually below. */ + + result = ma_engine_init(&engineConfig, &engines[engineCount]); + if (result != MA_SUCCESS) { + printf("Failed to initialize engine for %s.\n", pPlaybackDeviceInfos[c].name); + ma_device_uninit(&devices[engineCount]); + return -1; + } + + engineCount += 1; + break; + } else { + printf("Invalid device number.\n"); + } + } else { + printf("Invalid device number.\n"); + } + } + + printf("Device %d: %s\n", iChosenDevice+1, pPlaybackDeviceInfos[c].name); + } + + + /* We should now have our engine's initialized. We can now start them. */ + for (iEngine = 0; iEngine < engineCount; iEngine += 1) { + result = ma_engine_start(&engines[iEngine]); + if (result != MA_SUCCESS) { + printf("WARNING: Failed to start engine %d.\n", iEngine); + } + } + + + /* + At this point our engine's are running and outputting nothing but silence. To get them playing something we'll need + some sounds. In this example we're just using one sound per engine, but you can create as many as you like. Since + we're using a shared resource manager, the sound data will only be loaded once. This is how you would implement + multiple listeners. + */ + soundCount = 0; + for (iEngine = 0; iEngine < engineCount; iEngine += 1) { + /* Just one sound per engine in this example. We're going to be loading this asynchronously. */ + result = ma_sound_init_from_file(&engines[iEngine], argv[1], MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, NULL, NULL, &sounds[iEngine]); + if (result != MA_SUCCESS) { + printf("WARNING: Failed to load sound \"%s\"", argv[1]); + break; + } + + /* + The sound can be started as soon as ma_sound_init_from_file() returns, even for sounds that are initialized + with MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. The sound will start playing while it's being loaded. Note that if the + asynchronous loading process cannot keep up with the rate at which you try reading you'll end up glitching. + If this is an issue, you need to not load sounds asynchronously. + */ + result = ma_sound_start(&sounds[iEngine]); + if (result != MA_SUCCESS) { + printf("WARNING: Failed to start sound."); + } + + soundCount += 1; + } + + + printf("Press Enter to quit..."); + getchar(); + + for (;;) { + int c = getchar(); + if (c == '\n') { + break; + } + } + + /* Teardown. */ + + /* The application owns the `ma_sound` object which means you're responsible for uninitializing them. */ + for (iSound = 0; iSound < soundCount; iSound += 1) { + ma_sound_uninit(&sounds[iSound]); + } + + /* We can now uninitialize each engine. */ + for (iEngine = 0; iEngine < engineCount; iEngine += 1) { + ma_engine_uninit(&engines[iEngine]); + + /* + The engine has been uninitialized so now lets uninitialize the device. Do this first to ensure we don't + uninitialize the resource manager from under the device while the data callback is running. + */ + ma_device_uninit(&devices[iEngine]); + } + + /* + Do the resource manager last. This way we can guarantee the data callbacks of each device aren't trying to access + and data managed by the resource manager. + */ + ma_resource_manager_uninit(&resourceManager); + + return 0; +} diff --git a/examples/engine_hello_world.c b/examples/engine_hello_world.c new file mode 100644 index 00000000..224fc54b --- /dev/null +++ b/examples/engine_hello_world.c @@ -0,0 +1,34 @@ +/* +This example demonstrates how to initialize an audio engine and play a sound. + +This will play the sound specified on the command line. +*/ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../miniaudio_engine.h" + +int main(int argc, char** argv) +{ + ma_result result; + ma_engine engine; + + if (argc < 2) { + printf("No input file."); + return -1; + } + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + printf("Failed to initialize audio engine."); + return -1; + } + + ma_engine_play_sound(&engine, argv[1], NULL); + + printf("Press Enter to quit..."); + getchar(); + + ma_engine_uninit(&engine); + + return 0; +} diff --git a/examples/resource_manager.c b/examples/resource_manager.c new file mode 100644 index 00000000..55a1f939 --- /dev/null +++ b/examples/resource_manager.c @@ -0,0 +1,149 @@ +/* +Demonstrates how you can use the resource manager to manage loaded sounds. + +This example loads the first sound specified on the command line via the resource manager and then plays it using the +low level API. + +You can control whether or not you want to load the sound asynchronously and whether or not you want to store the data +in-memory or stream it. When storing the sound in-memory you can also control whether or not it is decoded. To do this, +specify a combination of the following options in `ma_resource_manager_data_source_init()`: + + * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC - Load asynchronously. + * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE - Store the sound in-memory in uncompressed/decoded format. + * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM - Stream the sound from disk rather than storing entirely in memory. Useful for music. + +The object returned by the resource manager is just a standard data source which means it can be plugged into any of +`ma_data_source_*()` APIs just like any other data source and it should just work. + +Internally, there's a background thread that's used to process jobs and enable asynchronicity. By default there is only +a single job thread, but this can be configured in the resource manager config. You can also implement your own threads +for processing jobs. That is more advanced, and beyond the scope of this example. + +When you initialize a resource manager you can specify the sample format, channels and sample rate to use when reading +data from the data source. This means the resource manager will ensure all sounds will have a standard format. When not +set, each sound will have their own formats and you'll need to do the necessary data conversion yourself. +*/ +#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../miniaudio_engine.h" + +#ifdef __EMSCRIPTEN__ +#include + +void main_loop__em(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + /* + The Emscripten build does not support threading which means we need to process jobs manually. If + there are no jobs needing to be processed this will return immediately with MA_NO_DATA_AVAILABLE. + */ + ma_resource_manager_process_next_job(pResourceManager); +} +#endif + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + ma_data_source_read_pcm_frames((ma_data_source*)pDevice->pUserData, pOutput, frameCount, NULL, MA_TRUE); + + (void)pInput; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_device_config deviceConfig; + ma_device device; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_resource_manager_data_source dataSource; + + if (argc < 2) { + printf("No input file."); + return -1; + } + + + /* We'll initialize the device first. */ + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &dataSource; /* <-- We'll be reading from this in the data callback. */ + + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize device."); + return -1; + } + + + /* + We have the device so now we want to initialize the resource manager. We'll use the resource manager to load a + sound based on the command line. + */ + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.decodedFormat = device.playback.format; + resourceManagerConfig.decodedChannels = device.playback.channels; + resourceManagerConfig.decodedSampleRate = device.sampleRate; + + /* + We're not supporting threading with Emscripten so go ahead and disable threading. It's important + that we set the appropriate flag and also the job thread count to 0. + */ +#ifdef __EMSCRIPTEN__ + resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + resourceManagerConfig.jobThreadCount = 0; +#endif + + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + + /* Now that we have a resource manager we can load a sound. */ + result = ma_resource_manager_data_source_init( + &resourceManager, + argv[1], + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, + NULL, /* Async notification. */ + &dataSource); + if (result != MA_SUCCESS) { + printf("Failed to load sound \"%s\".", argv[1]); + return -1; + } + + + /* Now that we have a sound we can start the device. */ + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to start device."); + return -1; + } + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(main_loop__em, &resourceManager, 0, 1); +#else + printf("Press Enter to quit...\n"); + getchar(); +#endif + + /* Teardown. */ + + /* Uninitialize the device first to ensure the data callback is stopped and doesn't try to access any data. */ + ma_device_uninit(&device); + + /* + Before uninitializing the resource manager we need to uninitialize every data source. The data source is owned by + the caller which means you're responsible for uninitializing it. + */ + ma_resource_manager_data_source_uninit(&dataSource); + + /* Uninitialize the resource manager after each data source. */ + ma_resource_manager_uninit(&resourceManager); + + return 0; +} diff --git a/examples/resource_manager_advanced.c b/examples/resource_manager_advanced.c new file mode 100644 index 00000000..a5fbdd0c --- /dev/null +++ b/examples/resource_manager_advanced.c @@ -0,0 +1,327 @@ +/* +Demonstrates how you can use the resource manager to manage loaded sounds. + +The resource manager can be used to create a data source whose resources are managed internally by miniaudio. The data +sources can then be read just like any other data source such as decoders and audio buffers. + +In this example we use the resource manager independently of the `ma_engine` API so that we can demonstrate how it can +be used by itself without getting it confused with `ma_engine`. + +The main feature of the resource manager is the ability to decode and stream audio data asynchronously. Asynchronicity +is achieved with a job system. The resource manager will issue jobs which are processed by a configurable number of job +threads. You can also implement your own custom job threads which this example also demonstrates. + +In this example we show how you can create a data source, mix them with other data sources, configure the number of job +threads to manage internally and how to implement your own custom job thread. +*/ +#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */ +#define MINIAUDIO_IMPLEMENTATION +#include "../../miniaudio.h" +#include "../miniaudio_engine.h" + +static ma_resource_manager_data_source g_dataSources[16]; +static ma_uint32 g_dataSourceCount; + + +/* +TODO: Consider putting these public functions in miniaudio.h. Will depend on ma_mix_pcm_frames_f32() +being merged into miniaudio.h (it's currently in miniaudio_engine.h). +*/ +static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, ma_format dataSourceFormat, ma_uint32 dataSourceChannels) +{ + /* + This function is intended to be used when the format and channel count of the data source is + known beforehand. The idea is to avoid overhead due to redundant calls to ma_data_source_get_data_format(). + */ + MA_ASSERT(pDataSource != NULL); + + if (dataSourceFormat == ma_format_f32) { + /* Fast path. No conversion necessary. */ + return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead, loop); + } else { + /* Slow path. Conversion necessary. */ + ma_result result; + ma_uint64 totalFramesRead; + ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint64 framesJustRead; + ma_uint64 framesToRead = frameCount - totalFramesRead; + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead, loop); + + ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); + totalFramesRead += framesJustRead; + + if (result != MA_SUCCESS) { + break; + } + } + + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_source_read_pcm_frames_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) +{ + ma_result result; + ma_format format; + ma_uint32 channels; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the data format of the data source. */ + } + + return ma_data_source_read_pcm_frames_f32_ex(pDataSource, pFramesOut, frameCount, pFramesRead, loop, format, channels); +} + +MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, float volume) +{ + ma_result result; + ma_format format; + ma_uint32 channels; + ma_uint64 totalFramesRead; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the data format of the data source. */ + } + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)]; + ma_uint64 tempCapInFrames = ma_countof(temp) / channels; + ma_uint64 framesJustRead; + ma_uint64 framesToRead = frameCount - totalFramesRead; + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + result = ma_data_source_read_pcm_frames_f32_ex(pDataSource, temp, framesToRead, &framesJustRead, loop, format, channels); + + ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), temp, framesJustRead, channels, volume); + totalFramesRead += framesJustRead; + + if (result != MA_SUCCESS) { + break; + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return MA_SUCCESS; +} + + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + /* + In this example we're just going to play our data sources layered on top of each other. This + assumes the device's format is f32 and that the buffer is not pre-silenced. + */ + ma_uint32 iDataSource; + + MA_ASSERT(pDevice->playback.format == ma_format_f32); + + (void)pInput; /* Unused. */ + + /* + If the device was configured with noPreSilencedOutputBuffer then you would need to silence the + buffer here, or make sure the first data source to be mixed is copied rather than mixed. + */ + /*ma_silence_pcm_frames(pOutput, frameCount, ma_format_f32, pDevice->playback.channels);*/ + + /* For each sound, mix as much data as we can. */ + for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) { + ma_data_source_read_pcm_frames_and_mix_f32(&g_dataSources[iDataSource], (float*)pOutput, frameCount, NULL, MA_TRUE, /* volume = */1); + } +} + +static ma_thread_result MA_THREADCALL custom_job_thread(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + for (;;) { + ma_result result; + ma_resource_manager_job job; + + /* + Retrieve a job from the queue first. This defines what it is you're about to do. By default this will be + blocking. You can initialize the resource manager with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING to not block in + which case MA_NO_DATA_AVAILABLE will be returned if no jobs are available. + + When the quit job is returned (MA_RESOURCE_MANAGER_JOB_QUIT), the return value will always be MA_CANCELLED. If you don't want + to check the return value (you should), you can instead check if the job code is MA_RESOURCE_MANAGER_JOB_QUIT and use that + instead. + */ + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + if (result == MA_CANCELLED) { + printf("CUSTOM JOB THREAD TERMINATING VIA MA_CANCELLED... "); + } else { + printf("CUSTOM JOB THREAD ERROR: %s. TERMINATING... ", ma_result_description(result)); + } + + break; + } + + /* + Terminate if we got a quit message. You don't need to terminate like this, but's a bit more robust. You can + just use a global variable or something similar if it's easier for your particular situation. The quit job + remains in the queue and will continue to be returned by future calls to ma_resource_manager_next_job(). The + reason for this is to give every job thread visibility to the quit job so they have a chance to exit. + + We won't actually be hitting this code because the call above will return MA_CANCELLED when the MA_RESOURCE_MANAGER_JOB_QUIT + event is received which means the `result != MA_SUCCESS` logic above will catch it. If you do not check the + return value of ma_resource_manager_next_job() you will want to check for MA_RESOURCE_MANAGER_JOB_QUIT like the code below. + */ + if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) { + printf("CUSTOM JOB THREAD TERMINATING VIA MA_RESOURCE_MANAGER_JOB_QUIT... "); + break; + } + + /* Call ma_resource_manager_process_job() to actually do the work to process the job. */ + printf("PROCESSING IN CUSTOM JOB THREAD: %d\n", job.toc.breakup.code); + ma_resource_manager_process_job(pResourceManager, &job); + } + + printf("TERMINATED\n"); + return (ma_thread_result)0; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_device_config deviceConfig; + ma_device device; + ma_resource_manager_config resourceManagerConfig; + ma_resource_manager resourceManager; + ma_thread jobThread; + int iFile; + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_f32; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = NULL; + + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize device."); + return -1; + } + + + /* We can start the device before loading any sounds. We'll just end up outputting silence. */ + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to start device."); + return -1; + } + + + /* + We have the device so now we want to initialize the resource manager. We'll use the resource manager to load some + sounds based on the command line. + */ + resourceManagerConfig = ma_resource_manager_config_init(); + + /* + We'll set a standard decoding format to save us to processing time at mixing time. If you're wanting to use + spatialization with your decoded sounds, you may want to consider leaving this as 0 to ensure the file's native + channel count is used so you can do proper spatialization. + */ + resourceManagerConfig.decodedFormat = device.playback.format; + resourceManagerConfig.decodedChannels = device.playback.channels; + resourceManagerConfig.decodedSampleRate = device.sampleRate; + + /* The number of job threads to be managed internally. Set this to 0 if you want to self-manage your job threads */ + resourceManagerConfig.jobThreadCount = 4; + + result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + + /* + Now that we have a resource manager we can set up our custom job thread. This is optional. Normally when doing + self-managed job threads you would set the internal job thread count to zero. We're doing both internal and + self-managed job threads in this example just for demonstration purposes. + */ + ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL); + + /* Create each data source from the resource manager. Note that the caller is the owner. */ + for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) { + result = ma_resource_manager_data_source_init( + &resourceManager, + argv[iFile+1], + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC /*| MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM*/, + NULL, /* Async notification. */ + &g_dataSources[iFile]); + + if (result != MA_SUCCESS) { + break; + } + + g_dataSourceCount += 1; + } + + printf("Press Enter to quit..."); + getchar(); + + + /* Teardown. */ + + /* + Uninitialize the device first to ensure the data callback is stopped and doesn't try to access + any data. + */ + ma_device_uninit(&device); + + /* + Our data sources need to be explicitly uninitialized. ma_resource_manager_uninit() will not do + it for us. This needs to be done before posting the quit event and uninitializing the resource + manager or else we'll get stuck in a deadlock because ma_resource_manager_data_source_uninit() + will be waiting for the job thread(s) to finish work, which will never happen because they were + just terminated. + */ + for (iFile = 0; (size_t)iFile < g_dataSourceCount; iFile += 1) { + ma_resource_manager_data_source_uninit(&g_dataSources[iFile]); + } + + /* + Before uninitializing the resource manager we need to make sure a quit event has been posted to + ensure we can get out of our custom thread. The call to ma_resource_manager_uninit() will also + do this, but we need to call it explicitly so that our self-managed thread can exit naturally. + You only need to post a quit job if you're using that as the exit indicator. You can instead + use whatever variable you want to terminate your job thread, but since this example is using a + quit job we need to post one. Note that you don't need to do this if you're not managing your + own threads - ma_resource_manager_uninit() alone will suffice in that case. + */ + ma_resource_manager_post_job_quit(&resourceManager); + ma_thread_wait(&jobThread); /* Wait for the custom job thread to finish so it doesn't try to access any data. */ + + /* Uninitialize the resource manager after each data source. */ + ma_resource_manager_uninit(&resourceManager); + + return 0; +} diff --git a/research/_examples/custom_decoder_engine.c b/research/_examples/custom_decoder_engine.c index f1be9415..46376687 100644 --- a/research/_examples/custom_decoder_engine.c +++ b/research/_examples/custom_decoder_engine.c @@ -1,254 +1 @@ -/* -Demonstrates how to implement a custom decoder. - -This example implements two custom decoders: - - * Vorbis via libvorbis - * Opus via libopus - -A custom decoder must implement a data source. In this example, the libvorbis data source is called -`ma_libvorbis` and the Opus data source is called `ma_libopus`. These two objects are compatible -with the `ma_data_source` APIs and can be taken straight from this example and used in real code. - -The custom decoding data sources (`ma_libvorbis` and `ma_libopus` in this example) are connected to -the decoder via the decoder config (`ma_decoder_config`). You need to implement a vtable for each -of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement. -The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional. -*/ -#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */ -#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */ -#define MINIAUDIO_IMPLEMENTATION -#include "../../miniaudio.h" -#include "../../extras/miniaudio_libvorbis.h" -#include "../../extras/miniaudio_libopus.h" -#include "../miniaudio_engine.h" - -#include - -static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libvorbis* pVorbis; - - (void)pUserData; - - pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); - if (pVorbis == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis); - if (result != MA_SUCCESS) { - ma_free(pVorbis, pAllocationCallbacks); - return result; - } - - *ppBackend = pVorbis; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libvorbis* pVorbis; - - (void)pUserData; - - pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); - if (pVorbis == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis); - if (result != MA_SUCCESS) { - ma_free(pVorbis, pAllocationCallbacks); - return result; - } - - *ppBackend = pVorbis; - - return MA_SUCCESS; -} - -static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) -{ - ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; - - (void)pUserData; - - ma_libvorbis_uninit(pVorbis, pAllocationCallbacks); - ma_free(pVorbis, pAllocationCallbacks); -} - -static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; - - (void)pUserData; - - return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis = -{ - ma_decoding_backend_init__libvorbis, - ma_decoding_backend_init_file__libvorbis, - NULL, /* onInitFileW() */ - NULL, /* onInitMemory() */ - ma_decoding_backend_uninit__libvorbis -}; - - - -static ma_result ma_decoding_backend_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libopus* pOpus; - - (void)pUserData; - - pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks); - if (pOpus == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus); - if (result != MA_SUCCESS) { - ma_free(pOpus, pAllocationCallbacks); - return result; - } - - *ppBackend = pOpus; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libopus* pOpus; - - (void)pUserData; - - pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks); - if (pOpus == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, pOpus); - if (result != MA_SUCCESS) { - ma_free(pOpus, pAllocationCallbacks); - return result; - } - - *ppBackend = pOpus; - - return MA_SUCCESS; -} - -static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) -{ - ma_libopus* pOpus = (ma_libopus*)pBackend; - - (void)pUserData; - - ma_libopus_uninit(pOpus, pAllocationCallbacks); - ma_free(pOpus, pAllocationCallbacks); -} - -static ma_result ma_decoding_backend_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_libopus* pOpus = (ma_libopus*)pBackend; - - (void)pUserData; - - return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus = -{ - ma_decoding_backend_init__libopus, - ma_decoding_backend_init_file__libopus, - NULL, /* onInitFileW() */ - NULL, /* onInitMemory() */ - ma_decoding_backend_uninit__libopus -}; - - - - -void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) -{ - ma_data_source* pDataSource = (ma_data_source*)pDevice->pUserData; - if (pDataSource == NULL) { - return; - } - - ma_data_source_read_pcm_frames(pDataSource, pOutput, frameCount, NULL, MA_TRUE); - - (void)pInput; -} - -int main(int argc, char** argv) -{ - ma_result result; - ma_resource_manager_config resourceManagerConfig; - ma_resource_manager resourceManager; - ma_engine_config engineConfig; - ma_engine engine; - - /* - Add your custom backend vtables here. The order in the array defines the order of priority. The - vtables will be passed in to the resource manager config. - */ - ma_decoding_backend_vtable* pCustomBackendVTables[] = - { - &g_ma_decoding_backend_vtable_libvorbis, - &g_ma_decoding_backend_vtable_libopus - }; - - - if (argc < 2) { - printf("No input file.\n"); - return -1; - } - - - /* Using custom decoding backends requires a resource manager. */ - resourceManagerConfig = ma_resource_manager_config_init(); - resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; - resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); - resourceManagerConfig.pCustomDecodingBackendUserData = NULL; /* <-- This will be passed in to the pUserData parameter of each function in the decoding backend vtables. */ - - result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); - if (result != MA_SUCCESS) { - printf("Failed to initialize resource manager."); - return -1; - } - - - /* Once we have a resource manager we can create the engine. */ - engineConfig = ma_engine_config_init(); - engineConfig.pResourceManager = &resourceManager; - - result = ma_engine_init(&engineConfig, &engine); - if (result != MA_SUCCESS) { - printf("Failed to initialize engine."); - return -1; - } - - - /* Now we can play our sound. */ - result = ma_engine_play_sound(&engine, argv[1], NULL); - if (result != MA_SUCCESS) { - printf("Failed to play sound."); - return -1; - } - - - printf("Press Enter to quit..."); - getchar(); - - return 0; -} \ No newline at end of file +#include "../../examples/custom_decoder_engine.c" \ No newline at end of file diff --git a/research/_examples/duplex_effect.c b/research/_examples/duplex_effect.c index b0f1d522..650a1f5f 100644 --- a/research/_examples/duplex_effect.c +++ b/research/_examples/duplex_effect.c @@ -1,149 +1 @@ -/* -Demonstrates how to apply an effect to a duplex stream using the node graph system. - -This example applies a vocoder effect to the input stream before outputting it. A custom node -called `ma_vocoder_node` is used to achieve the effect which can be found in the extras folder in -the miniaudio repository. The vocoder node uses https://github.com/blastbay/voclib to achieve the -effect. -*/ -#define MINIAUDIO_IMPLEMENTATION -#include "../../miniaudio.h" -#include "../miniaudio_engine.h" -#include "../_extras/nodes/ma_vocoder_node/ma_vocoder_node.c" - -#include - -#define DEVICE_FORMAT ma_format_f32; /* Must always be f32 for this example because the node graph system only works with this. */ -#define DEVICE_CHANNELS 1 /* For this example, always set to 1. */ - -static ma_waveform g_sourceData; /* The underlying data source of the excite node. */ -static ma_audio_buffer_ref g_exciteData; /* The underlying data source of the source node. */ -static ma_data_source_node g_sourceNode; /* A data source node containing the source data we'll be sending through to the vocoder. This will be routed into the first bus of the vocoder node. */ -static ma_data_source_node g_exciteNode; /* A data source node containing the excite data we'll be sending through to the vocoder. This will be routed into the second bus of the vocoder node. */ -static ma_vocoder_node g_vocoderNode; /* The vocoder node. */ -static ma_node_graph g_nodeGraph; - -void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) -{ - MA_ASSERT(pDevice->capture.format == pDevice->playback.format); - MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels); - - /* - The node graph system is a pulling style of API. At the lowest level of the chain will be a - node acting as a data source for the purpose of delivering the initial audio data. In our case, - the data source is our `pInput` buffer. We need to update the underlying data source so that it - read data from `pInput`. - */ - ma_audio_buffer_ref_set_data(&g_exciteData, pInput, frameCount); - - /* With the source buffer configured we can now read directly from the node graph. */ - ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL); -} - -int main(int argc, char** argv) -{ - ma_result result; - ma_device_config deviceConfig; - ma_device device; - ma_node_graph_config nodeGraphConfig; - ma_vocoder_node_config vocoderNodeConfig; - ma_data_source_node_config sourceNodeConfig; - ma_data_source_node_config exciteNodeConfig; - ma_waveform_config waveformConfig; - - deviceConfig = ma_device_config_init(ma_device_type_duplex); - deviceConfig.capture.pDeviceID = NULL; - deviceConfig.capture.format = DEVICE_FORMAT; - deviceConfig.capture.channels = DEVICE_CHANNELS; - deviceConfig.capture.shareMode = ma_share_mode_shared; - deviceConfig.playback.pDeviceID = NULL; - deviceConfig.playback.format = DEVICE_FORMAT; - deviceConfig.playback.channels = DEVICE_CHANNELS; - deviceConfig.dataCallback = data_callback; - result = ma_device_init(NULL, &deviceConfig, &device); - if (result != MA_SUCCESS) { - return result; - } - - - /* Now we can setup our node graph. */ - nodeGraphConfig = ma_node_graph_config_init(device.capture.channels); - - result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph); - if (result != MA_SUCCESS) { - printf("Failed to initialize node graph."); - goto done0; - } - - - /* Vocoder. Attached straight to the endpoint. */ - vocoderNodeConfig = ma_vocoder_node_config_init(device.capture.channels, device.sampleRate); - - result = ma_vocoder_node_init(&g_nodeGraph, &vocoderNodeConfig, NULL, &g_vocoderNode); - if (result != MA_SUCCESS) { - printf("Failed to initialize vocoder node."); - goto done1; - } - - ma_node_attach_output_bus(&g_vocoderNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0); - - /* Amplify the volume of the vocoder output because in my testing it is a bit quiet. */ - ma_node_set_output_bus_volume(&g_vocoderNode, 0, 4); - - - /* Source/carrier. Attached to input bus 0 of the vocoder node. */ - waveformConfig = ma_waveform_config_init(device.capture.format, device.capture.channels, device.sampleRate, ma_waveform_type_sawtooth, 1.0, 50); - - result = ma_waveform_init(&waveformConfig, &g_sourceData); - if (result != MA_SUCCESS) { - printf("Failed to initialize waveform for excite node."); - goto done3; - } - - sourceNodeConfig = ma_data_source_node_config_init(&g_sourceData, MA_FALSE); - - result = ma_data_source_node_init(&g_nodeGraph, &sourceNodeConfig, NULL, &g_sourceNode); - if (result != MA_SUCCESS) { - printf("Failed to initialize excite node."); - goto done3; - } - - ma_node_attach_output_bus(&g_sourceNode, 0, &g_vocoderNode, 0); - - - /* Excite/modulator. Attached to input bus 1 of the vocoder node. */ - result = ma_audio_buffer_ref_init(device.capture.format, device.capture.channels, NULL, 0, &g_exciteData); - if (result != MA_SUCCESS) { - printf("Failed to initialize audio buffer for source."); - goto done2; - } - - exciteNodeConfig = ma_data_source_node_config_init(&g_exciteData, MA_FALSE); - - result = ma_data_source_node_init(&g_nodeGraph, &exciteNodeConfig, NULL, &g_exciteNode); - if (result != MA_SUCCESS) { - printf("Failed to initialize source node."); - goto done2; - } - - ma_node_attach_output_bus(&g_exciteNode, 0, &g_vocoderNode, 1); - - - ma_device_start(&device); - - printf("Press Enter to quit...\n"); - getchar(); - - /* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */ - ma_device_stop(&device); - -/*done4:*/ ma_data_source_node_uninit(&g_exciteNode, NULL); -done3: ma_data_source_node_uninit(&g_sourceNode, NULL); -done2: ma_vocoder_node_uninit(&g_vocoderNode, NULL); -done1: ma_node_graph_uninit(&g_nodeGraph, NULL); -done0: ma_device_uninit(&device); - - (void)argc; - (void)argv; - return 0; -} +#include "../../examples/duplex_effect.c" \ No newline at end of file diff --git a/research/_examples/engine_hello_world.c b/research/_examples/engine_hello_world.c index 224fc54b..6684ab54 100644 --- a/research/_examples/engine_hello_world.c +++ b/research/_examples/engine_hello_world.c @@ -1,34 +1 @@ -/* -This example demonstrates how to initialize an audio engine and play a sound. - -This will play the sound specified on the command line. -*/ -#define MINIAUDIO_IMPLEMENTATION -#include "../../miniaudio.h" -#include "../miniaudio_engine.h" - -int main(int argc, char** argv) -{ - ma_result result; - ma_engine engine; - - if (argc < 2) { - printf("No input file."); - return -1; - } - - result = ma_engine_init(NULL, &engine); - if (result != MA_SUCCESS) { - printf("Failed to initialize audio engine."); - return -1; - } - - ma_engine_play_sound(&engine, argv[1], NULL); - - printf("Press Enter to quit..."); - getchar(); - - ma_engine_uninit(&engine); - - return 0; -} +#include "../../examples/engine_hello_world.c" \ No newline at end of file diff --git a/research/_examples/resource_manager.c b/research/_examples/resource_manager.c index 55a1f939..8b90474f 100644 --- a/research/_examples/resource_manager.c +++ b/research/_examples/resource_manager.c @@ -1,149 +1 @@ -/* -Demonstrates how you can use the resource manager to manage loaded sounds. - -This example loads the first sound specified on the command line via the resource manager and then plays it using the -low level API. - -You can control whether or not you want to load the sound asynchronously and whether or not you want to store the data -in-memory or stream it. When storing the sound in-memory you can also control whether or not it is decoded. To do this, -specify a combination of the following options in `ma_resource_manager_data_source_init()`: - - * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC - Load asynchronously. - * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE - Store the sound in-memory in uncompressed/decoded format. - * MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM - Stream the sound from disk rather than storing entirely in memory. Useful for music. - -The object returned by the resource manager is just a standard data source which means it can be plugged into any of -`ma_data_source_*()` APIs just like any other data source and it should just work. - -Internally, there's a background thread that's used to process jobs and enable asynchronicity. By default there is only -a single job thread, but this can be configured in the resource manager config. You can also implement your own threads -for processing jobs. That is more advanced, and beyond the scope of this example. - -When you initialize a resource manager you can specify the sample format, channels and sample rate to use when reading -data from the data source. This means the resource manager will ensure all sounds will have a standard format. When not -set, each sound will have their own formats and you'll need to do the necessary data conversion yourself. -*/ -#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */ -#define MINIAUDIO_IMPLEMENTATION -#include "../../miniaudio.h" -#include "../miniaudio_engine.h" - -#ifdef __EMSCRIPTEN__ -#include - -void main_loop__em(void* pUserData) -{ - ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; - MA_ASSERT(pResourceManager != NULL); - - /* - The Emscripten build does not support threading which means we need to process jobs manually. If - there are no jobs needing to be processed this will return immediately with MA_NO_DATA_AVAILABLE. - */ - ma_resource_manager_process_next_job(pResourceManager); -} -#endif - -void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) -{ - ma_data_source_read_pcm_frames((ma_data_source*)pDevice->pUserData, pOutput, frameCount, NULL, MA_TRUE); - - (void)pInput; -} - -int main(int argc, char** argv) -{ - ma_result result; - ma_device_config deviceConfig; - ma_device device; - ma_resource_manager_config resourceManagerConfig; - ma_resource_manager resourceManager; - ma_resource_manager_data_source dataSource; - - if (argc < 2) { - printf("No input file."); - return -1; - } - - - /* We'll initialize the device first. */ - deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.dataCallback = data_callback; - deviceConfig.pUserData = &dataSource; /* <-- We'll be reading from this in the data callback. */ - - result = ma_device_init(NULL, &deviceConfig, &device); - if (result != MA_SUCCESS) { - printf("Failed to initialize device."); - return -1; - } - - - /* - We have the device so now we want to initialize the resource manager. We'll use the resource manager to load a - sound based on the command line. - */ - resourceManagerConfig = ma_resource_manager_config_init(); - resourceManagerConfig.decodedFormat = device.playback.format; - resourceManagerConfig.decodedChannels = device.playback.channels; - resourceManagerConfig.decodedSampleRate = device.sampleRate; - - /* - We're not supporting threading with Emscripten so go ahead and disable threading. It's important - that we set the appropriate flag and also the job thread count to 0. - */ -#ifdef __EMSCRIPTEN__ - resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; - resourceManagerConfig.jobThreadCount = 0; -#endif - - result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); - if (result != MA_SUCCESS) { - ma_device_uninit(&device); - printf("Failed to initialize the resource manager."); - return -1; - } - - /* Now that we have a resource manager we can load a sound. */ - result = ma_resource_manager_data_source_init( - &resourceManager, - argv[1], - MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM, - NULL, /* Async notification. */ - &dataSource); - if (result != MA_SUCCESS) { - printf("Failed to load sound \"%s\".", argv[1]); - return -1; - } - - - /* Now that we have a sound we can start the device. */ - result = ma_device_start(&device); - if (result != MA_SUCCESS) { - ma_device_uninit(&device); - printf("Failed to start device."); - return -1; - } - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop_arg(main_loop__em, &resourceManager, 0, 1); -#else - printf("Press Enter to quit...\n"); - getchar(); -#endif - - /* Teardown. */ - - /* Uninitialize the device first to ensure the data callback is stopped and doesn't try to access any data. */ - ma_device_uninit(&device); - - /* - Before uninitializing the resource manager we need to uninitialize every data source. The data source is owned by - the caller which means you're responsible for uninitializing it. - */ - ma_resource_manager_data_source_uninit(&dataSource); - - /* Uninitialize the resource manager after each data source. */ - ma_resource_manager_uninit(&resourceManager); - - return 0; -} +#include "../../examples/resource_manager.c" \ No newline at end of file diff --git a/research/_examples/resource_manager_advanced.c b/research/_examples/resource_manager_advanced.c index a5fbdd0c..766a1e65 100644 --- a/research/_examples/resource_manager_advanced.c +++ b/research/_examples/resource_manager_advanced.c @@ -1,327 +1 @@ -/* -Demonstrates how you can use the resource manager to manage loaded sounds. - -The resource manager can be used to create a data source whose resources are managed internally by miniaudio. The data -sources can then be read just like any other data source such as decoders and audio buffers. - -In this example we use the resource manager independently of the `ma_engine` API so that we can demonstrate how it can -be used by itself without getting it confused with `ma_engine`. - -The main feature of the resource manager is the ability to decode and stream audio data asynchronously. Asynchronicity -is achieved with a job system. The resource manager will issue jobs which are processed by a configurable number of job -threads. You can also implement your own custom job threads which this example also demonstrates. - -In this example we show how you can create a data source, mix them with other data sources, configure the number of job -threads to manage internally and how to implement your own custom job thread. -*/ -#define MA_NO_ENGINE /* We're intentionally not using the ma_engine API here. */ -#define MINIAUDIO_IMPLEMENTATION -#include "../../miniaudio.h" -#include "../miniaudio_engine.h" - -static ma_resource_manager_data_source g_dataSources[16]; -static ma_uint32 g_dataSourceCount; - - -/* -TODO: Consider putting these public functions in miniaudio.h. Will depend on ma_mix_pcm_frames_f32() -being merged into miniaudio.h (it's currently in miniaudio_engine.h). -*/ -static ma_result ma_data_source_read_pcm_frames_f32_ex(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, ma_format dataSourceFormat, ma_uint32 dataSourceChannels) -{ - /* - This function is intended to be used when the format and channel count of the data source is - known beforehand. The idea is to avoid overhead due to redundant calls to ma_data_source_get_data_format(). - */ - MA_ASSERT(pDataSource != NULL); - - if (dataSourceFormat == ma_format_f32) { - /* Fast path. No conversion necessary. */ - return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead, loop); - } else { - /* Slow path. Conversion necessary. */ - ma_result result; - ma_uint64 totalFramesRead; - ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint64 tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); - - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - ma_uint64 framesJustRead; - ma_uint64 framesToRead = frameCount - totalFramesRead; - if (framesToRead > tempCapInFrames) { - framesToRead = tempCapInFrames; - } - - result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, framesToRead, &framesJustRead, loop); - - ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pFramesOut, totalFramesRead, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); - totalFramesRead += framesJustRead; - - if (result != MA_SUCCESS) { - break; - } - } - - return MA_SUCCESS; - } -} - -MA_API ma_result ma_data_source_read_pcm_frames_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) -{ - ma_result result; - ma_format format; - ma_uint32 channels; - - result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); - if (result != MA_SUCCESS) { - return result; /* Failed to retrieve the data format of the data source. */ - } - - return ma_data_source_read_pcm_frames_f32_ex(pDataSource, pFramesOut, frameCount, pFramesRead, loop, format, channels); -} - -MA_API ma_result ma_data_source_read_pcm_frames_and_mix_f32(ma_data_source* pDataSource, float* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop, float volume) -{ - ma_result result; - ma_format format; - ma_uint32 channels; - ma_uint64 totalFramesRead; - - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); - if (result != MA_SUCCESS) { - return result; /* Failed to retrieve the data format of the data source. */ - } - - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)]; - ma_uint64 tempCapInFrames = ma_countof(temp) / channels; - ma_uint64 framesJustRead; - ma_uint64 framesToRead = frameCount - totalFramesRead; - if (framesToRead > tempCapInFrames) { - framesToRead = tempCapInFrames; - } - - result = ma_data_source_read_pcm_frames_f32_ex(pDataSource, temp, framesToRead, &framesJustRead, loop, format, channels); - - ma_mix_pcm_frames_f32(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), temp, framesJustRead, channels, volume); - totalFramesRead += framesJustRead; - - if (result != MA_SUCCESS) { - break; - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesRead; - } - - return MA_SUCCESS; -} - - -void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) -{ - /* - In this example we're just going to play our data sources layered on top of each other. This - assumes the device's format is f32 and that the buffer is not pre-silenced. - */ - ma_uint32 iDataSource; - - MA_ASSERT(pDevice->playback.format == ma_format_f32); - - (void)pInput; /* Unused. */ - - /* - If the device was configured with noPreSilencedOutputBuffer then you would need to silence the - buffer here, or make sure the first data source to be mixed is copied rather than mixed. - */ - /*ma_silence_pcm_frames(pOutput, frameCount, ma_format_f32, pDevice->playback.channels);*/ - - /* For each sound, mix as much data as we can. */ - for (iDataSource = 0; iDataSource < g_dataSourceCount; iDataSource += 1) { - ma_data_source_read_pcm_frames_and_mix_f32(&g_dataSources[iDataSource], (float*)pOutput, frameCount, NULL, MA_TRUE, /* volume = */1); - } -} - -static ma_thread_result MA_THREADCALL custom_job_thread(void* pUserData) -{ - ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; - MA_ASSERT(pResourceManager != NULL); - - for (;;) { - ma_result result; - ma_resource_manager_job job; - - /* - Retrieve a job from the queue first. This defines what it is you're about to do. By default this will be - blocking. You can initialize the resource manager with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING to not block in - which case MA_NO_DATA_AVAILABLE will be returned if no jobs are available. - - When the quit job is returned (MA_RESOURCE_MANAGER_JOB_QUIT), the return value will always be MA_CANCELLED. If you don't want - to check the return value (you should), you can instead check if the job code is MA_RESOURCE_MANAGER_JOB_QUIT and use that - instead. - */ - result = ma_resource_manager_next_job(pResourceManager, &job); - if (result != MA_SUCCESS) { - if (result == MA_CANCELLED) { - printf("CUSTOM JOB THREAD TERMINATING VIA MA_CANCELLED... "); - } else { - printf("CUSTOM JOB THREAD ERROR: %s. TERMINATING... ", ma_result_description(result)); - } - - break; - } - - /* - Terminate if we got a quit message. You don't need to terminate like this, but's a bit more robust. You can - just use a global variable or something similar if it's easier for your particular situation. The quit job - remains in the queue and will continue to be returned by future calls to ma_resource_manager_next_job(). The - reason for this is to give every job thread visibility to the quit job so they have a chance to exit. - - We won't actually be hitting this code because the call above will return MA_CANCELLED when the MA_RESOURCE_MANAGER_JOB_QUIT - event is received which means the `result != MA_SUCCESS` logic above will catch it. If you do not check the - return value of ma_resource_manager_next_job() you will want to check for MA_RESOURCE_MANAGER_JOB_QUIT like the code below. - */ - if (job.toc.breakup.code == MA_RESOURCE_MANAGER_JOB_QUIT) { - printf("CUSTOM JOB THREAD TERMINATING VIA MA_RESOURCE_MANAGER_JOB_QUIT... "); - break; - } - - /* Call ma_resource_manager_process_job() to actually do the work to process the job. */ - printf("PROCESSING IN CUSTOM JOB THREAD: %d\n", job.toc.breakup.code); - ma_resource_manager_process_job(pResourceManager, &job); - } - - printf("TERMINATED\n"); - return (ma_thread_result)0; -} - -int main(int argc, char** argv) -{ - ma_result result; - ma_device_config deviceConfig; - ma_device device; - ma_resource_manager_config resourceManagerConfig; - ma_resource_manager resourceManager; - ma_thread jobThread; - int iFile; - - deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.playback.format = ma_format_f32; - deviceConfig.dataCallback = data_callback; - deviceConfig.pUserData = NULL; - - result = ma_device_init(NULL, &deviceConfig, &device); - if (result != MA_SUCCESS) { - printf("Failed to initialize device."); - return -1; - } - - - /* We can start the device before loading any sounds. We'll just end up outputting silence. */ - result = ma_device_start(&device); - if (result != MA_SUCCESS) { - ma_device_uninit(&device); - printf("Failed to start device."); - return -1; - } - - - /* - We have the device so now we want to initialize the resource manager. We'll use the resource manager to load some - sounds based on the command line. - */ - resourceManagerConfig = ma_resource_manager_config_init(); - - /* - We'll set a standard decoding format to save us to processing time at mixing time. If you're wanting to use - spatialization with your decoded sounds, you may want to consider leaving this as 0 to ensure the file's native - channel count is used so you can do proper spatialization. - */ - resourceManagerConfig.decodedFormat = device.playback.format; - resourceManagerConfig.decodedChannels = device.playback.channels; - resourceManagerConfig.decodedSampleRate = device.sampleRate; - - /* The number of job threads to be managed internally. Set this to 0 if you want to self-manage your job threads */ - resourceManagerConfig.jobThreadCount = 4; - - result = ma_resource_manager_init(&resourceManagerConfig, &resourceManager); - if (result != MA_SUCCESS) { - ma_device_uninit(&device); - printf("Failed to initialize the resource manager."); - return -1; - } - - /* - Now that we have a resource manager we can set up our custom job thread. This is optional. Normally when doing - self-managed job threads you would set the internal job thread count to zero. We're doing both internal and - self-managed job threads in this example just for demonstration purposes. - */ - ma_thread_create(&jobThread, ma_thread_priority_default, 0, custom_job_thread, &resourceManager, NULL); - - /* Create each data source from the resource manager. Note that the caller is the owner. */ - for (iFile = 0; iFile < ma_countof(g_dataSources) && iFile < argc-1; iFile += 1) { - result = ma_resource_manager_data_source_init( - &resourceManager, - argv[iFile+1], - MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC /*| MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM*/, - NULL, /* Async notification. */ - &g_dataSources[iFile]); - - if (result != MA_SUCCESS) { - break; - } - - g_dataSourceCount += 1; - } - - printf("Press Enter to quit..."); - getchar(); - - - /* Teardown. */ - - /* - Uninitialize the device first to ensure the data callback is stopped and doesn't try to access - any data. - */ - ma_device_uninit(&device); - - /* - Our data sources need to be explicitly uninitialized. ma_resource_manager_uninit() will not do - it for us. This needs to be done before posting the quit event and uninitializing the resource - manager or else we'll get stuck in a deadlock because ma_resource_manager_data_source_uninit() - will be waiting for the job thread(s) to finish work, which will never happen because they were - just terminated. - */ - for (iFile = 0; (size_t)iFile < g_dataSourceCount; iFile += 1) { - ma_resource_manager_data_source_uninit(&g_dataSources[iFile]); - } - - /* - Before uninitializing the resource manager we need to make sure a quit event has been posted to - ensure we can get out of our custom thread. The call to ma_resource_manager_uninit() will also - do this, but we need to call it explicitly so that our self-managed thread can exit naturally. - You only need to post a quit job if you're using that as the exit indicator. You can instead - use whatever variable you want to terminate your job thread, but since this example is using a - quit job we need to post one. Note that you don't need to do this if you're not managing your - own threads - ma_resource_manager_uninit() alone will suffice in that case. - */ - ma_resource_manager_post_job_quit(&resourceManager); - ma_thread_wait(&jobThread); /* Wait for the custom job thread to finish so it doesn't try to access any data. */ - - /* Uninitialize the resource manager after each data source. */ - ma_resource_manager_uninit(&resourceManager); - - return 0; -} +#include "../../examples/resource_manager_advanced.c" \ No newline at end of file