diff --git a/config_spec.yml b/config_spec.yml
index d013c806ac..bc7655a5c9 100644
--- a/config_spec.yml
+++ b/config_spec.yml
@@ -33,21 +33,73 @@ input:
port1:
peripheral_type_0: integer
peripheral_param_0: string
+ xblc1_settings:
+ output_device_name:
+ type: string
+ default: "Default"
+ output_device_volume:
+ type: number
+ default: 100.0
+ input_device_name:
+ type: string
+ default: "Default"
+ input_device_volume:
+ type: number
+ default: 100.0
peripheral_type_1: integer
peripheral_param_1: string
port2:
peripheral_type_0: integer
peripheral_param_0: string
+ xblc2_settings:
+ output_device_name:
+ type: string
+ default: "Default"
+ output_device_volume:
+ type: number
+ default: 100.0
+ input_device_name:
+ type: string
+ default: "Default"
+ input_device_volume:
+ type: number
+ default: 100.0
peripheral_type_1: integer
peripheral_param_1: string
port3:
peripheral_type_0: integer
peripheral_param_0: string
+ xblc3_settings:
+ output_device_name:
+ type: string
+ default: "Default"
+ output_device_volume:
+ type: number
+ default: 100.0
+ input_device_name:
+ type: string
+ default: "Default"
+ input_device_volume:
+ type: number
+ default: 100.0
peripheral_type_1: integer
peripheral_param_1: string
port4:
peripheral_type_0: integer
peripheral_param_0: string
+ xblc4_settings:
+ output_device_name:
+ type: string
+ default: "Default"
+ output_device_volume:
+ type: number
+ default: 100.0
+ input_device_name:
+ type: string
+ default: "Default"
+ input_device_volume:
+ type: number
+ default: 100.0
peripheral_type_1: integer
peripheral_param_1: string
gamecontrollerdb_path: string
diff --git a/data/meson.build b/data/meson.build
index 4a82350d44..ec28581d61 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -2,6 +2,7 @@ pfiles = [
'controller_mask.png',
'controller_mask_s.png',
'xmu_mask.png',
+ 'xblc_mask.png',
'logo_sdf.png',
'xemu_64x64.png',
'abxy.ttf',
diff --git a/data/xblc_mask.png b/data/xblc_mask.png
new file mode 100644
index 0000000000..0896d9f0b2
Binary files /dev/null and b/data/xblc_mask.png differ
diff --git a/data/xblc_mask.svg b/data/xblc_mask.svg
new file mode 100644
index 0000000000..988e0698aa
--- /dev/null
+++ b/data/xblc_mask.svg
@@ -0,0 +1,66 @@
+
\ No newline at end of file
diff --git a/hw/xbox/xblc.c b/hw/xbox/xblc.c
index 60badb10a7..eb7dc51d25 100644
--- a/hw/xbox/xblc.c
+++ b/hw/xbox/xblc.c
@@ -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 peak_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,228 @@ 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)
+float xblc_audio_stream_get_current_input_volume(void *dev)
{
- USBXBLCState *s = (USBXBLCState *)opaque;
+ USBXBLCState *s = (USBXBLCState*)dev;
+ return s->in.peak_volume / 32768.0f;
+}
+
+float xblc_audio_stream_get_output_volume(void *dev)
+{
+ USBXBLCState *s = (USBXBLCState*)dev;
+ return (float)s->out.volume / SDL_MIX_MAXVOLUME;
+}
+
+float xblc_audio_stream_get_input_volume(void *dev)
+{
+ USBXBLCState *s = (USBXBLCState*)dev;
+ return (float)s->in.volume / SDL_MIX_MAXVOLUME;
+}
+
+void xblc_audio_stream_set_output_volume(void *dev, float volume)
+{
+ USBXBLCState *s = (USBXBLCState*)dev;
+ s->out.volume = MIN(SDL_MIX_MAXVOLUME, MAX(0, (int)(volume * SDL_MIX_MAXVOLUME)));
+}
+void xblc_audio_stream_set_input_volume(void *dev, float volume)
+{
+ USBXBLCState *s = (USBXBLCState*)dev;
+ s->in.volume = MIN(SDL_MIX_MAXVOLUME, MAX(0, (int)(volume * SDL_MIX_MAXVOLUME)));
+}
+
+static void output_callback(void *userdata, uint8_t *stream, int len)
+{
+ USBXBLCState *s = (USBXBLCState *)userdata;
const uint8_t *data;
- uint32_t processed, max_len;
+ uint32_t max_len;
// Not enough data to send, wait a bit longer, fill with silence for now
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 {
+ // Write speaker data into audio backend
+ 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) {
+ 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_peak_amplitude(const int16_t *samples, int len) {
+ int max = 0;
+ for(int i = 0; i < len; 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;
+ uint8_t buffer[XBLC_FIFO_SIZE];
+
+ s->in.peak_volume = s->in.volume *
+ calc_peak_amplitude((int16_t*)stream, len / sizeof(int16_t)) / SDL_MIX_MAXVOLUME;
- // 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) {
+ 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);
+ } 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.peak_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 either channel needs to be initialized, initialize both channels
+ if (init_input_stream || init_output_stream) {
+ xblc_audio_channel_init(s, true, xblc->input_device_name);
+ xblc_audio_channel_init(s, false, xblc->output_device_name);
+ }
- 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)
{
@@ -298,6 +442,11 @@ static void usb_xblc_handle_data(USBDevice *dev, USBPacket *p)
to_process -= chunk_len;
}
+ // Ensure we fill the entire packet regardless of if we have audio data so we don't
+ // cause an underrun error.
+ if (p->actual_length < p->iov.size)
+ usb_packet_copy(p, (void *)silence, p->iov.size - p->actual_length);
+
break;
case USB_TOKEN_OUT:
// Speaker data - get data from usb packet then push to fifo.
@@ -312,27 +461,20 @@ static void usb_xblc_handle_data(USBDevice *dev, USBPacket *p)
assert(false);
break;
}
-
- // Ensure we fill the entire packet regardless of if we have audio data so we don't
- // cause an underrun error.
- if (p->actual_length < p->iov.size)
- usb_packet_copy(p, (void *)silence, p->iov.size - p->actual_length);
-
}
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 +491,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[] = {
diff --git a/hw/xbox/xblc.h b/hw/xbox/xblc.h
new file mode 100644
index 0000000000..c66a6b15dd
--- /dev/null
+++ b/hw/xbox/xblc.h
@@ -0,0 +1,42 @@
+/*
+ * 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 .
+ */
+
+#ifndef HW_XBOX_XBLC_H
+#define HW_XBOX_XBLC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void xblc_audio_stream_reinit(void *dev);
+
+/* Outputs a value on the interval [0, 1] where 0 is muted and 1 is full volume */
+float xblc_audio_stream_get_output_volume(void *dev);
+float xblc_audio_stream_get_input_volume(void *dev);
+
+/* Accepts a value on the interval [0, 1] where 0 is muted and 1 is full volume */
+void xblc_audio_stream_set_output_volume(void *dev, float volume);
+void xblc_audio_stream_set_input_volume(void *dev, float volume);
+float xblc_audio_stream_get_current_input_volume(void *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file
diff --git a/subprojects/cpp-httplib b/subprojects/cpp-httplib
new file mode 160000
index 0000000000..0f1b62c2b3
--- /dev/null
+++ b/subprojects/cpp-httplib
@@ -0,0 +1 @@
+Subproject commit 0f1b62c2b3d0898cbab7aa685c2593303ffdc1a2
diff --git a/ui/xemu-input.c b/ui/xemu-input.c
index 31a51eda9d..c808b9c938 100644
--- a/ui/xemu-input.c
+++ b/ui/xemu-input.c
@@ -33,6 +33,7 @@
#include "xemu-settings.h"
#include "sysemu/blockdev.h"
+#include "../hw/xbox/xblc.h"
// #define DEBUG_INPUT
@@ -126,6 +127,34 @@ static const char **peripheral_params_settings_map[4][2] = {
&g_config.input.peripherals.port4.peripheral_param_1 }
};
+static const char **xblc_input_device_map[4] = {
+ &g_config.input.peripherals.port1.xblc1_settings.input_device_name,
+ &g_config.input.peripherals.port2.xblc2_settings.input_device_name,
+ &g_config.input.peripherals.port3.xblc3_settings.input_device_name,
+ &g_config.input.peripherals.port4.xblc4_settings.input_device_name,
+};
+
+static float *xblc_input_volume_map[4] = {
+ &g_config.input.peripherals.port1.xblc1_settings.input_device_volume,
+ &g_config.input.peripherals.port2.xblc2_settings.input_device_volume,
+ &g_config.input.peripherals.port3.xblc3_settings.input_device_volume,
+ &g_config.input.peripherals.port4.xblc4_settings.input_device_volume,
+};
+
+static const char **xblc_output_device_map[4] = {
+ &g_config.input.peripherals.port1.xblc1_settings.output_device_name,
+ &g_config.input.peripherals.port2.xblc2_settings.output_device_name,
+ &g_config.input.peripherals.port3.xblc3_settings.output_device_name,
+ &g_config.input.peripherals.port4.xblc4_settings.output_device_name,
+};
+
+static float *xblc_output_volume_map[4] = {
+ &g_config.input.peripherals.port1.xblc1_settings.output_device_volume,
+ &g_config.input.peripherals.port2.xblc2_settings.output_device_volume,
+ &g_config.input.peripherals.port3.xblc3_settings.output_device_volume,
+ &g_config.input.peripherals.port4.xblc4_settings.output_device_volume,
+};
+
static int sdl_kbd_scancode_map[25];
static const char *get_bound_driver(int port)
@@ -149,6 +178,12 @@ static const char *get_bound_driver(int port)
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) {
@@ -217,7 +252,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);
@@ -340,7 +375,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);
@@ -392,6 +427,33 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
}
}
+void xemu_input_save_xblc_settings(int port, XblcState *xblc)
+{
+ const char *default_device_name = "Default";
+ *peripheral_types_settings_map[port][0] = PERIPHERAL_XBLC;
+ *xblc_output_device_map[port] = xblc->output_device_name == NULL ?
+ default_device_name : xblc->output_device_name;
+ *xblc_output_volume_map[port] = xblc->output_device_volume * 100;
+ *xblc_input_device_map[port] = xblc->input_device_name == NULL ?
+ default_device_name : xblc->input_device_name;
+ *xblc_input_volume_map[port] = xblc->input_device_volume * 100;
+}
+
+XblcState *xemu_input_load_xblc_settings(int port)
+{
+ const char *default_device_name = "Default";
+ XblcState *xblc = (XblcState*)g_malloc(sizeof(XblcState));
+ memset(xblc, 0, sizeof(XblcState));
+ xblc->output_device_name = *xblc_output_device_map[port] == NULL ?
+ default_device_name : *xblc_output_device_map[port];
+ xblc->output_device_volume = *xblc_output_volume_map[port] / 100;
+ xblc->input_device_name = *xblc_input_device_map[port] == NULL ?
+ default_device_name : *xblc_input_device_map[port];
+ xblc->input_device_volume = *xblc_input_volume_map[port] / 100;
+
+ return xblc;
+}
+
void xemu_input_update_controller(ControllerState *state)
{
int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
@@ -519,13 +581,12 @@ void xemu_input_bind(int index, ControllerState *state, int save)
assert(bound_controllers[index]->device != NULL);
Error *err = NULL;
- // Unbind any XMUs
+ // Unbind any peripherals
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
+ if (bound_controllers[index]->peripheral_types[i] != PERIPHERAL_NONE)
+ xemu_input_unbind_peripheral(index, i);
// Free up the XmuState and set the peripheral type to none
g_free(bound_controllers[index]->peripherals[i]);
@@ -612,6 +673,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)
{
@@ -716,71 +799,288 @@ 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, xblc->output_device_volume);
+ xblc_audio_stream_set_input_volume(xblc->dev, xblc->input_device_volume);
+
+ // Unref for eventual cleanup
+ qobject_unref(qdict);
+
+ if (!is_rebind) {
+ xemu_input_save_xblc_settings(player_index, xblc);
+ }
+
+ 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;
+ }
+
+ if (peripheral_type == PERIPHERAL_XBLC) {
+ bound_controllers[port]->peripheral_types[0] = peripheral_type;
+ bound_controllers[port]->peripherals[0] = xemu_input_load_xblc_settings(port);
+ XblcState *xblc = (XblcState*)bound_controllers[port]->peripherals[0];
+
+ 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;
}
}
}
diff --git a/ui/xemu-input.h b/ui/xemu-input.h
index 23c1a9f91b..7c6bbac514 100644
--- a/ui/xemu-input.h
+++ b/ui/xemu-input.h
@@ -71,13 +71,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;
@@ -130,9 +139,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);
+void xemu_input_save_xblc_settings(int port, XblcState *xblc);
+XblcState *xemu_input_load_xblc_settings(int port);
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
int peripheral_type,
const char *peripheral_parameter);
diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc
index 0a06b7f16a..6d5c21adb2 100644
--- a/ui/xui/gl-helpers.cc
+++ b/ui/xui/gl-helpers.cc
@@ -24,6 +24,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
@@ -32,9 +33,11 @@
#include
#include "ui/shader/xemu-logo-frag.h"
+#include "../../hw/xbox/xblc.h"
-Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
-GLuint g_controller_duke_tex, g_controller_s_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
+Fbo *controller_fbo, *xmu_fbo, *xblc_fbo, *logo_fbo;
+GLuint g_controller_duke_tex, g_controller_s_tex,
+ g_logo_tex, g_icon_tex, g_xmu_tex, g_xblc_tex;
enum class ShaderType {
Blit,
@@ -422,7 +425,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 {
@@ -434,7 +438,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)
@@ -450,6 +455,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);
@@ -853,6 +861,31 @@ 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) {
+ float volume = xblc_audio_stream_get_current_input_volume(xblc->dev);
+ RenderMeter(g_decal_shader, 4, 4, 252, 5, volume, primary_color + 0x40, primary_color + 0xFF);
+ }
+
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
void RenderLogo(uint32_t time)
{
uint32_t color = 0x62ca13ff;
diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh
index 82da963e6b..bd8f5bb21d 100644
--- a/ui/xui/gl-helpers.hh
+++ b/ui/xui/gl-helpers.hh
@@ -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 &png, int max_width = 0, int max_height = 0);
diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc
index 9bb5dcf33f..a4a38263e9 100644
--- a/ui/xui/main-menu.cc
+++ b/ui/xui/main-menu.cc
@@ -41,6 +41,7 @@
#include "../xemu-xbe.h"
#include "../thirdparty/fatx/fatx.h"
+#include "../../hw/xbox/xblc.h"
#define DEFAULT_XMU_SIZE 8388608
@@ -90,9 +91,6 @@ void MainMenuInputView::Draw()
// Dimensions of controller (rendered at origin)
float controller_width = 477.0f;
float controller_height = 395.0f;
- // Dimensions of XMU
- float xmu_x = 0, xmu_x_stride = 256, xmu_y = 0;
- float xmu_w = 256, xmu_h = 256;
// Setup rendering to fbo for controller and port images
controller_fbo->Target();
@@ -254,7 +252,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;
}
@@ -323,164 +321,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,
@@ -490,6 +332,369 @@ 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 int num_input_devices = 0;
+static int num_output_devices = 0;
+static const char **input_device_names = nullptr;
+static const char **output_device_names = nullptr;
+
+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;
+
+ int num_devices = SDL_GetNumAudioDevices(is_capture);
+ // Get pointers to the correct device name cache
+ const char ***device_names = (is_capture ? &input_device_names : &output_device_names);
+ int *num_device_names = (is_capture ? &num_input_devices : &num_output_devices);
+
+ // If the number of devices is incorrect, update the cache
+ if (num_devices != *num_device_names) {
+ *num_device_names = num_devices;
+ // Update the device name cache
+ if(*device_names == nullptr)
+ g_free(*device_names);
+ if(num_devices == 0)
+ *device_names = nullptr;
+ else {
+ *device_names = (const char**)g_malloc(num_devices * sizeof(const char*));
+ for(int i = 0; i < num_devices; i++)
+ {
+ (*device_names)[i] = SDL_GetAudioDeviceName(i, is_capture);
+ }
+ }
+ }
+
+ 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)) {
+ for(int device_index = -1; device_index < num_devices; device_index++) {
+ const char *device_name = default_device_name;
+ if(device_index >= 0)
+ device_name = (*device_names)[device_index];
+
+ // 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
+ xemu_input_save_xblc_settings(active, xblc);
+ }
+
+ 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);
+ xblc->output_device_volume = xblc_audio_stream_get_output_volume(xblc->dev);
+ 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: %.0f%%", 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) {
+ if(is_capture)
+ xblc_audio_stream_set_input_volume(xblc->dev, *ui_volume);
+ else
+ xblc_audio_stream_set_output_volume(xblc->dev, *ui_volume);
+
+ // Save the changes
+ xemu_input_save_xblc_settings(active, xblc);
+ }
+}
+
+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");
diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh
index bce3927333..8b794f652b 100644
--- a/ui/xui/main-menu.hh
+++ b/ui/xui/main-menu.hh
@@ -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