From 8f893ae815e660fc942b329b3797ebe4cae77f19 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Tue, 20 Dec 2022 23:20:05 -0500 Subject: [PATCH 1/8] Add first draft of microphone support - See https://github.com/libretro/RetroArch/pull/14731 - Not totally functional; still crashes in places and input is garbage --- src/libretro/input.cpp | 15 ++ src/libretro/input.h | 13 ++ .../libretro-common/include/libretro.h | 134 +++++++++++++++++- src/libretro/libretro.cpp | 97 +++++++++++-- src/libretro/libretro_core_options.h | 20 ++- 5 files changed, 264 insertions(+), 15 deletions(-) diff --git a/src/libretro/input.cpp b/src/libretro/input.cpp index 5d710f8c79..0be92f701e 100644 --- a/src/libretro/input.cpp +++ b/src/libretro/input.cpp @@ -55,9 +55,24 @@ void update_input(InputState *state) state->lid_closed = lid_closed_btn; } + state->previous_holding_noise_btn = state->holding_noise_btn; state->holding_noise_btn = !!input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2); state->swap_screens_btn = !!input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2); + if ( micNoiseType == MicInput && // If the player wants to use their real mic... + noise_button_required && // ...and they don't always want it hot... + (state->holding_noise_btn != state->previous_holding_noise_btn) && // ...and they just pressed or released the button... + micHandle != NULL && micInterface.supported // ...and the mic is valid... + ) + { + bool stateSet = micInterface.set_microphone_state(micHandle, state->holding_noise_btn); + // ...then set the state of the mic to the active staste of the noise button + + if (!stateSet) + log_cb(RETRO_LOG_ERROR, "[melonDS] Error setting state of microphone to %s\n", + state->holding_noise_btn ? "enabled" : "disabled"); + } + if(current_screen_layout != ScreenLayout::TopOnly) { switch(state->current_touch_mode) diff --git a/src/libretro/input.h b/src/libretro/input.h index 450bbde767..9209fb93fe 100644 --- a/src/libretro/input.h +++ b/src/libretro/input.h @@ -2,6 +2,7 @@ #define _INPUT_H #include "types.h" +#include enum TouchMode { @@ -11,12 +12,20 @@ enum TouchMode Joystick, }; +enum MicInputMode +{ + BlowNoise, + WhiteNoise, + MicInput, +}; + struct InputState { bool touching; int touch_x, touch_y; TouchMode current_touch_mode; + bool previous_holding_noise_btn = false; bool holding_noise_btn = false; bool swap_screens_btn = false; bool lid_closed = false; @@ -27,6 +36,10 @@ extern InputState input_state; bool cursor_enabled(InputState *state); extern bool libretro_supports_bitmasks; +extern bool noise_button_required; +extern retro_microphone_interface micInterface; +extern retro_microphone_t *micHandle; +extern MicInputMode micNoiseType; void update_input(InputState *state); diff --git a/src/libretro/libretro-common/include/libretro.h b/src/libretro/libretro-common/include/libretro.h index bb114aed35..217438cedb 100644 --- a/src/libretro/libretro-common/include/libretro.h +++ b/src/libretro/libretro-common/include/libretro.h @@ -289,6 +289,8 @@ enum retro_language RETRO_LANGUAGE_CZECH = 27, RETRO_LANGUAGE_CATALAN_VALENCIA = 28, RETRO_LANGUAGE_CATALAN = 29, + RETRO_LANGUAGE_BRITISH_ENGLISH = 30, + RETRO_LANGUAGE_HUNGARIAN = 31, RETRO_LANGUAGE_LAST, /* Ensure sizeof(enum) == sizeof(int) */ @@ -1765,6 +1767,22 @@ enum retro_mod * (see enum retro_savestate_context) */ +#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (73 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_microphone_interface * -- + * Returns an interface that can be used to receive audio from the audio driver. + * + * Create the interface and this callback will populate it. + * + * If the frontend and audio driver support microphones, + * all function pointers will be non-NULL. + * Otherwise, all function pointers will be NULL. + */ + +#define RETRO_ENVIRONMENT_GET_MICROPHONE_ENABLED (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* bool * -- + * Returns true if the user has enabled the microphone, + * regardless of whether the current audio driver supports it. + */ /* VFS functionality */ /* File paths: @@ -3009,13 +3027,13 @@ enum retro_savestate_context /* Savestate where you are guaranteed that the same instance will load the save state. * You can store internal pointers to code or data. - * It's still a full serialization and deserialization, and could be loaded or saved at any time. + * It's still a full serialization and deserialization, and could be loaded or saved at any time. * It won't be written to disk or sent over the network. */ RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE = 1, /* Savestate where you are guaranteed that the same emulator binary will load that savestate. - * You can skip anything that would slow down saving or loading state but you can not store internal pointers. + * You can skip anything that would slow down saving or loading state but you can not store internal pointers. * It won't be written to disk or sent over the network. * Example: "Second Instance" runahead */ @@ -3780,6 +3798,118 @@ struct retro_throttle_state float rate; }; +/** + * Opaque handle to a microphone that's been opened for use. + * You don't access microphone objects directly; + * use the retro_microphone_interface. + */ +typedef struct retro_microphone retro_microphone_t; + +/** + * Initializes a new microphone. + * Assuming that microphone support is enabled and provided by the frontend, + * you can call this whenever your core needs it. + * You could call this to keep a microphone throughout your core's lifetime, + * or you could call this when a microphone is plugged in to the emulated device. + * + * @returns \c NULL if a microphone couldn't be initialized. + * This likely means that no microphone is plugged in and recognized, + * or the maximum number of supported microphones has been reached. + */ +typedef retro_microphone_t *(RETRO_CALLCONV *retro_init_microphone_t)(void); + +/** + * Closes a microphone that was initialized with \c retro_init_microphone. + * Calling this function will stop all microphone activity + * and free up the resources that it allocated. + * Afterwards, the handle is invalid and must not be used. + * + * @param microphone Pointer to the microphone that was allocated by init_microphone. + * If \c NULL, this function does nothing. + */ +typedef void (RETRO_CALLCONV *retro_free_microphone_t)(retro_microphone_t *microphone); + +/** + * Enables or disables the given microphone. + * Microphones are disabled by default + * and must be explicitly enabled before they can be used. + * Disabled microphones will not process incoming audio samples, + * and will therefore have minimal impact on overall performance. + * You may enable microphones throughout the lifetime of a core, + * or only in instances where they're needed. + * + * Your core should be able to operate without microphone input; + * we suggest substituting silence in this case. + * + * Calling this function while the audio driver is paused + * \em will update the state of the microphone, + * but it won't reactivate it. + * You'll have to resume the audio driver for that. + * + * @param microphone Opaque handle to the microphone + * whose state will be adjusted. + * This will have been provided by \c init_microphone. + * @param state \c true if the microphone should receive audio input, + * @c false if it should be idle. + * @returns \c true if the microphone's state was successfully set, + * \c false if \c microphone is invalid + * or if there was an error. + */ +typedef bool (RETRO_CALLCONV *retro_set_microphone_state_t)(retro_microphone_t *microphone, bool state); + +/** + * Queries the active state of a microphone at the given index. + * Will return whether the microphone is enabled, + * even if the driver is paused. + * + * @param microphone Opaque handle to the microphone + * whose state will be queried. + * @return true if the provided \c microphone is valid and active, + * false if not or if there was an error. + */ +typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_microphone_t *microphone); + +/** + * Retrieves the input processed by the microphone since the previous frame. + * If called while the microphone or the audio driver are disabled, + * then nothing will be copied into data. + * + * @param microphone Opaque handle to the microphone + * whose recent input will be retrieved. + * @param data The buffer that will be used to store the microphone's data. + * Microphone input is in mono (i.e. one number per sample). + * Should be large enough to accommodate the expected number of samples per frame; + * for example, a 44.1kHz sample rate at 60 FPS would require space for 735 samples. + * @param data_length The size of the data buffer, in samples (\em not bytes). + * + * @return The number of samples that were collected this frame. + * Will return -1 if the microphone is disabled, + * the audio driver is paused, + * or there was an error. + */ +typedef int (RETRO_CALLCONV *retro_get_microphone_input_t)(retro_microphone_t *microphone, int16_t* data, size_t data_length); + +/** + * An interface for querying the microphone and accessing data read from it. + * All fields in this interface are populated by the frontend + * by way of the the RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE callback. + * All fields will be non-NULL, + * even if the frontend (or its audio driver) doesn't support microphones. + */ +struct retro_microphone_interface +{ + /** + * True if the driver and frontend support microphones. + * Set by the frontend. + */ + bool supported; + retro_init_microphone_t init_microphone; + retro_free_microphone_t free_microphone; + retro_set_microphone_state_t set_microphone_state; + retro_get_microphone_state_t get_microphone_state; + retro_get_microphone_input_t get_microphone_input; +}; + /* Callbacks */ /* Environment callback. Gives implementations a way of performing diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 867ead25ae..38b54511ea 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -50,6 +50,7 @@ bool refresh_opengl = true; bool swapped_screens = false; bool toggle_swap_screen = false; bool swap_screen_toggled = false; +bool noise_button_required = true; const int SLOT_1_2_BOOT = 1; @@ -63,7 +64,9 @@ static bool jit_options = true; #endif static void Mic_FeedNoise(); -static u8 micNoiseType; +MicInputMode micNoiseType = WhiteNoise; +retro_microphone_interface micInterface = {false}; +retro_microphone_t *micHandle = NULL; enum CurrentRenderer { @@ -95,12 +98,41 @@ void retro_init(void) sprintf(retro_saves_directory, "%s", dir); initialize_screnlayout_data(&screen_layout_data); + + bool micEnabled = false; + if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_ENABLED, &micEnabled) && micEnabled) + { // If the user has enabled the microphone... + log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support enabled in the frontend settings\n"); + if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE, &micInterface) && micInterface.supported) + { // ...and if the current audio driver supports microphones... + log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support available in current audio driver\n"); + micHandle = micInterface.init_microphone(); + + if (micHandle && micInterface.set_microphone_state(micHandle, true)) + { + log_cb(RETRO_LOG_INFO, "[melonDS] Initialized microphone\n"); + } + else + { + log_cb(RETRO_LOG_WARN, "[melonDS] Failed to initialize microphone, emulated device will receive silence\n"); + } + } + } } void retro_deinit(void) { libretro_supports_bitmasks = false; libretro_supports_option_categories = false; + + if (micHandle && micInterface.free_microphone) + { // If we were playing with the microphone... + micInterface.free_microphone(micHandle); + micHandle = NULL; + micInterface = {0}; + + log_cb(RETRO_LOG_DEBUG, "[melonDS] Released previously-allocated microphone\n"); + } } unsigned retro_api_version(void) @@ -558,10 +590,34 @@ static void check_variables(bool init) var.key = "melonds_mic_input"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (!strcmp(var.value, "Blow Noise")) - micNoiseType = 1; + if (!strcmp(var.value, "Microphone Input")) + micNoiseType = MicInput; + else if (!strcmp(var.value, "Blow Noise")) + micNoiseType = BlowNoise; + else + micNoiseType = WhiteNoise; + + if (micNoiseType != MicInput && micInterface.supported && micHandle) + { // If the player wants to stop using the real mic as the DS mic's input... + micInterface.set_microphone_state(micHandle, false); + } + } + + var.key = "melonds_need_button_mic_input"; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "With Button")) + noise_button_required = true; else - micNoiseType = 0; + noise_button_required = false; + + if ( noise_button_required && + micInterface.supported && + micHandle != NULL && + !input_state.holding_noise_btn) + { // If the player wants to require the noise button for mic input and they aren't already holding it... + micInterface.set_microphone_state(micHandle, false); + } } var.key = "melonds_audio_bitrate"; @@ -733,15 +789,34 @@ void retro_run(void) } } - if (input_state.holding_noise_btn) + if (input_state.holding_noise_btn || !noise_button_required) { - if (micNoiseType) - Mic_FeedNoise(); - else + switch (micNoiseType) { - s16 tmp[735]; - for (int i = 0; i < 735; i++) tmp[i] = rand() & 0xFFFF; - NDS::MicInputFrame(tmp, 735); + case WhiteNoise: // random noise + { + s16 tmp[735]; + for (int i = 0; i < 735; i++) tmp[i] = rand() & 0xFFFF; + NDS::MicInputFrame(tmp, 735); + break; + } + case BlowNoise: // blow noise + { + Mic_FeedNoise(); + break; + } + case MicInput: // microphone input + { + s16 tmp[735]; + if (micHandle && micInterface.supported && micInterface.get_microphone_state(micHandle)) + { // If the microphone is enabled and supported... + micInterface.get_microphone_input(micHandle, tmp, 735); + NDS::MicInputFrame(tmp, 735); + break; + } // If the mic isn't available, go to the default case + } + default: + NDS::MicInputFrame(NULL, 0); } } else diff --git a/src/libretro/libretro_core_options.h b/src/libretro/libretro_core_options.h index e50715cd8c..a927804509 100644 --- a/src/libretro/libretro_core_options.h +++ b/src/libretro/libretro_core_options.h @@ -13,9 +13,10 @@ /* ******************************** - * VERSION: 2.0 + * VERSION: 2.1 ******************************** * + * - 2.1: Add microphone support * - 2.0: Add support for core options v2 interface * - 1.3: Move translations to libretro_core_options_intl.h * - libretro_core_options_intl.h includes BOM and utf-8 @@ -259,9 +260,24 @@ struct retro_core_option_v2_definition option_defs_us[] = { { { "Blow Noise", NULL }, { "White Noise", NULL }, + { "Microphone Input", NULL}, { NULL, NULL }, }, - "Blow Noise" + "Microphone Input" + }, + { + "melonds_need_button_mic_input", + "Listen for Mic Input", + NULL, + "Set the microphone to be active when the mic button is held, or at all times.", + NULL, + "audio", + { + { "With Button", NULL }, + { "Always", NULL }, + { NULL, NULL }, + }, + "With Button" }, { "melonds_audio_bitrate", From 45c09b76f0370feb7502f0d77ad92690db4aa99b Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Wed, 21 Dec 2022 18:42:47 -0500 Subject: [PATCH 2/8] Fix a memory allocation mismatch - asan was complaining about it --- src/NDSCart_SRAMManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NDSCart_SRAMManager.cpp b/src/NDSCart_SRAMManager.cpp index ba31e8cb44..7558867287 100644 --- a/src/NDSCart_SRAMManager.cpp +++ b/src/NDSCart_SRAMManager.cpp @@ -67,7 +67,7 @@ void DeInit() } #endif - if (SecondaryBuffer) delete SecondaryBuffer; + if (SecondaryBuffer) delete[] SecondaryBuffer; SecondaryBuffer = NULL; Platform::Mutex_Free(SecondaryBufferLock); @@ -88,7 +88,7 @@ void Setup(const char* path, u8* buffer, u32 length) Buffer = buffer; Length = length; - if(SecondaryBuffer) delete SecondaryBuffer; // Delete secondary buffer, there might be previous state. + if(SecondaryBuffer) delete[] SecondaryBuffer; // Delete secondary buffer, there might be previous state. SecondaryBuffer = new u8[length]; SecondaryBufferLength = length; From d3cf2e3120c435378b7dc4ce720db5d8fb39b11a Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Wed, 21 Dec 2022 18:43:42 -0500 Subject: [PATCH 3/8] Use Melon's existing implementations of some mic functions --- Makefile.common | 1 + src/libretro/libretro.cpp | 24 +++--------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Makefile.common b/Makefile.common index 3150817b75..264c1f70f3 100644 --- a/Makefile.common +++ b/Makefile.common @@ -66,6 +66,7 @@ SOURCES_CXX := $(MELON_DIR)/NDS.cpp \ $(MELON_DIR)/Wifi.cpp \ $(MELON_DIR)/WifiAP.cpp \ $(MELON_DIR)/frontend/Util_ROM.cpp \ + $(MELON_DIR)/frontend/Util_Audio.cpp \ $(CORE_DIR)/config.cpp \ $(CORE_DIR)/input.cpp \ $(CORE_DIR)/libretro.cpp \ diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 38b54511ea..ee696cfec0 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -63,7 +63,6 @@ static bool hybrid_options = true; static bool jit_options = true; #endif -static void Mic_FeedNoise(); MicInputMode micNoiseType = WhiteNoise; retro_microphone_interface micInterface = {false}; retro_microphone_t *micHandle = NULL; @@ -802,7 +801,7 @@ void retro_run(void) } case BlowNoise: // blow noise { - Mic_FeedNoise(); + Frontend::Mic_FeedNoise(); // despite the name, this feeds a blow noise break; } case MicInput: // microphone input @@ -816,12 +815,12 @@ void retro_run(void) } // If the mic isn't available, go to the default case } default: - NDS::MicInputFrame(NULL, 0); + Frontend::Mic_FeedSilence(); } } else { - NDS::MicInputFrame(NULL, 0); + Frontend::Mic_FeedSilence(); } if (current_renderer != CurrentRenderer::None) NDS::RunFrame(); @@ -844,23 +843,6 @@ void retro_run(void) NDSCart_SRAMManager::Flush(); } -void Mic_FeedNoise() -{ - int sample_len = sizeof(mic_blow) / sizeof(u16); - static int sample_pos = 0; - - s16 tmp[735]; - - for (int i = 0; i < 735; i++) - { - tmp[i] = mic_blow[sample_pos]; - sample_pos++; - if (sample_pos >= sample_len) sample_pos = 0; - } - - NDS::MicInputFrame(tmp, 735); -} - static bool _handle_load_game(unsigned type, const struct retro_game_info *info) { /* From d7b4fce803d1a216e849191751622035eff47690 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Thu, 12 Jan 2023 20:46:25 -0500 Subject: [PATCH 4/8] Update libretro.h --- src/libretro/libretro-common/include/libretro.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libretro/libretro-common/include/libretro.h b/src/libretro/libretro-common/include/libretro.h index 217438cedb..99924fc08a 100644 --- a/src/libretro/libretro-common/include/libretro.h +++ b/src/libretro/libretro-common/include/libretro.h @@ -1776,6 +1776,8 @@ enum retro_mod * If the frontend and audio driver support microphones, * all function pointers will be non-NULL. * Otherwise, all function pointers will be NULL. + * + * Returns false if mic support is disabled */ #define RETRO_ENVIRONMENT_GET_MICROPHONE_ENABLED (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) @@ -3812,6 +3814,9 @@ typedef struct retro_microphone retro_microphone_t; * You could call this to keep a microphone throughout your core's lifetime, * or you could call this when a microphone is plugged in to the emulated device. * + * The returned handle will be valid until it's freed, + * even if the audio driver is reinitialized. + * * @returns \c NULL if a microphone couldn't be initialized. * This likely means that no microphone is plugged in and recognized, * or the maximum number of supported microphones has been reached. @@ -3876,18 +3881,19 @@ typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_micropho * * @param microphone Opaque handle to the microphone * whose recent input will be retrieved. - * @param data The buffer that will be used to store the microphone's data. + * @param samples The buffer that will be used to store the microphone's data. * Microphone input is in mono (i.e. one number per sample). * Should be large enough to accommodate the expected number of samples per frame; * for example, a 44.1kHz sample rate at 60 FPS would require space for 735 samples. - * @param data_length The size of the data buffer, in samples (\em not bytes). + * @param num_samples The size of the data buffer, in samples (\em not bytes). + * Microphone input is in mono, so a "frame" and a "sample" are equivalent in length here. * * @return The number of samples that were collected this frame. * Will return -1 if the microphone is disabled, * the audio driver is paused, * or there was an error. */ -typedef int (RETRO_CALLCONV *retro_get_microphone_input_t)(retro_microphone_t *microphone, int16_t* data, size_t data_length); +typedef int (RETRO_CALLCONV *retro_get_microphone_input_t)(retro_microphone_t *microphone, int16_t* samples, size_t num_samples); /** * An interface for querying the microphone and accessing data read from it. From aeabc1f23d9e29effb76ed1e9f78754c38f10cb5 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Mon, 13 Feb 2023 14:07:05 -0500 Subject: [PATCH 5/8] Update libretro.h --- .../libretro-common/include/libretro.h | 62 +++++++++++++++---- src/libretro/libretro.cpp | 33 +++++----- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/libretro/libretro-common/include/libretro.h b/src/libretro/libretro-common/include/libretro.h index 99924fc08a..1daa8557f2 100644 --- a/src/libretro/libretro-common/include/libretro.h +++ b/src/libretro/libretro-common/include/libretro.h @@ -1767,7 +1767,33 @@ enum retro_mod * (see enum retro_savestate_context) */ -#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (73 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT (73 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_hw_render_context_negotiation_interface * -- + * Before calling SET_HW_RNEDER_CONTEXT_NEGOTIATION_INTERFACE, a core can query + * which version of the interface is supported. + * + * Frontend looks at interface_type and returns the maximum supported + * context negotiation interface version. + * If the interface_type is not supported or recognized by the frontend, a version of 0 + * must be returned in interface_version and true is returned by frontend. + * + * If this environment call returns true with interface_version greater than 0, + * a core can always use a negotiation interface version larger than what the frontend returns, but only + * earlier versions of the interface will be used by the frontend. + * A frontend must not reject a negotiation interface version that is larger than + * what the frontend supports. Instead, the frontend will use the older entry points that it recognizes. + * If this is incompatible with a particular core's requirements, it can error out early. + * + * Backwards compatibility note: + * This environment call was introduced after Vulkan v1 context negotiation. + * If this environment call is not supported by frontend - i.e. the environment call returns false - + * only Vulkan v1 context negotiation is supported (if Vulkan HW rendering is supported at all). + * If a core uses Vulkan negotiation interface with version > 1, negotiation may fail unexpectedly. + * All future updates to the context negotiation interface implies that frontend must support + * this environment call to query support. + */ + +#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* struct retro_microphone_interface * -- * Returns an interface that can be used to receive audio from the audio driver. * @@ -1777,14 +1803,9 @@ enum retro_mod * all function pointers will be non-NULL. * Otherwise, all function pointers will be NULL. * - * Returns false if mic support is disabled + * Returns false if mic support is disabled. */ -#define RETRO_ENVIRONMENT_GET_MICROPHONE_ENABLED (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) - /* bool * -- - * Returns true if the user has enabled the microphone, - * regardless of whether the current audio driver supports it. - */ /* VFS functionality */ /* File paths: @@ -3817,9 +3838,15 @@ typedef struct retro_microphone retro_microphone_t; * The returned handle will be valid until it's freed, * even if the audio driver is reinitialized. * - * @returns \c NULL if a microphone couldn't be initialized. + * This should only be called from the main thread. + * + * @returns Pointer to the newly-opened microphone, + * or \c NULL if one couldn't be opened. * This likely means that no microphone is plugged in and recognized, * or the maximum number of supported microphones has been reached. + * + * @note Microphones are \em inactive by default; + * to begin recording, call set_microphone_state(new_handle, true). */ typedef retro_microphone_t *(RETRO_CALLCONV *retro_init_microphone_t)(void); @@ -3875,9 +3902,12 @@ typedef bool (RETRO_CALLCONV *retro_set_microphone_state_t)(retro_microphone_t * typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_microphone_t *microphone); /** - * Retrieves the input processed by the microphone since the previous frame. - * If called while the microphone or the audio driver are disabled, - * then nothing will be copied into data. + * Retrieves the input processed by the microphone since the last call. + * \em Must be called every frame unless \c microphone is disabled, + * similar to how \c retro_audio_sample_batch_t works. + * + * If using \c RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK, + * then this function should be called within the provided callback. * * @param microphone Opaque handle to the microphone * whose recent input will be retrieved. @@ -3888,10 +3918,16 @@ typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_micropho * @param num_samples The size of the data buffer, in samples (\em not bytes). * Microphone input is in mono, so a "frame" and a "sample" are equivalent in length here. * - * @return The number of samples that were collected this frame. + * @return The number of samples that were copied into \c samples. + * Will return 0 if \c microphone is still pending + * because the driver hasn't finished initializing. + * This is not an error. + * * Will return -1 if the microphone is disabled, * the audio driver is paused, * or there was an error. + * + * @see RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK */ typedef int (RETRO_CALLCONV *retro_get_microphone_input_t)(retro_microphone_t *microphone, int16_t* samples, size_t num_samples); @@ -3909,6 +3945,8 @@ struct retro_microphone_interface * Set by the frontend. */ bool supported; + + /** @copydoc retro_init_microphone_t */ retro_init_microphone_t init_microphone; retro_free_microphone_t free_microphone; retro_set_microphone_state_t set_microphone_state; diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index ee696cfec0..54c50ee4f9 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -98,25 +98,20 @@ void retro_init(void) initialize_screnlayout_data(&screen_layout_data); - bool micEnabled = false; - if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_ENABLED, &micEnabled) && micEnabled) - { // If the user has enabled the microphone... - log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support enabled in the frontend settings\n"); - if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE, &micInterface) && micInterface.supported) - { // ...and if the current audio driver supports microphones... - log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support available in current audio driver\n"); - micHandle = micInterface.init_microphone(); - - if (micHandle && micInterface.set_microphone_state(micHandle, true)) - { - log_cb(RETRO_LOG_INFO, "[melonDS] Initialized microphone\n"); - } - else - { - log_cb(RETRO_LOG_WARN, "[melonDS] Failed to initialize microphone, emulated device will receive silence\n"); - } - } - } + if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE, &micInterface) && micInterface.supported) + { // ...and if the current audio driver supports microphones... + log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support available in current audio driver\n"); + micHandle = micInterface.init_microphone(); + + if (micHandle) + { + log_cb(RETRO_LOG_INFO, "[melonDS] Initialized microphone\n"); + } + else + { + log_cb(RETRO_LOG_WARN, "[melonDS] Failed to initialize microphone, emulated device will receive silence\n"); + } + } } void retro_deinit(void) From 04f33cf8accf4ff707187a1cd4e9f35f0d7bcc58 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Thu, 16 Feb 2023 11:02:32 -0500 Subject: [PATCH 6/8] Update to reflect the latest changes in the mic interface --- .../libretro-common/include/libretro.h | 108 ++++++++++++------ src/libretro/libretro.cpp | 58 ++++++---- 2 files changed, 107 insertions(+), 59 deletions(-) diff --git a/src/libretro/libretro-common/include/libretro.h b/src/libretro/libretro-common/include/libretro.h index 1daa8557f2..d6be188d44 100644 --- a/src/libretro/libretro-common/include/libretro.h +++ b/src/libretro/libretro-common/include/libretro.h @@ -1795,15 +1795,11 @@ enum retro_mod #define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* struct retro_microphone_interface * -- - * Returns an interface that can be used to receive audio from the audio driver. + * Returns an interface that can be used to receive input from the microphone driver. * - * Create the interface and this callback will populate it. - * - * If the frontend and audio driver support microphones, - * all function pointers will be non-NULL. - * Otherwise, all function pointers will be NULL. - * - * Returns false if mic support is disabled. + * Returns true if microphone support is available, + * even if no microphones are plugged in. + * Returns false if mic support is disabled or unavailable. */ /* VFS functionality */ @@ -3828,6 +3824,26 @@ struct retro_throttle_state */ typedef struct retro_microphone retro_microphone_t; +/** + * Parameters for configuring a microphone. + * Some of these might not be honored, + * depending on the available hardware and driver configuration. + */ +typedef struct retro_microphone_params +{ + /** + * The desired sample rate of the microphone's input, in Hz. + * The microphone's input will be resampled, + * so cores can ask for whichever frequency they need. + * + * If zero, some reasonable default will be provided by the frontend + * (usually from its config file). + * + * @see retro_get_mic_rate_t + */ + unsigned rate; +} retro_microphone_params_t; + /** * Initializes a new microphone. * Assuming that microphone support is enabled and provided by the frontend, @@ -3840,26 +3856,50 @@ typedef struct retro_microphone retro_microphone_t; * * This should only be called from the main thread. * + * @param args[in] Parameters used to create the microphone. + * May be \c NULL, in which case the default value of each parameter will be used. + * * @returns Pointer to the newly-opened microphone, * or \c NULL if one couldn't be opened. * This likely means that no microphone is plugged in and recognized, * or the maximum number of supported microphones has been reached. * * @note Microphones are \em inactive by default; - * to begin recording, call set_microphone_state(new_handle, true). + * to begin recording, call set_mic_state(new_handle, true). + * @see retro_microphone_params_t */ -typedef retro_microphone_t *(RETRO_CALLCONV *retro_init_microphone_t)(void); +typedef retro_microphone_t *(RETRO_CALLCONV *retro_open_mic_t)(const retro_microphone_params_t *params); /** - * Closes a microphone that was initialized with \c retro_init_microphone. + * Closes a microphone that was initialized with \c open_mic. * Calling this function will stop all microphone activity * and free up the resources that it allocated. * Afterwards, the handle is invalid and must not be used. * - * @param microphone Pointer to the microphone that was allocated by init_microphone. + * A frontend may close opened microphones when unloading content, + * but cores should close their microphones anyway. + * + * @param microphone Pointer to the microphone that was allocated by \c open_mic. * If \c NULL, this function does nothing. */ -typedef void (RETRO_CALLCONV *retro_free_microphone_t)(retro_microphone_t *microphone); +typedef void (RETRO_CALLCONV *retro_close_mic_t)(retro_microphone_t *microphone); + +/** + * Returns the configured parameters of this microphone. + * These may differ from what was requested depending on + * the driver and device configuration. + * + * Will not change after the mic was opened. + * + * @param microphone[in] Opaque handle to the microphone + * whose parameters will be retrieved. + * @param params[out] The parameters object that the + * microphone's parameters will be copied to. + * + * @return \c true if the parameters were retrieved, + * \c false if there was an error. + */ +typedef bool (RETRO_CALLCONV *retro_get_mic_params_t)(const retro_microphone_t *microphone, retro_microphone_params_t *params); /** * Enables or disables the given microphone. @@ -3880,14 +3920,14 @@ typedef void (RETRO_CALLCONV *retro_free_microphone_t)(retro_microphone_t *micro * * @param microphone Opaque handle to the microphone * whose state will be adjusted. - * This will have been provided by \c init_microphone. + * This will have been provided by \c open_mic. * @param state \c true if the microphone should receive audio input, * @c false if it should be idle. * @returns \c true if the microphone's state was successfully set, * \c false if \c microphone is invalid * or if there was an error. */ -typedef bool (RETRO_CALLCONV *retro_set_microphone_state_t)(retro_microphone_t *microphone, bool state); +typedef bool (RETRO_CALLCONV *retro_set_mic_state_t)(retro_microphone_t *microphone, bool state); /** * Queries the active state of a microphone at the given index. @@ -3899,16 +3939,13 @@ typedef bool (RETRO_CALLCONV *retro_set_microphone_state_t)(retro_microphone_t * * @return true if the provided \c microphone is valid and active, * false if not or if there was an error. */ -typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_microphone_t *microphone); +typedef bool (RETRO_CALLCONV *retro_get_mic_state_t)(const retro_microphone_t *microphone); /** * Retrieves the input processed by the microphone since the last call. * \em Must be called every frame unless \c microphone is disabled, * similar to how \c retro_audio_sample_batch_t works. * - * If using \c RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK, - * then this function should be called within the provided callback. - * * @param microphone Opaque handle to the microphone * whose recent input will be retrieved. * @param samples The buffer that will be used to store the microphone's data. @@ -3926,32 +3963,31 @@ typedef bool (RETRO_CALLCONV *retro_get_microphone_state_t)(const retro_micropho * Will return -1 if the microphone is disabled, * the audio driver is paused, * or there was an error. - * - * @see RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK */ -typedef int (RETRO_CALLCONV *retro_get_microphone_input_t)(retro_microphone_t *microphone, int16_t* samples, size_t num_samples); +typedef int (RETRO_CALLCONV *retro_read_mic_t)(retro_microphone_t *microphone, int16_t* samples, size_t num_samples); + +#define RETRO_MICROPHONE_INTERFACE_VERSION 1 /** * An interface for querying the microphone and accessing data read from it. - * All fields in this interface are populated by the frontend - * by way of the the RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE callback. - * All fields will be non-NULL, - * even if the frontend (or its audio driver) doesn't support microphones. + * + * @see RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE */ struct retro_microphone_interface { /** - * True if the driver and frontend support microphones. - * Set by the frontend. + * The version of this microphone interface. + * Set by the core to request a particular version, + * and set by the frontend to indicate the returned version. + * 0 indicates that the interface is invalid or uninitialized. */ - bool supported; - - /** @copydoc retro_init_microphone_t */ - retro_init_microphone_t init_microphone; - retro_free_microphone_t free_microphone; - retro_set_microphone_state_t set_microphone_state; - retro_get_microphone_state_t get_microphone_state; - retro_get_microphone_input_t get_microphone_input; + unsigned interface_version; + retro_open_mic_t open_mic; + retro_close_mic_t close_mic; + retro_get_mic_params_t get_params; + retro_set_mic_state_t set_mic_state; + retro_get_mic_state_t get_mic_state; + retro_read_mic_t read_mic; }; /* Callbacks */ diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 54c50ee4f9..64373d0039 100644 --- a/src/libretro/libretro.cpp +++ b/src/libretro/libretro.cpp @@ -97,21 +97,6 @@ void retro_init(void) sprintf(retro_saves_directory, "%s", dir); initialize_screnlayout_data(&screen_layout_data); - - if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE, &micInterface) && micInterface.supported) - { // ...and if the current audio driver supports microphones... - log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support available in current audio driver\n"); - micHandle = micInterface.init_microphone(); - - if (micHandle) - { - log_cb(RETRO_LOG_INFO, "[melonDS] Initialized microphone\n"); - } - else - { - log_cb(RETRO_LOG_WARN, "[melonDS] Failed to initialize microphone, emulated device will receive silence\n"); - } - } } void retro_deinit(void) @@ -119,9 +104,9 @@ void retro_deinit(void) libretro_supports_bitmasks = false; libretro_supports_option_categories = false; - if (micHandle && micInterface.free_microphone) + if (micHandle && micInterface.close_mic) { // If we were playing with the microphone... - micInterface.free_microphone(micHandle); + micInterface.close_mic(micHandle); micHandle = NULL; micInterface = {0}; @@ -591,9 +576,9 @@ static void check_variables(bool init) else micNoiseType = WhiteNoise; - if (micNoiseType != MicInput && micInterface.supported && micHandle) + if (micNoiseType != MicInput && micInterface.interface_version && micHandle) { // If the player wants to stop using the real mic as the DS mic's input... - micInterface.set_microphone_state(micHandle, false); + micInterface.set_mic_state(micHandle, false); } } @@ -606,11 +591,11 @@ static void check_variables(bool init) noise_button_required = false; if ( noise_button_required && - micInterface.supported && + micInterface.interface_version && micHandle != NULL && !input_state.holding_noise_btn) { // If the player wants to require the noise button for mic input and they aren't already holding it... - micInterface.set_microphone_state(micHandle, false); + micInterface.set_mic_state(micHandle, false); } } @@ -802,9 +787,9 @@ void retro_run(void) case MicInput: // microphone input { s16 tmp[735]; - if (micHandle && micInterface.supported && micInterface.get_microphone_state(micHandle)) + if (micHandle && micInterface.interface_version && micInterface.get_mic_state(micHandle)) { // If the microphone is enabled and supported... - micInterface.get_microphone_input(micHandle, tmp, 735); + micInterface.read_mic(micHandle, tmp, 735); NDS::MicInputFrame(tmp, 735); break; } // If the mic isn't available, go to the default case @@ -969,6 +954,33 @@ static bool _handle_load_game(unsigned type, const struct retro_game_info *info) NDS::LoadGBAROM((u8*)info[1].data, info[1].size, gba_game_name, gba_save_path.c_str()); } + micInterface.interface_version = RETRO_MICROPHONE_INTERFACE_VERSION; + if (environ_cb(RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE, &micInterface)) + { // ...and if the current audio driver supports microphones... + if (micInterface.interface_version != RETRO_MICROPHONE_INTERFACE_VERSION) + { + log_cb(RETRO_LOG_WARN, "[melonDS] Expected mic interface version %u, got %u. Compatibility issues are possible.\n", + RETRO_MICROPHONE_INTERFACE_VERSION, micInterface.interface_version); + } + + log_cb(RETRO_LOG_DEBUG, "[melonDS] Microphone support available in current audio driver (version %u)\n", + micInterface.interface_version); + + retro_microphone_params_t params = { + .rate = 44100 // The core engine assumes this rate + }; + micHandle = micInterface.open_mic(¶ms); + + if (micHandle) + { + log_cb(RETRO_LOG_INFO, "[melonDS] Initialized microphone\n"); + } + else + { + log_cb(RETRO_LOG_WARN, "[melonDS] Failed to initialize microphone, emulated device will receive silence\n"); + } + } + return true; } From 513d9868ee90dd3d960ffb1c39c86eedaac55c37 Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Mon, 20 Feb 2023 20:11:08 -0500 Subject: [PATCH 7/8] Fix some compiler errors --- src/libretro/input.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libretro/input.cpp b/src/libretro/input.cpp index 0be92f701e..4d63865ac8 100644 --- a/src/libretro/input.cpp +++ b/src/libretro/input.cpp @@ -62,10 +62,10 @@ void update_input(InputState *state) if ( micNoiseType == MicInput && // If the player wants to use their real mic... noise_button_required && // ...and they don't always want it hot... (state->holding_noise_btn != state->previous_holding_noise_btn) && // ...and they just pressed or released the button... - micHandle != NULL && micInterface.supported // ...and the mic is valid... + micHandle != NULL && micInterface.interface_version > 0 // ...and the mic is valid... ) { - bool stateSet = micInterface.set_microphone_state(micHandle, state->holding_noise_btn); + bool stateSet = micInterface.set_mic_state(micHandle, state->holding_noise_btn); // ...then set the state of the mic to the active staste of the noise button if (!stateSet) From 16d6c1cd59cec60fdde23ef24ac78a0b69b6f13a Mon Sep 17 00:00:00 2001 From: Jesse Talavera-Greenberg Date: Mon, 5 Jun 2023 19:06:09 -0400 Subject: [PATCH 8/8] Update libretro.h --- .../libretro-common/include/libretro.h | 269 ++++++++++-------- 1 file changed, 157 insertions(+), 112 deletions(-) diff --git a/src/libretro/libretro-common/include/libretro.h b/src/libretro/libretro-common/include/libretro.h index d6be188d44..a5ee1f0f30 100644 --- a/src/libretro/libretro-common/include/libretro.h +++ b/src/libretro/libretro-common/include/libretro.h @@ -928,8 +928,6 @@ enum retro_mod * anything else. * It is recommended to expose all relevant pointers through * retro_get_memory_* as well. - * - * Can be called from retro_init and retro_load_game. */ #define RETRO_ENVIRONMENT_SET_GEOMETRY 37 /* const struct retro_game_geometry * -- @@ -1793,13 +1791,22 @@ enum retro_mod * this environment call to query support. */ -#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (74 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#define RETRO_ENVIRONMENT_GET_JIT_CAPABLE 74 + /* bool * -- + * Result is set to true if the frontend has already verified JIT can be + * used, mainly for use iOS/tvOS. On other platforms the result is true. + */ + +#define RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE (75 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* struct retro_microphone_interface * -- * Returns an interface that can be used to receive input from the microphone driver. * * Returns true if microphone support is available, * even if no microphones are plugged in. * Returns false if mic support is disabled or unavailable. + * + * This callback can be invoked at any time, + * even before the microphone driver is ready. */ /* VFS functionality */ @@ -1976,13 +1983,13 @@ struct retro_vfs_interface_info enum retro_hw_render_interface_type { - RETRO_HW_RENDER_INTERFACE_VULKAN = 0, - RETRO_HW_RENDER_INTERFACE_D3D9 = 1, - RETRO_HW_RENDER_INTERFACE_D3D10 = 2, - RETRO_HW_RENDER_INTERFACE_D3D11 = 3, - RETRO_HW_RENDER_INTERFACE_D3D12 = 4, + RETRO_HW_RENDER_INTERFACE_VULKAN = 0, + RETRO_HW_RENDER_INTERFACE_D3D9 = 1, + RETRO_HW_RENDER_INTERFACE_D3D10 = 2, + RETRO_HW_RENDER_INTERFACE_D3D11 = 3, + RETRO_HW_RENDER_INTERFACE_D3D12 = 4, RETRO_HW_RENDER_INTERFACE_GSKIT_PS2 = 5, - RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX + RETRO_HW_RENDER_INTERFACE_DUMMY = INT_MAX }; /* Base struct. All retro_hw_render_interface_* types @@ -2758,9 +2765,17 @@ enum retro_hw_context_type /* Vulkan, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE. */ RETRO_HW_CONTEXT_VULKAN = 6, - /* Direct3D, set version_major to select the type of interface - * returned by RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ - RETRO_HW_CONTEXT_DIRECT3D = 7, + /* Direct3D11, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_D3D11 = 7, + + /* Direct3D10, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_D3D10 = 8, + + /* Direct3D12, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_D3D12 = 9, + + /* Direct3D9, see RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE */ + RETRO_HW_CONTEXT_D3D9 = 10, RETRO_HW_CONTEXT_DUMMY = INT_MAX }; @@ -3819,8 +3834,7 @@ struct retro_throttle_state /** * Opaque handle to a microphone that's been opened for use. - * You don't access microphone objects directly; - * use the retro_microphone_interface. + * The underlying object is accessed or created with \c retro_microphone_interface_t. */ typedef struct retro_microphone retro_microphone_t; @@ -3845,127 +3859,43 @@ typedef struct retro_microphone_params } retro_microphone_params_t; /** - * Initializes a new microphone. - * Assuming that microphone support is enabled and provided by the frontend, - * you can call this whenever your core needs it. - * You could call this to keep a microphone throughout your core's lifetime, - * or you could call this when a microphone is plugged in to the emulated device. - * - * The returned handle will be valid until it's freed, - * even if the audio driver is reinitialized. - * - * This should only be called from the main thread. - * - * @param args[in] Parameters used to create the microphone. - * May be \c NULL, in which case the default value of each parameter will be used. - * - * @returns Pointer to the newly-opened microphone, - * or \c NULL if one couldn't be opened. - * This likely means that no microphone is plugged in and recognized, - * or the maximum number of supported microphones has been reached. - * - * @note Microphones are \em inactive by default; - * to begin recording, call set_mic_state(new_handle, true). - * @see retro_microphone_params_t + * @copydoc retro_microphone_interface::open_mic */ typedef retro_microphone_t *(RETRO_CALLCONV *retro_open_mic_t)(const retro_microphone_params_t *params); /** - * Closes a microphone that was initialized with \c open_mic. - * Calling this function will stop all microphone activity - * and free up the resources that it allocated. - * Afterwards, the handle is invalid and must not be used. - * - * A frontend may close opened microphones when unloading content, - * but cores should close their microphones anyway. - * - * @param microphone Pointer to the microphone that was allocated by \c open_mic. - * If \c NULL, this function does nothing. + * @copydoc retro_microphone_interface::close_mic */ typedef void (RETRO_CALLCONV *retro_close_mic_t)(retro_microphone_t *microphone); /** - * Returns the configured parameters of this microphone. - * These may differ from what was requested depending on - * the driver and device configuration. - * - * Will not change after the mic was opened. - * - * @param microphone[in] Opaque handle to the microphone - * whose parameters will be retrieved. - * @param params[out] The parameters object that the - * microphone's parameters will be copied to. - * - * @return \c true if the parameters were retrieved, - * \c false if there was an error. + * @copydoc retro_microphone_interface::get_params */ typedef bool (RETRO_CALLCONV *retro_get_mic_params_t)(const retro_microphone_t *microphone, retro_microphone_params_t *params); /** - * Enables or disables the given microphone. - * Microphones are disabled by default - * and must be explicitly enabled before they can be used. - * Disabled microphones will not process incoming audio samples, - * and will therefore have minimal impact on overall performance. - * You may enable microphones throughout the lifetime of a core, - * or only in instances where they're needed. - * - * Your core should be able to operate without microphone input; - * we suggest substituting silence in this case. - * - * Calling this function while the audio driver is paused - * \em will update the state of the microphone, - * but it won't reactivate it. - * You'll have to resume the audio driver for that. - * - * @param microphone Opaque handle to the microphone - * whose state will be adjusted. - * This will have been provided by \c open_mic. - * @param state \c true if the microphone should receive audio input, - * @c false if it should be idle. - * @returns \c true if the microphone's state was successfully set, - * \c false if \c microphone is invalid - * or if there was an error. + * @copydoc retro_microphone_interface::set_mic_state */ typedef bool (RETRO_CALLCONV *retro_set_mic_state_t)(retro_microphone_t *microphone, bool state); /** - * Queries the active state of a microphone at the given index. - * Will return whether the microphone is enabled, - * even if the driver is paused. - * - * @param microphone Opaque handle to the microphone - * whose state will be queried. - * @return true if the provided \c microphone is valid and active, - * false if not or if there was an error. + * @copydoc retro_microphone_interface::get_mic_state */ typedef bool (RETRO_CALLCONV *retro_get_mic_state_t)(const retro_microphone_t *microphone); /** - * Retrieves the input processed by the microphone since the last call. - * \em Must be called every frame unless \c microphone is disabled, - * similar to how \c retro_audio_sample_batch_t works. - * - * @param microphone Opaque handle to the microphone - * whose recent input will be retrieved. - * @param samples The buffer that will be used to store the microphone's data. - * Microphone input is in mono (i.e. one number per sample). - * Should be large enough to accommodate the expected number of samples per frame; - * for example, a 44.1kHz sample rate at 60 FPS would require space for 735 samples. - * @param num_samples The size of the data buffer, in samples (\em not bytes). - * Microphone input is in mono, so a "frame" and a "sample" are equivalent in length here. - * - * @return The number of samples that were copied into \c samples. - * Will return 0 if \c microphone is still pending - * because the driver hasn't finished initializing. - * This is not an error. - * - * Will return -1 if the microphone is disabled, - * the audio driver is paused, - * or there was an error. + * @copydoc retro_microphone_interface::read_mic */ typedef int (RETRO_CALLCONV *retro_read_mic_t)(retro_microphone_t *microphone, int16_t* samples, size_t num_samples); +/** + * The current version of the microphone interface. + * Will be incremented whenever \c retro_microphone_interface or \c retro_microphone_params_t + * receive new fields. + * + * Frontends using cores built against older mic interface versions + * should not access fields introduced in newer versions. + */ #define RETRO_MICROPHONE_INTERFACE_VERSION 1 /** @@ -3982,11 +3912,126 @@ struct retro_microphone_interface * 0 indicates that the interface is invalid or uninitialized. */ unsigned interface_version; + + /** + * Initializes a new microphone. + * Assuming that microphone support is enabled and provided by the frontend, + * cores may call this function whenever necessary. + * A microphone could be opened throughout a core's lifetime, + * or it could wait until a microphone is plugged in to the emulated device. + * + * The returned handle will be valid until it's freed, + * even if the audio driver is reinitialized. + * + * This function is not guaranteed to be thread-safe. + * + * @param args[in] Parameters used to create the microphone. + * May be \c NULL, in which case the default value of each parameter will be used. + * + * @returns Pointer to the newly-opened microphone, + * or \c NULL if one couldn't be opened. + * This likely means that no microphone is plugged in and recognized, + * or the maximum number of supported microphones has been reached. + * + * @note Microphones are \em inactive by default; + * to begin capturing audio, call \c set_mic_state. + * @see retro_microphone_params_t + */ retro_open_mic_t open_mic; + + /** + * Closes a microphone that was initialized with \c open_mic. + * Calling this function will stop all microphone activity + * and free up the resources that it allocated. + * Afterwards, the handle is invalid and must not be used. + * + * A frontend may close opened microphones when unloading content, + * but this behavior is not guaranteed. + * Cores should close their microphones when exiting, just to be safe. + * + * @param microphone Pointer to the microphone that was allocated by \c open_mic. + * If \c NULL, this function does nothing. + * + * @note The handle might be reused if another microphone is opened later. + */ retro_close_mic_t close_mic; + + /** + * Returns the configured parameters of this microphone. + * These may differ from what was requested depending on + * the driver and device configuration. + * + * Cores should check these values before they start fetching samples. + * + * Will not change after the mic was opened. + * + * @param microphone[in] Opaque handle to the microphone + * whose parameters will be retrieved. + * @param params[out] The parameters object that the + * microphone's parameters will be copied to. + * + * @return \c true if the parameters were retrieved, + * \c false if there was an error. + */ retro_get_mic_params_t get_params; + + /** + * Enables or disables the given microphone. + * Microphones are disabled by default + * and must be explicitly enabled before they can be used. + * Disabled microphones will not process incoming audio samples, + * and will therefore have minimal impact on overall performance. + * Cores may enable microphones throughout their lifetime, + * or only for periods where they're needed. + * + * Cores that accept microphone input should be able to operate without it; + * we suggest substituting silence in this case. + * + * @param microphone Opaque handle to the microphone + * whose state will be adjusted. + * This will have been provided by \c open_mic. + * @param state \c true if the microphone should receive audio input, + * \c false if it should be idle. + * @returns \c true if the microphone's state was successfully set, + * \c false if \c microphone is invalid + * or if there was an error. + */ retro_set_mic_state_t set_mic_state; + + /** + * Queries the active state of a microphone at the given index. + * Will return whether the microphone is enabled, + * even if the driver is paused. + * + * @param microphone Opaque handle to the microphone + * whose state will be queried. + * @return \c true if the provided \c microphone is valid and active, + * \c false if not or if there was an error. + */ retro_get_mic_state_t get_mic_state; + + /** + * Retrieves the input processed by the microphone since the last call. + * \em Must be called every frame unless \c microphone is disabled, + * similar to how \c retro_audio_sample_batch_t works. + * + * @param[in] microphone Opaque handle to the microphone + * whose recent input will be retrieved. + * @param[out] samples The buffer that will be used to store the microphone's data. + * Microphone input is in mono (i.e. one number per sample). + * Should be large enough to accommodate the expected number of samples per frame; + * for example, a 44.1kHz sample rate at 60 FPS would require space for 735 samples. + * @param[in] num_samples The size of the data buffer in samples (\em not bytes). + * Microphone input is in mono, so a "frame" and a "sample" are equivalent in length here. + * + * @return The number of samples that were copied into \c samples. + * If \c microphone is pending driver initialization, + * this function will copy silence of the requested length into \c samples. + * + * Will return -1 if the microphone is disabled, + * the audio driver is paused, + * or there was an error. + */ retro_read_mic_t read_mic; };