From b487c3cace54753720eb89be05455fe7b0b8d280 Mon Sep 17 00:00:00 2001 From: Zoran Vuckovic Date: Mon, 4 Jun 2018 07:48:08 +0200 Subject: [PATCH] Add MIDI support --- Makefile.common | 8 + Makefile.msvc | 1 + Makefile.win | 1 + command.c | 2 + config.def.h | 5 + configuration.c | 49 +++ configuration.h | 9 + driver.c | 13 + driver.h | 12 +- dynamic.c | 17 + intl/msg_hash_lbl.h | 12 + intl/msg_hash_us.c | 24 ++ intl/msg_hash_us.h | 22 + libretro-common/include/libretro.h | 17 + list_special.c | 10 + list_special.h | 1 + menu/cbs/menu_cbs_deferred_push.c | 7 + menu/cbs/menu_cbs_get_value.c | 1 + menu/cbs/menu_cbs_ok.c | 7 + menu/cbs/menu_cbs_sublabel.c | 20 + menu/cbs/menu_cbs_title.c | 6 + menu/drivers/materialui.c | 2 + menu/menu_cbs.h | 1 + menu/menu_displaylist.c | 21 + menu/menu_displaylist.h | 1 + menu/menu_setting.c | 174 +++++++- midi/drivers/null_midi.c | 98 +++++ midi/drivers/winmm_midi.c | 628 +++++++++++++++++++++++++++++ midi/midi_driver.c | 585 +++++++++++++++++++++++++++ midi/midi_driver.h | 77 ++++ msg_hash.h | 7 + qb/config.libs.sh | 1 + 32 files changed, 1832 insertions(+), 7 deletions(-) create mode 100644 midi/drivers/null_midi.c create mode 100644 midi/drivers/winmm_midi.c create mode 100644 midi/midi_driver.c create mode 100644 midi/midi_driver.h diff --git a/Makefile.common b/Makefile.common index 2cb48b05d9..d621a75042 100644 --- a/Makefile.common +++ b/Makefile.common @@ -264,6 +264,8 @@ OBJ += frontend/frontend.o \ $(LIBRETRO_COMM_DIR)/features/features_cpu.o \ performance_counters.o \ verbosity.o \ + midi/midi_driver.o \ + midi/drivers/null_midi.o ifeq ($(HAVE_RUNAHEAD), 1) DEFINES += -DHAVE_RUNAHEAD @@ -686,6 +688,12 @@ ifeq ($(HAVE_XAUDIO), 1) LIBS += -lole32 endif +ifeq ($(HAVE_WINMM), 1) + OBJ += midi/drivers/winmm_midi.o + DEFINES += -DHAVE_WINMM + LIBS += -lwinmm +endif + # Audio Resamplers ifeq ($(HAVE_NEON),1) diff --git a/Makefile.msvc b/Makefile.msvc index 48405fd5c2..3ece3af7a4 100644 --- a/Makefile.msvc +++ b/Makefile.msvc @@ -25,6 +25,7 @@ HAVE_XAUDIO := 1 HAVE_XINPUT := 1 HAVE_WASAPI := 0 HAVE_THREAD_STORAGE := 1 +HAVE_WINMM := 1 HAVE_RPNG := 1 HAVE_ZLIB := 1 diff --git a/Makefile.win b/Makefile.win index 26c3f7aa37..932d0f896f 100644 --- a/Makefile.win +++ b/Makefile.win @@ -18,6 +18,7 @@ HAVE_PYTHON = 0 DYNAMIC = 1 HAVE_XINPUT = 1 +HAVE_WINMM = 1 HAVE_SDL := 0 HAVE_SDL2 := 0 diff --git a/command.c b/command.c index d85fca7ac0..dcc5e27b1d 100644 --- a/command.c +++ b/command.c @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include "config.h" @@ -2040,6 +2041,7 @@ TODO: Add a setting for these tweaks */ command_event_save_auto_state(); break; case CMD_EVENT_AUDIO_STOP: + midi_driver_set_all_sounds_off(); return audio_driver_stop(); case CMD_EVENT_AUDIO_START: return audio_driver_start(rarch_ctl(RARCH_CTL_IS_SHUTDOWN, NULL)); diff --git a/config.def.h b/config.def.h index c232c3b8d1..aafa2a4297 100644 --- a/config.def.h +++ b/config.def.h @@ -689,6 +689,11 @@ static enum resampler_quality audio_resampler_quality_level = RESAMPLER_QUALITY_ static enum resampler_quality audio_resampler_quality_level = RESAMPLER_QUALITY_NORMAL; #endif +/* MIDI */ +static const char *midi_input = "Off"; +static const char *midi_output = "Off"; +static const unsigned midi_volume = 100; + #if defined(ANDROID) #if defined(ANDROID_ARM) static char buildbot_server_url[] = "http://buildbot.libretro.com/nightly/android/latest/armeabi-v7a/"; diff --git a/configuration.c b/configuration.c index f4a12c8cfb..1d09d5052f 100644 --- a/configuration.c +++ b/configuration.c @@ -52,6 +52,8 @@ #include "tasks/tasks_internal.h" +#include "../list_special.h" + static const char* invalid_filename_chars[] = { /* https://support.microsoft.com/en-us/help/905231/information-about-the-characters-that-you-cannot-use-in-site-names--fo */ "~", "#", "%", "&", "*", "{", "}", "\\", ":", "[", "]", "?", "/", "|", "\'", "\"", @@ -281,6 +283,11 @@ enum record_driver_enum RECORD_NULL }; +enum midi_driver_enum +{ + MIDI_WINMM = RECORD_NULL + 1, + MIDI_NULL +}; #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) || defined(__CELLOS_LV2__) static enum video_driver_enum VIDEO_DEFAULT_DRIVER = VIDEO_GL; @@ -386,6 +393,12 @@ static enum record_driver_enum RECORD_DEFAULT_DRIVER = RECORD_FFMPEG; static enum record_driver_enum RECORD_DEFAULT_DRIVER = RECORD_NULL; #endif +#ifdef HAVE_WINMM +static enum midi_driver_enum MIDI_DEFAULT_DRIVER = MIDI_WINMM; +#else +static enum midi_driver_enum MIDI_DEFAULT_DRIVER = MIDI_NULL; +#endif + #if defined(XENON) static enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_XENON360; #elif defined(_XBOX360) || defined(_XBOX) || defined(HAVE_XINPUT2) || defined(HAVE_XINPUT_XBOX1) @@ -1005,6 +1018,26 @@ const char *config_get_default_menu(void) return "null"; } +const char *config_get_default_midi(void) +{ + enum midi_driver_enum default_driver = MIDI_DEFAULT_DRIVER; + + switch (default_driver) + { + case MIDI_WINMM: + return "winmm"; + case MIDI_NULL: + break; + } + + return "null"; +} + +const char *config_get_midi_driver_options(void) +{ + return char_list_new_special(STRING_LIST_MIDI_DRIVERS, NULL); +} + bool config_overlay_enable_default(void) { if (g_defaults.overlay.set) @@ -1046,6 +1079,9 @@ static struct config_array_setting *populate_settings_array(settings_t *settings SETTING_ARRAY("bundle_assets_dst_path_subdir", settings->arrays.bundle_assets_dst_subdir, false, NULL, true); SETTING_ARRAY("led_driver", settings->arrays.led_driver, false, NULL, true); SETTING_ARRAY("netplay_mitm_server", settings->arrays.netplay_mitm_server, false, NULL, true); + SETTING_ARRAY("midi_driver", settings->arrays.midi_driver, false, NULL, true); + SETTING_ARRAY("midi_input", settings->arrays.midi_input, true, midi_input, true); + SETTING_ARRAY("midi_output", settings->arrays.midi_output, true, midi_output, true); *size = count; return tmp; @@ -1532,6 +1568,8 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings, SETTING_UINT("run_ahead_frames", &settings->uints.run_ahead_frames, true, 1, false); + SETTING_UINT("midi_volume", &settings->uints.midi_volume, true, midi_volume, false); + *size = count; return tmp; @@ -1585,6 +1623,7 @@ static void config_set_defaults(void) const char *def_led = config_get_default_led(); const char *def_location = config_get_default_location(); const char *def_record = config_get_default_record(); + const char *def_midi = config_get_default_midi(); const char *def_mitm = netplay_mitm_server; struct config_float_setting *float_settings = populate_settings_float (settings, &float_settings_size); struct config_bool_setting *bool_settings = populate_settings_bool (settings, &bool_settings_size); @@ -1665,6 +1704,9 @@ static void config_set_defaults(void) if (def_record) strlcpy(settings->arrays.record_driver, def_record, sizeof(settings->arrays.record_driver)); + if (def_midi) + strlcpy(settings->arrays.midi_driver, + def_midi, sizeof(settings->arrays.midi_driver)); if (def_mitm) strlcpy(settings->arrays.netplay_mitm_server, def_mitm, sizeof(settings->arrays.netplay_mitm_server)); @@ -1982,6 +2024,13 @@ static void config_set_defaults(void) free(temp_str); } + if (midi_input) + strlcpy(settings->arrays.midi_input, + midi_input, sizeof(settings->arrays.midi_input)); + if (midi_output) + strlcpy(settings->arrays.midi_output, + midi_output, sizeof(settings->arrays.midi_output)); + /* Avoid reloading config on every content load */ if (default_block_config_read) rarch_ctl(RARCH_CTL_SET_BLOCK_CONFIG_READ, NULL); diff --git a/configuration.h b/configuration.h index a95ba80e43..9b95360ee8 100644 --- a/configuration.h +++ b/configuration.h @@ -403,6 +403,8 @@ typedef struct settings unsigned led_map[MAX_LEDS]; unsigned run_ahead_frames; + + unsigned midi_volume; } uints; struct @@ -424,6 +426,7 @@ typedef struct settings char audio_resampler[32]; char input_driver[32]; char input_joypad_driver[32]; + char midi_driver[32]; char input_keyboard_layout[64]; @@ -437,6 +440,9 @@ typedef struct settings char bundle_assets_dst_subdir[PATH_MAX_LENGTH]; char netplay_mitm_server[255]; + + char midi_input[32]; + char midi_output[32]; } arrays; struct @@ -583,6 +589,9 @@ const char *config_get_default_joypad(void); **/ const char *config_get_default_menu(void); +const char *config_get_default_midi(void); +const char *config_get_midi_driver_options(void); + const char *config_get_default_record(void); /** diff --git a/driver.c b/driver.c index 989da59127..68f5b314d7 100644 --- a/driver.c +++ b/driver.c @@ -38,6 +38,7 @@ #include "location/location_driver.h" #include "wifi/wifi_driver.h" #include "led/led_driver.h" +#include "midi/midi_driver.h" #include "configuration.h" #include "core.h" #include "core_info.h" @@ -113,6 +114,12 @@ static const void *find_driver_nonempty(const char *label, int i, if (drv) strlcpy(s, record_driver_find_ident(i), len); } + else if (string_is_equal(label, "midi_driver")) + { + drv = midi_driver_find_handle(i); + if (drv) + strlcpy(s, midi_driver_find_ident(i), len); + } else if (string_is_equal(label, "audio_resampler_driver")) { drv = audio_resampler_driver_find_handle(i); @@ -396,6 +403,9 @@ void drivers_init(int flags) { led_driver_init(); } + + if (flags & DRIVER_MIDI_MASK) + midi_driver_init(); } @@ -459,6 +469,9 @@ void driver_uninit(int flags) if ((flags & DRIVER_AUDIO_MASK) && !audio_driver_owns_driver()) audio_driver_destroy_data(); + + if (flags & DRIVER_MIDI_MASK) + midi_driver_free(); } bool driver_ctl(enum driver_ctl_state state, void *data) diff --git a/driver.h b/driver.h index dbc56eb599..6f954ce519 100644 --- a/driver.h +++ b/driver.h @@ -35,7 +35,8 @@ RETRO_BEGIN_DECLS | DRIVER_MENU_MASK \ | DRIVERS_VIDEO_INPUT_MASK \ | DRIVER_WIFI_MASK \ - | DRIVER_LED_MASK ) + | DRIVER_LED_MASK \ + | DRIVER_MIDI_MASK ) #define DRIVERS_CMD_ALL_BUT_MENU \ ( DRIVER_AUDIO_MASK \ @@ -45,7 +46,8 @@ RETRO_BEGIN_DECLS | DRIVER_LOCATION_MASK \ | DRIVERS_VIDEO_INPUT_MASK \ | DRIVER_WIFI_MASK \ - | DRIVER_LED_MASK ) + | DRIVER_LED_MASK \ + | DRIVER_MIDI_MASK ) enum { @@ -57,7 +59,8 @@ enum DRIVER_MENU, DRIVERS_VIDEO_INPUT, DRIVER_WIFI, - DRIVER_LED + DRIVER_LED, + DRIVER_MIDI }; enum @@ -70,7 +73,8 @@ enum DRIVER_MENU_MASK = 1 << DRIVER_MENU, DRIVERS_VIDEO_INPUT_MASK = 1 << DRIVERS_VIDEO_INPUT, DRIVER_WIFI_MASK = 1 << DRIVER_WIFI, - DRIVER_LED_MASK = 1 << DRIVER_LED + DRIVER_LED_MASK = 1 << DRIVER_LED, + DRIVER_MIDI_MASK = 1 << DRIVER_MIDI }; enum driver_ctl_state diff --git a/dynamic.c b/dynamic.c index 91394d9509..f3e3631549 100644 --- a/dynamic.c +++ b/dynamic.c @@ -55,6 +55,7 @@ #include "performance_counters.h" #include "gfx/video_driver.h" #include "led/led_driver.h" +#include "midi/midi_driver.h" #include "cores/internal_cores.h" #include "frontend/frontend_driver.h" @@ -1778,6 +1779,22 @@ bool rarch_environment_cb(unsigned cmd, void *data) } } break; + + case RETRO_ENVIRONMENT_GET_MIDI_INTERFACE: + { + struct retro_midi_interface *midi_interface = + (struct retro_midi_interface *)data; + + if (midi_interface) + { + midi_interface->input_enabled = midi_driver_input_enabled; + midi_interface->output_enabled = midi_driver_output_enabled; + midi_interface->read = midi_driver_read; + midi_interface->write = midi_driver_write; + midi_interface->flush = midi_driver_flush; + } + } + break; default: RARCH_LOG("Environ UNSUPPORTED (#%u).\n", cmd); diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 5a5dab05f1..2c23f0256c 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -333,6 +333,8 @@ MSG_HASH(MENU_ENUM_LABEL_DEFERRED_PLAYLIST_SETTINGS_LIST, "deferred_playlist_settings") MSG_HASH(MENU_ENUM_LABEL_DEFERRED_PRIVACY_SETTINGS_LIST, "deferred_privacy_settings_list") +MSG_HASH(MENU_ENUM_LABEL_DEFERRED_MIDI_SETTINGS_LIST, + "deferred_midi_settings_list") MSG_HASH(MENU_ENUM_LABEL_DEFERRED_RDB_ENTRY_DETAIL, "deferred_rdb_entry_detail") MSG_HASH(MENU_ENUM_LABEL_DEFERRED_RECORDING_SETTINGS_LIST, @@ -789,6 +791,8 @@ MSG_HASH(MENU_ENUM_LABEL_POINTER_ENABLE, "menu_pointer_enable") MSG_HASH(MENU_ENUM_LABEL_PRIVACY_SETTINGS, "privacy_settings") +MSG_HASH(MENU_ENUM_LABEL_MIDI_SETTINGS, + "midi_settings") MSG_HASH(MENU_ENUM_LABEL_QUIT_RETROARCH, "quit_retroarch") MSG_HASH(MENU_ENUM_LABEL_RDB_ENTRY, @@ -889,6 +893,8 @@ MSG_HASH(MENU_ENUM_LABEL_RECORD_CONFIG, "record_config") MSG_HASH(MENU_ENUM_LABEL_RECORD_DRIVER, "record_driver") +MSG_HASH(MENU_ENUM_LABEL_MIDI_DRIVER, + "midi_driver") MSG_HASH(MENU_ENUM_LABEL_RECORD_ENABLE, "record_enable") MSG_HASH(MENU_ENUM_LABEL_RECORD_PATH, @@ -1517,3 +1523,9 @@ MSG_HASH(MENU_ENUM_LABEL_DEFERRED_QUICK_MENU_OVERRIDE_OPTIONS, "deferred_quick_menu_override_options") MSG_HASH(MENU_ENUM_LABEL_DISCORD_IN_MENU, "discord_in_menu") +MSG_HASH(MENU_ENUM_LABEL_MIDI_INPUT, + "midi_input") +MSG_HASH(MENU_ENUM_LABEL_MIDI_OUTPUT, + "midi_output") +MSG_HASH(MENU_ENUM_LABEL_MIDI_VOLUME, + "midi_volume") diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 5acc456d3f..99c38105e3 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -2037,6 +2037,30 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "Sets the blue value of the OSD text color. Valid values are between 0 and 255."); break; + case MENU_ENUM_LABEL_MIDI_DRIVER: + snprintf(s, len, + "MIDI driver to use."); + break; + case MENU_ENUM_LABEL_MIDI_INPUT: + snprintf(s, len, + "Sets the input device (driver specific).\n" + "When set to \"Off\", MIDI input will be disabled.\n" + "Device name can also be typed in."); + break; + case MENU_ENUM_LABEL_MIDI_OUTPUT: + snprintf(s, len, + "Sets the output device (driver specific).\n" + "When set to \"Off\", MIDI output will be disabled.\n" + "Device name can also be typed in.\n" + " \n" + "When MIDI output is enabled and core and game/app support MIDI output,\n" + "some or all sounds (depends on game/app) will be generated by MIDI device.\n" + "In case of \"null\" MIDI driver this means that those sounds won't be audible."); + break; + case MENU_ENUM_LABEL_MIDI_VOLUME: + snprintf(s, len, + "Sets the master volume of the output device."); + break; default: if (string_is_empty(s)) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE), len); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index fec6f3ec30..a7452e08e1 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1261,6 +1261,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_PRESENT, "Present") MSG_HASH(MENU_ENUM_LABEL_VALUE_PRIVACY_SETTINGS, "Privacy") +MSG_HASH(MENU_ENUM_LABEL_VALUE_MIDI_SETTINGS, + "MIDI") MSG_HASH(MENU_ENUM_LABEL_VALUE_QUIT_RETROARCH, "Quit RetroArch") MSG_HASH(MENU_ENUM_LABEL_VALUE_RDB_ENTRY_ANALOG, @@ -1331,6 +1333,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_RECORD_CONFIG, "Load Recording Config...") MSG_HASH(MENU_ENUM_LABEL_VALUE_RECORD_DRIVER, "Record Driver") +MSG_HASH(MENU_ENUM_LABEL_VALUE_MIDI_DRIVER, + "MIDI Driver") MSG_HASH(MENU_ENUM_LABEL_VALUE_RECORD_ENABLE, "Enable Recording") MSG_HASH(MENU_ENUM_LABEL_VALUE_RECORD_PATH, @@ -1977,6 +1981,8 @@ MSG_HASH(MENU_ENUM_SUBLABEL_USER_SETTINGS, "Change account, username, and language settings.") MSG_HASH(MENU_ENUM_SUBLABEL_PRIVACY_SETTINGS, "Change your privacy settings.") +MSG_HASH(MENU_ENUM_SUBLABEL_MIDI_SETTINGS, + "Change MIDI settings.") MSG_HASH(MENU_ENUM_SUBLABEL_DIRECTORY_SETTINGS, "Change default directories where files are located.") MSG_HASH(MENU_ENUM_SUBLABEL_PLAYLIST_SETTINGS, @@ -2864,6 +2870,10 @@ MSG_HASH( MENU_ENUM_SUBLABEL_RECORD_DRIVER, "Record driver to use." ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MIDI_DRIVER, + "MIDI driver to use." + ) MSG_HASH( MENU_ENUM_SUBLABEL_WIFI_DRIVER, "WiFi driver to use." @@ -3726,3 +3736,15 @@ MSG_HASH( MENU_ENUM_SUBLABEL_DISCORD_ALLOW, "Enable or disable Discord support. Will not work with the browser version, only native desktop client." ) +MSG_HASH(MENU_ENUM_LABEL_VALUE_MIDI_INPUT, + "Input") +MSG_HASH(MENU_ENUM_SUBLABEL_MIDI_INPUT, + "Select input device.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_MIDI_OUTPUT, + "Output") +MSG_HASH(MENU_ENUM_SUBLABEL_MIDI_OUTPUT, + "Select output device.") +MSG_HASH(MENU_ENUM_LABEL_VALUE_MIDI_VOLUME, + "Volume") +MSG_HASH(MENU_ENUM_SUBLABEL_MIDI_VOLUME, + "Set output volume (%).") diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 2516bba4ee..5f0cd6a893 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1163,6 +1163,23 @@ struct retro_led_interface * * State will never be saved when using Hard Disable Audio. */ +#define RETRO_ENVIRONMENT_GET_MIDI_INTERFACE (48 | RETRO_ENVIRONMENT_EXPERIMENTAL) + +typedef bool (RETRO_CALLCONV *retro_midi_input_enabled_t)(void); +typedef bool (RETRO_CALLCONV *retro_midi_output_enabled_t)(void); +typedef bool (RETRO_CALLCONV *retro_midi_read_t)(uint8_t *byte); +typedef bool (RETRO_CALLCONV *retro_midi_write_t)(uint8_t byte, uint32_t delta_time); +typedef bool (RETRO_CALLCONV *retro_midi_flush_t)(void); + +struct retro_midi_interface +{ + retro_midi_input_enabled_t input_enabled; + retro_midi_output_enabled_t output_enabled; + retro_midi_read_t read; + retro_midi_write_t write; + retro_midi_flush_t flush; +}; + #define RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE (41 | RETRO_ENVIRONMENT_EXPERIMENTAL) /* const struct retro_hw_render_interface ** -- * Returns an API specific rendering interface for accessing API specific data. diff --git a/list_special.c b/list_special.c index 4337afbddc..098dc2800a 100644 --- a/list_special.c +++ b/list_special.c @@ -50,6 +50,7 @@ #include "input/input_driver.h" #include "audio/audio_driver.h" #include "record/record_driver.h" +#include "midi/midi_driver.h" #include "configuration.h" struct string_list *dir_list_new_special(const char *input_dir, @@ -274,6 +275,15 @@ struct string_list *string_list_new_special(enum string_list_type type, string_list_append(s, opt, attr); } break; + case STRING_LIST_MIDI_DRIVERS: + for (i = 0; midi_driver_find_handle(i); i++) + { + const char *opt = midi_driver_find_ident(i); + *len += strlen(opt) + 1; + + string_list_append(s, opt, attr); + } + break; case STRING_LIST_SUPPORTED_CORES_PATHS: core_info_get_list(&core_info_list); diff --git a/list_special.h b/list_special.h index f0e8bfc285..b046ccaca1 100644 --- a/list_special.h +++ b/list_special.h @@ -53,6 +53,7 @@ enum string_list_type STRING_LIST_INPUT_JOYPAD_DRIVERS, STRING_LIST_INPUT_HID_DRIVERS, STRING_LIST_RECORD_DRIVERS, + STRING_LIST_MIDI_DRIVERS, STRING_LIST_SUPPORTED_CORES_PATHS, STRING_LIST_SUPPORTED_CORES_NAMES }; diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c index b039bc4aef..4c9b4f299f 100644 --- a/menu/cbs/menu_cbs_deferred_push.c +++ b/menu/cbs/menu_cbs_deferred_push.c @@ -155,6 +155,7 @@ generic_deferred_push(deferred_push_lakka_services_list, DISPLAYLIST_ generic_deferred_push(deferred_push_user_settings_list, DISPLAYLIST_USER_SETTINGS_LIST) generic_deferred_push(deferred_push_directory_settings_list, DISPLAYLIST_DIRECTORY_SETTINGS_LIST) generic_deferred_push(deferred_push_privacy_settings_list, DISPLAYLIST_PRIVACY_SETTINGS_LIST) +generic_deferred_push(deferred_push_midi_settings_list, DISPLAYLIST_MIDI_SETTINGS_LIST) generic_deferred_push(deferred_push_audio_settings_list, DISPLAYLIST_AUDIO_SETTINGS_LIST) generic_deferred_push(deferred_push_audio_mixer_settings_list, DISPLAYLIST_AUDIO_MIXER_SETTINGS_LIST) generic_deferred_push(deferred_push_input_settings_list, DISPLAYLIST_INPUT_SETTINGS_LIST) @@ -731,6 +732,12 @@ static int menu_cbs_init_bind_deferred_push_compare_label( return 0; } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MIDI_SETTINGS_LIST))) + { + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_midi_settings_list); + return 0; + } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CORE_CONTENT_DIRS_LIST))) { #ifdef HAVE_NETWORKING diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index d5cfeacf9c..556e137606 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -1971,6 +1971,7 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( case MENU_ENUM_LABEL_JOYPAD_DRIVER: case MENU_ENUM_LABEL_AUDIO_RESAMPLER_DRIVER: case MENU_ENUM_LABEL_RECORD_DRIVER: + case MENU_ENUM_LABEL_MIDI_DRIVER: case MENU_ENUM_LABEL_LOCATION_DRIVER: case MENU_ENUM_LABEL_CAMERA_DRIVER: case MENU_ENUM_LABEL_WIFI_DRIVER: diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 57b8da22c9..e379019018 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -316,6 +316,8 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl) return MENU_ENUM_LABEL_DEFERRED_DIRECTORY_SETTINGS_LIST; case ACTION_OK_DL_PRIVACY_SETTINGS_LIST: return MENU_ENUM_LABEL_DEFERRED_PRIVACY_SETTINGS_LIST; + case ACTION_OK_DL_MIDI_SETTINGS_LIST: + return MENU_ENUM_LABEL_DEFERRED_MIDI_SETTINGS_LIST; case ACTION_OK_DL_AUDIO_SETTINGS_LIST: return MENU_ENUM_LABEL_DEFERRED_AUDIO_SETTINGS_LIST; case ACTION_OK_DL_AUDIO_MIXER_SETTINGS_LIST: @@ -842,6 +844,7 @@ int generic_action_ok_displaylist_push(const char *path, case ACTION_OK_DL_USER_SETTINGS_LIST: case ACTION_OK_DL_DIRECTORY_SETTINGS_LIST: case ACTION_OK_DL_PRIVACY_SETTINGS_LIST: + case ACTION_OK_DL_MIDI_SETTINGS_LIST: case ACTION_OK_DL_AUDIO_SETTINGS_LIST: case ACTION_OK_DL_AUDIO_MIXER_SETTINGS_LIST: case ACTION_OK_DL_INPUT_HOTKEY_BINDS_LIST: @@ -3420,6 +3423,7 @@ default_action_ok_func(action_ok_user_list, ACTION_OK_DL_USER_SETTINGS_LIST) default_action_ok_func(action_ok_netplay_sublist, ACTION_OK_DL_NETPLAY) default_action_ok_func(action_ok_directory_list, ACTION_OK_DL_DIRECTORY_SETTINGS_LIST) default_action_ok_func(action_ok_privacy_list, ACTION_OK_DL_PRIVACY_SETTINGS_LIST) +default_action_ok_func(action_ok_midi_list, ACTION_OK_DL_MIDI_SETTINGS_LIST) default_action_ok_func(action_ok_rdb_entry, ACTION_OK_DL_RDB_ENTRY) default_action_ok_func(action_ok_mixer_stream_actions, ACTION_OK_DL_MIXER_STREAM_SETTINGS_LIST) default_action_ok_func(action_ok_browse_url_list, ACTION_OK_DL_BROWSE_URL_LIST) @@ -4662,6 +4666,9 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PRIVACY_SETTINGS: BIND_ACTION_OK(cbs, action_ok_privacy_list); break; + case MENU_ENUM_LABEL_MIDI_SETTINGS: + BIND_ACTION_OK(cbs, action_ok_midi_list); + break; case MENU_ENUM_LABEL_SCREEN_RESOLUTION: BIND_ACTION_OK(cbs, action_ok_video_resolution); break; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index a8c8bae35d..adc4da4eb2 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -75,6 +75,7 @@ default_sublabel_macro(action_bind_sublabel_saving_settings_list, MENU_ default_sublabel_macro(action_bind_sublabel_logging_settings_list, MENU_ENUM_SUBLABEL_LOGGING_SETTINGS) default_sublabel_macro(action_bind_sublabel_user_interface_settings_list, MENU_ENUM_SUBLABEL_USER_INTERFACE_SETTINGS) default_sublabel_macro(action_bind_sublabel_privacy_settings_list, MENU_ENUM_SUBLABEL_PRIVACY_SETTINGS) +default_sublabel_macro(action_bind_sublabel_midi_settings_list, MENU_ENUM_SUBLABEL_MIDI_SETTINGS) default_sublabel_macro(action_bind_sublabel_directory_settings_list, MENU_ENUM_SUBLABEL_DIRECTORY_SETTINGS) default_sublabel_macro(action_bind_sublabel_playlist_settings_list, MENU_ENUM_SUBLABEL_PLAYLIST_SETTINGS) default_sublabel_macro(action_bind_sublabel_network_settings_list, MENU_ENUM_SUBLABEL_NETWORK_SETTINGS) @@ -222,6 +223,7 @@ default_sublabel_macro(action_bind_sublabel_camera_driver, MENU_ default_sublabel_macro(action_bind_sublabel_location_driver, MENU_ENUM_SUBLABEL_LOCATION_DRIVER) default_sublabel_macro(action_bind_sublabel_menu_driver, MENU_ENUM_SUBLABEL_MENU_DRIVER) default_sublabel_macro(action_bind_sublabel_record_driver, MENU_ENUM_SUBLABEL_RECORD_DRIVER) +default_sublabel_macro(action_bind_sublabel_midi_driver, MENU_ENUM_SUBLABEL_MIDI_DRIVER) default_sublabel_macro(action_bind_sublabel_wifi_driver, MENU_ENUM_SUBLABEL_WIFI_DRIVER) default_sublabel_macro(action_bind_sublabel_filter_supported_extensions, MENU_ENUM_SUBLABEL_NAVIGATION_BROWSER_FILTER_SUPPORTED_EXTENSIONS_ENABLE) default_sublabel_macro(action_bind_sublabel_wallpaper, MENU_ENUM_SUBLABEL_MENU_WALLPAPER) @@ -421,6 +423,9 @@ default_sublabel_macro(action_bind_sublabel_netplay_mitm_server, default_sublabel_macro(action_bind_sublabel_core_delete, MENU_ENUM_SUBLABEL_CORE_DELETE) default_sublabel_macro(action_bind_sublabel_pause_hardcode_mode, MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE) default_sublabel_macro(action_bind_sublabel_resume_hardcode_mode, MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME) +default_sublabel_macro(action_bind_sublabel_midi_input, MENU_ENUM_SUBLABEL_MIDI_INPUT) +default_sublabel_macro(action_bind_sublabel_midi_output, MENU_ENUM_SUBLABEL_MIDI_OUTPUT) +default_sublabel_macro(action_bind_sublabel_midi_volume, MENU_ENUM_SUBLABEL_MIDI_VOLUME) static int action_bind_sublabel_cheevos_entry( file_list_t *list, @@ -1256,6 +1261,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_RECORD_DRIVER: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_record_driver); break; + case MENU_ENUM_LABEL_MIDI_DRIVER: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_midi_driver); + break; case MENU_ENUM_LABEL_MENU_DRIVER: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_driver); break; @@ -1680,6 +1688,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PRIVACY_SETTINGS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_privacy_settings_list); break; + case MENU_ENUM_LABEL_MIDI_SETTINGS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_midi_settings_list); + break; case MENU_ENUM_LABEL_DIRECTORY_SETTINGS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_directory_settings_list); break; @@ -1758,6 +1769,15 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_resume_hardcode_mode); break; + case MENU_ENUM_LABEL_MIDI_INPUT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_midi_input); + break; + case MENU_ENUM_LABEL_MIDI_OUTPUT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_midi_output); + break; + case MENU_ENUM_LABEL_MIDI_VOLUME: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_midi_volume); + break; default: case MSG_UNKNOWN: return -1; diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c index 07501c9817..96517f7cc6 100644 --- a/menu/cbs/menu_cbs_title.c +++ b/menu/cbs/menu_cbs_title.c @@ -133,6 +133,7 @@ default_title_macro(action_get_lakka_services_list, MENU_ENUM_LABEL_ default_title_macro(action_get_user_settings_list, MENU_ENUM_LABEL_VALUE_USER_SETTINGS) default_title_macro(action_get_directory_settings_list, MENU_ENUM_LABEL_VALUE_DIRECTORY_SETTINGS) default_title_macro(action_get_privacy_settings_list, MENU_ENUM_LABEL_VALUE_PRIVACY_SETTINGS) +default_title_macro(action_get_midi_settings_list, MENU_ENUM_LABEL_VALUE_MIDI_SETTINGS) default_title_macro(action_get_updater_settings_list, MENU_ENUM_LABEL_VALUE_UPDATER_SETTINGS) default_title_macro(action_get_audio_settings_list, MENU_ENUM_LABEL_VALUE_AUDIO_SETTINGS) default_title_macro(action_get_audio_mixer_settings_list, MENU_ENUM_LABEL_VALUE_AUDIO_MIXER_SETTINGS) @@ -448,6 +449,11 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_privacy_settings_list); return 0; } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MIDI_SETTINGS_LIST))) + { + BIND_ACTION_GET_TITLE(cbs, action_get_midi_settings_list); + return 0; + } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CORE_CONTENT_DIRS_LIST))) { BIND_ACTION_GET_TITLE(cbs, action_get_download_core_content_list); diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 91c4ca65bd..9e1a326be1 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -2679,6 +2679,8 @@ static void materialui_list_insert(void *userdata, || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PRIVACY_SETTINGS)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_MIDI_SETTINGS)) + || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_MENU_VIEWS_SETTINGS)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_QUICK_MENU_VIEWS_SETTINGS)) diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index 1d88b379fe..788de3f207 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -127,6 +127,7 @@ enum ACTION_OK_DL_USER_SETTINGS_LIST, ACTION_OK_DL_DIRECTORY_SETTINGS_LIST, ACTION_OK_DL_PRIVACY_SETTINGS_LIST, + ACTION_OK_DL_MIDI_SETTINGS_LIST, ACTION_OK_DL_BROWSE_URL_START, ACTION_OK_DL_CONTENT_SETTINGS }; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 0c187fb785..5156b76481 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4966,6 +4966,9 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) ret = menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_RECORD_DRIVER, PARSE_ONLY_STRING_OPTIONS, false); + ret = menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MIDI_DRIVER, + PARSE_ONLY_STRING_OPTIONS, false); #ifdef HAVE_LAKKA ret = menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_WIFI_DRIVER, @@ -5942,6 +5945,22 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NO_SETTINGS_FOUND, 0, 0, 0); + info->need_refresh = true; + info->need_push = true; + break; + case DISPLAYLIST_MIDI_SETTINGS_LIST: + menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); + + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MIDI_INPUT, + PARSE_ONLY_STRING, false); + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MIDI_OUTPUT, + PARSE_ONLY_STRING, false); + menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MIDI_VOLUME, + PARSE_ONLY_UINT, false); + info->need_refresh = true; info->need_push = true; break; @@ -6365,6 +6384,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_DIRECTORY_SETTINGS, PARSE_ACTION, false); ret = menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_PRIVACY_SETTINGS, PARSE_ACTION, false); + ret = menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MIDI_SETTINGS, PARSE_ACTION, false); info->need_push = true; break; case DISPLAYLIST_HORIZONTAL: diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index 895c9e3ea6..e329afcc9d 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -141,6 +141,7 @@ enum menu_displaylist_ctl_state DISPLAYLIST_USER_SETTINGS_LIST, DISPLAYLIST_DIRECTORY_SETTINGS_LIST, DISPLAYLIST_PRIVACY_SETTINGS_LIST, + DISPLAYLIST_MIDI_SETTINGS_LIST, DISPLAYLIST_RECORDING_SETTINGS_LIST, DISPLAYLIST_PLAYLIST_SETTINGS_LIST, DISPLAYLIST_ACCOUNTS_CHEEVOS_LIST, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 86e65ab5a8..d6880db25f 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -76,6 +76,7 @@ #include "../record/record_driver.h" #include "../audio/audio_driver.h" #include "../input/input_driver.h" +#include "../midi/midi_driver.h" #include "../tasks/tasks_internal.h" #include "../config.def.h" #include "../ui/ui_companion_driver.h" @@ -122,7 +123,8 @@ enum settings_list_type SETTINGS_LIST_USER_ACCOUNTS, SETTINGS_LIST_USER_ACCOUNTS_CHEEVOS, SETTINGS_LIST_DIRECTORY, - SETTINGS_LIST_PRIVACY + SETTINGS_LIST_PRIVACY, + SETTINGS_LIST_MIDI }; struct bool_entry @@ -347,6 +349,90 @@ static int setting_string_action_right_driver(void *data, return 0; } +int setting_string_action_left_midi_input(void *data, bool wraparound) +{ + struct string_list *list = midi_driver_get_avail_inputs(); + + if (list && list->size > 1) + { + rarch_setting_t *setting = (rarch_setting_t*)data; + int i = string_list_find_elem(list, setting->value.target.string) - 2; + + if (wraparound && i == -1) + i = (int)list->size - 1; + if (i >= 0) + { + strlcpy(setting->value.target.string, list->elems[i].data, setting->size); + return 0; + } + } + + return -1; +} + +int setting_string_action_right_midi_input(void *data, bool wraparound) +{ + struct string_list *list = midi_driver_get_avail_inputs(); + + if (list && list->size > 1) + { + rarch_setting_t *setting = (rarch_setting_t*)data; + int i = string_list_find_elem(list, setting->value.target.string); + + if (wraparound && i == (int)list->size) + i = 0; + if (i >= 0 && i < (int)list->size) + { + strlcpy(setting->value.target.string, list->elems[i].data, setting->size); + return 0; + } + } + + return -1; +} + +int setting_string_action_left_midi_output(void *data, bool wraparound) +{ + struct string_list *list = midi_driver_get_avail_outputs(); + + if (list && list->size > 1) + { + rarch_setting_t *setting = (rarch_setting_t*)data; + int i = string_list_find_elem(list, setting->value.target.string) - 2; + + if (wraparound && i == -1) + i = (int)list->size - 1; + if (i >= 0) + { + strlcpy(setting->value.target.string, list->elems[i].data, setting->size); + return 0; + } + } + + return -1; +} + +int setting_string_action_right_midi_output(void *data, bool wraparound) +{ + struct string_list *list = midi_driver_get_avail_outputs(); + + if (list && list->size > 1) + { + rarch_setting_t *setting = (rarch_setting_t*)data; + int i = string_list_find_elem(list, setting->value.target.string); + + if (wraparound && i == (int)list->size) + i = 0; + if (i >= 0 && i < (int)list->size) + { + strlcpy(setting->value.target.string, list->elems[i].data, setting->size); + return 0; + } + } + + return -1; +} + static void setting_get_string_representation_uint_video_rotation(void *data, char *s, size_t len) { @@ -1537,6 +1623,15 @@ void general_write_handler(void *data) case MENU_ENUM_LABEL_VIDEO_WINDOW_SHOW_DECORATIONS: video_display_server_set_window_decorations(settings->bools.video_window_show_decorations); break; + case MENU_ENUM_LABEL_MIDI_INPUT: + midi_driver_set_input(settings->arrays.midi_input); + break; + case MENU_ENUM_LABEL_MIDI_OUTPUT: + midi_driver_set_output(settings->arrays.midi_output); + break; + case MENU_ENUM_LABEL_MIDI_VOLUME: + midi_driver_set_volume(settings->uints.midi_volume); + break; default: break; } @@ -2495,6 +2590,14 @@ static bool setting_append_list( &subgroup_info, parent_group); + CONFIG_ACTION( + list, list_info, + MENU_ENUM_LABEL_MIDI_SETTINGS, + MENU_ENUM_LABEL_VALUE_MIDI_SETTINGS, + &group_info, + &subgroup_info, + parent_group); + for (user = 0; user < MAX_USERS; user++) setting_append_list_input_player_options(list, list_info, parent_group, user); @@ -2504,7 +2607,7 @@ static bool setting_append_list( case SETTINGS_LIST_DRIVERS: { unsigned i; - struct string_options_entry string_options_entries[10]; + struct string_options_entry string_options_entries[11]; START_GROUP(list, list_info, &group_info, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DRIVER_SETTINGS), parent_group); menu_settings_list_current_add_enum_idx(list, list_info, MENU_ENUM_LABEL_DRIVER_SETTINGS); @@ -2584,6 +2687,13 @@ static bool setting_append_list( string_options_entries[9].default_value = config_get_default_record(); string_options_entries[9].values = config_get_record_driver_options(); + string_options_entries[10].target = settings->arrays.midi_driver; + string_options_entries[10].len = sizeof(settings->arrays.midi_driver); + string_options_entries[10].name_enum_idx = MENU_ENUM_LABEL_MIDI_DRIVER; + string_options_entries[10].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_MIDI_DRIVER; + string_options_entries[10].default_value = config_get_default_midi(); + string_options_entries[10].values = config_get_midi_driver_options(); + for (i = 0; i < ARRAY_SIZE(string_options_entries); i++) { CONFIG_STRING_OPTIONS( @@ -7907,6 +8017,63 @@ static bool setting_append_list( SD_FLAG_NONE); } + END_SUB_GROUP(list, list_info, parent_group); + END_GROUP(list, list_info, parent_group); + break; + case SETTINGS_LIST_MIDI: + START_GROUP(list, list_info, &group_info, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MIDI_SETTINGS), parent_group); + + parent_group = msg_hash_to_str(MENU_ENUM_LABEL_MIDI_SETTINGS); + + START_SUB_GROUP(list, list_info, "State", + &group_info, &subgroup_info, parent_group); + + CONFIG_STRING( + list, list_info, + settings->arrays.midi_input, + sizeof(settings->arrays.midi_input), + MENU_ENUM_LABEL_MIDI_INPUT, + MENU_ENUM_LABEL_VALUE_MIDI_INPUT, + midi_input, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + (*list)[list_info->index - 1].action_left = setting_string_action_left_midi_input; + (*list)[list_info->index - 1].action_right = setting_string_action_right_midi_input; + + CONFIG_STRING( + list, list_info, + settings->arrays.midi_output, + sizeof(settings->arrays.midi_output), + MENU_ENUM_LABEL_MIDI_OUTPUT, + MENU_ENUM_LABEL_VALUE_MIDI_OUTPUT, + midi_output, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ALLOW_INPUT); + (*list)[list_info->index - 1].action_left = setting_string_action_left_midi_output; + (*list)[list_info->index - 1].action_right = setting_string_action_right_midi_output; + + CONFIG_UINT( + list, list_info, + &settings->uints.midi_volume, + MENU_ENUM_LABEL_MIDI_VOLUME, + MENU_ENUM_LABEL_VALUE_MIDI_VOLUME, + midi_volume, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + menu_settings_list_current_add_range(list, list_info, 0.0f, 100.0f, 1.0f, true, true); + END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); break; @@ -8028,7 +8195,8 @@ static rarch_setting_t *menu_setting_new_internal(rarch_setting_info_t *list_inf SETTINGS_LIST_USER_ACCOUNTS, SETTINGS_LIST_USER_ACCOUNTS_CHEEVOS, SETTINGS_LIST_DIRECTORY, - SETTINGS_LIST_PRIVACY + SETTINGS_LIST_PRIVACY, + SETTINGS_LIST_MIDI }; const char *root = msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU); rarch_setting_t *list = (rarch_setting_t*)calloc( diff --git a/midi/drivers/null_midi.c b/midi/drivers/null_midi.c new file mode 100644 index 0000000000..b0cacdb85c --- /dev/null +++ b/midi/drivers/null_midi.c @@ -0,0 +1,98 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2018 The RetroArch team + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include + +#include +#include + +#include "../midi_driver.h" + +static bool null_midi_get_avail_inputs(struct string_list *inputs) +{ + union string_list_elem_attr attr = {0}; + + return string_list_append(inputs, "Null", attr); +} + +static bool null_midi_get_avail_outputs(struct string_list *outputs) +{ + union string_list_elem_attr attr = {0}; + + return string_list_append(outputs, "Null", attr); +} + +static void *null_midi_init(const char *input, const char *output) +{ + (void)input; + (void)output; + + return (void*)-1; +} + +static void null_midi_free(void *p) +{ + (void)p; +} + +static bool null_midi_set_input(void *p, const char *input) +{ + (void)p; + + return input == NULL || !strcmp(input, "Null"); +} + +static bool null_midi_set_output(void *p, const char *output) +{ + (void)p; + + return output == NULL || !strcmp(output, "Null"); +} + +static bool null_midi_read(void *p, midi_event_t *event) +{ + (void)p; + (void)event; + + return false; +} + +static bool null_midi_write(void *p, const midi_event_t *event) +{ + (void)p; + (void)event; + + return true; +} + +static bool null_midi_flush(void *p) +{ + (void)p; + + return true; +} + +midi_driver_t midi_null = { + "null", + null_midi_get_avail_inputs, + null_midi_get_avail_outputs, + null_midi_init, + null_midi_free, + null_midi_set_input, + null_midi_set_output, + null_midi_read, + null_midi_write, + null_midi_flush +}; diff --git a/midi/drivers/winmm_midi.c b/midi/drivers/winmm_midi.c new file mode 100644 index 0000000000..78f3faae51 --- /dev/null +++ b/midi/drivers/winmm_midi.c @@ -0,0 +1,628 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2018 The RetroArch team + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include + +#include +#include +#include + +#include "../midi_driver.h" + +#define WINMM_MIDI_BUF_CNT 3 +#define WINMM_MIDI_BUF_LEN 1024 + +typedef struct +{ + MIDIHDR header; + DWORD data[WINMM_MIDI_BUF_LEN]; +} winmm_midi_buffer_t; + +typedef struct +{ + uint8_t data[WINMM_MIDI_BUF_LEN * 4]; + midi_event_t events[WINMM_MIDI_BUF_LEN]; + int rd_idx; + int wr_idx; +} winmm_midi_queue_t; + +typedef struct +{ + HMIDIIN in_dev; + HMIDISTRM out_dev; + winmm_midi_queue_t in_queue; + winmm_midi_buffer_t out_bufs[WINMM_MIDI_BUF_CNT]; + int out_buf_idx; + double tick_dur; +} winmm_midi_t; + +static void winmm_midi_free(void *p); + +static bool winmm_midi_queue_read(winmm_midi_queue_t *q, midi_event_t *ev) +{ + int i; + midi_event_t *src_ev; + + if (q->rd_idx == q->wr_idx) + return false; + + if (ev->data_size < q->events[q->rd_idx].data_size) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: Input queue read failed (event data too small).\n"); +#endif + return false; + } + + src_ev = &q->events[q->rd_idx]; + + for (i = 0; i < src_ev->data_size; ++i) + ev->data[i] = src_ev->data[i]; + + ev->data_size = src_ev->data_size; + ev->delta_time = src_ev->delta_time; + + if (q->rd_idx + 1 == WINMM_MIDI_BUF_LEN) + q->rd_idx = 0; + else + ++q->rd_idx; + + return true; +} + +static bool winmm_midi_queue_write(winmm_midi_queue_t *q, const midi_event_t *ev) +{ + int rd_idx = q->rd_idx; + midi_event_t *dest_ev; + int wr_avail; + int i; + + if (q->wr_idx >= rd_idx) + wr_avail = WINMM_MIDI_BUF_LEN - q->wr_idx + rd_idx; + else + wr_avail = rd_idx - q->wr_idx - 1; + + if (wr_avail < 1) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: Input queue overflow.\n"); +#endif + return false; + } + + dest_ev = &q->events[q->wr_idx]; + if (ev->data_size > 4) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: Input queue write failed (event too big).\n"); +#endif + return false; + } + + for (i = 0; i < ev->data_size; ++i) + dest_ev->data[i] = ev->data[i]; + + dest_ev->data_size = ev->data_size; + dest_ev->delta_time = ev->delta_time; + + if (q->wr_idx + 1 == WINMM_MIDI_BUF_LEN) + q->wr_idx = 0; + else + ++q->wr_idx; + + return true; +} + +static void winmm_midi_queue_init(winmm_midi_queue_t *q) +{ + int i, j; + + for (i = j = 0; i < WINMM_MIDI_BUF_LEN; ++i, j += 4) + { + q->events[i].data = &q->data[j]; + q->events[i].delta_time = 0; + } + + q->rd_idx = 0; + q->wr_idx = 0; +} + +static void CALLBACK winmm_midi_input_callback(HMIDIIN dev, UINT msg, + DWORD_PTR q, DWORD_PTR par1, DWORD_PTR par2) +{ + winmm_midi_queue_t *queue = (winmm_midi_queue_t*)q; + midi_event_t event; + uint8_t data[3]; + (void)dev; + + if (msg == MIM_OPEN) + winmm_midi_queue_init(queue); + else if (msg == MIM_DATA) + { + data[0] = (uint8_t)(par1 & 0xFF); + data[1] = (uint8_t)((par1 >> 8) & 0xFF); + data[2] = (uint8_t)((par1 >> 16) & 0xFF); + + event.data = data; + event.data_size = midi_driver_get_event_size(data[0]); + event.delta_time = 0; + + if (!winmm_midi_queue_write(queue, &event)) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: Input event dropped.\n"); +#endif + } + } + else if(msg == MIM_LONGDATA) + { +#ifdef DEBUG + RARCH_WARN("[MIDI]: SysEx input not implemented, event dropped.\n"); +#endif + } +} + +static HMIDIIN winmm_midi_open_input_device(const char *dev_name, + winmm_midi_queue_t *queue) +{ + UINT dev_count = midiInGetNumDevs(); + HMIDIIN dev = NULL; + MIDIINCAPSA caps; + MMRESULT mmr; + UINT i; + + for (i = 0; i < dev_count; ++i) + { + mmr = midiInGetDevCapsA(i, &caps, sizeof(caps)); + if (mmr == MMSYSERR_NOERROR) + { + if (!strcmp(caps.szPname, dev_name)) + { + mmr = midiInOpen(&dev, i, (DWORD_PTR)winmm_midi_input_callback, + (DWORD_PTR)queue, CALLBACK_FUNCTION); + if (mmr != MMSYSERR_NOERROR) + RARCH_ERR("[MIDI]: midiInOpen failed with error %d.\n", mmr); + break; + } + } + else + RARCH_WARN("[MIDI]: midiInGetDevCapsA failed with error %d.\n", mmr); + } + + return dev; +} + +static HMIDISTRM winmm_midi_open_output_device(const char *dev_name) +{ + UINT dev_count = midiOutGetNumDevs(); + HMIDISTRM dev = NULL; + MIDIOUTCAPSA caps; + MMRESULT mmr; + UINT i; + + for (i = 0; i < dev_count; ++i) + { + mmr = midiOutGetDevCapsA(i, &caps, sizeof(caps)); + if (mmr == MMSYSERR_NOERROR) + { + if (!strcmp(caps.szPname, dev_name)) + { + mmr = midiStreamOpen(&dev, &i, 1, 0, 0, CALLBACK_NULL); + if (mmr != MMSYSERR_NOERROR) + RARCH_ERR("[MIDI]: midiStreamOpen failed with error %d.\n", mmr); + break; + } + } + else + RARCH_WARN("[MIDI]: midiOutGetDevCapsA failed with error %d.\n", mmr); + } + + return dev; +} + +static bool winmm_midi_init_clock(HMIDISTRM out_dev, double *tick_dur) +{ + MIDIPROPTIMEDIV division; + MIDIPROPTEMPO tempo; + MMRESULT mmr; + + tempo.cbStruct = sizeof(tempo); + mmr = midiStreamProperty(out_dev, (LPBYTE)&tempo, + MIDIPROP_GET | MIDIPROP_TEMPO); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: Current tempo unavailable (error %d).\n", mmr); + return false; + } + + division.dwTimeDiv = 3; + while (tempo.dwTempo / division.dwTimeDiv > 320) + division.dwTimeDiv *= 2; + + division.cbStruct = sizeof(division); + mmr = midiStreamProperty(out_dev, (LPBYTE)&division, + MIDIPROP_SET | MIDIPROP_TIMEDIV); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: Time division change failed (error %d).\n", mmr); + return false; + } + + *tick_dur = (double)tempo.dwTempo / (double)division.dwTimeDiv; +#ifdef DEBUG + RARCH_LOG("[MIDI]: Tick duration %f us.\n", *tick_dur); +#endif + + return true; +} + +static bool winmm_midi_init_output_buffers(HMIDISTRM dev, winmm_midi_buffer_t *bufs) +{ + MMRESULT mmr; + int i; + + for (i = 0; i < WINMM_MIDI_BUF_CNT; ++i) + { + bufs[i].header.dwBufferLength = sizeof(DWORD) * WINMM_MIDI_BUF_LEN; + bufs[i].header.dwBytesRecorded = 0; + bufs[i].header.dwFlags = 0; + bufs[i].header.lpData = (LPSTR)bufs[i].data; + + mmr = midiOutPrepareHeader((HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR)); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiOutPrepareHeader failed with error %d.\n", mmr); + + while (--i <= 0) + midiOutUnprepareHeader((HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR)); + + return false; + } + } + + return true; +} + +static void winmm_midi_free_output_buffers(HMIDISTRM dev, winmm_midi_buffer_t *bufs) +{ + MMRESULT mmr; + int i; + + for (i = 0; i < WINMM_MIDI_BUF_CNT; ++i) + { + mmr = midiOutUnprepareHeader((HMIDIOUT)dev, &bufs[i].header, sizeof(MIDIHDR)); + if (mmr != MMSYSERR_NOERROR) + RARCH_ERR("[MIDI]: midiOutUnprepareHeader failed with error %d.\n", mmr); + } +} + +static bool winmm_midi_write_short_event(winmm_midi_buffer_t *buf, + const uint8_t *data, size_t data_size, DWORD delta_time) +{ + DWORD i = buf->header.dwBytesRecorded / sizeof(DWORD); + + if (buf->header.dwBytesRecorded + sizeof(DWORD) * 3 > + sizeof(DWORD) * WINMM_MIDI_BUF_LEN) + return false; + + buf->data[i++] = delta_time; + buf->data[i++] = 0; + buf->data[i] = MEVT_F_SHORT << 24; + if (data_size == 0) + buf->data[i] |= MEVT_NOP; + else + { + buf->data[i] |= MEVT_SHORTMSG << 24 | data[0]; + if (data_size > 1) + buf->data[i] |= data[1] << 8; + if (data_size > 2) + buf->data[i] |= data[2] << 16; + } + + buf->header.dwBytesRecorded += sizeof(DWORD) * 3; + + return true; +} + +static bool winmm_midi_write_long_event(winmm_midi_buffer_t *buf, + const uint8_t *data, size_t data_size, DWORD delta_time) +{ + DWORD i = buf->header.dwBytesRecorded / sizeof(DWORD); + + if (buf->header.dwBytesRecorded + sizeof(DWORD) * 3 + data_size > + sizeof(DWORD) * WINMM_MIDI_BUF_LEN) + return false; + + buf->data[i++] = delta_time; + buf->data[i++] = 0; + buf->data[i++] = MEVT_F_LONG << 24 | MEVT_LONGMSG << 24 | data_size; + + memcpy(&buf->data[i], data, data_size); + buf->header.dwBytesRecorded += sizeof(DWORD) * 3 + data_size; + + return true; +} + +static bool winmm_midi_get_avail_inputs(struct string_list *inputs) +{ + union string_list_elem_attr attr = {0}; + UINT dev_count = midiInGetNumDevs(); + MIDIINCAPSA caps; + MMRESULT mmr; + UINT i; + + for (i = 0; i < dev_count; ++i) + { + mmr = midiInGetDevCapsA(i, &caps, sizeof(caps)); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiInGetDevCapsA failed with error %d.\n", mmr); + return false; + } + + if (!string_list_append(inputs, caps.szPname, attr)) + { + RARCH_ERR("[MIDI]: string_list_append failed.\n"); + return false; + } + } + + return true; +} + +static bool winmm_midi_get_avail_outputs(struct string_list *outputs) +{ + union string_list_elem_attr attr = {0}; + UINT dev_count = midiOutGetNumDevs(); + MIDIOUTCAPSA caps; + MMRESULT mmr; + UINT i; + + for (i = 0; i < dev_count; ++i) + { + mmr = midiOutGetDevCapsA(i, &caps, sizeof(caps)); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiOutGetDevCapsA failed with error %d.\n", mmr); + return false; + } + + if (!string_list_append(outputs, caps.szPname, attr)) + { + RARCH_ERR("[MIDI]: string_list_append failed.\n"); + return false; + } + } + + return true; +} + +static void *winmm_midi_init(const char *input, const char *output) +{ + winmm_midi_t *d = (winmm_midi_t*)calloc(sizeof(winmm_midi_t), 1); + bool err = false; + MMRESULT mmr; + + if (!d) + { + RARCH_ERR("[MIDI]: Out of memory.\n"); + return NULL; + } + + if (input) + { + d->in_dev = winmm_midi_open_input_device(input, &d->in_queue); + if (!d->in_dev) + err = true; + else + { + mmr = midiInStart(d->in_dev); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiInStart failed with error %d.\n", mmr); + err = true; + } + } + } + + if (output) + { + d->out_dev = winmm_midi_open_output_device(output); + if (!d->out_dev) + err = true; + else if (!winmm_midi_init_clock(d->out_dev, &d->tick_dur)) + err = true; + else if (!winmm_midi_init_output_buffers(d->out_dev, d->out_bufs)) + err = true; + else + { + mmr = midiStreamRestart(d->out_dev); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiStreamRestart failed with error %d.\n", mmr); + err = true; + } + } + } + + if (err) + { + winmm_midi_free(d); + return NULL; + } + + return d; +} + +static void winmm_midi_free(void *p) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + + if (d->in_dev) + { + midiInStop(d->in_dev); + midiInClose(d->in_dev); + } + + if (d->out_dev) + { + midiStreamStop(d->out_dev); + winmm_midi_free_output_buffers(d->out_dev, d->out_bufs); + midiStreamClose(d->out_dev); + } + + free(d); +} + +static bool winmm_midi_set_input(void *p, const char *input) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + HMIDIIN new_dev = NULL; + MMRESULT mmr; + + if (input) + { + new_dev = winmm_midi_open_input_device(input, &d->in_queue); + if (!new_dev) + return false; + } + + if (d->in_dev) + { + midiInStop(d->in_dev); + midiInClose(d->in_dev); + } + + d->in_dev = new_dev; + if (d->in_dev) + { + mmr = midiInStart(d->in_dev); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiInStart failed with error %d.\n", mmr); + return false; + } + } + + return true; +} + +static bool winmm_midi_set_output(void *p, const char *output) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + HMIDISTRM new_dev = NULL; + MMRESULT mmr; + int i; + + if (output) + { + new_dev = winmm_midi_open_output_device(output); + if (!new_dev) + return false; + } + + if (d->out_dev) + { + midiStreamStop(d->out_dev); + winmm_midi_free_output_buffers(d->out_dev, d->out_bufs); + midiStreamClose(d->out_dev); + } + + d->out_dev = new_dev; + if (d->out_dev) + { + if (!winmm_midi_init_output_buffers(d->out_dev, d->out_bufs)) + return false; + + d->out_buf_idx = 0; + + mmr = midiStreamRestart(d->out_dev); + if (mmr != MMSYSERR_NOERROR) + { + RARCH_ERR("[MIDI]: midiStreamRestart failed with error %d.\n", mmr); + return false; + } + } + + return true; +} + +static bool winmm_midi_read(void *p, midi_event_t *event) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + + return winmm_midi_queue_read(&d->in_queue, event); +} + +static bool winmm_midi_write(void *p, const midi_event_t *event) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + winmm_midi_buffer_t *buf = &d->out_bufs[d->out_buf_idx]; + DWORD delta_time; + + if (buf->header.dwFlags & MHDR_INQUEUE) + return false; + + if (buf->header.dwFlags & MHDR_DONE) + { + buf->header.dwBytesRecorded = 0; + buf->header.dwFlags ^= MHDR_DONE; + } + + delta_time = (DWORD)((double)event->delta_time / d->tick_dur); + if (event->data_size < 4) + return winmm_midi_write_short_event(buf, event->data, + event->data_size, delta_time); + + return winmm_midi_write_long_event(buf, event->data, + event->data_size, delta_time); +} + +static bool winmm_midi_flush(void *p) +{ + winmm_midi_t *d = (winmm_midi_t*)p; + winmm_midi_buffer_t *buf = &d->out_bufs[d->out_buf_idx]; + MMRESULT mmr; + + if (buf->header.dwBytesRecorded) + { + mmr = midiStreamOut(d->out_dev, &buf->header, sizeof(buf->header)); + if (mmr != MMSYSERR_NOERROR) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: midiStreamOut failed with error %d.\n", mmr); +#endif + return false; + } + + if (++d->out_buf_idx == WINMM_MIDI_BUF_CNT) + d->out_buf_idx = 0; + } + + return true; +} + +midi_driver_t midi_winmm = { + "winmm", + winmm_midi_get_avail_inputs, + winmm_midi_get_avail_outputs, + winmm_midi_init, + winmm_midi_free, + winmm_midi_set_input, + winmm_midi_set_output, + winmm_midi_read, + winmm_midi_write, + winmm_midi_flush +}; diff --git a/midi/midi_driver.c b/midi/midi_driver.c new file mode 100644 index 0000000000..b1a64f35d6 --- /dev/null +++ b/midi/midi_driver.c @@ -0,0 +1,585 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2018 The RetroArch team + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include + +#include +#include +#include + +#include "midi_driver.h" + +#define MIDI_DRIVER_BUF_SIZE 4096 + +#define MIDI_DRIVER_COUNT (int)(sizeof(midi_drivers) / sizeof(midi_driver_t*)) + +extern midi_driver_t midi_null; +extern midi_driver_t midi_winmm; + +static midi_driver_t *midi_drivers[] = { +#ifdef HAVE_WINMM + &midi_winmm, +#endif + &midi_null +}; + +static midi_driver_t *midi_drv = &midi_null; +static void *midi_drv_data; +static struct string_list *midi_drv_inputs; +static struct string_list *midi_drv_outputs; +static bool midi_drv_input_enabled; +static bool midi_drv_output_enabled; +static uint8_t *midi_drv_input_buffer; +static uint8_t *midi_drv_output_buffer; +static midi_event_t midi_drv_input_event; +static midi_event_t midi_drv_output_event; +static bool midi_drv_output_pending; + +static const uint8_t midi_drv_ev_sizes[128] = +{ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +static midi_driver_t *midi_driver_find_driver(const char *ident) +{ + int i; + + for (i = 0; i < MIDI_DRIVER_COUNT; ++i) + { + if (!strcmp(midi_drivers[i]->ident, ident)) + return midi_drivers[i]; + } + + RARCH_ERR("[MIDI]: Unknown driver \"%s\", falling back to \"null\" driver.\n"); + + return &midi_null; +} + +const void *midi_driver_find_handle(int index) +{ + if (index < 0 || index >= MIDI_DRIVER_COUNT) + return NULL; + + return midi_drivers[index]; +} + +const char *midi_driver_find_ident(int index) +{ + if (index < 0 || index >= MIDI_DRIVER_COUNT) + return NULL; + + return midi_drivers[index]->ident; +} + +struct string_list *midi_driver_get_avail_inputs(void) +{ + return midi_drv_inputs; +} + +struct string_list *midi_driver_get_avail_outputs(void) +{ + return midi_drv_outputs; +} +bool midi_driver_set_all_sounds_off(void) +{ + uint8_t data[3] = { 0xB0, 120, 0 }; + bool result = true; + midi_event_t event; + uint8_t i; + + if (!midi_drv_data || !midi_drv_output_enabled) + return false; + + event.data = data; + event.data_size = sizeof(data); + event.delta_time = 0; + + for (i = 0; i < 16; ++i) + { + data[0] = 0xB0 | i; + + if (!midi_drv->write(midi_drv_data, &event)) + result = false; + } + + if (!midi_drv->flush(midi_drv_data)) + result = false; + + if (!result) + RARCH_ERR("[MIDI]: All sounds off failed.\n"); + + return result; +} + +bool midi_driver_set_volume(unsigned volume) +{ + uint8_t data[8] = { 0xF0, 0x7F, 0x7F, 0x04, 0x01, 0, 0, 0xF7 }; + const char *err_str = NULL; + midi_event_t event; + + if (!midi_drv_data || !midi_drv_output_enabled) + return false; + + volume = (unsigned)(163.83 * volume + 0.5); + if (volume > 16383) + volume = 16383; + + data[5] = (uint8_t)(volume & 0x7F); + data[6] = (uint8_t)(volume >> 7); + + event.data = data; + event.data_size = sizeof(data); + event.delta_time = 0; + + if (!midi_drv->write(midi_drv_data, &event)) + { + RARCH_ERR("[MIDI]: Volume change failed.\n"); + return false; + } + + return true; +} + +bool midi_driver_init_io_buffers(void) +{ + if (midi_drv_input_enabled) + { + midi_drv_input_buffer = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE); + if (!midi_drv_input_buffer) + return false; + + midi_drv_input_event.data = midi_drv_input_buffer; + midi_drv_input_event.data_size = 0; + } + + if (midi_drv_output_enabled) + { + midi_drv_output_buffer = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE); + if (!midi_drv_output_buffer) + return false; + + midi_drv_output_event.data = midi_drv_output_buffer; + midi_drv_output_event.data_size = 0; + } + + return true; +} + +bool midi_driver_init(void) +{ + settings_t *sett = config_get_ptr(); + midi_drv_inputs = string_list_new(); + midi_drv_outputs = string_list_new(); + union string_list_elem_attr attr = {0}; + const char *err_str = NULL; + + RARCH_LOG("[MIDI]: Initializing ...\n"); + + if (!sett) + err_str = "settings unavailable"; + else if (!midi_drv_inputs || !midi_drv_outputs) + err_str = "string_list_new failed"; + else if (!string_list_append(midi_drv_inputs, "Off", attr) || + !string_list_append(midi_drv_outputs, "Off", attr)) + err_str = "string_list_append failed"; + else + { + char * input = NULL; + char * output = NULL; + + midi_drv = midi_driver_find_driver(sett->arrays.midi_driver); + if (strcmp(midi_drv->ident, sett->arrays.midi_driver)) + strcpy(sett->arrays.midi_driver, midi_drv->ident); + + if (!midi_drv->get_avail_inputs(midi_drv_inputs)) + err_str = "list of input devices unavailable"; + else if (!midi_drv->get_avail_outputs(midi_drv_outputs)) + err_str = "list of output devices unavailable"; + else + { + if (strcmp(sett->arrays.midi_input, "Off")) + { + if (string_list_find_elem(midi_drv_inputs, sett->arrays.midi_input)) + input = sett->arrays.midi_input; + else + { + RARCH_WARN("[MIDI]: Input device \"%s\" unavailable.\n", + sett->arrays.midi_input); + strcpy(sett->arrays.midi_input, "Off"); + } + } + + if (strcmp(sett->arrays.midi_output, "Off")) + { + if (string_list_find_elem(midi_drv_outputs, sett->arrays.midi_output)) + output = sett->arrays.midi_output; + else + { + RARCH_WARN("[MIDI]: Output device \"%s\" unavailable.\n", + sett->arrays.midi_output); + strcpy(sett->arrays.midi_output, "Off"); + } + } + + midi_drv_data = midi_drv->init(input, output); + if (!midi_drv_data) + err_str = "driver init failed"; + else + { + midi_drv_input_enabled = input != NULL; + midi_drv_output_enabled = output != NULL; + + if (!midi_driver_init_io_buffers()) + err_str = "out of memory"; + else + { + if (input) + RARCH_LOG("[MIDI]: Input device \"%s\".\n", input); + else + RARCH_LOG("[MIDI]: Input disabled.\n"); + if (output) + { + RARCH_LOG("[MIDI]: Output device \"%s\".\n", output); + midi_driver_set_volume(sett->uints.midi_volume); + } + else + RARCH_LOG("[MIDI]: Output disabled.\n"); + } + } + } + } + + if (err_str) + { + midi_driver_free(); + RARCH_ERR("[MIDI]: Initialization failed (%s).\n", err_str); + } + else + RARCH_LOG("[MIDI]: Initialized \"%s\" driver.\n", midi_drv->ident); + + return err_str == NULL;; +} + +void midi_driver_free(void) +{ + if (midi_drv_data) + { + midi_drv->free(midi_drv_data); + midi_drv_data = NULL; + } + + if (midi_drv_inputs) + { + string_list_free(midi_drv_inputs); + midi_drv_inputs = NULL; + } + if (midi_drv_outputs) + { + string_list_free(midi_drv_outputs); + midi_drv_outputs = NULL; + } + + if (midi_drv_input_buffer) + { + free(midi_drv_input_buffer); + midi_drv_input_buffer = NULL; + } + if (midi_drv_output_buffer) + { + free(midi_drv_output_buffer); + midi_drv_output_buffer = NULL; + } + + midi_drv_input_enabled = false; + midi_drv_output_enabled = false; +} + +bool midi_driver_set_input(const char *input) +{ + if (!midi_drv_data) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: midi_driver_set_input called on uninitialized driver.\n"); +#endif + return false; + } + + if (!strcmp(input, "Off")) + input = NULL; + + if (!midi_drv->set_input(midi_drv_data, input)) + { + if (input) + RARCH_ERR("[MIDI]: Failed to change input device to \"%s\".\n", input); + else + RARCH_ERR("[MIDI]: Failed to disable input.\n"); + return false; + } + + if (input) + RARCH_LOG("[MIDI]: Input device changed to \"%s\".\n", input); + else + RARCH_LOG("[MIDI]: Input disabled.\n"); + + midi_drv_input_enabled = input != NULL; + + return true; +} + +bool midi_driver_set_output(const char *output) +{ + if (!midi_drv_data) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: midi_driver_set_output called on uninitialized driver.\n"); +#endif + return false; + } + + if (!strcmp(output, "Off")) + output = NULL; + + if (!midi_drv->set_output(midi_drv_data, output)) + { + if (output) + RARCH_ERR("[MIDI]: Failed to change output device to \"%s\".\n", output); + else + RARCH_ERR("[MIDI]: Failed to disable output.\n"); + return false; + } + + if (output) + { + settings_t *sett = config_get_ptr(); + + midi_drv_output_enabled = true; + RARCH_LOG("[MIDI]: Output device changed to \"%s\".\n", output); + + if (sett) + midi_driver_set_volume(sett->uints.midi_volume); + else + RARCH_ERR("[MIDI]: Volume change failed (settings unavailable).\n"); + } + else + { + midi_drv_output_enabled = false; + RARCH_LOG("[MIDI]: Output disabled.\n"); + } + + return true; +} + +bool midi_driver_input_enabled(void) +{ + return midi_drv_input_enabled; +} + +bool midi_driver_output_enabled(void) +{ + return midi_drv_output_enabled; +} + +bool midi_driver_read(uint8_t *byte) +{ + static int i; + + if (!midi_drv_data || !midi_drv_input_enabled || !byte) + { +#ifdef DEBUG + if (!midi_drv_data) + RARCH_ERR("[MIDI]: midi_driver_read called on uninitialized driver.\n"); + else if (!midi_drv_input_enabled) + RARCH_ERR("[MIDI]: midi_driver_read called when input is disabled.\n"); + else + RARCH_ERR("[MIDI]: midi_driver_read called with null pointer.\n"); +#endif + return false; + } + + if (i == midi_drv_input_event.data_size) + { + midi_drv_input_event.data_size = MIDI_DRIVER_BUF_SIZE; + if (!midi_drv->read(midi_drv_data, &midi_drv_input_event)) + { + midi_drv_input_event.data_size = i; + return false; + } + + i = 0; + +#ifdef DEBUG + if (midi_drv_input_event.data_size == 1) + RARCH_LOG("[MIDI]: In [0x%02X].\n", + midi_drv_input_event.data[0]); + else if (midi_drv_input_event.data_size == 2) + RARCH_LOG("[MIDI]: In [0x%02X, 0x%02X].\n", + midi_drv_input_event.data[0], + midi_drv_input_event.data[1]); + else if (midi_drv_input_event.data_size == 3) + RARCH_LOG("[MIDI]: In [0x%02X, 0x%02X, 0x%02X].\n", + midi_drv_input_event.data[0], + midi_drv_input_event.data[1], + midi_drv_input_event.data[2]); + else + RARCH_LOG("[MIDI]: In [0x%02X, ...], size %u.\n", + midi_drv_input_event.data[0], + midi_drv_input_event.data_size); +#endif + } + + *byte = midi_drv_input_event.data[i++]; + + return true; +} + +bool midi_driver_write(uint8_t byte, uint32_t delta_time) +{ + static int event_size; + + if (!midi_drv_data || !midi_drv_output_enabled) + { +#ifdef DEBUG + if (!midi_drv_data) + RARCH_ERR("[MIDI]: midi_driver_write called on uninitialized driver.\n"); + else + RARCH_ERR("[MIDI]: midi_driver_write called when output is disabled.\n"); +#endif + return false; + } + + if (byte >= 0x80) + { + if (midi_drv_output_event.data_size && + midi_drv_output_event.data[0] == 0xF0) + { + if (byte == 0xF7) + event_size = midi_drv_output_event.data_size + 1; + else + { + if (!midi_drv->write(midi_drv_data, &midi_drv_output_event)) + return false; + +#ifdef DEBUG + if (midi_drv_output_event.data_size == 1) + RARCH_LOG("[MIDI]: Out [0x%02X].\n", + midi_drv_output_event.data[0]); + else if (midi_drv_output_event.data_size == 2) + RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X].\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data[1]); + else if (midi_drv_output_event.data_size == 3) + RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X, 0x%02X].\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data[1], + midi_drv_output_event.data[2]); + else + RARCH_LOG("[MIDI]: Out [0x%02X, ...], size %u.\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data_size); +#endif + + midi_drv_output_pending = true; + event_size = midi_driver_get_event_size(byte); + midi_drv_output_event.data_size = 0; + midi_drv_output_event.delta_time = 0; + } + } + else + { + event_size = midi_driver_get_event_size(byte); + midi_drv_output_event.data_size = 0; + midi_drv_output_event.delta_time = 0; + } + } + + if (midi_drv_output_event.data_size < MIDI_DRIVER_BUF_SIZE) + { + midi_drv_output_event.data[midi_drv_output_event.data_size] = byte; + ++midi_drv_output_event.data_size; + midi_drv_output_event.delta_time += delta_time; + } + else + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: Output event dropped.\n"); +#endif + return false; + } + + if (midi_drv_output_event.data_size == event_size) + { + if (!midi_drv->write(midi_drv_data, &midi_drv_output_event)) + return false; + +#ifdef DEBUG + if (midi_drv_output_event.data_size == 1) + RARCH_LOG("[MIDI]: Out [0x%02X].\n", + midi_drv_output_event.data[0]); + else if (midi_drv_output_event.data_size == 2) + RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X].\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data[1]); + else if (midi_drv_output_event.data_size == 3) + RARCH_LOG("[MIDI]: Out [0x%02X, 0x%02X, 0x%02X].\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data[1], + midi_drv_output_event.data[2]); + else + RARCH_LOG("[MIDI]: Out [0x%02X, ...], size %u.\n", + midi_drv_output_event.data[0], + midi_drv_output_event.data_size); +#endif + + midi_drv_output_pending = true; + } + + return true; +} + +bool midi_driver_flush(void) +{ + if (!midi_drv_data) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: midi_driver_flush called on uninitialized driver.\n"); +#endif + return false; + } + + if (midi_drv_output_pending) + midi_drv_output_pending = !midi_drv->flush(midi_drv_data); + + return !midi_drv_output_pending; +} + +size_t midi_driver_get_event_size(uint8_t status) +{ + if (status < 0x80) + { +#ifdef DEBUG + RARCH_ERR("[MIDI]: midi_driver_get_event_size called with invalid status.\n"); +#endif + return 0; + } + + return midi_drv_ev_sizes[status - 0x80]; +} diff --git a/midi/midi_driver.h b/midi/midi_driver.h new file mode 100644 index 0000000000..a3030385c7 --- /dev/null +++ b/midi/midi_driver.h @@ -0,0 +1,77 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2018 The RetroArch team + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef __MIDI_DRIVER__H +#define __MIDI_DRIVER__H + +#include +#include + +RETRO_BEGIN_DECLS + +struct string_list; + +typedef struct +{ + uint8_t *data; + size_t data_size; + uint32_t delta_time; +} midi_event_t; + +typedef struct midi_driver +{ + const char *ident; + + bool (*get_avail_inputs)(struct string_list *inputs); + bool (*get_avail_outputs)(struct string_list *outputs); + + void *(*init)(const char *input, const char *output); + void (*free)(void *p); + + bool (*set_input)(void *p, const char *input); + bool (*set_output)(void *p, const char *output); + + bool (*read)(void *p, midi_event_t *event); + bool (*write)(void *p, const midi_event_t *event); + bool (*flush)(void *p); +} midi_driver_t; + +const void *midi_driver_find_handle(int index); +const char *midi_driver_find_ident(int index); + +struct string_list *midi_driver_get_avail_inputs(void); +struct string_list *midi_driver_get_avail_outputs(void); + +bool midi_driver_set_all_sounds_off(void); +bool midi_driver_set_volume(unsigned volume); + +bool midi_driver_init(void); +void midi_driver_free(void); + +bool midi_driver_set_input(const char *input); +bool midi_driver_set_output(const char *output); + +bool midi_driver_input_enabled(void); +bool midi_driver_output_enabled(void); + +bool midi_driver_read(uint8_t *byte); +bool midi_driver_write(uint8_t byte, uint32_t delta_time); +bool midi_driver_flush(void); + +size_t midi_driver_get_event_size(uint8_t status); + +RETRO_END_DECLS + +#endif diff --git a/msg_hash.h b/msg_hash.h index 5947f5e307..a28350e1a4 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -903,6 +903,7 @@ enum msg_hash_enums MENU_LABEL(USER_SETTINGS), MENU_LABEL(DIRECTORY_SETTINGS), MENU_LABEL(PRIVACY_SETTINGS), + MENU_LABEL(MIDI_SETTINGS), MENU_LABEL(NETWORK_SETTINGS), MENU_LABEL(NETPLAY_LAN_SCAN_SETTINGS), @@ -1030,6 +1031,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_DEFERRED_USER_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_DIRECTORY_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_PRIVACY_SETTINGS_LIST, + MENU_ENUM_LABEL_DEFERRED_MIDI_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_LOGGING_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_AUDIO_SETTINGS_LIST, MENU_ENUM_LABEL_DEFERRED_AUDIO_MIXER_SETTINGS_LIST, @@ -1280,6 +1282,7 @@ enum msg_hash_enums MENU_LABEL(RECORD_DRIVER), MENU_LABEL(VIDEO_DRIVER), MENU_LABEL(INPUT_DRIVER), + MENU_LABEL(MIDI_DRIVER), MENU_ENUM_LABEL_VIDEO_DRIVER_GL, MENU_ENUM_LABEL_VIDEO_DRIVER_SDL2, @@ -1861,6 +1864,10 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_QT_FILE_READ_OPEN_FAILED, MENU_ENUM_LABEL_VALUE_QT_FILE_DOES_NOT_EXIST, + MENU_LABEL(MIDI_INPUT), + MENU_LABEL(MIDI_OUTPUT), + MENU_LABEL(MIDI_VOLUME), + MSG_LAST }; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index ab7bf0f0d6..089939fc35 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -322,6 +322,7 @@ if [ "$OS" = 'Win32' ]; then HAVE_WASAPI=yes HAVE_XAUDIO=yes + HAVE_WINMM=yes else HAVE_D3D9=no HAVE_D3D10=no