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/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; diff --git a/src/libretro/input.cpp b/src/libretro/input.cpp index 5d710f8c79..4d63865ac8 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.interface_version > 0 // ...and the mic is valid... + ) + { + 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) + 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..a5ee1f0f30 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) */ @@ -926,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 * -- @@ -1765,6 +1765,50 @@ enum retro_mod * (see enum retro_savestate_context) */ +#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_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 */ /* File paths: @@ -1939,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 @@ -2721,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 }; @@ -3009,13 +3061,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 +3832,209 @@ struct retro_throttle_state float rate; }; +/** + * Opaque handle to a microphone that's been opened for use. + * The underlying object is accessed or created with \c retro_microphone_interface_t. + */ +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; + +/** + * @copydoc retro_microphone_interface::open_mic + */ +typedef retro_microphone_t *(RETRO_CALLCONV *retro_open_mic_t)(const retro_microphone_params_t *params); + +/** + * @copydoc retro_microphone_interface::close_mic + */ +typedef void (RETRO_CALLCONV *retro_close_mic_t)(retro_microphone_t *microphone); + +/** + * @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); + +/** + * @copydoc retro_microphone_interface::set_mic_state + */ +typedef bool (RETRO_CALLCONV *retro_set_mic_state_t)(retro_microphone_t *microphone, bool state); + +/** + * @copydoc retro_microphone_interface::get_mic_state + */ +typedef bool (RETRO_CALLCONV *retro_get_mic_state_t)(const retro_microphone_t *microphone); + +/** + * @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 + +/** + * An interface for querying the microphone and accessing data read from it. + * + * @see RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE + */ +struct retro_microphone_interface +{ + /** + * 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. + */ + 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; +}; + /* Callbacks */ /* Environment callback. Gives implementations a way of performing diff --git a/src/libretro/libretro.cpp b/src/libretro/libretro.cpp index 867ead25ae..64373d0039 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; @@ -62,8 +63,9 @@ static bool hybrid_options = true; 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 { @@ -101,6 +103,15 @@ void retro_deinit(void) { libretro_supports_bitmasks = false; libretro_supports_option_categories = false; + + if (micHandle && micInterface.close_mic) + { // If we were playing with the microphone... + micInterface.close_mic(micHandle); + micHandle = NULL; + micInterface = {0}; + + log_cb(RETRO_LOG_DEBUG, "[melonDS] Released previously-allocated microphone\n"); + } } unsigned retro_api_version(void) @@ -558,10 +569,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 = 0; + micNoiseType = WhiteNoise; + + 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_mic_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 + noise_button_required = false; + + if ( noise_button_required && + 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_mic_state(micHandle, false); + } } var.key = "melonds_audio_bitrate"; @@ -733,20 +768,39 @@ 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 + { + Frontend::Mic_FeedNoise(); // despite the name, this feeds a blow noise + break; + } + case MicInput: // microphone input + { + s16 tmp[735]; + if (micHandle && micInterface.interface_version && micInterface.get_mic_state(micHandle)) + { // If the microphone is enabled and supported... + micInterface.read_mic(micHandle, tmp, 735); + NDS::MicInputFrame(tmp, 735); + break; + } // If the mic isn't available, go to the default case + } + default: + Frontend::Mic_FeedSilence(); } } else { - NDS::MicInputFrame(NULL, 0); + Frontend::Mic_FeedSilence(); } if (current_renderer != CurrentRenderer::None) NDS::RunFrame(); @@ -769,23 +823,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) { /* @@ -917,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; } 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",