mirror of
https://github.com/xemu-project/xemu.git
synced 2025-04-02 11:11:48 -04:00
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:
parent
5896b9dc91
commit
918634ba00
10 changed files with 1066 additions and 294 deletions
|
@ -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
BIN
data/xblc_mask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
292
hw/xbox/xblc.c
292
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 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
38
hw/xbox/xblc.h
Normal 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
|
454
ui/xemu-input.c
454
ui/xemu-input.c
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue