/* 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 "../configuration.h" #include "../verbosity.h" #include "midi_driver.h" #define MIDI_DRIVER_BUF_SIZE 4096 extern midi_driver_t midi_null; extern midi_driver_t midi_winmm; extern midi_driver_t midi_alsa; static midi_driver_t *midi_drivers[] = { #if defined(HAVE_ALSA) && !defined(HAVE_HAKCHI) &midi_alsa, #endif #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) { unsigned i; for (i = 0; i < ARRAY_SIZE(midi_drivers); ++i) { if (string_is_equal(midi_drivers[i]->ident, ident)) return midi_drivers[i]; } RARCH_ERR("[MIDI]: Unknown driver \"%s\", falling back to \"null\" driver.\n", ident); return &midi_null; } const void *midi_driver_find_handle(int index) { if (index < 0 || index >= ARRAY_SIZE(midi_drivers)) return NULL; return midi_drivers[index]; } const char *midi_driver_find_ident(int index) { if (index < 0 || index >= ARRAY_SIZE(midi_drivers)) 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) { midi_event_t event; uint8_t i; uint8_t data[3] = { 0xB0, 120, 0 }; bool result = true; 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) { midi_event_t event; uint8_t data[8] = { 0xF0, 0x7F, 0x7F, 0x04, 0x01, 0, 0, 0xF7 }; 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) { midi_drv_input_buffer = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE); midi_drv_output_buffer = (uint8_t*)malloc(MIDI_DRIVER_BUF_SIZE); if (!midi_drv_input_buffer || !midi_drv_output_buffer) return false; midi_drv_input_event.data = midi_drv_input_buffer; midi_drv_input_event.data_size = 0; 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 *settings = config_get_ptr(); union string_list_elem_attr attr = {0}; const char *err_str = NULL; midi_drv_inputs = string_list_new(); midi_drv_outputs = string_list_new(); RARCH_LOG("[MIDI]: Initializing ...\n"); if (!settings) 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(settings->arrays.midi_driver); if (strcmp(midi_drv->ident, settings->arrays.midi_driver)) strlcpy(settings->arrays.midi_driver, midi_drv->ident, sizeof(settings->arrays.midi_driver)); 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 (string_is_not_equal(settings->arrays.midi_input, "Off")) { if (string_list_find_elem(midi_drv_inputs, settings->arrays.midi_input)) input = settings->arrays.midi_input; else { RARCH_WARN("[MIDI]: Input device \"%s\" unavailable.\n", settings->arrays.midi_input); strlcpy(settings->arrays.midi_input, "Off", sizeof(settings->arrays.midi_input)); } } if (string_is_not_equal(settings->arrays.midi_output, "Off")) { if (string_list_find_elem(midi_drv_outputs, settings->arrays.midi_output)) output = settings->arrays.midi_output; else { RARCH_WARN("[MIDI]: Output device \"%s\" unavailable.\n", settings->arrays.midi_output); strlcpy(settings->arrays.midi_output, "Off", sizeof(settings->arrays.midi_output)); } } 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(settings->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 (string_is_equal(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 (string_is_equal(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 *settings = config_get_ptr(); midi_drv_output_enabled = true; RARCH_LOG("[MIDI]: Output device changed to \"%s\".\n", output); if (settings) midi_driver_set_volume(settings->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 = (int)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 = (int)midi_driver_get_event_size(byte); midi_drv_output_event.data_size = 0; midi_drv_output_event.delta_time = 0; } } else { event_size = (int)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]; }