From e1eb387acfc98f1d0ac4e755ce771856f6aca4fb Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 17 Mar 2019 12:49:47 +1000 Subject: [PATCH] Add simple_mixing example. --- examples/simple_mixing.c | 191 ++++++++++++++++++++++++++++++++ tests/ma_test_0.vcxproj | 13 ++- tests/ma_test_0.vcxproj.filters | 3 + 3 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 examples/simple_mixing.c diff --git a/examples/simple_mixing.c b/examples/simple_mixing.c new file mode 100644 index 00000000..6c0b0785 --- /dev/null +++ b/examples/simple_mixing.c @@ -0,0 +1,191 @@ +/* +Usage: simple_mixing [input file 0] [input file 1] ... [input file n] +Example: simple_mixing file1.wav file2.flac +*/ + +#define DR_FLAC_IMPLEMENTATION +#include "../extras/dr_flac.h" // Enables FLAC decoding. +#define DR_MP3_IMPLEMENTATION +#include "../extras/dr_mp3.h" // Enables MP3 decoding. +#define DR_WAV_IMPLEMENTATION +#include "../extras/dr_wav.h" // Enables WAV decoding. + +#define MINIAUDIO_IMPLEMENTATION +#include "../miniaudio.h" + +#include + +/* +For simplicity, this example requires the device use floating point samples. +*/ +#define SAMPLE_FORMAT ma_format_f32 +#define CHANNEL_COUNT 2 +#define SAMPLE_RATE 48000 + +ma_uint32 g_decoderCount; +ma_decoder* g_pDecoders; +ma_bool32* g_pDecodersAtEnd; + +ma_event g_stopEvent; /* <-- Signaled by the audio thread, waited on by the main thread. */ + +ma_bool32 are_all_decoders_at_end() +{ + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + if (g_pDecodersAtEnd[iDecoder] == MA_FALSE) { + return MA_FALSE; + } + } + + return MA_TRUE; +} + +ma_uint32 read_and_mix_pcm_frames_f32(ma_decoder* pDecoder, float* pOutputF32, ma_uint32 frameCount) +{ + /* + The way mixing works is that we just read into a temporary buffer, then take the contents of that buffer and mix it with the + contents of the output buffer by simply adding the sample together. You could also clip the samples to -1..+1, but I'm not + doing that in this example. + */ + float temp[4096]; + ma_uint32 tempCapInFrames = ma_countof(temp) / CHANNEL_COUNT; + ma_uint32 totalFramesRead = 0; + + while (totalFramesRead < frameCount) { + ma_uint32 framesReadThisIteration; + ma_uint32 totalFramesRemaining = frameCount - totalFramesRead; + ma_uint32 framesToReadThisIteration = tempCapInFrames; + if (framesToReadThisIteration > totalFramesRemaining) { + framesToReadThisIteration = totalFramesRemaining; + } + + framesReadThisIteration = (ma_uint32)ma_decoder_read_pcm_frames(pDecoder, temp, framesToReadThisIteration); + if (framesReadThisIteration == 0) { + break; + } + + /* Mix the frames together. */ + for (ma_uint32 iSample = 0; iSample < framesReadThisIteration*CHANNEL_COUNT; ++iSample) { + pOutputF32[totalFramesRead*CHANNEL_COUNT + iSample] += temp[iSample]; + } + + totalFramesRead += framesReadThisIteration; + + if (framesReadThisIteration < framesToReadThisIteration) { + break; /* Reached EOF. */ + } + } + + return totalFramesRead; +} + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + float* pOutputF32 = (float*)pOutput; + + ma_assert(pDevice->playback.format == SAMPLE_FORMAT); /* <-- Important for this example. */ + + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + if (!g_pDecodersAtEnd[iDecoder]) { + ma_uint32 framesRead = read_and_mix_pcm_frames_f32(&g_pDecoders[iDecoder], pOutputF32, frameCount); + if (framesRead < frameCount) { + g_pDecodersAtEnd[iDecoder] = MA_TRUE; + } + } + } + + /* + If at the end all of our decoders are at the end we need to stop. We cannot stop the device in the callback. Instead we need to + signal an event to indicate that it's stopped. The main thread will be waiting on the event, after which it will stop the event. + */ + if (are_all_decoders_at_end()) { + ma_event_signal(&g_stopEvent); + } + + (void)pInput; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_decoder_config decoderConfig; + + if (argc < 2) { + printf("No input files.\n"); + return -1; + } + + g_decoderCount = argc-1; + g_pDecoders = (ma_decoder*)malloc(sizeof(*g_pDecoders) * g_decoderCount); + g_pDecodersAtEnd = (ma_bool32*) malloc(sizeof(*g_pDecodersAtEnd) * g_decoderCount); + + /* In this example, all decoders need to have the same output format. */ + decoderConfig = ma_decoder_config_init(SAMPLE_FORMAT, CHANNEL_COUNT, SAMPLE_RATE); + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + result = ma_decoder_init_file(argv[1+iDecoder], &decoderConfig, &g_pDecoders[iDecoder]); + if (result != MA_SUCCESS) { + for (ma_uint32 iDecoder2 = 0; iDecoder2 < iDecoder; ++iDecoder2) { + ma_decoder_uninit(&g_pDecoders[iDecoder2]); + } + free(g_pDecoders); + free(g_pDecodersAtEnd); + + printf("Failed to load %s.\n", argv[1+iDecoder]); + return -3; + } + g_pDecodersAtEnd[iDecoder] = MA_FALSE; + } + + /* Create only a single device. The decoders will be mixed together in the callback. In this example the data format needs to be the same as the decoders. */ + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.format = SAMPLE_FORMAT; + config.playback.channels = CHANNEL_COUNT; + config.sampleRate = SAMPLE_RATE; + config.dataCallback = data_callback; + config.pUserData = NULL; + + ma_device device; + if (ma_device_init(NULL, &config, &device) != MA_SUCCESS) { + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + ma_decoder_uninit(&g_pDecoders[iDecoder]); + } + free(g_pDecoders); + free(g_pDecodersAtEnd); + + printf("Failed to open playback device.\n"); + return -3; + } + + /* + We can't stop in the audio thread so we instead need to use an event. We wait on this thread in the main thread, and signal it in the audio thread. This + needs to be done before starting the device. We need a context to initialize the event, which we can get from the device. Alternatively you can initialize + a context separately, but we don't need to do that for this example. + */ + ma_event_init(device.pContext, &g_stopEvent); + + /* Now we start playback and wait for the audio thread to tell us to stop. */ + if (ma_device_start(&device) != MA_SUCCESS) { + ma_device_uninit(&device); + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + ma_decoder_uninit(&g_pDecoders[iDecoder]); + } + free(g_pDecoders); + free(g_pDecodersAtEnd); + + printf("Failed to start playback device.\n"); + return -4; + } + + printf("Waiting for playback to complete...\n"); + ma_event_wait(&g_stopEvent); + + /* Getting here means the audio thread has signaled that the device should be stopped. */ + ma_device_uninit(&device); + + for (ma_uint32 iDecoder = 0; iDecoder < g_decoderCount; ++iDecoder) { + ma_decoder_uninit(&g_pDecoders[iDecoder]); + } + free(g_pDecoders); + free(g_pDecodersAtEnd); + + return 0; +} diff --git a/tests/ma_test_0.vcxproj b/tests/ma_test_0.vcxproj index cd60e772..89e4d525 100644 --- a/tests/ma_test_0.vcxproj +++ b/tests/ma_test_0.vcxproj @@ -294,13 +294,14 @@ true true + - false - false - false - false - false - false + true + true + true + true + true + true true diff --git a/tests/ma_test_0.vcxproj.filters b/tests/ma_test_0.vcxproj.filters index 2a6605c3..c61f04a3 100644 --- a/tests/ma_test_0.vcxproj.filters +++ b/tests/ma_test_0.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files +