Added UI for attaching XBLC to expansion port

Converted Audio to use SDL2 Audio instead of QEMU Audio so that users can choose which audio devices to use
This commit is contained in:
specialfred453@gmail.com 2025-01-15 19:14:39 -05:00
parent 5896b9dc91
commit 918634ba00
10 changed files with 1066 additions and 294 deletions

View file

@ -1,6 +1,7 @@
pfiles = [
'controller_mask.png',
'xmu_mask.png',
'xblc_mask.png',
'logo_sdf.png',
'xemu_64x64.png',
'abxy.ttf',

BIN
data/xblc_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -2,6 +2,7 @@
* QEMU USB Xbox Live Communicator (XBLC) Device
*
* Copyright (c) 2022 Ryan Wendland
* Copyright (c) 2025 faha223
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -26,8 +27,8 @@
#include "hw/usb.h"
#include "hw/usb/desc.h"
#include "ui/xemu-input.h"
#include "audio/audio.h"
#include "qemu/fifo8.h"
#include "xblc.h"
//#define DEBUG_XBLC
#ifdef DEBUG_XBLC
@ -51,32 +52,32 @@
#define XBLC_MAX_PACKET 48
#define XBLC_FIFO_SIZE (XBLC_MAX_PACKET * 100) //~100 ms worth of audio at 16bit 24kHz
#define NULL_DEFAULT(a, b) (a == NULL ? b : a)
static const uint8_t silence[256] = {0};
static const uint16_t xblc_sample_rates[5] = {
8000, 11025, 16000, 22050, 24000
};
typedef struct XBLCStream {
char *device_name;
SDL_AudioDeviceID voice;
SDL_AudioSpec spec;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
int volume;
int average_volume;
} XBLCStream;
typedef struct USBXBLCState {
USBDevice dev;
uint8_t device_index;
uint8_t auto_gain_control;
uint16_t sample_rate;
QEMUSoundCard card;
struct audsettings as;
struct {
SWVoiceOut* voice;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
} out;
struct {
SWVoiceIn *voice;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
} in;
XBLCStream out;
XBLCStream in;
} USBXBLCState;
enum {
@ -164,85 +165,235 @@ static void usb_xblc_handle_reset(USBDevice *dev)
USBXBLCState *s = (USBXBLCState *)dev;
DPRINTF("[XBLC] Reset\n");
if(s->in.voice != 0)
SDL_LockAudioDevice(s->in.voice);
if(s->out.voice != 0)
SDL_LockAudioDevice(s->out.voice);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
if(s->in.voice != 0)
SDL_UnlockAudioDevice(s->in.voice);
if(s->out.voice != 0)
SDL_UnlockAudioDevice(s->out.voice);
}
static void output_callback(void *opaque, int avail)
int xblc_audio_stream_get_average_input_volume(void *dev)
{
USBXBLCState *s = (USBXBLCState *)opaque;
USBXBLCState *s = (USBXBLCState*)dev;
return s->in.average_volume;
}
int xblc_audio_stream_get_output_volume(void *dev)
{
USBXBLCState *s = (USBXBLCState*)dev;
return s->out.volume;
}
int xblc_audio_stream_get_input_volume(void *dev)
{
USBXBLCState *s = (USBXBLCState*)dev;
return s->in.volume;
}
void xblc_audio_stream_set_output_volume(void *dev, int volume)
{
USBXBLCState *s = (USBXBLCState*)dev;
s->out.volume = volume;
}
void xblc_audio_stream_set_input_volume(void *dev, int volume)
{
USBXBLCState *s = (USBXBLCState*)dev;
s->in.volume = volume;
}
static void output_callback(void *userdata, uint8_t *stream, int len)
{
USBXBLCState *s = (USBXBLCState *)userdata;
const uint8_t *data;
uint32_t processed, max_len;
// Not enough data to send, wait a bit longer, fill with silence for now
uint32_t max_len;
if (fifo8_num_used(&s->out.fifo) < XBLC_MAX_PACKET) {
do {
processed = AUD_write(s->out.voice, (void *)silence, ARRAY_SIZE(silence));
avail -= processed;
} while (avail > 0 && processed >= XBLC_MAX_PACKET);
return;
}
memcpy(stream, (void*)silence, MIN(len, ARRAY_SIZE(silence)));
} else {
while (len > 0 && !fifo8_is_empty(&s->out.fifo)) {
max_len = MIN(fifo8_num_used(&s->out.fifo), (uint32_t)len);
data = fifo8_pop_bufptr(&s->out.fifo, max_len, &max_len);
// Write speaker data into audio backend
while (avail > 0 && !fifo8_is_empty(&s->out.fifo)) {
max_len = MIN(fifo8_num_used(&s->out.fifo), avail);
data = fifo8_pop_bufptr(&s->out.fifo, max_len, &max_len);
processed = AUD_write(s->out.voice, (void *)data, max_len);
avail -= processed;
if (processed < max_len) return;
if(s->out.volume > SDL_MIX_MAXVOLUME) {
memcpy(stream, data, max_len);
SDL_MixAudioFormat(stream, data, AUDIO_S16LSB, max_len, MIN(s->out.volume - SDL_MIX_MAXVOLUME, SDL_MIX_MAXVOLUME));
} else if(s->out.volume < SDL_MIX_MAXVOLUME) {
memset(stream, 0, len);
SDL_MixAudioFormat(stream, data, AUDIO_S16LSB, max_len, MAX(0, s->out.volume));
} else {
memcpy(stream, data, max_len);
}
stream += max_len;
len -= max_len;
}
}
}
static void input_callback(void *opaque, int avail)
static int calc_average_amplitude(const int16_t *samples, int len) {
int max = 0;
for(int i = 0; i < len / 2; i++) {
max = MAX(max, abs(samples[i]));
}
return max;
}
static void input_callback(void *userdata, uint8_t *stream, int len)
{
USBXBLCState *s = (USBXBLCState *)opaque;
uint32_t processed, max_len;
USBXBLCState *s = (USBXBLCState *)userdata;
s->in.average_volume = s->in.volume *
calc_average_amplitude((int16_t*)stream, len / 2) / 128;
// Get microphone data from audio backend
while (avail > 0 && !fifo8_is_full(&s->in.fifo)) {
max_len = MIN(sizeof(s->in.packet), fifo8_num_free(&s->in.fifo));
processed = AUD_read(s->in.voice, s->in.packet, max_len);
avail -= processed;
fifo8_push_all(&s->in.fifo, s->in.packet, processed);
if (processed < max_len) return;
// Don't try to put more into the queue than will fit
uint32_t max_len = MIN(len, fifo8_num_free(&s->in.fifo));
if (max_len > 0) {
if(s->in.volume < SDL_MIX_MAXVOLUME) {
uint8_t *buffer = g_malloc(max_len);
memset(buffer, 0, max_len);
SDL_MixAudioFormat(buffer, stream, AUDIO_S16LSB, max_len, MAX(s->in.volume, 0));
fifo8_push_all(&s->in.fifo, buffer, max_len);
g_free(buffer);
} else {
fifo8_push_all(&s->in.fifo, stream, max_len);
}
}
}
// Flush excess/old data - this can happen if the user program stops the iso transfers after it
// has setup the xblc.
while (avail > 0)
#ifdef DEBUG_XBLC
static const char *GetFormatString(SDL_AudioFormat format)
{
switch(format)
{
processed = AUD_read(s->in.voice, s->in.packet, XBLC_MAX_PACKET);
avail -= processed;
if (processed == 0) break;
case AUDIO_S16LSB:
return "AUDIO_S16LSB";
case AUDIO_S16MSB:
return "AUDIO_S16MSB";
case AUDIO_S32LSB:
return "AUDIO_S32LSB";
case AUDIO_S32MSB:
return "AUDIO_S32MSB";
case AUDIO_F32LSB:
return "AUDIO_F32LSB";
case AUDIO_F32MSB:
return "AUDIO_F32MSB";
default:
return "Unknown";
}
}
#endif
static void xblc_audio_channel_init(USBXBLCState *s, bool capture, const char *device_name)
{
XBLCStream *channel = capture ? &s->in : &s->out;
if(channel->voice != 0) {
SDL_PauseAudioDevice(channel->voice, 1);
SDL_CloseAudioDevice(channel->voice);
channel->voice = 0;
}
if(channel->device_name != NULL)
g_free(channel->device_name);
if(device_name == NULL)
channel->device_name = NULL;
else
channel->device_name = g_strdup(device_name);
fifo8_reset(&channel->fifo);
if(capture)
s->in.average_volume = 0;
SDL_AudioSpec desired_spec;
desired_spec.channels = 1;
desired_spec.freq = s->sample_rate;
desired_spec.format = AUDIO_S16LSB;
desired_spec.samples = 100;
desired_spec.userdata = (void*)s;
desired_spec.callback = capture ? input_callback : output_callback;
channel->voice = SDL_OpenAudioDevice(device_name,
(int)capture,
&desired_spec,
&channel->spec,
0);
DPRINTF("%sputDevice: %s\n", capture ? "In" : "Out", NULL_DEFAULT(device_name, "Default"));
DPRINTF("%sputDevice: Wanted %d Channels, Obtained %d Channels\n", capture ? "In" : "Out", desired_spec.channels, channel->spec.channels);
DPRINTF("%sputDevice: Wanted %d hz, Obtained %d hz\n", capture ? "In" : "Out", desired_spec.freq, channel->spec.freq);
DPRINTF("%sputDevice: Wanted %s, Obtained %s\n", capture ? "In" : "Out", GetFormatString(desired_spec.format), GetFormatString(channel->spec.format));
DPRINTF("%sputDevice: Wanted samples %d, Obtained samples %d\n", capture ? "In" : "Out", desired_spec.samples, channel->spec.samples);
SDL_PauseAudioDevice(channel->voice, 0);
}
static bool should_init_stream(const XBLCStream *stream, const char *requested_device_name)
{
// If the voice has not been initialized, initialize it
if (stream->voice == 0)
return true;
// If one of the names is null and the other is not, initialize it
if ((stream->device_name == NULL) ^ (requested_device_name == NULL))
return true;
// If neither name is null, but they don't match, initialize it
if (stream->device_name != NULL &&
requested_device_name != NULL &&
strcmp(stream->device_name, requested_device_name) != 0)
return true;
// We don't need to initialize it
return false;
}
static void xblc_audio_stream_init(USBDevice *dev, uint16_t sample_rate)
{
USBXBLCState *s = (USBXBLCState *)dev;
bool init_input_stream = false, init_output_stream = false;
AUD_set_active_out(s->out.voice, FALSE);
AUD_set_active_in(s->in.voice, FALSE);
ControllerState *controller = xemu_input_get_bound(s->device_index);
assert(controller->peripheral_types[0] == PERIPHERAL_XBLC);
assert(controller->peripherals[0] != NULL);
fifo8_reset(&s->in.fifo);
fifo8_reset(&s->out.fifo);
XblcState *xblc = (XblcState*)controller->peripherals[0];
s->as.freq = sample_rate;
s->as.nchannels = 1;
s->as.fmt = AUDIO_FORMAT_S16;
s->as.endianness = 0;
if(s->sample_rate != sample_rate) {
init_input_stream = true;
init_output_stream = true;
s->sample_rate = sample_rate;
}
s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_XBLC "-speaker",
s, output_callback, &s->as);
init_input_stream |= should_init_stream(&s->in, xblc->input_device_name);
init_output_stream |= should_init_stream(&s->out, xblc->output_device_name);
s->in.voice = AUD_open_in(&s->card, s->in.voice, TYPE_USB_XBLC "-microphone",
s, input_callback, &s->as);
if (init_input_stream) {
xblc_audio_channel_init(s, true, xblc->input_device_name);
} else {
DPRINTF("Input Stream will not change\n");
}
if (init_output_stream) {
xblc_audio_channel_init(s, false, xblc->output_device_name);
} else {
DPRINTF("Output Stream will not change\n");
}
AUD_set_active_out(s->out.voice, TRUE);
AUD_set_active_in(s->in.voice, TRUE);
DPRINTF("[XBLC] Init audio streams at %d Hz\n", sample_rate);
}
void xblc_audio_stream_reinit(void *dev)
{
USBXBLCState *s = (USBXBLCState *)dev;
xblc_audio_stream_init(dev, s->sample_rate);
}
static void usb_xblc_handle_control(USBDevice *dev, USBPacket *p,
int request, int value, int index, int length, uint8_t *data)
{
@ -324,15 +475,14 @@ static void usb_xbox_communicator_unrealize(USBDevice *dev)
{
USBXBLCState *s = USB_XBLC(dev);
AUD_set_active_out(s->out.voice, false);
AUD_set_active_in(s->in.voice, false);
SDL_PauseAudioDevice(s->in.voice, 1);
SDL_PauseAudioDevice(s->out.voice, 1);
fifo8_destroy(&s->out.fifo);
fifo8_destroy(&s->in.fifo);
AUD_close_out(&s->card, s->out.voice);
AUD_close_in(&s->card, s->in.voice);
AUD_remove_card(&s->card);
SDL_CloseAudioDevice(s->in.voice);
SDL_CloseAudioDevice(s->out.voice);
}
static void usb_xblc_class_initfn(ObjectClass *klass, void *data)
@ -349,10 +499,12 @@ static void usb_xbox_communicator_realize(USBDevice *dev, Error **errp)
USBXBLCState *s = USB_XBLC(dev);
usb_desc_create_serial(dev);
usb_desc_init(dev);
AUD_register_card(TYPE_USB_XBLC, &s->card, errp);
fifo8_create(&s->in.fifo, XBLC_FIFO_SIZE);
fifo8_create(&s->out.fifo, XBLC_FIFO_SIZE);
s->in.volume = SDL_MIX_MAXVOLUME;
s->out.volume = SDL_MIX_MAXVOLUME;
}
static Property xblc_properties[] = {

38
hw/xbox/xblc.h Normal file
View file

@ -0,0 +1,38 @@
/*
* QEMU USB Xbox Live Communicator (XBLC) Device
*
* Copyright (c) 2025 faha223
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HW_XBOX_XBLC_H
#define HW_XBOX_XBLC_H
#ifdef __cplusplus
extern "C" {
#endif
void xblc_audio_stream_reinit(void *dev);
int xblc_audio_stream_get_output_volume(void *dev);
int xblc_audio_stream_get_input_volume(void *dev);
void xblc_audio_stream_set_output_volume(void *dev, int volume);
void xblc_audio_stream_set_input_volume(void *dev, int volume);
int xblc_audio_stream_get_average_input_volume(void *dev);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -33,6 +33,7 @@
#include "xemu-settings.h"
#include "sysemu/blockdev.h"
#include "../hw/xbox/xblc.h"
// #define DEBUG_INPUT
@ -121,6 +122,12 @@ static int sdl_kbd_scancode_map[25];
static const int port_map[4] = { 3, 4, 1, 2 };
const char *peripheral_type_names[3] = {
"None",
"Memory Unit",
"Xbox Live Communicator"
};
void xemu_input_init(void)
{
if (g_config.input.background_input_capture) {
@ -184,7 +191,7 @@ void xemu_input_init(void)
char buf[128];
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
xemu_queue_notification(buf);
xemu_input_rebind_xmu(port);
xemu_input_rebind_peripherals(port);
}
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
@ -307,7 +314,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
char buf[128];
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
xemu_queue_notification(buf);
xemu_input_rebind_xmu(port);
xemu_input_rebind_peripherals(port);
}
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
@ -359,6 +366,88 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
}
}
char *xemu_input_serialize_xblc_settings(XblcState *xblc)
{
const char *default_device_name = "Default";
const char *output_device_label = xblc->output_device_name == NULL ?
default_device_name : xblc->output_device_name;
const char *input_device_label = xblc->input_device_name == NULL ?
default_device_name : xblc->input_device_name;
return g_strdup_printf("%d|%d|%s|%s", (int)(200 * xblc->output_device_volume), (int)(200 * xblc->input_device_volume), output_device_label, input_device_label);
}
XblcState *xemu_input_deserialize_xblc_settings(const char *param)
{
const char *default_device_name = "Default";
char temp[1024];
memset(temp, 0, 1024);
XblcState *xblc = (XblcState*)g_malloc(sizeof(XblcState));
memset(xblc, 0, sizeof(XblcState));
xblc->input_device_volume = 0.5f;
xblc->output_device_volume = 0.5f;
if(param != NULL) {
char *output_device = NULL;
char *input_device = NULL;
int output_device_volume = 100;
int input_device_volume = 100;
DPRINTF("Parameters: %s\n", param);
char *delimiterPtr = strchr(param, '|');
if(delimiterPtr != NULL) {
memset(temp, 0, 1024);
memcpy(temp, param, delimiterPtr - param);
output_device_volume = atoi(temp);
param = delimiterPtr+1;
delimiterPtr = strchr(param, '|');
if(delimiterPtr != NULL) {
memset(temp, 0, 1024);
memcpy(temp, param, delimiterPtr - param);
input_device_volume = atoi(temp);
param = delimiterPtr+1;
delimiterPtr = strchr(param, '|');
if(delimiterPtr != NULL) {
output_device = g_strndup(param, delimiterPtr - param);
input_device = g_strdup(delimiterPtr+1);
DPRINTF("Output Volume: %d\n", output_device_volume);
DPRINTF("Input Volume: %d\n", input_device_volume);
DPRINTF("Output Device: %s\n", output_device);
DPRINTF("Input Device: %s\n", input_device);
if(strcmp(output_device, default_device_name) == 0) {
g_free((void*)output_device);
output_device = NULL;
}
if(strcmp(input_device, default_device_name) == 0) {
g_free((void*)input_device);
input_device = NULL;
}
xblc->output_device_name = output_device;
xblc->output_device_volume = output_device_volume / 200.0f;
xblc->input_device_name = input_device;
xblc->input_device_volume = input_device_volume / 200.0f;
} else {
DPRINTF("Delimiter not found in %s\n", param);
}
} else {
DPRINTF("Delimiter not found in %s\n", param);
}
} else {
DPRINTF("Delimiter not found in %s\n", param);
}
} else {
DPRINTF("Param is NULL\n");
}
return xblc;
}
void xemu_input_update_controller(ControllerState *state)
{
int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
@ -489,10 +578,16 @@ void xemu_input_bind(int index, ControllerState *state, int save)
// Unbind any XMUs
for (int i = 0; i < 2; i++) {
if (bound_controllers[index]->peripherals[i]) {
// If this was an XMU, unbind the XMU
if (bound_controllers[index]->peripheral_types[i] ==
PERIPHERAL_XMU)
xemu_input_unbind_xmu(index, i);
// If a peripheral was bound, unbind the peripheral
switch (bound_controllers[index]->peripheral_types[i]) {
case PERIPHERAL_XMU:
case PERIPHERAL_XBLC:
xemu_input_unbind_peripheral(index, i);
break;
default:
assert(false);
break;
}
// Free up the XmuState and set the peripheral type to none
g_free(bound_controllers[index]->peripherals[i]);
@ -577,6 +672,28 @@ void xemu_input_bind(int index, ControllerState *state, int save)
}
}
static void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
{
assert(player_index >= 0 && player_index < 4);
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
ControllerState *state = bound_controllers[player_index];
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU)
return;
XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index];
if (xmu != NULL) {
if (xmu->dev != NULL) {
qdev_unplug((DeviceState *)xmu->dev, &error_abort);
object_unref(OBJECT(xmu->dev));
xmu->dev = NULL;
}
g_free((void *)xmu->filename);
xmu->filename = NULL;
}
}
bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
const char *filename, bool is_rebind)
{
@ -681,71 +798,294 @@ bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
return true;
}
void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
static void xemu_input_rebind_xmu(int port, int expansion_slot_index)
{
assert(player_index >= 0 && player_index < 4);
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
enum peripheral_type peripheral_type =
(enum peripheral_type)(*peripheral_types_settings_map[port][expansion_slot_index]);
ControllerState *state = bound_controllers[player_index];
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU)
// If peripheralType is out of range, change the settings for this
// controller and peripheral port to default
if (peripheral_type < PERIPHERAL_NONE ||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
xemu_save_peripheral_settings(port, expansion_slot_index, PERIPHERAL_NONE, NULL);
peripheral_type = PERIPHERAL_NONE;
return;
}
XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index];
if (xmu != NULL) {
if (xmu->dev != NULL) {
qdev_unplug((DeviceState *)xmu->dev, &error_abort);
object_unref(OBJECT(xmu->dev));
xmu->dev = NULL;
const char *param = *peripheral_params_settings_map[port][expansion_slot_index];
if (peripheral_type == PERIPHERAL_XMU) {
if (param != NULL && strlen(param) > 0) {
// This is an XMU and needs to be bound to this controller
if (qemu_access(param, R_OK | W_OK) == 0) {
bound_controllers[port]->peripheral_types[expansion_slot_index] =
peripheral_type;
bound_controllers[port]->peripherals[expansion_slot_index] =
g_malloc(sizeof(XmuState));
memset(bound_controllers[port]->peripherals[expansion_slot_index], 0,
sizeof(XmuState));
bool did_bind = xemu_input_bind_xmu(port, expansion_slot_index, param, true);
if (did_bind) {
char *buf =
g_strdup_printf("Connected Memory Unit %s to Player %d Expansion Slot %c",
param, port + 1, 'A' + expansion_slot_index);
xemu_queue_notification(buf);
g_free(buf);
}
} else {
char *buf =
g_strdup_printf("Unable to bind Memory Unit at %s to Player %d Expansion Slot %c",
param, port + 1, 'A' + expansion_slot_index);
xemu_queue_error_message(buf);
g_free(buf);
}
}
g_free((void *)xmu->filename);
xmu->filename = NULL;
}
}
void xemu_input_rebind_xmu(int port)
static void xemu_input_unbind_xblc(int player_index)
{
const int expansion_slot_index = 0;
assert(player_index >= 0 && player_index < 4);
ControllerState *state = bound_controllers[player_index];
if(state == NULL)
return;
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XBLC)
return;
XblcState *xblc = (XblcState *)state->peripherals[expansion_slot_index];
if (xblc != NULL) {
if (xblc->dev != NULL) {
qdev_unplug((DeviceState *)xblc->dev, &error_abort);
object_unref(OBJECT(xblc->dev));
xblc->dev = NULL;
}
g_free((void *)xblc->output_device_name);
g_free((void *)xblc->input_device_name);
xblc->output_device_name = NULL;
xblc->input_device_name = NULL;
}
}
bool xemu_input_bind_xblc(int player_index, const char *output_device,
const char *input_device, bool is_rebind)
{
// Xbox Live Communicator is keyed such that it can only go into expansion slot 0
DPRINTF("Connecting Xbox Live Communicator Headset\n");
DPRINTF("XBLC Output Device: %s\n", output_device);
DPRINTF("XBLC Input Device: %s\n", input_device);
assert(player_index >= 0 && player_index < 4);
ControllerState *player = bound_controllers[player_index];
if(player == NULL)
return false;
enum peripheral_type peripheral_type =
player->peripheral_types[0];
if (peripheral_type != PERIPHERAL_XBLC)
return false;
XblcState *xblc = (XblcState *)player->peripherals[0];
// Unbind existing XBLC
if (xblc->dev != NULL) {
xemu_input_unbind_xblc(player_index);
}
// Look for any other XBLCs that are using these devices
for (int player_i = 0; player_i < 4; player_i++) {
ControllerState *state = bound_controllers[player_i];
if (state != NULL) {
if (state->peripheral_types[0] == PERIPHERAL_XBLC) {
XblcState *xblc_i =
(XblcState *)state->peripherals[0];
assert(xblc_i);
if(xblc_i->dev != NULL) {
bool already_bound = false;
if (!((xblc_i->output_device_name == NULL) ^ (output_device == NULL))) {
if(output_device ==NULL)
already_bound = true;
else
already_bound = strcmp(xblc_i->output_device_name, output_device) == 0;
}
if (already_bound) {
if(output_device == NULL)
output_device = "Default";
char *buf =
g_strdup_printf("Output Device %s is already mounted on "
"player %d slot %c\r\n", output_device,
player_i + 1, 'A');
xemu_queue_notification(buf);
g_free(buf);
return false;
}
already_bound = false;
if (!((xblc_i->input_device_name == NULL) ^ (input_device == NULL))) {
if(input_device == NULL)
already_bound = true;
else
already_bound = strcmp(xblc_i->input_device_name, input_device) == 0;
}
if (already_bound) {
char *buf =
g_strdup_printf("Input Device %s is already mounted on "
"player %d slot %c\r\n", input_device,
player_i + 1, 'A');
xemu_queue_notification(buf);
g_free(buf);
return false;
}
}
}
}
}
if(xblc->input_device_name != NULL)
g_free((void*)xblc->input_device_name);
if(input_device == NULL)
xblc->input_device_name = NULL;
else
xblc->input_device_name = g_strdup(input_device);
if(xblc->output_device_name != NULL)
g_free((void*)xblc->output_device_name);
if(output_device == NULL)
xblc->output_device_name = NULL;
else
xblc->output_device_name = g_strdup(output_device);
char *tmp;
static int id_counter = 0;
tmp = g_strdup_printf("xblc_%d", id_counter++);
// Create the usb-storage device
QDict *qdict = qdict_new();
// Specify device driver
qdict_put_str(qdict, "driver", "usb-xblc");
// Specify index/port
tmp = g_strdup_printf("1.%d.2", port_map[player_index]);
qdict_put_str(qdict, "port", tmp);
g_free(tmp);
// Create the device
QemuOpts *opts =
qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &error_abort);
DeviceState *dev = qdev_device_add(opts, &error_abort);
assert(dev);
xblc->dev = (void *)dev;
xblc_audio_stream_set_output_volume(xblc->dev, (int)(256 * xblc->output_device_volume));
xblc_audio_stream_set_input_volume(xblc->dev, (int)(256 * xblc->input_device_volume));
// Unref for eventual cleanup
qobject_unref(qdict);
if (!is_rebind) {
char *buf = xemu_input_serialize_xblc_settings(xblc);
xemu_save_peripheral_settings(player_index, 0, peripheral_type, buf);
g_free(buf);
}
return true;
}
static void xemu_input_rebind_xblc(int port)
{
enum peripheral_type peripheral_type =
(enum peripheral_type)(*peripheral_types_settings_map[port][0]);
// If peripheralType is out of range, change the settings for this
// controller and peripheral port to default
if (peripheral_type < PERIPHERAL_NONE ||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
xemu_save_peripheral_settings(port, 0, PERIPHERAL_NONE, NULL);
peripheral_type = PERIPHERAL_NONE;
return;
}
const char *param = *peripheral_params_settings_map[port][0];
if (peripheral_type == PERIPHERAL_XBLC) {
bound_controllers[port]->peripheral_types[0] = peripheral_type;
bound_controllers[port]->peripherals[0] = xemu_input_deserialize_xblc_settings(param);
XblcState *xblc = (XblcState*)bound_controllers[port]->peripherals[0];
DPRINTF("XLBC Parameter: %s\n", param);
char *output_temp = xblc->output_device_name == NULL ? NULL : g_strdup(xblc->output_device_name);
char *input_temp = xblc->input_device_name == NULL ? NULL : g_strdup(xblc->input_device_name);
bool did_bind = xemu_input_bind_xblc(port, output_temp, input_temp, true);
if (did_bind) {
char *buf =
g_strdup_printf("Connected Xbox Live Communicator Headset to Player %d Expansion Slot A",
port + 1);
xemu_queue_notification(buf);
g_free(buf);
}
if(output_temp != NULL)
g_free(output_temp);
if(input_temp != NULL)
g_free(input_temp);
}
}
void xemu_input_unbind_peripheral(int player_index, int expansion_slot_index)
{
ControllerState *state = bound_controllers[player_index];
if(state != NULL)
{
switch(state->peripheral_types[expansion_slot_index])
{
case PERIPHERAL_XMU:
xemu_input_unbind_xmu(player_index, expansion_slot_index);
break;
case PERIPHERAL_XBLC:
assert(player_index == 0);
xemu_input_unbind_xblc(player_index);
break;
case PERIPHERAL_NONE:
break;
default:
assert(false);
}
}
}
void xemu_input_rebind_peripherals(int port)
{
// Try to bind peripherals back to controller
for (int i = 0; i < 2; i++) {
enum peripheral_type peripheral_type =
(enum peripheral_type)(*peripheral_types_settings_map[port][i]);
// If peripheralType is out of range, change the settings for this
// controller and peripheral port to default
if (peripheral_type < PERIPHERAL_NONE ||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL);
peripheral_type = PERIPHERAL_NONE;
}
const char *param = *peripheral_params_settings_map[port][i];
if (peripheral_type == PERIPHERAL_XMU) {
if (param != NULL && strlen(param) > 0) {
// This is an XMU and needs to be bound to this controller
if (qemu_access(param, R_OK | W_OK) == 0) {
bound_controllers[port]->peripheral_types[i] =
peripheral_type;
bound_controllers[port]->peripherals[i] =
g_malloc(sizeof(XmuState));
memset(bound_controllers[port]->peripherals[i], 0,
sizeof(XmuState));
bool did_bind = xemu_input_bind_xmu(port, i, param, true);
if (did_bind) {
char *buf =
g_strdup_printf("Connected XMU %s to port %d%c",
param, port + 1, 'A' + i);
xemu_queue_notification(buf);
g_free(buf);
}
} else {
char *buf =
g_strdup_printf("Unable to bind XMU at %s to port %d%c",
param, port + 1, 'A' + i);
xemu_queue_error_message(buf);
g_free(buf);
switch(peripheral_type)
{
case PERIPHERAL_XMU:
xemu_input_rebind_xmu(port, i);
break;
case PERIPHERAL_XBLC:
if(i != 0) {
// An Xbox Live Communicator Headset cannot be plugged into Expansion Slot B
xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL);
continue;
}
}
xemu_input_rebind_xblc(port);
break;
default:
continue;
}
}
}

View file

@ -65,13 +65,22 @@ enum controller_input_device_type {
INPUT_DEVICE_SDL_GAMECONTROLLER,
};
enum peripheral_type { PERIPHERAL_NONE, PERIPHERAL_XMU, PERIPHERAL_TYPE_COUNT };
enum peripheral_type { PERIPHERAL_NONE, PERIPHERAL_XMU, PERIPHERAL_XBLC, PERIPHERAL_TYPE_COUNT };
extern const char *peripheral_type_names[3];
typedef struct XmuState {
const char *filename;
void *dev;
} XmuState;
typedef struct XblcState {
const char *output_device_name;
const char *input_device_name;
float input_device_volume;
float output_device_volume;
void *dev;
} XblcState;
typedef struct ControllerState {
QTAILQ_ENTRY(ControllerState) entry;
@ -123,9 +132,13 @@ ControllerState *xemu_input_get_bound(int index);
void xemu_input_bind(int index, ControllerState *state, int save);
bool xemu_input_bind_xmu(int player_index, int peripheral_port_index,
const char *filename, bool is_rebind);
void xemu_input_rebind_xmu(int port);
void xemu_input_unbind_xmu(int player_index, int peripheral_port_index);
bool xemu_input_bind_xblc(int player_index, const char *output_device,
const char *input_device, bool is_rebind);
void xemu_input_unbind_peripheral(int player_index, int expansion_slot_index);
void xemu_input_rebind_peripherals(int port);
int xemu_input_get_controller_default_bind_port(ControllerState *state, int start);
char *xemu_input_serialize_xblc_settings(XblcState *xblc);
XblcState *xemu_input_deserialize_xblc_settings(const char *param);
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
int peripheral_type,
const char *peripheral_parameter);

View file

@ -23,6 +23,7 @@
#include "data/logo_sdf.png.h"
#include "data/xemu_64x64.png.h"
#include "data/xmu_mask.png.h"
#include "data/xblc_mask.png.h"
#include "notifications.hh"
#include "stb_image.h"
#include <fpng.h>
@ -31,9 +32,10 @@
#include <vector>
#include "ui/shader/xemu-logo-frag.h"
#include "../../hw/xbox/xblc.h"
Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
GLuint g_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
Fbo *controller_fbo, *xmu_fbo, *xblc_fbo, *logo_fbo;
GLuint g_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex, g_xblc_tex;
enum class ShaderType {
Blit,
@ -421,7 +423,8 @@ static const struct rect tex_items[] = {
{ 67, 48, 28, 28 }, // obj_port_lbl_2
{ 67, 20, 28, 28 }, // obj_port_lbl_3
{ 95, 76, 28, 28 }, // obj_port_lbl_4
{ 0, 0, 512, 512 } // obj_xmu
{ 0, 0, 512, 512 }, // obj_xmu
{ 0, 0, 512, 512 }, // obj_xblc
};
enum tex_item_names {
@ -433,7 +436,8 @@ enum tex_item_names {
obj_port_lbl_2,
obj_port_lbl_3,
obj_port_lbl_4,
obj_xmu
obj_xmu,
obj_xblc
};
void InitCustomRendering(void)
@ -447,6 +451,9 @@ void InitCustomRendering(void)
g_xmu_tex = LoadTextureFromMemory(xmu_mask_data, xmu_mask_size);
xmu_fbo = new Fbo(512, 256);
g_xblc_tex = LoadTextureFromMemory(xblc_mask_data, xblc_mask_size);
xblc_fbo = new Fbo(256, 256);
g_logo_tex = LoadTextureFromMemory(logo_sdf_data, logo_sdf_size);
g_logo_shader = NewDecalShader(ShaderType::Logo);
logo_fbo = new Fbo(512, 512);
@ -672,6 +679,32 @@ void RenderXmu(float frame_x, float frame_y, uint32_t primary_color,
glUseProgram(0);
}
void RenderXblc(XblcState *xblc, float frame_x, float frame_y,
uint32_t primary_color, uint32_t secondary_color)
{
glUseProgram(g_decal_shader->prog);
glBindVertexArray(g_decal_shader->vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, g_xblc_tex);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ZERO);
// Render xblc
RenderDecal(g_decal_shader, frame_x, frame_y, 256, 256,
tex_items[obj_xblc].x, tex_items[obj_xblc].y,
tex_items[obj_xblc].w, tex_items[obj_xblc].h, primary_color,
secondary_color, 0);
if(xblc->dev != NULL) {
int average_volume = xblc_audio_stream_get_average_input_volume(xblc->dev);
float percent = average_volume / 32768.0f;
RenderMeter(g_decal_shader, 4, 4, 252, 5, percent, primary_color + 0x40, primary_color + 0xFF);
}
glBindVertexArray(0);
glUseProgram(0);
}
void RenderLogo(uint32_t time)
{
uint32_t color = 0x62ca13ff;

View file

@ -38,7 +38,7 @@ public:
void Restore();
};
extern Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
extern Fbo *controller_fbo, *xmu_fbo, *xblc_fbo, *logo_fbo;
extern GLuint g_icon_tex;
void InitCustomRendering(void);
@ -49,6 +49,8 @@ void RenderControllerPort(float frame_x, float frame_y, int i,
uint32_t port_color);
void RenderXmu(float frame_x, float frame_y, uint32_t primary_color,
uint32_t secondary_color);
void RenderXblc(XblcState *state, float frame_x, float frame_y,
uint32_t primary_color, uint32_t secondary_color);
void RenderFramebuffer(GLint tex, int width, int height, bool flip);
void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]);
bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector<uint8_t> &png, int max_width = 0, int max_height = 0);

View file

@ -41,6 +41,7 @@
#include "../xemu-xbe.h"
#include "../thirdparty/fatx/fatx.h"
#include "../../hw/xbox/xblc.h"
#define DEFAULT_XMU_SIZE 8388608
@ -208,7 +209,7 @@ void MainMenuInputView::Draw()
// If we previously had no controller connected, we can rebind
// the XMU
if (bound_state == NULL)
xemu_input_rebind_xmu(active);
xemu_input_rebind_peripherals(active);
bound_state = iter;
}
@ -277,164 +278,8 @@ void MainMenuInputView::Draw()
ImGui::PopFont();
ImGui::SetCursorPos(pos);
if (bound_state) {
SectionTitle("Expansion Slots");
// Begin a 2-column layout to render the expansion slots
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
g_viewport_mgr.Scale(ImVec2(0, 12)));
ImGui::Columns(2, "mixed", false);
xmu_fbo->Target();
id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
const char *comboLabels[2] = { "###ExpansionSlotA",
"###ExpansionSlotB" };
for (int i = 0; i < 2; i++) {
// Display a combo box to allow the user to choose the type of
// peripheral they want to use
enum peripheral_type selected_type =
bound_state->peripheral_types[i];
const char *peripheral_type_names[2] = { "None", "Memory Unit" };
const char *selected_peripheral_type =
peripheral_type_names[selected_type];
ImGui::SetNextItemWidth(-FLT_MIN);
if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type,
ImGuiComboFlags_NoArrowButton)) {
// Handle all available peripheral types
for (int j = 0; j < 2; j++) {
bool is_selected = selected_type == j;
ImGui::PushID(j);
const char *selectable_label = peripheral_type_names[j];
if (ImGui::Selectable(selectable_label, is_selected)) {
// Free any existing peripheral
if (bound_state->peripherals[i] != NULL) {
if (bound_state->peripheral_types[i] ==
PERIPHERAL_XMU) {
// Another peripheral was already bound.
// Unplugging
xemu_input_unbind_xmu(active, i);
}
// Free the existing state
g_free((void *)bound_state->peripherals[i]);
bound_state->peripherals[i] = NULL;
}
// Change the peripheral type to the newly selected type
bound_state->peripheral_types[i] =
(enum peripheral_type)j;
// Allocate state for the new peripheral
if (j == PERIPHERAL_XMU) {
bound_state->peripherals[i] =
g_malloc(sizeof(XmuState));
memset(bound_state->peripherals[i], 0,
sizeof(XmuState));
}
xemu_save_peripheral_settings(
active, i, bound_state->peripheral_types[i], NULL);
}
if (is_selected) {
ImGui::SetItemDefaultFocus();
}
ImGui::PopID();
}
ImGui::EndCombo();
}
DrawComboChevron();
// Set an X offset to center the image button within the column
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() -
xmu_w * g_viewport_mgr.m_scale -
2 * port_padding * g_viewport_mgr.m_scale) /
2));
selected_type = bound_state->peripheral_types[i];
if (selected_type == PERIPHERAL_XMU) {
float x = xmu_x + i * xmu_x_stride;
float y = xmu_y;
XmuState *xmu = (XmuState *)bound_state->peripherals[i];
if (xmu->filename != NULL && strlen(xmu->filename) > 0) {
RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00);
} else {
RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00);
}
ImVec2 xmu_display_size;
if (ImGui::GetContentRegionMax().x <
xmu_h * g_viewport_mgr.m_scale) {
xmu_display_size.x = ImGui::GetContentRegionMax().x / 2;
xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w;
} else {
xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale,
xmu_h * g_viewport_mgr.m_scale);
}
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() - xmu_display_size.x) /
2.0));
ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1),
ImVec2(0.5f * (i + 1), 0));
// Button to generate a new XMU
ImGui::PushID(i);
if (ImGui::Button("New Image", ImVec2(250, 0))) {
int flags = NOC_FILE_DIALOG_SAVE |
NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION;
const char *new_path = PausedFileOpen(
flags, img_file_filters, NULL, "xmu.img");
if (new_path) {
if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) {
// XMU was created successfully. Bind it
xemu_input_bind_xmu(active, i, new_path, false);
} else {
// Show alert message
char *msg = g_strdup_printf(
"Unable to create XMU image at %s", new_path);
xemu_queue_error_message(msg);
g_free(msg);
}
}
}
const char *xmu_port_path = NULL;
if (xmu->filename == NULL)
xmu_port_path = g_strdup("");
else
xmu_port_path = g_strdup(xmu->filename);
if (FilePicker("Image", &xmu_port_path, img_file_filters)) {
if (strlen(xmu_port_path) == 0) {
xemu_input_unbind_xmu(active, i);
} else {
xemu_input_bind_xmu(active, i, xmu_port_path, false);
}
}
g_free((void *)xmu_port_path);
ImGui::PopID();
}
ImGui::NextColumn();
}
xmu_fbo->Restore();
ImGui::PopStyleVar(); // ItemSpacing
ImGui::Columns(1);
}
if (bound_state)
DrawExpansionSlotOptions(active);
SectionTitle("Options");
Toggle("Auto-bind controllers", &g_config.input.auto_bind,
@ -444,6 +289,350 @@ void MainMenuInputView::Draw()
"Capture even if window is unfocused (requires restart)");
}
void MainMenuInputView::DrawExpansionSlotOptions(int active)
{
SectionTitle("Expansion Slots");
// Begin a 2-column layout to render the expansion slots
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
g_viewport_mgr.Scale(ImVec2(8, 12)));
ImGui::Columns(2, nullptr, false);
for (int i = 0; i < 2; i++) {
DrawExpansionSlotOptions(active, i);
ImGui::NextColumn();
}
ImGui::Columns(1);
ImGui::PopStyleVar(); // ItemSpacing
}
void MainMenuInputView::DrawExpansionSlotOptions(int active, int expansion_slot_index)
{
ControllerState *bound_state = xemu_input_get_bound(active);
const char *comboLabels[2] = { "###ExpansionSlotA",
"###ExpansionSlotB" };
// Display a combo box to allow the user to choose the type of
// peripheral they want to use
enum peripheral_type selected_type =
bound_state->peripheral_types[expansion_slot_index];
enum peripheral_type peripheral_types[2][3] = {
{ PERIPHERAL_NONE, PERIPHERAL_XMU, PERIPHERAL_XBLC }, // Slot A
{ PERIPHERAL_NONE, PERIPHERAL_XMU } // Slot B
};
const char *selected_peripheral_type = peripheral_type_names[selected_type];
ImGui::SetNextItemWidth(ImGui::GetColumnWidth() - (g_viewport_mgr.m_scale * 10));
if (ImGui::BeginCombo(comboLabels[expansion_slot_index], selected_peripheral_type, ImGuiComboFlags_NoArrowButton)) {
// Handle all available peripheral types
for (int j = 0; j < 3 - expansion_slot_index; j++) {
bool is_selected = selected_type == j;
ImGui::PushID(j);
const char *selectable_label = peripheral_type_names[peripheral_types[expansion_slot_index][j]];
if (ImGui::Selectable(selectable_label, is_selected)) {
// Free any existing peripheral
if (bound_state->peripherals[expansion_slot_index] != NULL) {
xemu_input_unbind_peripheral(active, expansion_slot_index);
// Free the existing state
g_free((void *)bound_state->peripherals[expansion_slot_index]);
bound_state->peripherals[expansion_slot_index] = NULL;
}
// Change the peripheral type to the newly selected type
bound_state->peripheral_types[expansion_slot_index] =
(enum peripheral_type)j;
// Allocate state for the new peripheral
switch(j)
{
case PERIPHERAL_XMU:
bound_state->peripherals[expansion_slot_index] = g_malloc(sizeof(XmuState));
memset(bound_state->peripherals[expansion_slot_index], 0, sizeof(XmuState));
xemu_save_peripheral_settings(active, expansion_slot_index, bound_state->peripheral_types[expansion_slot_index], NULL);
break;
case PERIPHERAL_XBLC:
bound_state->peripherals[expansion_slot_index] = g_malloc(sizeof(XblcState));
memset(bound_state->peripherals[expansion_slot_index], 0, sizeof(XblcState));
XblcState *xblc = (XblcState*)bound_state->peripherals[expansion_slot_index];
xblc->output_device_volume = 0.5;
xblc->input_device_volume = 0.5;
if(xemu_input_bind_xblc(active, NULL, NULL, false)) {
char *buf = g_strdup_printf(
"Connected Xbox Live Communicator Headset to Player %d Expansion Slot %c.",
active + 1, 'A' + expansion_slot_index);
xemu_queue_notification(buf);
g_free(buf);
}
break;
}
}
if (is_selected) {
ImGui::SetItemDefaultFocus();
}
ImGui::PopID();
}
ImGui::EndCombo();
}
DrawComboChevron();
// Set an X offset to center the image button within the column
selected_type = bound_state->peripheral_types[expansion_slot_index];
if (selected_type == PERIPHERAL_XMU) {
DrawXmuSettings(active, expansion_slot_index);
} else if(selected_type == PERIPHERAL_XBLC) {
DrawXblcSettings(active, expansion_slot_index);
}
}
void MainMenuInputView::DrawXmuSettings(int active, int expansion_slot_index)
{
// Dimensions of XMU
const float xmu_x = 0, xmu_x_stride = 256, xmu_y = 0;
const float xmu_w = 256, xmu_h = 256;
const int port_padding = 8;
float max_width = ImGui::GetColumnWidth() - (10 * g_viewport_mgr.m_scale);
const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
ControllerState *bound_state = xemu_input_get_bound(active);
assert(bound_state);
float x = xmu_x + expansion_slot_index * xmu_x_stride;
float y = xmu_y;
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() -
xmu_w * g_viewport_mgr.m_scale -
2 * port_padding * g_viewport_mgr.m_scale) /
2));
xmu_fbo->Target();
ImTextureID id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
XmuState *xmu = (XmuState *)bound_state->peripherals[expansion_slot_index];
uint32_t fg_color = (xmu->filename != NULL && strlen(xmu->filename) > 0) ? 0x81dc8a00 : 0x1f1f1f00;
RenderXmu(x, y, fg_color, 0x0f0f0f00);
ImVec2 xmu_display_size;
if (max_width < xmu_w * g_viewport_mgr.m_scale) {
xmu_display_size.x = max_width;
xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w;
} else {
xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale,
xmu_h * g_viewport_mgr.m_scale);
}
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() - xmu_display_size.x) /
2.0));
ImGui::Image(id, xmu_display_size, ImVec2(0.5f * expansion_slot_index, 1),
ImVec2(0.5f * (expansion_slot_index + 1), 0));
ImVec2 pos = ImGui::GetCursorPos();
xmu_fbo->Restore();
ImGui::SetCursorPos(pos);
// Button to generate a new XMU
ImGui::PushID(expansion_slot_index);
ImGui::SetNextItemWidth(max_width);
if (ImGui::Button("New Image", ImVec2(250, 0))) {
int flags = NOC_FILE_DIALOG_SAVE |
NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION;
const char *new_path = PausedFileOpen(
flags, img_file_filters, NULL, "xmu.img");
if (new_path) {
if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) {
// XMU was created successfully. Bind it
xemu_input_bind_xmu(active, expansion_slot_index, new_path, false);
} else {
// Show alert message
char *msg = g_strdup_printf(
"Unable to create XMU image at %s", new_path);
xemu_queue_error_message(msg);
g_free(msg);
}
}
}
const char *xmu_port_path = NULL;
if (xmu->filename == NULL)
xmu_port_path = g_strdup("");
else
xmu_port_path = g_strdup(xmu->filename);
if (FilePicker("Image", &xmu_port_path, img_file_filters)) {
if (strlen(xmu_port_path) == 0) {
xemu_input_unbind_peripheral(active, expansion_slot_index);
} else {
xemu_input_bind_xmu(active, expansion_slot_index, xmu_port_path, false);
}
}
g_free((void*)xmu_port_path);
ImGui::PopID();
}
static void DrawAudioDeviceSelectComboBox(int active, XblcState *xblc, int is_capture)
{
ControllerState *bound_state = xemu_input_get_bound(active);
assert(bound_state);
float max_width = ImGui::GetColumnWidth() - (10 * g_viewport_mgr.m_scale);
const char *default_device_name = "Default";
const char *selected_device = (is_capture == 0) ? xblc->output_device_name : xblc->input_device_name;
if(selected_device == NULL)
selected_device = default_device_name;
const char *combo_label = (is_capture == 0) ? "###Speaker" : "###Microphone";
// ImGui::Text("%s", label_text);
ImGui::SetNextItemWidth(max_width);
if(ImGui::BeginCombo(combo_label, selected_device, ImGuiComboFlags_NoArrowButton)) {
int numOutputDevices = SDL_GetNumAudioDevices(is_capture);
for(int device_index = -1; device_index < numOutputDevices; device_index++) {
const char *device_name = default_device_name;
if(device_index >= 0)
device_name = SDL_GetAudioDeviceName(device_index, is_capture);
// Default: device_index is -1, label is "Default", value is NULL
bool is_selected = (device_index == -1) && (selected_device == default_device_name);
// If not default, strings are safe to compare
if(!is_selected && selected_device != default_device_name)
is_selected = strcmp(device_name, selected_device) == 0;
if(ImGui::Selectable(device_name, is_selected)) {
if(is_capture == 0) {
// Free existing output_device_name, if it's not NULL
if(xblc->output_device_name != NULL)
g_free((void*)xblc->output_device_name);
// If device_index is -1, set it to NULL
xblc->output_device_name = (device_index == -1) ? NULL : g_strdup(device_name);
} else {
// Free existing input_device_name, if it's not NULL
if(xblc->input_device_name != NULL)
g_free((void*)xblc->input_device_name);
// If device_index is -1, set it to NULL
xblc->input_device_name = (device_index == -1) ? NULL : g_strdup(device_name);
}
// If the usb-xblc device is already bound, reinitialize it
if(xblc->dev != NULL)
xblc_audio_stream_reinit(xblc->dev);
// Save the changes
char *buf = xemu_input_serialize_xblc_settings(xblc);
xemu_save_peripheral_settings(active, 0, PERIPHERAL_XBLC, buf);
g_free(buf);
}
if (is_selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
DrawComboChevron();
}
static void DrawVolumeControlSlider(int active, XblcState *xblc, int is_capture)
{
float max_width = ImGui::GetColumnWidth() - (10 * g_viewport_mgr.m_scale);
xblc->input_device_volume = xblc_audio_stream_get_input_volume(xblc->dev) / 128.0f;
xblc->output_device_volume = xblc_audio_stream_get_output_volume(xblc->dev) / 128.0f;
float *ui_volume = (is_capture == 0) ? &xblc->output_device_volume : &xblc->input_device_volume;
float original_volume = *ui_volume;
const char *label = (is_capture == 0) ? "Speaker" : "Microphone";
char description[32];
sprintf(description, "Volume: %.2f%%", 100 * original_volume);
// This is not respected. Not sure why
ImGui::SetNextItemWidth(max_width);
// Render the slider
ImGui::PushID(label);
Slider(label, ui_volume, description);
ImGui::PopID();
// If the slider value has changed, update the backend value
if(*ui_volume != original_volume) {
int adjusted_volume = (int)(*ui_volume * 128);
if(is_capture == 0) {
xblc_audio_stream_set_output_volume(xblc->dev, adjusted_volume);
} else {
xblc_audio_stream_set_input_volume(xblc->dev, adjusted_volume);
}
// Save the changes
char *buf = xemu_input_serialize_xblc_settings(xblc);
xemu_save_peripheral_settings(active, 0, PERIPHERAL_XBLC, buf);
g_free(buf);
}
}
void MainMenuInputView::DrawXblcSettings(int active, int expansion_slot_index)
{
// Dimensions of XBLC
const float xblc_x = 0, xblc_y = 0;
const float xblc_w = 256, xblc_h = 256;
const int port_padding = 8;
float max_width = ImGui::GetColumnWidth() - (10 * g_viewport_mgr.m_scale);
ControllerState *bound_state = xemu_input_get_bound(active);
assert(bound_state);
float x = xblc_x;
float y = xblc_y;
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((max_width -
xblc_w * g_viewport_mgr.m_scale -
2 * port_padding * g_viewport_mgr.m_scale) /
2));
xblc_fbo->Target();
ImTextureID id = (ImTextureID)(intptr_t)xblc_fbo->Texture();
XblcState *xblc = (XblcState *)bound_state->peripherals[expansion_slot_index];
uint32_t fg_color = (xblc->dev == NULL) ? 0x1f1f1f00 : 0x81dc8a00;
RenderXblc(xblc, x, y, fg_color, 0x0f0f0f00);
ImVec2 xblc_display_size;
if (max_width < xblc_w * g_viewport_mgr.m_scale) {
xblc_display_size.x = max_width;
xblc_display_size.y = xblc_display_size.x * xblc_h / xblc_w;
} else {
xblc_display_size = ImVec2(xblc_w * g_viewport_mgr.m_scale,
xblc_h * g_viewport_mgr.m_scale);
}
ImGui::SetCursorPosX(
ImGui::GetCursorPosX() +
(int)((ImGui::GetColumnWidth() - xblc_display_size.x) / 2.0));
ImGui::Image(id, xblc_display_size, ImVec2(0, 1), ImVec2(1, 0));
xblc_fbo->Restore();
DrawVolumeControlSlider(active, xblc, 0);
DrawAudioDeviceSelectComboBox(active, xblc, 0);
DrawVolumeControlSlider(active, xblc, 1);
DrawAudioDeviceSelectComboBox(active, xblc, 1);
}
void MainMenuDisplayView::Draw()
{
SectionTitle("Renderer");

View file

@ -48,6 +48,10 @@ class MainMenuInputView : public virtual MainMenuTabView
{
public:
void Draw() override;
void DrawExpansionSlotOptions(int active);
void DrawExpansionSlotOptions(int active, int expansion_slot_index);
void DrawXmuSettings(int active, int expansion_slot_index);
void DrawXblcSettings(int active, int expansion_slot_index);
};
class MainMenuDisplayView : public virtual MainMenuTabView