Commit 2671e075 authored by David Reid's avatar David Reid

Add examples for the high level API.

parent eac61ddc
/*
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 <stdio.h>
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
/*
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 <stdio.h>
#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;
}
This diff is collapsed.
/*
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;
}
/*
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 <emscripten.h>
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;
}
This diff is collapsed.
/*
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 <stdio.h>
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
/*
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 <stdio.h>
#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
/*
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
/*
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 <emscripten.h>
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment