From 19cbee7bc4a15a75579eb47a82bca4f77e547a66 Mon Sep 17 00:00:00 2001 From: David Reid Date: Mon, 29 Jun 2026 08:06:27 +1000 Subject: [PATCH] Initial work on the CLI tool. --- CMakeLists.txt | 26 +- tools/cli/miniaudio_cli.c | 1439 +++++++++++++++++++ tools/cli/miniaudio_cli_node.c | 2360 ++++++++++++++++++++++++++++++++ 3 files changed, 3824 insertions(+), 1 deletion(-) create mode 100644 tools/cli/miniaudio_cli.c create mode 100644 tools/cli/miniaudio_cli_node.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 763379c1..522d8fc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1029,7 +1029,29 @@ endif() if (MINIAUDIO_BUILD_TOOLS) set(TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools) - add_executable(madoc ${TOOLS_DIR}/madoc/madoc.c) + add_executable(miniaudio_cli ${TOOLS_DIR}/cli/miniaudio_cli.c) + target_link_libraries(miniaudio_cli PRIVATE ${COMMON_LINK_LIBRARIES}) + set_target_properties(miniaudio_cli PROPERTIES OUTPUT_NAME miniaudio) + if(MINIAUDIO_NO_RUNTIME_LINKING) + if(NOT MINIAUDIO_NO_SDL2 AND TARGET PkgConfig::SDL2) + target_link_libraries(miniaudio_cli PRIVATE PkgConfig::SDL2) + endif() + endif() + if (TARGET libvorbis_interface) + target_link_libraries(miniaudio_cli PRIVATE libvorbis_interface) + target_compile_definitions(miniaudio_cli PRIVATE MA_CLI_ENABLE_LIBVORBIS) + else() + message(STATUS "miniaudio_libvorbis is disabled. Vorbis support is disabled in miniaudio_cli.") + endif() + if (TARGET libopus_interface) + target_link_libraries(miniaudio_cli PRIVATE libopus_interface) + target_compile_definitions(miniaudio_cli PRIVATE MA_CLI_ENABLE_LIBOPUS) + else() + message(STATUS "miniaudio_libopus is disabled. Opus support is disabled in miniaudio_cli.") + endif() + if(MINIAUDIO_NO_THREADING) + target_compile_definitions(miniaudio_cli PRIVATE MA_NO_THREADING) # We want to be able to test device I/O without threading. + endif() add_executable(audioconverter ${TOOLS_DIR}/audioconverter/audioconverter.c) target_link_libraries(audioconverter PRIVATE ${COMMON_LINK_LIBRARIES}) @@ -1045,6 +1067,8 @@ if (MINIAUDIO_BUILD_TOOLS) target_compile_definitions(audioconverter PRIVATE MA_NO_LIBOPUS) message(STATUS "miniaudio_libopus is disabled. Opus support is disabled in audioconverter.") endif() + + add_executable(madoc ${TOOLS_DIR}/madoc/madoc.c) endif() diff --git a/tools/cli/miniaudio_cli.c b/tools/cli/miniaudio_cli.c new file mode 100644 index 00000000..fe664b99 --- /dev/null +++ b/tools/cli/miniaudio_cli.c @@ -0,0 +1,1439 @@ +#include "../../external/fs/fs.c" +#include "../../miniaudio.c" + +#if defined(MA_CLI_ENABLE_LIBVORBIS) +#include "../../extras/decoders/libvorbis/miniaudio_libvorbis.c" +#endif +#if defined(MA_CLI_ENABLE_LIBOPUS) +#include "../../extras/decoders/libopus/miniaudio_libopus.c" +#endif + +static const char* pHelpOverview = + "USAGE: \n" + " miniaudio [version | -v | --version] [help | -h | --help] [] \n" + " [] : [] : ... \n" + " \n" + " The output of one command can be used as the input to another. Commands are\n" + " executed left to right, and are separated with ':'. \n" + " \n" + " miniaudio help commands List all available commands. \n" + " miniaudio help Print help for a specific command. \n" + " \n" + "EXAMPLES: \n" + " miniaudio play sound.mp3 \n" + " Play a sound from a file. \n" + " \n" + " miniaudio record output.wav \n" + " Record to a file. \n" + " \n" + " miniaudio record : play \n" + " Record and play back. \n" + " \n" + "OPTIONS: \n" + " --backend [backend1,backend2,...] \n" + " Sets the backend prioritization for playback and capture. Backends \n" + " should be comma separated and not have spaces. Available backends: \n" + " \n" + #if defined(MA_HAS_WASAPI) + " wasapi \n" + #endif + #if defined(MA_HAS_DSOUND) + " directsound \n" + #endif + #if defined(MA_HAS_WINMM) + " winmm \n" + #endif + #if defined(MA_HAS_COREAUDIO) + " coreaudio \n" + #endif + #if defined(MA_HAS_PIPEWIRE) + " pipewire \n" + #endif + #if defined(MA_HAS_PULSEAUDIO) + " pulseaudio \n" + #endif + #if defined(MA_HAS_JACK) + " jack \n" + #endif + #if defined(MA_HAS_ALSA) + " alsa \n" + #endif + #if defined(MA_HAS_SNDIO) + " sndio \n" + #endif + #if defined(MA_HAS_AUDIO4) + " audio4 \n" + #endif + #if defined(MA_HAS_OSS) + " oss \n" + #endif + #if defined(MA_HAS_AAUDIO) + " aaudio \n" + #endif + #if defined(MA_HAS_OPENSL) + " opensl \n" + #endif + #if defined(MA_HAS_WEBAUDIO) + " webaudio \n" + #endif + #if defined(MA_HAS_DREAMCAST) + " dreamcast \n" + #endif + #if defined(MA_HAS_XAUDIO) + " xaudio \n" + #endif + #if defined(MA_HAS_VITA) + " vita \n" + #endif + #if defined(MA_HAS_NULL) + " null \n" + #endif + #if defined(MA_HAS_SDL2) + " sdl2 \n" + #endif + " \n" + " Example: --backend pulseaudio,pipewire \n" + " \n" + "ALLOWABLE SAMPLE FORMATS: \n" + " u8 8-bit unsigned integer \n" + " s16 16-bit signed integer \n" + " s24 24-bit signed integer (tightly packed) \n" + " s32 32-bit signed integer \n" + " f32 32-bit floating point \n" +; + +#define MA_CLI_HELP_PLAY \ + "USAGE: \n"\ + " play \n"\ + " \n"\ + "DESCRIPTION: \n"\ + " Plays a sound. If no inputs are specified, the sound will be read from the \n"\ + " output of the previous command. \n"\ + " \n"\ + " Only a single play command is allowed, and there are no outputs which means\n"\ + " it would typically always be the last command. \n"\ + " \n"\ + "EXAMPLES: \n"\ + " miniaudio play sound.mp3 \n"\ + " Play a sound from a file. \n"\ + " \n"\ + " miniaudio decode sound.mp3 : play \n"\ + " Play a sound from a file. \n"\ + " \n"\ + " miniaudio waveform : play \n"\ + " Play a sine wave. \n"\ + " \n"\ + " miniaudio record : play \n"\ + " Capture from microphone and play back. \n"\ + " \n"\ + "OPTIONS: \n"\ + " -f, --format [default: system default] \n"\ + " The device sample format. \n"\ + " \n"\ + " -c, --channels [default: system default] \n"\ + " The device channel count. \n"\ + " \n"\ + " -r, --rate [default: system default] \n"\ + " The device sample rate. \n"\ + +#define MA_CLI_HELP_RECORD \ + "USAGE: \n"\ + " record \n"\ + " \n"\ + "DESCRIPTION: \n"\ + " Records from a microphone. \n"\ + " \n"\ + "EXAMPLES: \n"\ + " miniaudio record sound.wav \n"\ + " Record a sound an output to a file. \n"\ + " \n"\ + " miniaudio record : encode sound.wav \n"\ + " Record a sound an output to a file. \n"\ + " \n"\ + " miniaudio record : play \n"\ + " Capture from microphone and play back. \n"\ + " \n"\ + "OPTIONS: \n"\ + " -f, --format [default: system default] \n"\ + " The device sample format. \n"\ + " \n"\ + " -c, --channels [default: system default] \n"\ + " The device channel count. \n"\ + " \n"\ + " -r, --rate [default: system default] \n"\ + " The device sample rate. \n"\ + +#define MA_CLI_HELP_DECODE \ + "USAGE: \n"\ + " decode : \n"\ + " \n"\ + "DESCRIPTION: \n"\ + " Decodes a file. You would typically route the output to the input of \n"\ + " another command. \n"\ + " \n"\ + "EXAMPLES: \n"\ + " miniaudio decode sound.mp3 : play \n"\ + " Decode a file and play it. \n"\ + " \n"\ + " miniaudio decode sound.mp3 : encode sound.wav \n"\ + " Decode and re-encode a file. \n"\ + " \n"\ + "OPTIONS: \n"\ + " -f, --format [default: file default] \n"\ + " The output sample format. \n"\ + " \n"\ + " -c, --channels [default: file default] \n"\ + " The output channel count. \n"\ + " \n"\ + " -r, --rate [default: file default] \n"\ + " The output sample rate. \n"\ + +#define MA_CLI_HELP_ENCODE \ + "USAGE: \n"\ + " : encode \n"\ + " \n"\ + "DESCRIPTION: \n"\ + " Encodes a file. You would typically route the output of another command to \n"\ + " the input of this command. \n"\ + " \n"\ + "EXAMPLES: \n"\ + " miniaudio record : encode sound.wav \n"\ + " Capture audio from a microphone and then output to a file. \n"\ + " \n"\ + " miniaudio decode sound.mp3 : encode sound.wav \n"\ + " Decode and re-encode a file. \n"\ + " \n"\ + "OPTIONS: \n"\ + " -f, --format [default: file default] \n"\ + " The output sample format. \n"\ + " \n"\ + " -c, --channels [default: file default] \n"\ + " The output channel count. \n"\ + " \n"\ + " -r, --rate [default: file default] \n"\ + " The output sample rate. \n"\ + +#define MA_CLI_HELP_WAVEFORM \ + "USAGE: \n"\ + " waveform \n"\ + " \n"\ + "DESCRIPTION: \n"\ + " Generates a sine wave. \n"\ + " \n"\ + " This does not accept any inputs. \n"\ + " \n"\ + "OPTIONS: \n"\ + " -f, --format [default: f32] \n"\ + " The output sample format. \n"\ + " \n"\ + " -c, --channels [default: 1] \n"\ + " The output channel count. \n"\ + " \n"\ + " -r, --rate [default: 48000] \n"\ + " The output sample rate. \n"\ + " \n"\ + " --type [default: sine] \n"\ + " The waveform type. Allowable values: \n"\ + " sine \n"\ + " square \n"\ + " triangle \n"\ + " sawtooth \n"\ + " \n"\ + " --amplitude [default: 0.1] \n"\ + " Controls the volume of the output. A value of 1 will be quite harsh so \n"\ + " take care when setting this. \n"\ + " \n"\ + " --frequency [default: 220] \n"\ + " The frequency of the generated sine wave. Larger values will be higher \n"\ + " pitched. \n"\ + +#include + +#define MA_CLI_DEFAULT_PERIOD_SIZE_IN_FRAMES 512 + +/* These should be in priority order. */ +#if defined(MA_CLI_ENABLE_LIBVORBIS) + #define MA_CLI_DECODING_BACKEND_LIBVORBIS ma_decoding_backend_libvorbis, +#else + #define MA_CLI_DECODING_BACKEND_LIBVORBIS +#endif + +#if defined(MA_CLI_ENABLE_LIBOPUS) + #define MA_CLI_DECODING_BACKEND_LIBOPUS ma_decoding_backend_libopus, +#else + #define MA_CLI_DECODING_BACKEND_LIBOPUS +#endif + +#define MA_CLI_DECODING_BACKENDS \ + MA_CLI_DECODING_BACKEND_LIBVORBIS \ + MA_CLI_DECODING_BACKEND_LIBOPUS \ + ma_decoding_backend_wav, \ + ma_decoding_backend_flac, \ + ma_decoding_backend_mp3 /* <-- MP3 seems to be worst/slowest at detecting whether or not it's a valid file so we'll put this last. */ + + +static fs_file* MA_CLI_STDIN; +static fs_file* MA_CLI_STDOUT; +static fs_file* MA_CLI_STDERR; + +static ma_result ma_result_from_fs(fs_result result) +{ + return (ma_result)result; +} + + +/* BEG ma_cli_args */ +typedef struct +{ + const char* pToken; + size_t tokenLen; + struct + { + int iarg; + int argc; + char** argv; + } private; +} ma_cli_args; + +ma_cli_args ma_cli_args_first(int argc, char** argv); +ma_bool32 ma_cli_args_at_end(ma_cli_args* pArgs); +ma_bool32 ma_cli_args_next(ma_cli_args* pArgs); + +ma_cli_args ma_cli_args_first(int argc, char** argv) +{ + ma_cli_args args; + + MA_ZERO_OBJECT(&args); + args.private.iarg = 0; + args.private.argc = argc; + args.private.argv = argv; + ma_cli_args_next(&args); + + return args; +} + +ma_bool32 ma_cli_args_at_end(ma_cli_args* pArgs) +{ + if (pArgs == NULL) { + return MA_TRUE; + } + + return pArgs->private.iarg >= pArgs->private.argc; +} + +ma_bool32 ma_cli_args_next(ma_cli_args* pArgs) +{ + if (pArgs == NULL) { + return MA_FALSE; + } + + if (ma_cli_args_at_end(pArgs)) { + return MA_FALSE; + } + + /* Some setup for the first iteration. */ + if (pArgs->pToken == NULL && pArgs->private.iarg == 0 && pArgs->private.argc > 0) { + pArgs->pToken = pArgs->private.argv[0]; + pArgs->tokenLen = 0; + } + + if (pArgs->pToken == NULL) { + return MA_FALSE; + } + + pArgs->pToken += pArgs->tokenLen; + if (pArgs->pToken[0] == '\0') { + /* Reached the end of the token. Move to the next argument. */ + pArgs->private.iarg += 1; + if (ma_cli_args_at_end(pArgs)) { + return MA_FALSE; + } + + pArgs->pToken = pArgs->private.argv[pArgs->private.iarg]; + } + + /* + For now we are just treating each whole argument as a single token, but later on I want to make it + so each argument can be split up into parts so we can avoid excessive space separations. This can + come later once things has stabilized a bit. + */ + pArgs->tokenLen = strlen(pArgs->pToken); + + return MA_TRUE; +} + +ma_bool32 ma_cli_args_equal(ma_cli_args* pArgs, const char* pName) +{ + return fs_strncmp(pName, pArgs->pToken, pArgs->tokenLen) == 0; +} +/* END ma_cli_args */ + + +static void ma_cli_process_node_graph(ma_node_graph* pNodeGraph); + +#include "miniaudio_cli_node.c" + + +typedef struct +{ + ma_cli_node_vtable* pVTable; + ma_uint32 inputCount; + ma_uint32 outputCount; /* Set this to ~0 if the output count is variable, such as a splitter. */ + const char* pName; + const char* pSummary; + const char* pHelp; +} ma_cli_command; + +static ma_cli_command pCommands[] = +{ + { &ma_gNodeVTable_Play, 1, 0, "play", "Plays a sound.", MA_CLI_HELP_PLAY }, + { &ma_gNodeVTable_Record, 0, 1, "record", "Records from a microphone.", MA_CLI_HELP_RECORD }, + { &ma_gNodeVTable_Decode, 0, 1, "decode", "Decode a sound.", MA_CLI_HELP_DECODE }, + { &ma_gNodeVTable_Encode, 1, 0, "encode", "Encode a sound.", MA_CLI_HELP_ENCODE }, + { &ma_gNodeVTable_Waveform, 0, 1, "waveform", "Generates a waveform tone.", MA_CLI_HELP_WAVEFORM } +}; + + + +/* BEG ma_cli_node_list */ +typedef struct +{ + ma_cli_node* items[255]; + ma_uint32 count; +} ma_cli_node_list; + +MA_API ma_cli_node_list* ma_cli_node_list_push(ma_cli_node_list* pList, ma_cli_node* pNode) +{ + if (pList == NULL) { + pList = (ma_cli_node_list*)ma_calloc(sizeof(ma_cli_node_list), NULL); + if (pList == NULL) { + return NULL; + } + } + + if (pList->count >= ma_countof(pList->items)) { + return NULL; /* Ran out of space. */ /* TODO: Just dynamically expand this. */ + } + + pList->items[pList->count] = pNode; + pList->count += 1; + + return pList; +} + +MA_API ma_uint32 ma_cli_node_list_count(ma_cli_node_list* pList) +{ + if (pList == NULL) { + return 0; + } + + return pList->count; +} + +MA_API ma_cli_node* ma_cli_node_list_get(ma_cli_node_list* pList, ma_uint32 index) +{ + if (pList == NULL) { + return NULL; + } + + return pList->items[index]; +} +/* END ma_cli_node_list */ + + + +/* Global program state. */ +static ma_cli_node_list* g_pNodes = NULL; +static ma_cli_play* g_ppPlayNodes[16]; +static ma_uint32 g_playNodeCount = 0; +static ma_cli_record* g_ppRecordNodes[16]; +static ma_uint32 g_recordNodeCount = 0; +static ma_device_backend_config g_backends[32]; +static ma_uint32 g_backendCount = 0; + + +static ma_result ma_cli_track_play_node(ma_cli_play* pPlay) +{ + if (g_playNodeCount >= ma_countof(g_ppPlayNodes)) { + fs_file_writef(MA_CLI_STDERR, "Too many play nodes. You can have a maximum of %d.\n", (int)ma_countof(g_ppPlayNodes)); + return MA_INVALID_OPERATION; + } + + g_ppPlayNodes[g_playNodeCount] = pPlay; + g_playNodeCount += 1; + + return MA_SUCCESS; +} + +static ma_uint32 ma_cli_get_play_node_count(void) +{ + return g_playNodeCount; +} + +static ma_cli_play* ma_cli_get_play_node(ma_uint32 index) +{ + return g_ppPlayNodes[index]; +} + + +static ma_result ma_cli_track_record_node(ma_cli_record* pRecord) +{ + if (g_recordNodeCount >= ma_countof(g_ppRecordNodes)) { + fs_file_writef(MA_CLI_STDERR, "Too many record nodes. You can have a maximum of %d.\n", (int)ma_countof(g_ppRecordNodes)); + return MA_INVALID_OPERATION; + } + + g_ppRecordNodes[g_recordNodeCount] = pRecord; + g_recordNodeCount += 1; + + return MA_SUCCESS; +} + +static ma_uint32 ma_cli_get_record_node_count(void) +{ + return g_recordNodeCount; +} + +static ma_cli_record* ma_cli_get_record_node(ma_uint32 index) +{ + return g_ppRecordNodes[index]; +} + + +static ma_result ma_cli_track_node(ma_cli_node* pNode) +{ + g_pNodes = ma_cli_node_list_push(g_pNodes, pNode); + if (g_pNodes == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (pNode->pVTable == &ma_gNodeVTable_Play) { + return ma_cli_track_play_node((ma_cli_play*)pNode); + } + + if (pNode->pVTable == &ma_gNodeVTable_Record) { + return ma_cli_track_record_node((ma_cli_record*)pNode); + } + + return MA_SUCCESS; +} + +static ma_uint32 ma_cli_get_node_count(void) +{ + return ma_cli_node_list_count(g_pNodes); +} + +static ma_cli_node* ma_cli_get_node(ma_uint32 index) +{ + return ma_cli_node_list_get(g_pNodes, index); +} + + +static ma_cli_node* ma_cli_parse_command(ma_cli_args* pArgs, ma_cli_node* pPreviousNode); + + +static void ma_cli_version(void) +{ + /* The CLI version is just the version of miniaudio itself. */ + fs_file_writef(MA_CLI_STDOUT, "miniaudio v%d.%d.%d\n", MA_VERSION_MAJOR, MA_VERSION_MINOR, MA_VERSION_REVISION); +} + +static void ma_cli_help_overview(void) +{ + ma_cli_version(); + fs_file_writef(MA_CLI_STDOUT, "\n%s\n", pHelpOverview); + + /* Now print our decoding backends. */ + fs_file_writef(MA_CLI_STDOUT, "AVAILABLE DECODING BACKENDS:\n"); + { + size_t iDecodingBackend; + ma_decoding_backend_vtable* pDecodingBackends[] = + { + MA_CLI_DECODING_BACKENDS + }; + + for (iDecodingBackend = 0; iDecodingBackend < ma_countof(pDecodingBackends); iDecodingBackend += 1) { + if (pDecodingBackends[iDecodingBackend] != NULL && pDecodingBackends[iDecodingBackend]->onInfo != NULL) { + ma_decoding_backend_info backendInfo; + pDecodingBackends[iDecodingBackend]->onInfo(NULL, &backendInfo); + fs_file_writef(MA_CLI_STDOUT, " %s (via %s)\n", backendInfo.pName, backendInfo.pLibraryName); + } + } + } +} + +static void ma_cli_help_command(const char* pOpName) +{ + size_t iCommand; + + for (iCommand = 0; iCommand < ma_countof(pCommands); iCommand += 1) { + if (strcmp(pCommands[iCommand].pName, pOpName) == 0) { + fs_file_writef(MA_CLI_STDOUT, "%s\n", pCommands[iCommand].pHelp); + break; + } + } +} + +static void ma_cli_help_commands(void) +{ + size_t iCommand; + + for (iCommand = 0; iCommand < ma_countof(pCommands); iCommand += 1) { + fs_file_writef(MA_CLI_STDOUT, "%-20s%s\n", pCommands[iCommand].pName, pCommands[iCommand].pSummary); + } +} + +static void ma_cli_help(int argc, char** argv) +{ + if (argc > 2) { + int iarg; + + for (iarg = 2; iarg < argc; iarg += 1) { + if (strcmp(argv[iarg], "commands") == 0) { + ma_cli_help_commands(); + } else { + ma_cli_help_command(argv[iarg]); + } + } + } else { + ma_cli_help_overview(); + } +} + +typedef struct ma_cli_backend_name_map +{ + const char* pName; + ma_device_backend_vtable* pVTable; +} ma_cli_backend_name_map; + +static ma_device_backend_vtable* ma_cli_backend_vtable_from_string(const char* pName, size_t nameLen) +{ + size_t i; + ma_cli_backend_name_map backends[] = + { + #if defined(MA_HAS_WASAPI) + { "wasapi", ma_device_backend_wasapi }, + #endif + #if defined(MA_HAS_DSOUND) + { "directsound", ma_device_backend_dsound }, + #endif + #if defined(MA_HAS_WINMM) + { "winmm", ma_device_backend_winmm }, + #endif + #if defined(MA_HAS_COREAUDIO) + { "coreaudio", ma_device_backend_coreaudio }, + #endif + #if defined(MA_HAS_PIPEWIRE) + { "pipewire", ma_device_backend_pipewire }, + #endif + #if defined(MA_HAS_PULSEAUDIO) + { "pulseaudio", ma_device_backend_pulseaudio }, + #endif + #if defined(MA_HAS_JACK) + { "jack", ma_device_backend_jack }, + #endif + #if defined(MA_HAS_ALSA) + { "alsa", ma_device_backend_alsa }, + #endif + #if defined(MA_HAS_SNDIO) + { "sndio", ma_device_backend_sndio }, + #endif + #if defined(MA_HAS_AUDIO4) + { "audio4", ma_device_backend_audio4 }, + #endif + #if defined(MA_HAS_OSS) + { "oss", ma_device_backend_oss }, + #endif + #if defined(MA_HAS_AAUDIO) + { "aaudio", ma_device_backend_aaudio }, + #endif + #if defined(MA_HAS_OPENSL) + { "opensl", ma_device_backend_opensl }, + #endif + #if defined(MA_HAS_WEBAUDIO) + { "webaudio", ma_device_backend_webaudio }, + #endif + #if defined(MA_HAS_DREAMCAST) + { "dreamcast", ma_device_backend_dreamcast }, + #endif + #if defined(MA_HAS_XAUDIO) + { "xaudio", ma_device_backend_xaudio }, + #endif + #if defined(MA_HAS_VITA) + { "vita", ma_device_backend_vita }, + #endif + #if defined(MA_HAS_NULL) + { "null", ma_device_backend_null } + #endif + }; + + for (i = 0; i < sizeof(backends)/sizeof(backends[0]); i += 1) { + if (fs_strncmp(backends[i].pName, pName, nameLen) == 0 && backends[i].pName[nameLen] == '\0') { + return backends[i].pVTable; + } + } + + return NULL; +} + +static ma_bool32 ma_cli_parse_backends(const char* pList) +{ + const char* pStart; + const char* pEnd; + + g_backendCount = 0; + + pStart = pList; + for (;;) { + size_t nameLen; + ma_device_backend_vtable* pVTable; + + pEnd = pStart; + while (*pEnd != ',' && *pEnd != '\0') { + pEnd += 1; + } + + nameLen = (size_t)(pEnd - pStart); + if (nameLen > 0) { + if (g_backendCount >= ma_countof(g_backends)) { + fs_file_writef(MA_CLI_STDERR, "Too many backends specified. Maximum is %d.\n", (int)ma_countof(g_backends)); + return MA_FALSE; + } + + pVTable = ma_cli_backend_vtable_from_string(pStart, nameLen); + if (pVTable == NULL) { + fs_file_writef(MA_CLI_STDERR, "Unknown or unsupported backend \"%.*s\".\n", (int)nameLen, pStart); + return MA_FALSE; + } + + g_backends[g_backendCount] = ma_device_backend_config_init(pVTable, NULL); + g_backendCount += 1; + } + + if (*pEnd == '\0') { + break; + } + + pStart = pEnd + 1; + } + + return MA_TRUE; +} + +static ma_format ma_cli_format_from_string(const char* pFormat, size_t formatLen) +{ + if (fs_strncmp("f32", pFormat, formatLen) == 0) { + return ma_format_f32; + } + if (fs_strncmp("s16", pFormat, formatLen) == 0) { + return ma_format_s16; + } + if (fs_strncmp("s32", pFormat, formatLen) == 0) { + return ma_format_s32; + } + if (fs_strncmp("s24", pFormat, formatLen) == 0) { + return ma_format_s24; + } + if (fs_strncmp("u8", pFormat, formatLen) == 0) { + return ma_format_u8; + } + + return ma_format_unknown; +} + + + +static ma_cli_node* ma_cli_create_node(void* pConfig) +{ + ma_result result; + ma_cli_node* pNode; + + pNode = ma_cli_node_create(pConfig); + if (pNode == NULL) { + return NULL; + } + + result = ma_cli_track_node(pNode); + if (result != MA_SUCCESS) { + ma_cli_node_delete(pNode); + return NULL; + } + + return pNode; +} + +static void ma_cli_delete_node(ma_cli_node* pNode) +{ + ma_cli_node_delete(pNode); +} + + +static ma_bool32 ma_cli_try_parse_format(ma_cli_args* pArgs, ma_format* pFormat) +{ + if (ma_cli_args_equal(pArgs, "-f") || ma_cli_args_equal(pArgs, "--format")) { + if (ma_cli_args_next(pArgs)) { + *pFormat = ma_cli_format_from_string(pArgs->pToken, pArgs->tokenLen); + + if (*pFormat == ma_format_unknown) { + fs_file_writef(MA_CLI_STDERR, "Unknown format \"%.*s\".\n", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting format after \"%.*s\".\n", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + } else { + /* Not a format switch. This is not an error - it's just ignored. */ + } + + return MA_TRUE; +} + +static ma_bool32 ma_cli_try_parse_channels(ma_cli_args* pArgs, ma_uint32* pChannels) +{ + if (ma_cli_args_equal(pArgs, "-c") || ma_cli_args_equal(pArgs, "--channels")) { + if (ma_cli_args_next(pArgs)) { + char temp[64]; + ma_strncpy_s(temp, sizeof(temp), pArgs->pToken, pArgs->tokenLen); + + *pChannels = atoi(temp); + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting channel count after \"%.*s\".\n", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + } else { + /* Not a format switch. This is not an error - it's just ignored. */ + } + + return MA_TRUE; +} + +static ma_bool32 ma_cli_try_parse_rate(ma_cli_args* pArgs, ma_uint32* pSampleRate) +{ + if (ma_cli_args_equal(pArgs, "-r") || ma_cli_args_equal(pArgs, "--rate")) { + if (ma_cli_args_next(pArgs)) { + char temp[64]; + ma_strncpy_s(temp, sizeof(temp), pArgs->pToken, pArgs->tokenLen); + + *pSampleRate = atoi(temp); + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting sample rate after \"%.*s\".\n", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + } else { + /* Not a format switch. This is not an error - it's just ignored. */ + } + + return MA_TRUE; +} + + +static ma_cli_node* ma_cli_parse_command(ma_cli_args* pArgs, ma_cli_node* pPreviousNode) +{ + ma_cli_command* pCommand = NULL; + size_t iCommand; + ma_cli_node_config nodeConfig; + ma_cli_node* pNode = NULL; + const char* pCommandName; + size_t commandNameLen; + ma_cli_args firstArgs = *pArgs; + ma_cli_node_list* pInputNodes = NULL; + ma_cli_node_list* pOutputNodes = NULL; + ma_bool32 areArgNodesInputs = MA_TRUE; + ma_uint32 iNode; + const char* pEncoderFilePath = NULL; + size_t encoderFilePathLen = 0; + + pCommandName = pArgs->pToken; + commandNameLen = pArgs->tokenLen; + + for (iCommand = 0; iCommand < ma_countof(pCommands); iCommand += 1) { + if (ma_cli_args_equal(pArgs, pCommands[iCommand].pName)) { + pCommand = &pCommands[iCommand]; + } + } + + if (pCommand == NULL) { + fs_file_writef(MA_CLI_STDOUT, "Unknown command: \"%.*s\".\n", (int)pArgs->tokenLen, pArgs->pToken); + return NULL; + } + + if (pPreviousNode != NULL) { + pInputNodes = ma_cli_node_list_push(pInputNodes, pPreviousNode); + } + + if (pCommand->inputCount == 0 || pCommand->outputCount > 1) { + areArgNodesInputs = MA_FALSE; + } + + + nodeConfig = ma_cli_node_config_init_default(pCommand->pVTable); + nodeConfig.pBackends = g_backends; + nodeConfig.backendCount = g_backendCount; + + /* The current argument will be sitting on the command name. Skip past it. */ + ma_cli_args_next(pArgs); + + /* Parse arguments. */ + for (;;) { + ma_bool32 hasArgBeenParsed = MA_FALSE; + + if (ma_cli_args_at_end(pArgs) || ma_cli_args_equal(pArgs, ":") || ma_cli_args_equal(pArgs, "]")) { + break; + } + + if (!ma_cli_try_parse_format (pArgs, &nodeConfig.format )) { return NULL; } + if (!ma_cli_try_parse_channels(pArgs, &nodeConfig.channels )) { return NULL; } + if (!ma_cli_try_parse_rate (pArgs, &nodeConfig.sampleRate)) { return NULL; } + + if (pArgs->tokenLen >= 2 && pArgs->pToken[0] == '-') { + if (pCommand->pVTable->onArg != NULL) { + if (!pCommand->pVTable->onArg(pArgs, &nodeConfig)) { + fs_file_writef(MA_CLI_STDERR, "Unknown argument '%.*s' for command '%.*s'.\n", (int)pArgs->tokenLen, pArgs->pToken, (int)commandNameLen, pCommandName); + return NULL; + } + } + } else { + /* It's not a conventional `--arg` or `-arg` style argument. */ + if (ma_cli_args_equal(pArgs, "[")) { + /* + It's either an input node or an output node. When it's an input node we want to parse + it here so that this node can possibly infer it's format/channels/rate. If it's an + output node, it needs to be parsed in a second pass so that they can infer this nodes + format/channels/rate, which is only fully known after the node has been initialized. + */ + if (areArgNodesInputs) { + if (ma_cli_args_next(pArgs)) { + ma_node* pInputNode = ma_cli_parse_command(pArgs, NULL); + if (pInputNode == NULL) { + return NULL; /* ma_cli_parse_command() will have posted any error messages. */ + } + + pInputNodes = ma_cli_node_list_push(pInputNodes, pInputNode); + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting command after \"[\".\n"); + return NULL; + } + } else { + /* It's an output node. This needs to be parsed in a second pass. Just skip over this one for now. */ + int depth = 0; + + for (;;) { + if (ma_cli_args_at_end(pArgs) || ma_cli_args_equal(pArgs, ":") || ma_cli_args_equal(pArgs, "]")) { + break; + } + + /* */ if (ma_cli_args_equal(pArgs, "[")) { + depth += 1; + } else if (ma_cli_args_equal(pArgs, "]")) { + depth -= 1; + } else { + /* It's an argument inside []. Skip it for now. */ + } + + if (depth == 0) { + break; + } + + ma_cli_args_next(pArgs); + } + } + } else { + /* + Could be a file path. If the node if input-only we wire up a decode node. If it's an + output-only node, we wire up a decode node. + */ + + /* Only encode and decode nodes have special handling of file paths. */ + if (pCommand->pVTable == &ma_gNodeVTable_Decode || pCommand->pVTable == &ma_gNodeVTable_Encode) { + nodeConfig.pFilePath = pArgs->pToken; + nodeConfig.filePathLen = pArgs->tokenLen; + } else { + if (pCommand->outputCount == 0) { + /* Need to wire up a decoder as an input node. */ + ma_cli_node* pDecodeNode = NULL; + ma_cli_node_config decodeConfig; + + decodeConfig = ma_cli_node_config_init_default(&ma_gNodeVTable_Decode); + decodeConfig.pFilePath = pArgs->pToken; + decodeConfig.filePathLen = pArgs->tokenLen; + + pDecodeNode = ma_cli_create_node(&decodeConfig); + if (pDecodeNode == NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to create decoder for '%.*s'.\n", (int)decodeConfig.filePathLen, decodeConfig.pFilePath); + return NULL; + } + + pInputNodes = ma_cli_node_list_push(pInputNodes, pDecodeNode); + if (pInputNodes == NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to push child node.\n"); + return NULL; + } + } else if (pCommand->inputCount == 0) { + /* + Need to wire up an encoder as an output node, but it needs to be delay till after + this node has been initialized. + */ + pEncoderFilePath = pArgs->pToken; + encoderFilePathLen = pArgs->tokenLen; + } + } + } + } + + ma_cli_args_next(pArgs); + } + + /* If the node takes inputs, infer the format/channels/rate from the first input, if there is one. */ + if (pInputNodes != NULL && pInputNodes->count > 0) { + /* TODO: Format. This requires support from the node graph itself. */ + + if (nodeConfig.channels == 0) { + nodeConfig.channels = ma_cli_node_get_output_channels(pInputNodes->items[0], 0); + } + + if (nodeConfig.sampleRate == 0) { + nodeConfig.sampleRate = ma_cli_node_get_output_sample_rate(pInputNodes->items[0]); + } + } + + /* We should now have enough information to create the node. Inputs and outputs will be attached next. */ + pNode = ma_cli_create_node(&nodeConfig); + if (pNode == NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to create '%.*s' node.\n", (int)commandNameLen, pCommandName); + return NULL; + } + + /* We now need to parse parameters for output nodes (those in square brackets). */ + if (!areArgNodesInputs) { + *pArgs = firstArgs; + + for (;;) { + if (ma_cli_args_at_end(pArgs) || ma_cli_args_equal(pArgs, ":") || ma_cli_args_equal(pArgs, "]")) { + break; + } + + if (ma_cli_args_equal(pArgs, "[")) { + if (ma_cli_args_next(pArgs)) { + ma_node* pOutputNode = ma_cli_parse_command(pArgs, pNode); + + /* TODO: Do something with the output node? */ + (void)pOutputNode; + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting command after \"[\".\n"); + return NULL; + } + } + + ma_cli_args_next(pArgs); + } + } + + /* Wire up all the inputs. */ + if (pInputNodes != NULL) { + for (iNode = 0; iNode < pInputNodes->count; iNode += 1) { + ma_cli_node* pInputNode = pInputNodes->items[iNode]; + ma_uint32 outputChannels; + ma_uint32 outputSampleRate; + ma_uint32 inputChannels; + ma_uint32 inputSampleRate; + + outputChannels = ma_cli_node_get_output_channels(pInputNode, 0); + outputSampleRate = ma_cli_node_get_output_sample_rate(pInputNode); + inputChannels = ma_cli_node_get_input_channels(pNode, 0); + inputSampleRate = ma_cli_node_get_output_sample_rate(pNode); /* *Output* sample rate is correct here. */ + + if (outputChannels != inputChannels || outputSampleRate != inputSampleRate) { + /* Converter necessary. */ + ma_cli_converter_config converterConfig; + ma_cli_node* pConverterNode; + + converterConfig = ma_cli_converter_config_init(inputChannels, outputChannels, inputSampleRate, outputSampleRate); + + pConverterNode = ma_cli_create_node(&converterConfig); + if (pConverterNode == NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to create converter.\n"); + return NULL; + } + + /* Now the converter needs to be wired up and swapped out in the child list. */ + ma_cli_node_attach_output(pInputNode, 0, pConverterNode, 0); + + /* Swap the child for the converter in the child list. This ensures the converter node is attached as the input to the main node. */ + pInputNodes->items[iNode] = pConverterNode; + pInputNode = pInputNodes->items[iNode]; + } else { + /* Converter not necessary. */ + } + + ma_cli_node_attach_output(pInputNode, 0, pNode, 0); + } + } + + /* + If we need an encoder, wire that up now. The encoder node will become the return value, and it + will not allow outputs beyond it. + */ + if (pEncoderFilePath != NULL) { + ma_cli_node_config encodeConfig; + ma_node* pEncodeNode; + + encodeConfig = ma_cli_node_config_init_default(&ma_gNodeVTable_Encode); + encodeConfig.pFilePath = pEncoderFilePath; + encodeConfig.filePathLen = encoderFilePathLen; + + pEncodeNode = ma_cli_create_node(&encodeConfig); + if (pEncodeNode != NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to create encoder for '%.*s'.\n", (int)encoderFilePathLen, pEncoderFilePath); + return NULL; /* Failed to create encode node. */ + } + + ma_cli_node_attach_output(pNode, 0, pEncodeNode, 0); + + pNode = pEncodeNode; + } + + return pNode; +} + + +#if defined(_WIN32) +/* TODO: Win32 SIGINT handler. */ +static volatile LONG g_shouldQuit = 0; + +static BOOL WINAPI ma_cli_handle_sigint(DWORD type) +{ + switch (type) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + { + InterlockedExchange(&g_shouldQuit, 1); + return TRUE; + } + + default: return FALSE; + } +} + +static ma_bool32 ma_cli_wants_to_quit(void) +{ + return InterlockedCompareExchange(&g_shouldQuit, 0, 0) != 0; +} +#else +static volatile sig_atomic_t g_shouldQuit = 0; + +static void ma_cli_handle_sigint(int signum) +{ + (void)signum; + g_shouldQuit = 1; +} + +static ma_bool32 ma_cli_wants_to_quit(void) +{ + return g_shouldQuit != 0; +} +#endif + + +static void ma_cli_process_node_graph(ma_node_graph* pNodeGraph) +{ + float temp[4096]; /* TODO: Make this a heap allocation for robustness. */ + ma_uint32 frameCount = ma_node_graph_get_processing_size_in_frames(pNodeGraph); + ma_uint64 framesRead; + ma_result result; + + MA_ASSERT(frameCount <= ma_countof(temp)/ma_node_graph_get_channels(pNodeGraph)); + + result = ma_node_graph_read_pcm_frames(pNodeGraph, temp, frameCount, &framesRead); + if (result != MA_SUCCESS || framesRead == 0) { + g_shouldQuit = 1; + } +} + + +int main(int argc, char** argv) +{ + ma_result result; + int iarg; + int iFirstNodeArg; + ma_cli_args args; + ma_cli_node* pLastNode = NULL; + ma_uint32 iNode; + ma_uint32 iPlayNode; + ma_uint32 iRecordNode; + ma_cli_node* pEndpointNode = NULL; + ma_node_graph_config nodeGraphConfig; + ma_node_graph nodeGraph; + + /* + We want to handle SIGINT so we can do cleanup and close encoders. This is relevant for when + one of the input sources is a microphone which has no end point. + */ + #if defined(_WIN32) + { + if (!SetConsoleCtrlHandler(ma_cli_handle_sigint, TRUE)) { + printf("SetConsoleCtrlHandler() failed."); + return 1; + } + } + #else + { + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_handler = ma_cli_handle_sigint; + sa.sa_flags = 0; + + if (sigaction(SIGINT, &sa, NULL) != 0) { + printf("sigaction() failed."); + return 1; + } + } + #endif + + fs_file_open(NULL, FS_STDIN, FS_READ, &MA_CLI_STDIN); + fs_file_open(NULL, FS_STDOUT, FS_WRITE, &MA_CLI_STDOUT); + fs_file_open(NULL, FS_STDOUT, FS_WRITE, &MA_CLI_STDERR); + + if (argc < 2) { + ma_cli_help_overview(); + return 1; + } + + if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + ma_cli_help(argc, argv); + return 0; + } + + if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) { + ma_cli_version(); + return 0; + } + + /* Parse global options before building the command graph. */ + iFirstNodeArg = 1; + + for (iarg = 1; iarg < argc; iarg += 1) { + if (strcmp(argv[iarg], "--backend") == 0) { + iarg += 1; + if (iarg >= argc) { + fs_file_writef(MA_CLI_STDERR, "Expecting backend list after \"--backend\".\n"); + return 1; + } + + if (!ma_cli_parse_backends(argv[iarg])) { + return 1; + } + + iFirstNodeArg = iarg + 1; /* +1 because the first argument is "--backend" and the second is the backend list. */ + } + } + + /* Build the graph from the arguments. */ + for (args = ma_cli_args_first(argc - iFirstNodeArg, argv + iFirstNodeArg); !ma_cli_args_at_end(&args); ma_cli_args_next(&args)) { + ma_cli_node* pNode = NULL; + + pNode = ma_cli_parse_command(&args, pLastNode); + if (pNode == NULL) { + return 1; /* Parsing failed. A relevant message will have already been printed so no need to print a message here. */ + } + + /* The returned node needs to become the input of the next node. */ + pLastNode = pNode; + + /*printf("arg: %.*s\n", (int)args.tokenLen, args.pToken);*/ + } + + /* + In order for our nodes to actually get processed we need to make sure everything is connected to the + endpoint. To do this we can just create a single endpoint node, with one input for each unique channel + count. We just look at nodes that do not have their outputs connected to anything. + */ + { + ma_cli_endpoint_config endpointConfig; + ma_uint32 inputChannels[MA_MAX_NODE_INPUT_COUNT]; + ma_uint32 iInputChannels; + ma_uint32 iOutput; + + MA_ZERO_MEMORY(inputChannels, sizeof(inputChannels)); + + endpointConfig = ma_cli_endpoint_config_init(); + endpointConfig.inputCount = 0; /* Will be updated below. */ + endpointConfig.pInputChannelCounts = inputChannels; + + /* Determine the unique channel counts and input count. */ + for (iNode = 0; iNode < ma_cli_get_node_count(); iNode += 1) { + ma_cli_node* pNode = ma_cli_get_node(iNode); + + for (iOutput = 0; iOutput < ma_cli_node_get_output_count(pNode); iOutput += 1) { + if (pNode->pOutputs[iOutput].pInputNode == NULL) { + ma_uint32 channels = ma_cli_node_get_output_channels(pNode, iOutput); + + for (iInputChannels = 0; iInputChannels < endpointConfig.inputCount; iInputChannels += 1) { + if (inputChannels[iInputChannels] == channels) { + break; + } + } + + if (iInputChannels == endpointConfig.inputCount) { + inputChannels[iInputChannels] = channels; + endpointConfig.inputCount += 1; + } + } + } + } + + pEndpointNode = ma_cli_create_node(&endpointConfig); + if (pEndpointNode == NULL) { + fs_file_writef(MA_CLI_STDERR, "Failed to create endpoint node.\n"); + return 1; + } + + /* Wire up the inputs. */ + for (iNode = 0; iNode < ma_cli_get_node_count(); iNode += 1) { + ma_cli_node* pNode = ma_cli_get_node(iNode); + if (pNode != pEndpointNode) { + for (iOutput = 0; iOutput < ma_cli_node_get_output_count(pNode); iOutput += 1) { + if (pNode->pOutputs[iOutput].pInputNode == NULL) { + ma_uint32 channels = ma_cli_node_get_output_channels(pNode, iOutput); + + for (iInputChannels = 0; iInputChannels < endpointConfig.inputCount; iInputChannels += 1) { + if (inputChannels[iInputChannels] == channels) { + break; + } + } + + if (iInputChannels > ma_cli_node_get_input_count(pEndpointNode)) { + fs_file_writef(MA_CLI_STDERR, "Could not attach node to endpoint.\n"); + return 1; + } + + ma_cli_node_attach_output(pNode, iOutput, pEndpointNode, iInputChannels); + } + } + } + } + } + + /* Now we need to build the miniaudio node graph. First we need a node graph. */ + nodeGraphConfig = ma_node_graph_config_init(ma_cli_node_get_output_channels(pEndpointNode, 0)); + if (ma_cli_get_play_node_count() > 0) { + nodeGraphConfig.processingSizeInFrames = ma_device_get_period_size_in_frames(ma_cli_play_get_device(ma_cli_get_play_node(0))); + } else { + nodeGraphConfig.processingSizeInFrames = MA_CLI_DEFAULT_PERIOD_SIZE_IN_FRAMES; + } + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); + if (result != MA_SUCCESS) { + fs_file_writef(MA_CLI_STDERR, "Failed to initialize node graph.\n"); + return 1; + } + + /* Now that we have the graph we can initialize the base nodes. */ + for (iNode = 0; iNode < ma_cli_get_node_count(); iNode += 1) { + result = ma_cli_node_init_base_node(ma_cli_get_node(iNode), &nodeGraph); + if (result != MA_SUCCESS) { + fs_file_writef(MA_CLI_STDERR, "Failed to initialize base node.\n"); + return 1; + } + } + + /* Finally we can wire up the inputs and outputs. After this the node graph has been set up and we can start processing. */ + for (iNode = 0; iNode < ma_cli_get_node_count(); iNode += 1) { + ma_uint32 iOutput; + ma_cli_node* pNode; + + pNode = ma_cli_get_node(iNode); + MA_ASSERT(pNode != NULL); + + for (iOutput = 0; iOutput < ma_cli_node_get_output_count(pNode); iOutput += 1) { + if (pNode->pOutputs[iOutput].pInputNode != NULL) { /* <-- Can be null for the endpoint. Not an error. */ + ma_node_attach_output_bus(&pNode->baseNode, iOutput, &pNode->pOutputs[iOutput].pInputNode->baseNode, pNode->pOutputs[iOutput].inputNodeInputIndex); + } + } + } + + /* Our endpoint needs to be attached to the miniaudio endpoint. */ + ma_node_attach_output_bus(&pEndpointNode->baseNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); + + + /* Now we need to process. Any playback and capture devices need to be started. */ + for (iRecordNode = 0; iRecordNode < ma_cli_get_record_node_count(); iRecordNode += 1) { + ma_cli_record* pRecord = ma_cli_get_record_node(iRecordNode); + MA_ASSERT(pRecord != NULL); + + result = ma_device_start(ma_cli_record_get_device(pRecord)); + if (result != MA_SUCCESS) { + fs_file_writef(MA_CLI_STDERR, "Failed to start device.\n"); + return 1; + } + } + + for (iPlayNode = 0; iPlayNode < ma_cli_get_play_node_count(); iPlayNode += 1) { + ma_cli_play* pPlay = ma_cli_get_play_node(iPlayNode); + MA_ASSERT(pPlay != NULL); + + result = ma_device_start(ma_cli_play_get_device(pPlay)); + if (result != MA_SUCCESS) { + fs_file_writef(MA_CLI_STDERR, "Failed to start device.\n"); + return 1; + } + } + + /* If we have a play or record node, the first one needs to be responsible for doing the node graph processing. */ + if (ma_cli_get_record_node_count() > 0) { + ma_cli_record_set_node_graph(ma_cli_get_record_node(0), &nodeGraph); + } else if (ma_cli_get_play_node_count() > 0) { + ma_cli_play_set_node_graph(ma_cli_get_play_node(0), &nodeGraph); + } + + /* We need to process in a different way depending on whether or not we are outputting to a device. */ + while (!ma_cli_wants_to_quit()) { + /* If we have any devices we need to step them. */ + for (iRecordNode = 0; iRecordNode < ma_cli_get_record_node_count(); iRecordNode += 1) { + ma_cli_record* pRecord = ma_cli_get_record_node(iRecordNode); + MA_ASSERT(pRecord != NULL); + + #if 1 + while (ma_device_step(ma_cli_record_get_device(pRecord), MA_BLOCKING_MODE_BLOCKING) == MA_SUCCESS) { + /* If the device has been processed, get out. Otherwise keep waiting. */ + if (pRecord->pNodeGraph != NULL || pRecord->cursor > 0) { + break; + } + } + #else + ma_device_step(ma_cli_record_get_device(pRecord), MA_BLOCKING_MODE_BLOCKING); + #endif + } + + /* Process the node graph, but only if there are no play nodes. When a play node is present, node processing will be done by the first play node. */ + if (ma_cli_get_play_node_count() == 0 && ma_cli_get_record_node_count() == 0) { + ma_cli_process_node_graph(&nodeGraph); + } + + /* Playback devices. */ + for (iPlayNode = 0; iPlayNode < ma_cli_get_play_node_count(); iPlayNode += 1) { + ma_cli_play* pPlay = ma_cli_get_play_node(iPlayNode); + MA_ASSERT(pPlay != NULL); + + #if 1 + while (ma_device_step(ma_cli_play_get_device(pPlay), MA_BLOCKING_MODE_BLOCKING) == MA_SUCCESS) { + /* If the device has been processed, get out. Otherwise keep waiting. */ + if (pPlay->pNodeGraph != NULL || pPlay->cursor > 0) { + break; + } + } + #else + ma_device_step(ma_cli_play_get_device(pPlay), MA_BLOCKING_MODE_BLOCKING); + #endif + } + } + + /* Delete every node in the graph. Needed to ensure encoders are uninitialized in particular. */ + for (iNode = 0; iNode < ma_cli_get_node_count(); iNode += 1) { + ma_cli_delete_node(ma_cli_get_node(iNode)); + } + + return 0; +} diff --git a/tools/cli/miniaudio_cli_node.c b/tools/cli/miniaudio_cli_node.c new file mode 100644 index 00000000..ead8f4f5 --- /dev/null +++ b/tools/cli/miniaudio_cli_node.c @@ -0,0 +1,2360 @@ +/* +NOTE: + +This file is temporary and will eventually be integrated into miniaudio_cli.c pending some changes to the +node graph system in miniaudio. +*/ + +typedef struct ma_cli_play_config ma_cli_play_config; +struct ma_cli_play_config +{ + ma_uint32 periodSizeInFrames; +}; + +typedef struct ma_cli_record_config ma_cli_record_config; +struct ma_cli_record_config +{ + ma_uint32 periodSizeInFrames; +}; + +typedef struct ma_cli_decode_config ma_cli_decode_config; +struct ma_cli_decode_config +{ + int _unused; +}; + +typedef struct ma_cli_encode_config ma_cli_encode_config; +struct ma_cli_encode_config +{ + int _unused; +}; + +typedef struct ma_cli_waveform_config ma_cli_waveform_config; +struct ma_cli_waveform_config +{ + ma_waveform_type type; + double amplitude; + double frequency; + const ma_allocation_callbacks* pAllocationCallbacks; +}; + + + +/* BEG miniaudio_cli_node.h */ +typedef struct ma_cli_node_vtable ma_cli_node_vtable; +typedef struct ma_cli_node_info ma_cli_node_info; +typedef struct ma_cli_node_config ma_cli_node_config; +typedef struct ma_cli_node ma_cli_node; + + +struct ma_cli_node_info +{ + const char* pName; +}; + +struct ma_cli_node_vtable +{ + void (* onInfo )(ma_cli_node_info* pInfo); + void (* onDefaultConfig)(ma_cli_node_config* pConfig); + ma_bool32 (* onArg )(ma_cli_args* pArgs, ma_cli_node_config* pConfig); + size_t (* onSizeof )(const void* pConfig); + ma_result (* onInit )(const void* pConfig, ma_cli_node* pNode); + void (* onUninit )(ma_cli_node* pNode); + void (* onProcess )(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); +}; + +typedef struct ma_cli_node_output ma_cli_node_output; +struct ma_cli_node_output +{ + /* Immutable. */ + ma_cli_node* pNode; /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + ma_uint8 outputIndex; /* The index of the output bus on pNode that this output bus represents. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + ma_uint8 inputNodeInputIndex; /* The index of the input bus on the input. Required for detaching. Will only be used within the spinlock so does not need to be atomic. */ + MA_ATOMIC(4, ma_uint32) refCount; /* Reference count for some thread-safety when detaching. */ + MA_ATOMIC(4, ma_bool32) isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_cli_node_output*) pNext; /* If null, it's the tail node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_cli_node_output*) pPrev; /* If null, it's the head node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_cli_node*) pInputNode; /* The node that this output bus is attached to. Required for detaching. */ +}; + +typedef struct ma_cli_node_input ma_cli_node_input; +struct ma_cli_node_input +{ + ma_cli_node_output head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + MA_ATOMIC(4, ma_uint32) nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ +}; + +struct ma_cli_node_config +{ + ma_cli_node_vtable* pVTable; + ma_uint32 inputCount; /* Maximum of 255. */ + ma_uint32 outputCount; /* Maximum of 255. */ + const ma_uint32* pInputChannelCounts; + const ma_uint32* pOutputChannelCounts; + ma_uint32 flags; /* A combination of MA_NODE_FLAG_*. Can be 0. */ + const ma_allocation_callbacks* pAllocationCallbacks; + + /* These should be considered output format/channels/rate. */ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; /* Can be 0 if unknown. This is for informational purposes in case something higher level wants to query it. */ + + /* Node-specific config properties. */ + ma_cli_waveform_config waveform; + ma_cli_play_config play; + ma_cli_record_config record; + ma_cli_decode_config decode; + + /* File paths for decode and encode nodes. */ + const char* pFilePath; + size_t filePathLen; + + /* Backends for play and record nodes. */ + ma_device_backend_config* pBackends; + ma_uint32 backendCount; +}; + +MA_API ma_cli_node_config ma_cli_node_config_init(ma_cli_node_vtable* pVTable, ma_uint32 inputCount, ma_uint32 outputCount, const ma_uint32* pInputChannelCounts, const ma_uint32* pOutputChannelCounts); +MA_API ma_cli_node_config ma_cli_node_config_init_default(ma_cli_node_vtable* pVTable); + +struct ma_cli_node +{ + ma_node_base baseNode; + const ma_cli_node_vtable* pVTable; + ma_allocation_callbacks allocationCallbacks; + ma_cli_node_input* pInputs; + ma_cli_node_output* pOutputs; + ma_uint8 inputCount; + ma_uint8 outputCount; + ma_uint16 flags; + ma_uint32 outputSampleRate; /* Can be 0 if unknown. This is for informational purposes in case something higher level wants to query it. */ +}; + +MA_API ma_result ma_cli_node_init(const ma_cli_node_config* pNodeConfig, ma_cli_node* pNode); +MA_API void ma_cli_node_uninit(ma_cli_node* pNode); +MA_API ma_uint32 ma_cli_node_get_input_count(const ma_cli_node* pNode); +MA_API ma_uint32 ma_cli_node_get_output_count(const ma_cli_node* pNode); +MA_API ma_uint32 ma_cli_node_get_input_channels(const ma_cli_node* pNode, ma_uint32 inputIndex); +MA_API ma_uint32 ma_cli_node_get_output_channels(const ma_cli_node* pNode, ma_uint32 outputIndex); +MA_API ma_uint32 ma_cli_node_get_output_sample_rate(const ma_cli_node* pNode); +MA_API ma_result ma_cli_node_attach_output(ma_cli_node* pNode, ma_uint32 outputIndex, ma_cli_node* pOtherNode, ma_uint32 otherNodeInputIndex); +MA_API ma_result ma_cli_node_detach_output(ma_cli_node* pNode, ma_uint32 outputIndex); +MA_API ma_result ma_cli_node_detach_all_outputs(ma_cli_node* pNode); +MA_API ma_result ma_cli_node_init_base_node(ma_cli_node* pNode, ma_node_graph* pNodeGraph); +/* END miniaudio_cli_node.h */ + + +/* BEG miniaudio_cli_node.c */ +#define MA_MAX_NODE_INPUT_COUNT 255 +#define MA_MAX_NODE_OUTPUT_COUNT 255 + +MA_API ma_cli_node_config ma_cli_node_config_init_default(ma_cli_node_vtable* pVTable) +{ + ma_cli_node_config config; + + MA_ZERO_OBJECT(&config); + config.pVTable = pVTable; + + if (pVTable->onDefaultConfig != NULL) { + pVTable->onDefaultConfig(&config); + } + + return config; +} + +MA_API ma_cli_node_config ma_cli_node_config_init(ma_cli_node_vtable* pVTable, ma_uint32 inputCount, ma_uint32 outputCount, const ma_uint32* pInputChannelCounts, const ma_uint32* pOutputChannelCounts) +{ + ma_cli_node_config config = ma_cli_node_config_init_default(pVTable); + + config.inputCount = inputCount; + config.outputCount = outputCount; + config.pInputChannelCounts = pInputChannelCounts; + config.pOutputChannelCounts = pOutputChannelCounts; + + return config; +} + + + + +static ma_result ma_cli_node_output_init(ma_cli_node* pNode, ma_uint32 outputIndex, ma_uint32 channels, ma_cli_node_output* pOutput) +{ + MA_ASSERT(pOutput != NULL); + MA_ASSERT(outputIndex < MA_MAX_NODE_BUS_COUNT); + MA_ASSERT(outputIndex < ma_cli_node_get_output_count(pNode)); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pOutput); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pOutput->pNode = pNode; + pOutput->outputIndex = (ma_uint8)outputIndex; + pOutput->channels = (ma_uint8)channels; + + return MA_SUCCESS; +} + +static void ma_cli_node_output_lock(ma_cli_node_output* pOutput) +{ + ma_spinlock_lock(&pOutput->lock); +} + +static void ma_cli_node_output_unlock(ma_cli_node_output* pOutput) +{ + ma_spinlock_unlock(&pOutput->lock); +} + +static ma_uint32 ma_cli_node_output_get_channels(const ma_cli_node_output* pOutput) +{ + return pOutput->channels; +} + +static void ma_cli_node_output_set_is_attached(ma_cli_node_output* pOutput, ma_bool32 isAttached) +{ + ma_atomic_exchange_32(&pOutput->isAttached, isAttached); +} + +static ma_bool32 ma_cli_node_output_is_attached(ma_cli_node_output* pOutput) +{ + return ma_atomic_load_32(&pOutput->isAttached); +} + + +static ma_result ma_cli_node_input_init(ma_uint32 channels, ma_cli_node_input* pInput) +{ + MA_ASSERT(pInput != NULL); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pInput); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pInput->channels = (ma_uint8)channels; + + return MA_SUCCESS; +} + +static void ma_cli_node_input_lock(ma_cli_node_input* pInput) +{ + MA_ASSERT(pInput != NULL); + + ma_spinlock_lock(&pInput->lock); +} + +static void ma_cli_node_input_unlock(ma_cli_node_input* pInput) +{ + MA_ASSERT(pInput != NULL); + + ma_spinlock_unlock(&pInput->lock); +} + + +static void ma_cli_node_input_next_begin(ma_cli_node_input* pInput) +{ + ma_atomic_fetch_add_32(&pInput->nextCounter, 1); +} + +static void ma_cli_node_input_next_end(ma_cli_node_input* pInput) +{ + ma_atomic_fetch_sub_32(&pInput->nextCounter, 1); +} + +static ma_uint32 ma_cli_node_input_get_next_counter(ma_cli_node_input* pInput) +{ + return ma_atomic_load_32(&pInput->nextCounter); +} + + +static ma_uint32 ma_cli_node_input_get_channels(const ma_cli_node_input* pInput) +{ + return pInput->channels; +} + + +static void ma_cli_node_input_detach__no_output_lock(ma_cli_node_input* pInput, ma_cli_node_output* pOutput) +{ + MA_ASSERT(pInput != NULL); + MA_ASSERT(pOutput != NULL); + + /* + Mark the output bus as detached first. This will prevent future iterations on the audio thread + from iterating this output bus. + */ + ma_cli_node_output_set_is_attached(pOutput, MA_FALSE); + + /* + We cannot use the output bus lock here since it'll be getting used at a higher level, but we do + still need to use the input bus lock since we'll be updating pointers on two different output + buses. The same rules apply here as the attaching case. Although we're using a lock here, we're + *not* using a lock when iterating over the list in the audio thread. We therefore need to craft + this in a way such that the iteration on the audio thread doesn't break. + + The first thing to do is swap out the "next" pointer of the previous output bus with the + new "next" output bus. This is the operation that matters for iteration on the audio thread. + After that, the previous pointer on the new "next" pointer needs to be updated, after which + point the linked list will be in a good state. + */ + ma_cli_node_input_lock(pInput); + { + ma_cli_node_output* pOldPrev = (ma_cli_node_output*)ma_atomic_load_ptr(&pOutput->pPrev); + ma_cli_node_output* pOldNext = (ma_cli_node_output*)ma_atomic_load_ptr(&pOutput->pNext); + + if (pOldPrev != NULL) { + ma_atomic_exchange_ptr(&pOldPrev->pNext, pOldNext); /* <-- This is where the output bus is detached from the list. */ + } + if (pOldNext != NULL) { + ma_atomic_exchange_ptr(&pOldNext->pPrev, pOldPrev); /* <-- This is required for detachment. */ + } + } + ma_cli_node_input_unlock(pInput); + + /* At this point the output bus is detached and the linked list is completely unaware of it. Reset some data for safety. */ + ma_atomic_exchange_ptr(&pOutput->pNext, NULL); /* Using atomic exchanges here, mainly for the benefit of analysis tools which don't always recognize spinlocks. */ + ma_atomic_exchange_ptr(&pOutput->pPrev, NULL); /* As above. */ + pOutput->pInputNode = NULL; + pOutput->inputNodeInputIndex = 0; + + + /* + For thread-safety reasons, we don't want to be returning from this straight away. We need to + wait for the audio thread to finish with the output bus. There's two things we need to wait + for. The first is the part that selects the next output bus in the list, and the other is the + part that reads from the output bus. Basically all we're doing is waiting for the input bus + to stop referencing the output bus. + + We're doing this part last because we want the section above to run while the audio thread + is finishing up with the output bus, just for efficiency reasons. We marked the output bus as + detached right at the top of this function which is going to prevent the audio thread from + iterating the output bus again. + */ + + /* Part 1: Wait for the current iteration to complete. */ + while (ma_cli_node_input_get_next_counter(pInput) > 0) { + ma_yield(); + } + + /* Part 2: Wait for any reads to complete. */ + while (ma_atomic_load_32(&pOutput->refCount) > 0) { + ma_yield(); + } + + /* + At this point we're done detaching and we can be guaranteed that the audio thread is not going + to attempt to reference this output bus again (until attached again). + */ +} + +#if 0 /* Not used at the moment, but leaving here in case I need it later. */ +static void ma_cli_node_input_detach(ma_cli_node_input* pInput, ma_cli_node_output* pOutput) +{ + MA_ASSERT(pInput != NULL); + MA_ASSERT(pOutput != NULL); + + ma_cli_node_output_lock(pOutput); + { + ma_cli_node_input_detach__no_output_lock(pInput, pOutput); + } + ma_cli_node_output_unlock(pOutput); +} +#endif + +static void ma_cli_node_input_attach(ma_cli_node_input* pInput, ma_cli_node_output* pOutput, ma_node* pNewInputNode, ma_uint32 inputNodeInputIndex) +{ + MA_ASSERT(pInput != NULL); + MA_ASSERT(pOutput != NULL); + + ma_cli_node_output_lock(pOutput); + { + ma_cli_node_output* pOldInputNode = (ma_cli_node_output*)ma_atomic_load_ptr(&pOutput->pInputNode); + + /* Detach from any existing attachment first if necessary. */ + if (pOldInputNode != NULL) { + ma_cli_node_input_detach__no_output_lock(pInput, pOutput); + } + + /* + At this point we can be sure the output bus is not attached to anything. The linked list in the + old input bus has been updated so that pOutput will not get iterated again. + */ + pOutput->pInputNode = pNewInputNode; /* No need for an atomic assignment here because modification of this variable always happens within a lock. */ + pOutput->inputNodeInputIndex = (ma_uint8)inputNodeInputIndex; + + /* + Now we need to attach the output bus to the linked list. This involves updating two pointers on + two different output buses so I'm going to go ahead and keep this simple and just use a lock. + There are ways to do this without a lock, but it's just too hard to maintain for its value. + + Although we're locking here, it's important to remember that we're *not* locking when iterating + and reading audio data since that'll be running on the audio thread. As a result we need to be + careful how we craft this so that we don't break iteration. What we're going to do is always + attach the new item so that it becomes the first item in the list. That way, as we're iterating + we won't break any links in the list and iteration will continue safely. The detaching case will + also be crafted in a way as to not break list iteration. It's important to remember to use + atomic exchanges here since no locking is happening on the audio thread during iteration. + */ + ma_cli_node_input_lock(pInput); + { + ma_cli_node_output* pNewPrev = &pInput->head; + ma_cli_node_output* pNewNext = (ma_cli_node_output*)ma_atomic_load_ptr(&pInput->head.pNext); + + /* Update the local output bus. */ + ma_atomic_exchange_ptr(&pOutput->pPrev, pNewPrev); + ma_atomic_exchange_ptr(&pOutput->pNext, pNewNext); + + /* Update the other output buses to point back to the local output bus. */ + ma_atomic_exchange_ptr(&pInput->head.pNext, pOutput); /* <-- This is where the output bus is actually attached to the input bus. */ + + /* Do the previous pointer last. This is only used for detachment. */ + if (pNewNext != NULL) { + ma_atomic_exchange_ptr(&pNewNext->pPrev, pOutput); + } + } + ma_cli_node_input_unlock(pInput); + + /* + Mark the node as attached last. This is used to controlling whether or the output bus will be + iterated on the audio thread. Mainly required for detachment purposes. + */ + ma_cli_node_output_set_is_attached(pOutput, MA_TRUE); + } + ma_cli_node_output_unlock(pOutput); +} + +static ma_cli_node_output* ma_cli_node_input_next(ma_cli_node_input* pInput, ma_cli_node_output* pOutput) +{ + ma_cli_node_output* pNext; + + MA_ASSERT(pInput != NULL); + + if (pOutput == NULL) { + return NULL; + } + + ma_cli_node_input_next_begin(pInput); + { + pNext = pOutput; + for (;;) { + pNext = (ma_cli_node_output*)ma_atomic_load_ptr(&pNext->pNext); + if (pNext == NULL) { + break; /* Reached the end. */ + } + + if (ma_cli_node_output_is_attached(pNext) == MA_FALSE) { + continue; /* The node is not attached. Keep checking. */ + } + + /* The next node has been selected. */ + break; + } + + /* We need to increment the reference count of the selected node. */ + if (pNext != NULL) { + ma_atomic_fetch_add_32(&pNext->refCount, 1); + } + + /* The previous node is no longer being referenced. */ + ma_atomic_fetch_sub_32(&pOutput->refCount, 1); + } + ma_cli_node_input_next_end(pInput); + + return pNext; +} + +static ma_cli_node_output* ma_cli_node_input_first(ma_cli_node_input* pInput) +{ + return ma_cli_node_input_next(pInput, &pInput->head); +} + + +static void ma_cli_node_vtable_info(const ma_cli_node_vtable* pVTable, ma_cli_node_info* pInfo) +{ + MA_ASSERT(pVTable != NULL); + + if (pVTable->onInfo == NULL) { + return; + } + + pVTable->onInfo(pInfo); +} + +static size_t ma_cli_node_vtable_sizeof(const ma_cli_node_vtable* pVTable, const void* pConfig) +{ + MA_ASSERT(pVTable != NULL); + + if (pVTable->onSizeof == NULL) { + return 0; + } + + return pVTable->onSizeof(pConfig); +} + +static ma_result ma_cli_node_vtable_init(const ma_cli_node_vtable* pVTable, const void* pConfig, ma_cli_node* pNode) +{ + MA_ASSERT(pVTable != NULL); + + if (pVTable->onInit == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pVTable->onInit(pConfig, pNode); +} + +static void ma_cli_node_vtable_uninit(const ma_cli_node_vtable* pVTable, ma_cli_node* pNode) +{ + MA_ASSERT(pVTable != NULL); + + if (pVTable->onUninit == NULL) { + return; + } + + pVTable->onUninit(pNode); +} + +void ma_cli_node_vtable_process(const ma_cli_node_vtable* pVTable, ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + MA_ASSERT(pVTable != NULL); + + if (pVTable->onProcess == NULL) { + return; + } + + pVTable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); +} + + +static void ma_cli_node_get_info(ma_cli_node* pNode, ma_cli_node_info* pInfo) +{ + if (pInfo == NULL) { + return; + } + + MA_ZERO_OBJECT(pInfo); + + if (pNode == NULL) { + return; + } + + ma_cli_node_vtable_info(pNode->pVTable, pInfo); +} + + +MA_API ma_result ma_cli_node_init(const ma_cli_node_config* pConfig, ma_cli_node* pNode) +{ + ma_result result; + ma_uint32 iInput; + ma_uint32 iOutput; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->inputCount > MA_MAX_NODE_INPUT_COUNT || pConfig->outputCount > MA_MAX_NODE_OUTPUT_COUNT) { + return MA_INVALID_ARGS; /* Too many inputs or outputs. */ + } + + pNode->pVTable = pConfig->pVTable; + pNode->inputCount = (ma_uint8 )pConfig->inputCount; + pNode->outputCount = (ma_uint8 )pConfig->outputCount; + pNode->flags = (ma_uint16)pConfig->flags; + pNode->pInputs = (ma_cli_node_input* )ma_malloc(sizeof(*pNode->pInputs ) * pNode->inputCount, pConfig->pAllocationCallbacks); + pNode->pOutputs = (ma_cli_node_output*)ma_malloc(sizeof(*pNode->pOutputs) * pNode->outputCount, pConfig->pAllocationCallbacks); + pNode->allocationCallbacks = ma_allocation_callbacks_init_copy(pConfig->pAllocationCallbacks); + pNode->outputSampleRate = pConfig->sampleRate; + + /* We need to run an initialization step for each input and output bus. */ + for (iInput = 0; iInput < ma_cli_node_get_input_count(pNode); iInput += 1) { + result = ma_cli_node_input_init(pConfig->pInputChannelCounts[iInput], &pNode->pInputs[iInput]); + if (result != MA_SUCCESS) { + return result; + } + } + + for (iOutput = 0; iOutput < ma_cli_node_get_output_count(pNode); iOutput += 1) { + result = ma_cli_node_output_init(pNode, iOutput, pConfig->pOutputChannelCounts[iOutput], &pNode->pOutputs[iOutput]); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_node_uninit(ma_cli_node* pNode) +{ + if (pNode == NULL) { + return; + } + + ma_free(pNode->pInputs, &pNode->allocationCallbacks); + ma_free(pNode->pOutputs, &pNode->allocationCallbacks); +} + +MA_API ma_cli_node* ma_cli_node_create(const void* pConfig) +{ + ma_cli_node_config* pNodeConfig = (ma_cli_node_config*)pConfig; /* The first member of the config should be a ma_cli_node_config object. */ + ma_cli_node* pNode; + ma_result result; + + if (pNodeConfig == NULL || pNodeConfig->pVTable == NULL) { + return NULL; + } + + pNode = (ma_cli_node*)ma_malloc(ma_cli_node_vtable_sizeof(pNodeConfig->pVTable, pConfig), pNodeConfig->pAllocationCallbacks); + if (pNode == NULL) { + return NULL; + } + + result = ma_cli_node_vtable_init(pNodeConfig->pVTable, pConfig, pNode); + if (result != MA_SUCCESS) { + ma_free(pNode, pNodeConfig->pAllocationCallbacks); + return NULL; + } + + return pNode; +} + +MA_API void ma_cli_node_delete(ma_cli_node* pNode) +{ + ma_allocation_callbacks allocationCallbacks; + + if (pNode == NULL) { + return; + } + + allocationCallbacks = pNode->allocationCallbacks; + + ma_cli_node_vtable_uninit(pNode->pVTable, pNode); + ma_free(pNode, &allocationCallbacks); +} + + +MA_API ma_uint32 ma_cli_node_get_input_count(const ma_cli_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return pNode->inputCount; +} + +MA_API ma_uint32 ma_cli_node_get_output_count(const ma_cli_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return pNode->outputCount; +} + +MA_API ma_uint32 ma_cli_node_get_input_channels(const ma_cli_node* pNode, ma_uint32 inputIndex) +{ + if (pNode == NULL) { + return 0; + } + + if (inputIndex >= ma_cli_node_get_input_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_cli_node_input_get_channels(&pNode->pInputs[inputIndex]); +} + +MA_API ma_uint32 ma_cli_node_get_output_channels(const ma_cli_node* pNode, ma_uint32 outputIndex) +{ + if (pNode == NULL) { + return 0; + } + + if (outputIndex >= ma_cli_node_get_output_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_cli_node_output_get_channels(&pNode->pOutputs[outputIndex]); +} + +MA_API ma_uint32 ma_cli_node_get_output_sample_rate(const ma_cli_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return pNode->outputSampleRate; +} + +MA_API ma_result ma_cli_node_attach_output(ma_cli_node* pNode, ma_uint32 outputIndex, ma_cli_node* pOtherNode, ma_uint32 otherNodeInputIndex) +{ + if (pNode == NULL || pOtherNode == NULL) { + return MA_INVALID_ARGS; + } + + if (pNode == pOtherNode) { + return MA_INVALID_OPERATION; /* Cannot attach a node to itself. */ + } + + if (outputIndex >= ma_cli_node_get_output_count(pNode) || otherNodeInputIndex >= ma_cli_node_get_input_count(pOtherNode)) { + return MA_INVALID_OPERATION; /* Invalid bus index. */ + } + + /* The output channel count of the output node must be the same as the input channel count of the input node. */ + if (ma_cli_node_get_output_channels(pNode, outputIndex) != ma_cli_node_get_input_channels(pOtherNode, otherNodeInputIndex)) { + return MA_INVALID_OPERATION; /* Channel count is incompatible. */ + } + + /* This will deal with detaching if the output bus is already attached to something. */ + ma_cli_node_input_attach(&pOtherNode->pInputs[otherNodeInputIndex], &pNode->pOutputs[outputIndex], pOtherNode, otherNodeInputIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_cli_node_detach_output(ma_cli_node* pNode, ma_uint32 outputIndex) +{ + ma_result result = MA_SUCCESS; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + if (outputIndex >= ma_cli_node_get_output_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* We need to lock the output bus because we need to inspect the input node and grab its input bus. */ + ma_cli_node_output_lock(&pNode->pOutputs[outputIndex]); + { + ma_cli_node* pInputNode = pNode->pOutputs[outputIndex].pInputNode; + if (pInputNode != NULL) { + ma_cli_node_input_detach__no_output_lock(&pInputNode->pInputs[pNode->pOutputs[outputIndex].inputNodeInputIndex], &pNode->pOutputs[outputIndex]); + } + } + ma_cli_node_output_unlock(&pNode->pOutputs[outputIndex]); + + return result; +} + +MA_API ma_result ma_cli_node_detach_all_outputs(ma_cli_node* pNode) +{ + ma_uint32 iOutputBus; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + for (iOutputBus = 0; iOutputBus < ma_cli_node_get_output_count(pNode); iOutputBus += 1) { + ma_cli_node_detach_output(pNode, iOutputBus); + } + + return MA_SUCCESS; +} + + + +static void ma_cli_node__on_node_process(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_node* pCLINode = (ma_cli_node*)pNode; + pCLINode->pVTable->onProcess(pCLINode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); +} + +static ma_node_vtable ma_gNodeVTable_CLINode = +{ + ma_cli_node__on_node_process, + NULL, /* onGetRequiredInputFrameCount */ + MA_NODE_BUS_COUNT_UNKNOWN, + MA_NODE_BUS_COUNT_UNKNOWN, + 0 +}; + +MA_API ma_result ma_cli_node_init_base_node(ma_cli_node* pNode, ma_node_graph* pNodeGraph) +{ + ma_result result; + ma_node_config baseNodeConfig; + ma_uint32 inputCount; + ma_uint32 outputCount; + ma_uint32 inputChannelCounts[MA_MAX_NODE_INPUT_COUNT]; + ma_uint32 outputChannelCounts[MA_MAX_NODE_OUTPUT_COUNT]; + ma_uint32 iInput; + ma_uint32 iOutput; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + inputCount = ma_cli_node_get_input_count(pNode); + outputCount = ma_cli_node_get_output_count(pNode); + + for (iInput = 0; iInput < inputCount; iInput += 1) { + inputChannelCounts[iInput] = ma_cli_node_get_input_channels(pNode, iInput); + } + + for (iOutput = 0; iOutput < outputCount; iOutput += 1) { + outputChannelCounts[iOutput] = ma_cli_node_get_output_channels(pNode, iOutput); + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.pVTable = &ma_gNodeVTable_CLINode; + baseNodeConfig.inputBusCount = inputCount; + baseNodeConfig.outputBusCount = outputCount; + baseNodeConfig.pInputChannels = inputChannelCounts; + baseNodeConfig.pOutputChannels = outputChannelCounts; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, &pNode->allocationCallbacks, &pNode->baseNode); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} +/* END miniaudio_cli_node.c */ + + + +/* BEG miniaudio_cli_waveform.h */ +//MA_API ma_cli_waveform_config ma_cli_waveform_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_waveform_type type, double amplitude, double frequency); + + +typedef struct ma_cli_waveform ma_cli_waveform; +struct ma_cli_waveform +{ + ma_cli_node node; + ma_waveform waveform; +}; + +MA_API ma_result ma_cli_waveform_init(const ma_cli_node_config* pConfig, ma_cli_waveform* pWaveform); +MA_API void ma_cli_waveform_uninit(ma_cli_waveform* pWaveform); +MA_API ma_cli_node* ma_cli_waveform_node(ma_cli_waveform* pWaveform); +MA_API ma_result ma_cli_waveform_read_pcm_frames(ma_cli_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +/* END miniaudio_cli_waveform.h */ + +/* BEG miniaudio_cli_waveform.c */ +static void ma_cli_waveform__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "waveform"; +} + +static void ma_cli_waveform__node_on_default_config(ma_cli_node_config* pConfig) +{ + pConfig->format = ma_format_f32; + pConfig->channels = 1; + pConfig->sampleRate = 48000; + pConfig->waveform.type = ma_waveform_type_sine; + pConfig->waveform.amplitude = 0.1; + pConfig->waveform.frequency = 220; +} + +static ma_bool32 ma_cli_waveform__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + if (ma_cli_args_equal(pArgs, "--type")) { + if (ma_cli_args_next(pArgs)) { + /* */ if (ma_cli_args_equal(pArgs, "sine")) { + pConfig->waveform.type = ma_waveform_type_sine; + } else if (ma_cli_args_equal(pArgs, "square")) { + pConfig->waveform.type = ma_waveform_type_square; + } else if (ma_cli_args_equal(pArgs, "triangle")) { + pConfig->waveform.type = ma_waveform_type_triangle; + } else if (ma_cli_args_equal(pArgs, "sawtooth")) { + pConfig->waveform.type = ma_waveform_type_sawtooth; + } else { + fs_file_writef(MA_CLI_STDERR, "Unknown waveform type \"%.*s\"", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + + return MA_TRUE; + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting waveform type after `--type`."); + return MA_FALSE; + } + } + + if (ma_cli_args_equal(pArgs, "--amplitude")) { + if (ma_cli_args_next(pArgs)) { + char amplitudeStr[32]; + + if (fs_strncpy_s(amplitudeStr, sizeof(amplitudeStr), pArgs->pToken, pArgs->tokenLen) != 0) { + fs_file_writef(MA_CLI_STDERR, "Invalid waveform amplitude \"%.*s\"", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + + pConfig->waveform.amplitude = atof(amplitudeStr); + return MA_TRUE; + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting number after `--amplitude`."); + return MA_FALSE; + } + } + + if (ma_cli_args_equal(pArgs, "--frequency")) { + if (ma_cli_args_next(pArgs)) { + char frequencyStr[32]; + + if (fs_strncpy_s(frequencyStr, sizeof(frequencyStr), pArgs->pToken, pArgs->tokenLen) != 0) { + fs_file_writef(MA_CLI_STDERR, "Invalid waveform frequency \"%.*s\"", (int)pArgs->tokenLen, pArgs->pToken); + return MA_FALSE; + } + + pConfig->waveform.frequency = (ma_uint32)atoi(frequencyStr); + return MA_TRUE; + } else { + fs_file_writef(MA_CLI_STDERR, "Expecting number after `--frequency`."); + return MA_FALSE; + } + } + + return MA_FALSE; +} + +static size_t ma_cli_waveform__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_waveform); +} + +static ma_result ma_cli_waveform__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_waveform_init((ma_cli_node_config*)pConfig, (ma_cli_waveform*)pNode); +} + +static void ma_cli_waveform__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_waveform_uninit((ma_cli_waveform*)pNode); +} + +static void ma_cli_waveform__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_waveform* pWaveform = (ma_cli_waveform*)pNode; + ma_uint64 frameCount; + ma_uint64 framesRead; + + /* A waveform takes no input. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + frameCount = *pFrameCountOut; + + if (pWaveform->waveform.config.format == ma_format_f32) { + ma_cli_waveform_read_pcm_frames(pWaveform, ppFramesOut[0], frameCount, &framesRead); + } else { + /* Need to convert. */ + ma_uint32 temp[1024]; + ma_uint32 tempCap = sizeof(temp) / ma_get_bytes_per_frame(pWaveform->waveform.config.format, pWaveform->waveform.config.channels); + ma_uint64 totalFramesProcessed; + float* pRunningFramesOut; + + pRunningFramesOut = ppFramesOut[0]; + + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint64 framesRemaining = frameCount - totalFramesProcessed; + ma_uint64 framesToReadThisIteration = framesRemaining; + if (framesToReadThisIteration > tempCap) { + framesToReadThisIteration = tempCap; + } + + ma_cli_waveform_read_pcm_frames(pWaveform, temp, framesToReadThisIteration, NULL); + ma_pcm_convert(pRunningFramesOut, ma_format_f32, temp, pWaveform->waveform.config.format, framesToReadThisIteration * pWaveform->waveform.config.channels, ma_dither_mode_none); + + totalFramesProcessed += framesToReadThisIteration; + pRunningFramesOut += framesToReadThisIteration * pWaveform->waveform.config.channels; + } + + framesRead = totalFramesProcessed; + } + + *pFrameCountOut = (ma_uint32)framesRead; +} + +static ma_cli_node_vtable ma_gNodeVTable_Waveform = +{ + ma_cli_waveform__node_on_info, + ma_cli_waveform__node_on_default_config, + ma_cli_waveform__node_on_arg, + ma_cli_waveform__node_on_sizeof, + ma_cli_waveform__node_on_init, + ma_cli_waveform__node_on_uninit, + ma_cli_waveform__node_on_process +}; + + +#if 0 +static ma_cli_node_config ma_cli_node_config_init_waveform(const ma_uint32* pInputChannelCounts) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Waveform, 0, 1, NULL, pInputChannelCounts); +} + +MA_API ma_cli_waveform_config ma_cli_waveform_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_waveform_type type, double amplitude, double frequency) +{ + ma_cli_waveform_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_waveform(NULL); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + config.type = type; + config.amplitude = amplitude; + config.frequency = frequency; + + return config; +} +#endif + + +MA_API ma_result ma_cli_waveform_init(const ma_cli_node_config* pConfig, ma_cli_waveform* pWaveform) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_waveform_config waveformConfig; + + if (pWaveform == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pWaveform); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + nodeConfig = *pConfig; + nodeConfig.outputCount = 1; + nodeConfig.pOutputChannelCounts = &pConfig->channels; + + result = ma_cli_node_init(&nodeConfig, &pWaveform->node); + if (result != MA_SUCCESS) { + return result; + } + + waveformConfig = ma_waveform_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->waveform.type, pConfig->waveform.amplitude, pConfig->waveform.frequency); + + result = ma_waveform_init(&waveformConfig, &pWaveform->waveform); + if (result != MA_SUCCESS) { + ma_cli_node_uninit(&pWaveform->node); + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_waveform_uninit(ma_cli_waveform* pWaveform) +{ + ma_cli_node_uninit(&pWaveform->node); + ma_waveform_uninit(&pWaveform->waveform); +} + +MA_API ma_cli_node* ma_cli_waveform_node(ma_cli_waveform* pWaveform) +{ + return (ma_cli_node*)pWaveform; +} + +MA_API ma_result ma_cli_waveform_read_pcm_frames(ma_cli_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + if (pWaveform == NULL) { + return MA_INVALID_ARGS; + } + + return ma_waveform_read_pcm_frames(&pWaveform->waveform, pFramesOut, frameCount, pFramesRead); +} +/* END miniaudio_cli_waveform.c */ + + +/* BEG miniaudio_cli_decode.h */ +typedef struct ma_cli_decode ma_cli_decode; +struct ma_cli_decode +{ + ma_cli_node node; + ma_decoder decoder; +}; + +MA_API ma_result ma_cli_decode_init(const ma_cli_node_config* pConfig, ma_cli_decode* pDecode); +MA_API void ma_cli_decode_uninit(ma_cli_decode* pDecode); +MA_API ma_cli_node* ma_cli_decode_node(ma_cli_decode* pDecode); +MA_API ma_result ma_cli_decode_read_pcm_frames(ma_cli_decode* pDecode, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +/* END miniaudio_cli_decode.h */ + +/* BEG miniaudio_cli_decode.c */ +static void ma_cli_decode__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "decode"; +} + +static void ma_cli_decode__node_on_default_config(ma_cli_node_config* pConfig) +{ + (void)pConfig; +} + +static ma_bool32 ma_cli_decode__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_decode__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_decode); +} + +static ma_result ma_cli_decode__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_decode_init((ma_cli_node_config*)pConfig, (ma_cli_decode*)pNode); +} + +static void ma_cli_decode__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_decode_uninit((ma_cli_decode*)pNode); +} + +static void ma_cli_decode__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_decode* pDecode = (ma_cli_decode*)pNode; + ma_result result; + ma_uint64 frameCount; + ma_uint64 framesRead; + + /* A decoder takes no input. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + frameCount = *pFrameCountOut; + + if (pDecode->decoder.outputFormat == ma_format_f32) { + ma_cli_decode_read_pcm_frames(pDecode, ppFramesOut[0], frameCount, &framesRead); + } else { + /* Need to convert. */ + ma_uint32 temp[1024]; + ma_uint32 tempCap = sizeof(temp) / ma_get_bytes_per_frame(pDecode->decoder.outputFormat, pDecode->decoder.outputChannels); + ma_uint64 totalFramesProcessed; + float* pRunningFramesOut; + + pRunningFramesOut = ppFramesOut[0]; + + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint64 framesJustRead; + ma_uint64 framesRemaining = frameCount - totalFramesProcessed; + ma_uint64 framesToReadThisIteration = framesRemaining; + if (framesToReadThisIteration > tempCap) { + framesToReadThisIteration = tempCap; + } + + result = ma_cli_decode_read_pcm_frames(pDecode, temp, framesToReadThisIteration, &framesJustRead); + if (result != MA_SUCCESS) { + break; + } + + ma_pcm_convert(pRunningFramesOut, ma_format_f32, temp, pDecode->decoder.outputFormat, framesJustRead * pDecode->decoder.outputChannels, ma_dither_mode_none); + + totalFramesProcessed += framesJustRead; + pRunningFramesOut += framesJustRead * pDecode->decoder.outputChannels; + } + + framesRead = totalFramesProcessed; + } + + *pFrameCountOut = (ma_uint32)framesRead; +} + +static ma_cli_node_vtable ma_gNodeVTable_Decode = +{ + ma_cli_decode__node_on_info, + ma_cli_decode__node_on_default_config, + ma_cli_decode__node_on_arg, + ma_cli_decode__node_on_sizeof, + ma_cli_decode__node_on_init, + ma_cli_decode__node_on_uninit, + ma_cli_decode__node_on_process +}; + +#if 0 +static ma_cli_node_config ma_cli_node_config_init_decode(void) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Decode, 0, 0, NULL, NULL); +} + +MA_API ma_cli_decode_config ma_cli_decode_config_init(void) +{ + ma_cli_decode_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_decode(); + + return config; +} +#endif + + +static ma_result ma_cli_decode__on_read(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) +{ + fs_file* pFile = (fs_file*)pDecoder->pUserData; + fs_result result; + + result = fs_file_read(pFile, pBufferOut, bytesToRead, pBytesRead); + if (result != FS_SUCCESS) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static ma_result ma_cli_decode__on_seek(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) +{ + fs_file* pFile = (fs_file*)pDecoder->pUserData; + fs_result result; + + result = fs_file_seek(pFile, byteOffset, (fs_seek_origin)origin); + if (result != FS_SUCCESS) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static ma_result ma_cli_decode__on_tell(ma_decoder* pDecoder, ma_int64* pCursor) +{ + fs_file* pFile = (fs_file*)pDecoder->pUserData; + fs_result result; + + result = fs_file_tell(pFile, pCursor); + if (result != FS_SUCCESS) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + + +MA_API ma_result ma_cli_decode_init(const ma_cli_node_config* pConfig, ma_cli_decode* pDecode) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_decoder_config decoderConfig; + ma_decoding_backend_vtable* pDecodingBackends[] = + { + MA_CLI_DECODING_BACKENDS + }; + + if (pDecode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDecode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + decoderConfig = ma_decoder_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate); + decoderConfig.ppBackendVTables = pDecodingBackends; + decoderConfig.backendCount = ma_countof(pDecodingBackends); + + if (pConfig->pFilePath != NULL) { + size_t filePathLen; + char* pFilePath; + + filePathLen = pConfig->filePathLen; + if (filePathLen == (size_t)-1) { + filePathLen = strlen(pConfig->pFilePath); + } + + pFilePath = (char*)ma_malloc(filePathLen + 1, pConfig->pAllocationCallbacks); + if (pFilePath == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_strcpy_s(pFilePath, filePathLen + 1, pConfig->pFilePath); + result = ma_decoder_init_file(pFilePath, &decoderConfig, &pDecode->decoder); + ma_free(pFilePath, pConfig->pAllocationCallbacks); + } else { + return MA_INVALID_ARGS; + } + + if (result != MA_SUCCESS) { + fs_file_writef(MA_CLI_STDERR, "ma_decoder_init() failed. result = %s\n", ma_result_description(result)); + ma_cli_node_uninit(&pDecode->node); + return result; + } + + + nodeConfig = *pConfig; + nodeConfig.inputCount = 0; + nodeConfig.outputCount = 1; + nodeConfig.pOutputChannelCounts = &pDecode->decoder.outputChannels; + nodeConfig.sampleRate = pDecode->decoder.outputSampleRate; + nodeConfig.pAllocationCallbacks = pConfig->pAllocationCallbacks; + + result = ma_cli_node_init(&nodeConfig, &pDecode->node); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_decode_uninit(ma_cli_decode* pDecode) +{ + ma_cli_node_uninit(&pDecode->node); + ma_decoder_uninit(&pDecode->decoder); +} + +MA_API ma_cli_node* ma_cli_decode_node(ma_cli_decode* pDecode) +{ + return (ma_cli_node*)pDecode; +} + +MA_API ma_result ma_cli_decode_read_pcm_frames(ma_cli_decode* pDecode, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + if (pDecode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_decoder_read_pcm_frames(&pDecode->decoder, pFramesOut, frameCount, pFramesRead); +} +/* END miniaudio_cli_decode.c */ + + +/* BEG miniaudio_cli_play.h */ +typedef struct ma_cli_play ma_cli_play; +struct ma_cli_play +{ + ma_cli_node node; + ma_device device; + void* pBuffer; + ma_uint32 bufferSizeInFrames; + ma_uint32 cursor; + ma_node_graph* pNodeGraph; /* When non-null, this node will be the one that needs to process the node graph. */ +}; + +MA_API ma_result ma_cli_play_init(const ma_cli_node_config* pConfig, ma_cli_play* pPlay); +MA_API void ma_cli_play_uninit(ma_cli_play* pPlay); +MA_API ma_cli_node* ma_cli_play_node(ma_cli_play* pPlay); +MA_API ma_result ma_cli_play_read_pcm_frames(ma_cli_play* pPlay, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead); +MA_API ma_device* ma_cli_play_get_device(ma_cli_play* pPlay); +MA_API void ma_cli_play_set_node_graph(ma_cli_play* pPlay, ma_node_graph* pNodeGraph); +/* END miniaudio_cli_play.h */ + +/* BEG miniaudio_cli_play.c */ +static void ma_cli_play__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "play"; +} + +static void ma_cli_play__node_on_default_config(ma_cli_node_config* pConfig) +{ + pConfig->play.periodSizeInFrames = MA_CLI_DEFAULT_PERIOD_SIZE_IN_FRAMES; +} + +static ma_bool32 ma_cli_play__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_play__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_play); +} + +static ma_result ma_cli_play__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_play_init((ma_cli_node_config*)pConfig, (ma_cli_play*)pNode); +} + +static void ma_cli_play__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_play_uninit((ma_cli_play*)pNode); +} + +static void ma_cli_play__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_play* pPlay = (ma_cli_play*)pNode; + ma_uint32 frameCount = *pFrameCountOut; + ma_format format = pPlay->device.playback.format; + ma_uint32 channels = pPlay->device.playback.channels; + + (void)pFrameCountIn; + + /* The data is passed straight through, with a copy stored in our internal buffer. */ + MA_COPY_MEMORY(ppFramesOut[0], ppFramesIn[0], frameCount * ma_get_bytes_per_frame(format, channels)); + + /* Make a copy for our internal buffer. */ + if (frameCount > pPlay->bufferSizeInFrames) { + frameCount = pPlay->bufferSizeInFrames; + } + + if (format == ma_format_f32) { + MA_COPY_MEMORY(pPlay->pBuffer, ppFramesIn[0], frameCount * ma_get_bytes_per_frame(format, channels)); + } else { + ma_pcm_convert(pPlay->pBuffer, format, ppFramesIn[0], ma_format_f32, frameCount * channels, ma_dither_mode_none); + } + + /* Silence out the tail of the buffer if the frame count is less than the buffer size. Should never happen, but just for safety. */ + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pPlay->pBuffer, frameCount, format, channels), pPlay->bufferSizeInFrames - frameCount, format, channels); + + pPlay->cursor = 0; +} + +static ma_cli_node_vtable ma_gNodeVTable_Play = +{ + ma_cli_play__node_on_info, + ma_cli_play__node_on_default_config, + ma_cli_play__node_on_arg, + ma_cli_play__node_on_sizeof, + ma_cli_play__node_on_init, + ma_cli_play__node_on_uninit, + ma_cli_play__node_on_process +}; + +#if 0 +static ma_cli_node_config ma_cli_node_config_init_play(const ma_uint32* pInputChannelCounts, const ma_uint32* pOutputChannelCounts) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Play, 1, 1, pInputChannelCounts, pOutputChannelCounts); +} + + +MA_API ma_cli_play_config ma_cli_play_config_init() +{ + ma_cli_play_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_play(NULL, NULL); + config.format = ma_format_f32; + config.channels = 0; + config.sampleRate = 0; + config.periodSizeInFrames = 1024; + + return config; +} +#endif + + +static void ma_cli_play_device_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_cli_play* pPlay = (ma_cli_play*)ma_device_get_user_data(pDevice); + + if (pPlay->pNodeGraph != NULL) { + ma_cli_process_node_graph(pPlay->pNodeGraph); + } + + ma_cli_play_read_pcm_frames(pPlay, pFramesOut, frameCount, NULL); + + (void)pFramesIn; +} + +MA_API ma_result ma_cli_play_init(const ma_cli_node_config* pConfig, ma_cli_play* pPlay) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_device_config deviceConfig; + + if (pPlay == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPlay); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = pConfig->format; + deviceConfig.playback.channels = pConfig->channels; + deviceConfig.sampleRate = pConfig->sampleRate; + deviceConfig.periodSizeInFrames = pConfig->play.periodSizeInFrames; + deviceConfig.dataCallback = ma_cli_play_device_data_callback; + deviceConfig.pUserData = pPlay; + deviceConfig.threadingMode = MA_THREADING_MODE_SINGLE_THREADED; /* Devices will be stepped manually. */ + + result = ma_device_init_ex((pConfig->backendCount == 0) ? NULL : pConfig->pBackends, pConfig->backendCount, NULL, &deviceConfig, &pPlay->device); + if (result != MA_SUCCESS) { + ma_cli_node_uninit(&pPlay->node); + return result; + } + + + nodeConfig = *pConfig; + nodeConfig.inputCount = 1; + nodeConfig.outputCount = 1; + nodeConfig.pInputChannelCounts = &pPlay->device.playback.channels; + nodeConfig.pOutputChannelCounts = &pPlay->device.playback.channels; + nodeConfig.channels = pPlay->device.playback.channels; + nodeConfig.sampleRate = pPlay->device.sampleRate; + nodeConfig.pAllocationCallbacks = pConfig->pAllocationCallbacks; + + result = ma_cli_node_init(&nodeConfig, &pPlay->node); + if (result != MA_SUCCESS) { + return result; + } + + /* + We need an internal buffer. The idea is that it'll be filled during node graph processing and then + emptied from the data device callback. + */ + pPlay->bufferSizeInFrames = ma_device_get_period_size_in_frames(&pPlay->device); + + pPlay->pBuffer = ma_calloc(pPlay->bufferSizeInFrames * ma_get_bytes_per_frame(pPlay->device.playback.format, pPlay->device.playback.channels), nodeConfig.pAllocationCallbacks); + if (pPlay->pBuffer == NULL) { + ma_cli_node_uninit(&pPlay->node); + return MA_OUT_OF_MEMORY; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_play_uninit(ma_cli_play* pPlay) +{ + ma_allocation_callbacks allocationCallbacks; + + if (pPlay == NULL) { + return; + } + + allocationCallbacks = pPlay->node.allocationCallbacks; + + ma_cli_node_uninit(&pPlay->node); + ma_device_uninit(&pPlay->device); + ma_free(pPlay->pBuffer, &allocationCallbacks); +} + +MA_API ma_cli_node* ma_cli_play_node(ma_cli_play* pPlay) +{ + return (ma_cli_node*)pPlay; +} + +MA_API ma_result ma_cli_play_read_pcm_frames(ma_cli_play* pPlay, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) +{ + ma_uint32 framesAvailable; + ma_uint32 bpf; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pPlay == NULL || pFramesOut == NULL) { + return MA_INVALID_ARGS; + } + + framesAvailable = pPlay->bufferSizeInFrames - pPlay->cursor; + + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + bpf = ma_get_bytes_per_frame(pPlay->device.playback.format, pPlay->device.playback.channels); + + MA_COPY_MEMORY(pFramesOut, ma_offset_ptr(pPlay->pBuffer, pPlay->cursor * bpf), frameCount * bpf); + pPlay->cursor += frameCount; + + return MA_SUCCESS; +} + +MA_API ma_device* ma_cli_play_get_device(ma_cli_play* pPlay) +{ + if (pPlay == NULL) { + return NULL; + } + + return &pPlay->device; +} + +MA_API void ma_cli_play_set_node_graph(ma_cli_play* pPlay, ma_node_graph* pNodeGraph) +{ + if (pPlay == NULL) { + return; + } + + pPlay->pNodeGraph = pNodeGraph; +} +/* END miniaudio_cli_play.c */ + + +/* BEG miniaudio_cli_record.h */ +typedef struct ma_cli_record ma_cli_record; +struct ma_cli_record +{ + ma_cli_node node; + ma_device device; + void* pBuffer; + ma_uint32 bufferSizeInFrames; + ma_uint32 cursor; + ma_node_graph* pNodeGraph; /* When non-null, this node will be the one that needs to process the node graph. */ +}; + +MA_API ma_result ma_cli_record_init(const ma_cli_node_config* pConfig, ma_cli_record* pRecord); +MA_API void ma_cli_record_uninit(ma_cli_record* pRecord); +MA_API ma_cli_node* ma_cli_record_node(ma_cli_record* pRecord); +MA_API ma_result ma_cli_record_read_pcm_frames(ma_cli_record* pRecord, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead); +MA_API ma_device* ma_cli_record_get_device(ma_cli_record* pRecord); +MA_API void ma_cli_record_set_node_graph(ma_cli_record* pRecord, ma_node_graph* pNodeGraph); +/* END miniaudio_cli_record.h */ + +/* BEG miniaudio_cli_record.c */ +static void ma_cli_record__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "record"; +} + +static void ma_cli_record__node_on_default_config(ma_cli_node_config* pConfig) +{ + pConfig->record.periodSizeInFrames = MA_CLI_DEFAULT_PERIOD_SIZE_IN_FRAMES; +} + +static ma_bool32 ma_cli_record__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_record__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_record); +} + +static ma_result ma_cli_record__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_record_init((ma_cli_node_config*)pConfig, (ma_cli_record*)pNode); +} + +static void ma_cli_record__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_record_uninit((ma_cli_record*)pNode); +} + +static void ma_cli_record__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_record* pRecord = (ma_cli_record*)pNode; + ma_uint32 frameCount = *pFrameCountOut; + ma_format format = pRecord->device.capture.format; + ma_uint32 channels = pRecord->device.capture.channels; + + /* No input. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + /* We copy from our internal cache to the output buffer. */ + if (frameCount > pRecord->bufferSizeInFrames) { + frameCount = pRecord->bufferSizeInFrames; + } + + if (format == ma_format_f32) { + MA_COPY_MEMORY(ppFramesOut[0], pRecord->pBuffer, frameCount * ma_get_bytes_per_frame(format, channels)); + } else { + ma_pcm_convert(ppFramesOut[0], ma_format_f32, pRecord->pBuffer, format, frameCount * channels, ma_dither_mode_none); + } + + /* Silence out the tail of the buffer if the frame count is less than the buffer size. */ + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pRecord->pBuffer, frameCount, format, channels), pRecord->bufferSizeInFrames - frameCount, format, channels); + + pRecord->cursor = 0; + + *pFrameCountOut = frameCount; +} + +static ma_cli_node_vtable ma_gNodeVTable_Record = +{ + ma_cli_record__node_on_info, + ma_cli_record__node_on_default_config, + ma_cli_record__node_on_arg, + ma_cli_record__node_on_sizeof, + ma_cli_record__node_on_init, + ma_cli_record__node_on_uninit, + ma_cli_record__node_on_process +}; + +#if 0 +static ma_cli_node_config ma_cli_node_config_init_record(const ma_uint32* pOutputChannelCounts) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Record, 0, 1, NULL, pOutputChannelCounts); +} + + +MA_API ma_cli_record_config ma_cli_record_config_init() +{ + ma_cli_record_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_record(NULL); + config.format = ma_format_f32; + config.channels = 0; + config.sampleRate = 0; + config.periodSizeInFrames = 1024; + + return config; +} +#endif + + +static void ma_cli_record_device_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_cli_record* pRecord = (ma_cli_record*)ma_device_get_user_data(pDevice); + + MA_COPY_MEMORY(pRecord->pBuffer, pFramesIn, frameCount * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); + pRecord->bufferSizeInFrames = frameCount; + + if (pRecord->pNodeGraph != NULL) { + ma_cli_process_node_graph(pRecord->pNodeGraph); + } + + (void)pFramesIn; +} + +MA_API ma_result ma_cli_record_init(const ma_cli_node_config* pConfig, ma_cli_record* pRecord) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_device_config deviceConfig; + + if (pRecord == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pRecord); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + deviceConfig = ma_device_config_init(ma_device_type_capture); + deviceConfig.capture.format = pConfig->format; + deviceConfig.capture.channels = pConfig->channels; + deviceConfig.sampleRate = pConfig->sampleRate; + deviceConfig.periodSizeInFrames = pConfig->record.periodSizeInFrames; + deviceConfig.dataCallback = ma_cli_record_device_data_callback; + deviceConfig.pUserData = pRecord; + deviceConfig.threadingMode = MA_THREADING_MODE_SINGLE_THREADED; /* Devices will be stepped manually. */ + + result = ma_device_init_ex((pConfig->backendCount == 0) ? NULL : pConfig->pBackends, pConfig->backendCount, NULL, &deviceConfig, &pRecord->device); + if (result != MA_SUCCESS) { + ma_cli_node_uninit(&pRecord->node); + return result; + } + + + nodeConfig = *pConfig; + nodeConfig.outputCount = 1; + nodeConfig.pOutputChannelCounts = &pRecord->device.capture.channels; + nodeConfig.sampleRate = pRecord->device.sampleRate; + nodeConfig.pAllocationCallbacks = pConfig->pAllocationCallbacks; + + result = ma_cli_node_init(&nodeConfig, &pRecord->node); + if (result != MA_SUCCESS) { + return result; + } + + + /* + We need an internal buffer. The idea is that it'll be filled during node graph processing and then + emptied from the data device callback. + */ + pRecord->bufferSizeInFrames = ma_device_get_period_size_in_frames(&pRecord->device); + + pRecord->pBuffer = ma_calloc(pRecord->bufferSizeInFrames * ma_get_bytes_per_frame(pRecord->device.capture.format, pRecord->device.capture.channels), nodeConfig.pAllocationCallbacks); + if (pRecord->pBuffer == NULL) { + ma_cli_node_uninit(&pRecord->node); + return MA_OUT_OF_MEMORY; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_record_uninit(ma_cli_record* pRecord) +{ + ma_allocation_callbacks allocationCallbacks; + + if (pRecord == NULL) { + return; + } + + allocationCallbacks = pRecord->node.allocationCallbacks; + + ma_cli_node_uninit(&pRecord->node); + ma_device_uninit(&pRecord->device); + ma_free(pRecord->pBuffer, &allocationCallbacks); +} + +MA_API ma_cli_node* ma_cli_record_node(ma_cli_record* pRecord) +{ + return (ma_cli_node*)pRecord; +} + +MA_API ma_result ma_cli_record_read_pcm_frames(ma_cli_record* pRecord, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) +{ + ma_uint32 framesAvailable; + ma_uint32 bpf; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pRecord == NULL || pFramesOut == NULL) { + return MA_INVALID_ARGS; + } + + framesAvailable = pRecord->bufferSizeInFrames - pRecord->cursor; + + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + bpf = ma_get_bytes_per_frame(pRecord->device.capture.format, pRecord->device.capture.channels); + + MA_COPY_MEMORY(pFramesOut, ma_offset_ptr(pRecord->pBuffer, pRecord->cursor * bpf), frameCount * bpf); + pRecord->cursor += frameCount; + + return MA_SUCCESS; +} + +MA_API ma_device* ma_cli_record_get_device(ma_cli_record* pRecord) +{ + if (pRecord == NULL) { + return NULL; + } + + return &pRecord->device; +} + +MA_API void ma_cli_record_set_node_graph(ma_cli_record* pRecord, ma_node_graph* pNodeGraph) +{ + if (pRecord == NULL) { + return; + } + + pRecord->pNodeGraph = pNodeGraph; +} +/* END miniaudio_cli_record.c */ + + +/* BEG miniaudio_cli_encode.h */ +typedef struct ma_cli_encode ma_cli_encode; +struct ma_cli_encode +{ + ma_cli_node node; + ma_encoding_format encodingFormat; /* When unknown, will use raw. */ + ma_encoder encoder; + ma_format format; + ma_uint32 channels; + fs_file* pFile; + ma_bool32 isOwnerOfFile; +}; + +MA_API ma_result ma_cli_encode_init(const ma_cli_node_config* pConfig, ma_cli_encode* pEncode); +MA_API void ma_cli_encode_uninit(ma_cli_encode* pEncode); +MA_API ma_cli_node* ma_cli_encode_node(ma_cli_encode* pEncode); +/* END miniaudio_cli_encode.h */ + +/* BEG miniaudio_cli_encode.c */ +static void ma_cli_encode__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "encode"; +} + +static void ma_cli_encode__node_on_default_config(ma_cli_node_config* pConfig) +{ + (void)pConfig; +} + +static ma_bool32 ma_cli_encode__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_encode__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_encode); +} + +static ma_result ma_cli_encode__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_encode_init((ma_cli_node_config*)pConfig, (ma_cli_encode*)pNode); +} + +static void ma_cli_encode__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_encode_uninit((ma_cli_encode*)pNode); +} + +static void ma_cli_encode__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_encode* pEncode = (ma_cli_encode*)pNode; + ma_result result; + ma_uint64 frameCount; + ma_uint64 framesRead; + ma_uint32 bpf = ma_get_bytes_per_frame(pEncode->format, pEncode->channels); + + frameCount = *pFrameCountIn; + + if (pEncode->format == ma_format_f32) { + if (pEncode->pFile != NULL) { + fs_file_write(pEncode->pFile, ppFramesIn[0], (size_t)(frameCount * bpf), NULL); + } else { + ma_encoder_write_pcm_frames(&pEncode->encoder, ppFramesIn[0], frameCount, NULL); + } + } else { + /* Need to convert. */ + ma_uint32 temp[1024]; + ma_uint32 tempCap = sizeof(temp) / ma_get_bytes_per_frame(pEncode->format, pEncode->channels); + ma_uint64 totalFramesProcessed; + const float* pRunningFramesIn; + + pRunningFramesIn = ppFramesIn[0]; + + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint64 framesRemaining = frameCount - totalFramesProcessed; + ma_uint64 framesToWriteThisIteration = framesRemaining; + if (framesToWriteThisIteration > tempCap) { + framesToWriteThisIteration = tempCap; + } + + ma_pcm_convert(temp, pEncode->format, pRunningFramesIn, ma_format_f32, framesToWriteThisIteration, ma_dither_mode_triangle); + + if (pEncode->pFile != NULL) { + result = ma_result_from_fs(fs_file_write(pEncode->pFile, ppFramesIn[0], (size_t)(framesToWriteThisIteration * bpf), NULL)); + } else { + result = ma_encoder_write_pcm_frames(&pEncode->encoder, ppFramesIn[0], framesToWriteThisIteration, NULL); + } + + if (result != MA_SUCCESS) { + break; + } + + totalFramesProcessed += framesToWriteThisIteration; + pRunningFramesIn += framesToWriteThisIteration * pEncode->channels; + } + + framesRead = totalFramesProcessed; + } +} + +static ma_cli_node_vtable ma_gNodeVTable_Encode = +{ + ma_cli_encode__node_on_info, + ma_cli_encode__node_on_default_config, + ma_cli_encode__node_on_arg, + ma_cli_encode__node_on_sizeof, + ma_cli_encode__node_on_init, + ma_cli_encode__node_on_uninit, + ma_cli_encode__node_on_process +}; + + +MA_API ma_result ma_cli_encode_init(const ma_cli_node_config* pConfig, ma_cli_encode* pEncode) +{ + ma_result result; + ma_cli_node_config nodeConfig; + + if (pEncode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEncode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pEncode->format = pConfig->format; + if (pEncode->format == ma_format_unknown) { + pEncode->format = ma_format_f32; + } + + pEncode->channels = pConfig->channels; + + if (pConfig->pFilePath != NULL) { + /* Open the file. */ + size_t filePathLen; + char* pFilePath; + + filePathLen = pConfig->filePathLen; + if (filePathLen == (size_t)-1) { + filePathLen = strlen(pConfig->pFilePath); + } + + pFilePath = (char*)ma_malloc(filePathLen + 1, pConfig->pAllocationCallbacks); + if (pFilePath == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_strcpy_s(pFilePath, filePathLen + 1, pConfig->pFilePath); + + pEncode->encodingFormat = ma_encoding_format_from_path(pFilePath); + if (pEncode->encodingFormat == ma_encoding_format_unknown) { + result = ma_result_from_fs(fs_file_open(NULL, pFilePath, FS_WRITE, &pEncode->pFile)); + } else { + ma_encoder_config encoderConfig = ma_encoder_config_init(pEncode->encodingFormat, pEncode->format, pEncode->channels, pConfig->sampleRate); + + result = ma_encoder_init_file(pFilePath, &encoderConfig, &pEncode->encoder); + } + + ma_free(pFilePath, pConfig->pAllocationCallbacks); + + if (result != MA_SUCCESS) { + return result; + } + + pEncode->isOwnerOfFile = MA_TRUE; + } else { + return MA_INVALID_ARGS; + } + + nodeConfig = *pConfig; + nodeConfig.inputCount = 1; + nodeConfig.outputCount = 1; + nodeConfig.pInputChannelCounts = &pConfig->channels; + nodeConfig.pOutputChannelCounts = &pConfig->channels; + nodeConfig.pAllocationCallbacks = pConfig->pAllocationCallbacks; + + result = ma_cli_node_init(&nodeConfig, &pEncode->node); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_encode_uninit(ma_cli_encode* pEncode) +{ + if (pEncode == NULL) { + return; + } + + if (pEncode->pFile != NULL) { + if (pEncode->isOwnerOfFile) { + fs_file_close(pEncode->pFile); + } + } else { + ma_encoder_uninit(&pEncode->encoder); + } + + ma_cli_node_uninit(&pEncode->node); +} + +MA_API ma_cli_node* ma_cli_encode_node(ma_cli_encode* pEncode) +{ + return (ma_cli_node*)pEncode; +} +/* END miniaudio_cli_encode.c */ + + +/* BEG miniaudio_cli_converter.h */ +typedef struct ma_cli_converter_config ma_cli_converter_config; +struct ma_cli_converter_config +{ + ma_cli_node_config nodeConfig; + ma_uint32 inputChannels; + ma_uint32 outputChannels; + ma_uint32 inputSampleRate; + ma_uint32 outputSampleRate; +}; + +MA_API ma_cli_converter_config ma_cli_converter_config_init(ma_uint32 inputChannels, ma_uint32 outputChannels, ma_uint32 inputSampleRate, ma_uint32 outputSampleRate); + + +typedef struct ma_cli_converter ma_cli_converter; +struct ma_cli_converter +{ + ma_cli_node node; + ma_data_converter dataConverter; +}; + +MA_API ma_result ma_cli_converter_init(const ma_cli_converter_config* pConfig, ma_cli_converter* pConverter); +MA_API void ma_cli_converter_uninit(ma_cli_converter* pConverter); +MA_API ma_cli_node* ma_cli_converter_node(ma_cli_converter* pConverter); +/* END miniaudio_cli_converter.h */ + +/* BEG miniaudio_cli_converter.c */ +static void ma_cli_converter__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "converter"; +} + +static void ma_cli_converter__node_on_default_config(ma_cli_node_config* pConfig) +{ + (void)pConfig; +} + +static ma_bool32 ma_cli_converter__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_converter__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_converter); +} + +static ma_result ma_cli_converter__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_converter_init((ma_cli_converter_config*)pConfig, (ma_cli_converter*)pNode); +} + +static void ma_cli_converter__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_converter_uninit((ma_cli_converter*)pNode); +} + +static void ma_cli_converter__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_cli_converter* pConverter = (ma_cli_converter*)pNode; + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + + (void)pNode; + (void)ppFramesIn; + (void)pFrameCountIn; + (void)ppFramesOut; + (void)pFrameCountOut; + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + ma_data_converter_process_pcm_frames(&pConverter->dataConverter, ppFramesIn[0], &frameCountIn, ppFramesOut[0], &frameCountOut); + + *pFrameCountIn = (ma_uint32)frameCountIn; + *pFrameCountOut = (ma_uint32)frameCountOut; +} + +static ma_cli_node_vtable ma_gNodeVTable_Converter = +{ + ma_cli_converter__node_on_info, + ma_cli_converter__node_on_default_config, + ma_cli_converter__node_on_arg, + ma_cli_converter__node_on_sizeof, + ma_cli_converter__node_on_init, + ma_cli_converter__node_on_uninit, + ma_cli_converter__node_on_process +}; + +static ma_cli_node_config ma_cli_node_config_init_converter(ma_uint32 inputCount, ma_uint32 outputCount, const ma_uint32* pInputChannelCounts, const ma_uint32* pOutputChannelCounts) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Converter, inputCount, outputCount, pInputChannelCounts, pOutputChannelCounts); +} + + +MA_API ma_cli_converter_config ma_cli_converter_config_init(ma_uint32 inputChannels, ma_uint32 outputChannels, ma_uint32 inputSampleRate, ma_uint32 outputSampleRate) +{ + ma_cli_converter_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_converter(0, 0, NULL, NULL); + config.inputChannels = inputChannels; + config.outputChannels = outputChannels; + config.inputSampleRate = inputSampleRate; + config.outputSampleRate = outputSampleRate; + + return config; +} + + +MA_API ma_result ma_cli_converter_init(const ma_cli_converter_config* pConfig, ma_cli_converter* pConverter) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_data_converter_config dataConverterConfig; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pConverter); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + dataConverterConfig = ma_data_converter_config_init(ma_format_f32, ma_format_f32, pConfig->inputChannels, pConfig->outputChannels, pConfig->inputSampleRate, pConfig->outputSampleRate); + + result = ma_data_converter_init(&dataConverterConfig, pConfig->nodeConfig.pAllocationCallbacks, &pConverter->dataConverter); + if (result != MA_SUCCESS) { + return result; + } + + + nodeConfig = ma_cli_node_config_init_converter(1, 1, &pConfig->inputChannels, &pConfig->outputChannels); + + result = ma_cli_node_init(&nodeConfig, &pConverter->node); + if (result != MA_SUCCESS) { + ma_data_converter_uninit(&pConverter->dataConverter, pConfig->nodeConfig.pAllocationCallbacks); + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_converter_uninit(ma_cli_converter* pConverter) +{ + if (pConverter == NULL) { + return; + } + + ma_data_converter_uninit(&pConverter->dataConverter, &pConverter->node.allocationCallbacks); + ma_cli_node_uninit(&pConverter->node); +} + +MA_API ma_cli_node* ma_cli_converter_node(ma_cli_converter* pConverter) +{ + return (ma_cli_node*)pConverter; +} +/* END miniaudio_cli_converter.c */ + + +/* BEG miniaudio_cli_endpoint.h */ +typedef struct ma_cli_endpoint_config ma_cli_endpoint_config; +struct ma_cli_endpoint_config +{ + ma_cli_node_config nodeConfig; + ma_uint32 inputCount; + ma_uint32* pInputChannelCounts; +}; + +MA_API ma_cli_endpoint_config ma_cli_endpoint_config_init(void); + + +typedef struct ma_cli_endpoint ma_cli_endpoint; +struct ma_cli_endpoint +{ + ma_cli_node node; +}; + +MA_API ma_result ma_cli_endpoint_init(const ma_cli_endpoint_config* pConfig, ma_cli_endpoint* pEndpoint); +MA_API void ma_cli_endpoint_uninit(ma_cli_endpoint* pEndpoint); +MA_API ma_cli_node* ma_cli_endpoint_node(ma_cli_endpoint* pEndpoint); +/* END miniaudio_cli_endpoint.h */ + +/* BEG miniaudio_cli_endpoint.c */ +static void ma_cli_endpoint__node_on_info(ma_cli_node_info* pInfo) +{ + pInfo->pName = "endpoint"; +} + +static void ma_cli_endpoint__node_on_default_config(ma_cli_node_config* pConfig) +{ + (void)pConfig; +} + +static ma_bool32 ma_cli_endpoint__node_on_arg(ma_cli_args* pArgs, ma_cli_node_config* pConfig) +{ + (void)pArgs; + (void)pConfig; + + return MA_FALSE; +} + +static size_t ma_cli_endpoint__node_on_sizeof(const void* pConfig) +{ + return sizeof(ma_cli_endpoint); +} + +static ma_result ma_cli_endpoint__node_on_init(const void* pConfig, ma_cli_node* pNode) +{ + return ma_cli_endpoint_init((ma_cli_endpoint_config*)pConfig, (ma_cli_endpoint*)pNode); +} + +static void ma_cli_endpoint__node_on_uninit(ma_cli_node* pNode) +{ + ma_cli_endpoint_uninit((ma_cli_endpoint*)pNode); +} + +static void ma_cli_endpoint__node_on_process(ma_cli_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* Our endpoint doesn't care about data because we'll never be doing anything with the output. */ + (void)pNode; + (void)ppFramesIn; + (void)pFrameCountIn; + (void)ppFramesOut; + (void)pFrameCountOut; +} + +static ma_cli_node_vtable ma_gNodeVTable_Endpoint = +{ + ma_cli_endpoint__node_on_info, + ma_cli_endpoint__node_on_default_config, + ma_cli_endpoint__node_on_arg, + ma_cli_endpoint__node_on_sizeof, + ma_cli_endpoint__node_on_init, + ma_cli_endpoint__node_on_uninit, + ma_cli_endpoint__node_on_process +}; + +static ma_cli_node_config ma_cli_node_config_init_endpoint(ma_uint32 inputCount, ma_uint32 outputCount, const ma_uint32* pInputChannelCounts, const ma_uint32* pOutputChannelCounts) +{ + return ma_cli_node_config_init(&ma_gNodeVTable_Endpoint, inputCount, outputCount, pInputChannelCounts, pOutputChannelCounts); +} + + +MA_API ma_cli_endpoint_config ma_cli_endpoint_config_init(void) +{ + ma_cli_endpoint_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_cli_node_config_init_endpoint(0, 0, NULL, NULL); + + return config; +} + + +MA_API ma_result ma_cli_endpoint_init(const ma_cli_endpoint_config* pConfig, ma_cli_endpoint* pEndpoint) +{ + ma_result result; + ma_cli_node_config nodeConfig; + ma_uint32 outputChannels = 1; /* Doesn't matter what we set this to since the endpoint will be discarding everything. */ + + if (pEndpoint == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEndpoint); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + nodeConfig = ma_cli_node_config_init_endpoint(pConfig->inputCount, 1, pConfig->pInputChannelCounts, &outputChannels); + + result = ma_cli_node_init(&nodeConfig, &pEndpoint->node); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_cli_endpoint_uninit(ma_cli_endpoint* pEndpoint) +{ + if (pEndpoint == NULL) { + return; + } + + ma_cli_node_uninit(&pEndpoint->node); +} + +MA_API ma_cli_node* ma_cli_endpoint_node(ma_cli_endpoint* pEndpoint) +{ + return (ma_cli_node*)pEndpoint; +} +/* END miniaudio_cli_endpoint.c */