This commit is contained in:
Fred Hallock 2025-04-01 01:57:23 -04:00 committed by GitHub
commit 154ab17da5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1165 additions and 302 deletions

View file

@ -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

View file

@ -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',

BIN
data/xblc_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

66
data/xblc_mask.svg Normal file
View file

@ -0,0 +1,66 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<!-- Headset -->
<path d="m158.23,604.8c0,0 11.11,8.82 65.87,8.82c54.76,0 75.24,-10.95 74.95,-11.24c-0.29,-0.29 14.1,23.14 22.19,56.48c8.1,33.33 10.95,63.33 10.67,63.05c-0.29,-0.29 -38.76,21.71 -102.57,22.67c-63.81,0.95 -105.24,-21.43 -104.76,-21.43c0.48,0 -1.43,-26.67 10.48,-62.86c11.9,-36.19 21.43,-55.24 21.43,-55.24c0.58,-0.08 1.16,-0.17 1.74,-0.25z" fill="#000000" fill-opacity="null" id="svg_2" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m132.19,166c-28.1,5.24 -38.57,8.57 -63.81,24.76c-25.24,16.19 -42.38,41.9 -40.95,58.57c1.43,16.67 5.71,37.14 6.19,37.14c0.48,0 9.52,2.38 13.81,-12.38c4.29,-14.76 18.1,-25.71 34.29,-35.71c16.19,-10 26.19,-15.24 46.19,-21.9c20,-6.67 29.05,-22.38 29.05,-24.76" fill="#000000" fill-opacity="null" id="svg_8" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m697,596c69,35 175,88 207,87c32,-1 83,-22 93,31c10,53 -49,63 -68,47c-19,-16 -17,-10 -48,-38c-31,-28 -209,-94 -213,-103c-4,-9 -40,-59 29,-24z" fill="#000000" id="svg_3" stroke="#ff0000" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="501.45" cy="401.8" fill="#000000" fill-opacity="null" id="svg_7" rx="114.39" ry="83.88" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0) matrix(0.96334, -0.268284, 0.268284, 0.96334, -89.4121, 149.26)"/>
<path d="m559.48,345.57c-28.7,-7.83 -64.78,-13.04 -98.26,6.09c-33.48,19.13 -49.57,37.83 -52.61,60c-3.04,22.17 1.3,43.04 16.52,53.91c15.22,10.87 63.48,19.13 80,18.7c16.52,-0.43 70,-28.7 84.35,-46.96c14.35,-18.26 24.35,-46.09 5.65,-70.43c-18.7,-24.35 -35.65,-21.3 -35.65,-21.3l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_29" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m547.3,361.22c-33.48,-7.83 -71.3,-1.74 -98.7,18.26c-27.39,20 -39.13,52.61 -26.09,73.48c13.04,20.87 57.39,26.96 83.48,24.78c26.09,-2.17 46.09,-10 62.61,-22.61c16.52,-12.61 17.83,-13.48 23.04,-26.09c5.22,-12.61 6.09,-24.78 -3.48,-40.43c-9.57,-15.65 -40.87,-27.39 -40.87,-27.39l0.01,0z" fill="#000000" fill-opacity="null" id="svg_30" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m513.83,367.74c-15.65,1.3 -33.91,2.17 -59.13,22.17c-25.22,20 -26.52,44.35 -25.65,59.57c0.87,15.22 39.57,36.96 79.57,30c40,-6.96 55.22,-17.83 68.26,-33.04c13.04,-15.22 14.78,-28.7 9.57,-43.48c-5.22,-14.78 -20,-24.78 -30,-29.13c-10,-4.35 -26.96,-7.39 -42.61,-6.09l-0.01,0z" fill="#000000" fill-opacity="null" id="svg_31" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m437.74,484.26c-12.17,-10.87 -12.17,-35.22 -7.39,-41.3c4.78,-6.09 30.43,-3.48 38.26,-0.43c7.83,3.04 10.43,3.91 16.61,7.48c6.17,3.57 14.7,10.78 16.87,17.74c2.17,6.96 3.91,9.13 0,16.52c-3.91,7.39 -54.35,6.52 -53.91,6.52c0.43,0 -10.43,-6.52 -10.43,-6.52l-0.01,-0.01z" fill="#000000" fill-opacity="null" id="svg_33" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m439.91,494.26c-4.78,-6.52 -4.35,-17.83 3.91,-23.91c8.26,-6.09 13.91,-7.83 24.78,-8.7c10.87,-0.87 16.52,1.74 22.61,7.39c6.09,5.65 -0.87,18.26 -1.22,17.91c-0.35,-0.35 -14,7.74 -25.74,9.04c-11.74,1.3 -24.35,-1.74 -24.35,-1.74l0.01,0.01z" fill="#000000" fill-opacity="null" id="svg_32" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m704.97,593.59c21.72,10.69 21.72,19.66 16.55,31.38c-5.17,11.72 -20,20.34 -39.66,12.07c-19.66,-8.28 -62.07,-25.17 -130.34,-63.79c-68.28,-38.62 -89.31,-48.97 -103.79,-62.76c-14.48,-13.79 -5.17,-26.55 0.69,-30.69c5.86,-4.14 17.59,-14.48 33.79,-4.48c16.21,10 61.38,35.17 102.07,57.93c40.69,22.76 120.69,60.34 120.69,60.34z" fill="#000000" fill-opacity="null" id="svg_6" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m716.27,160.05c6.49,2.43 15.68,40.27 15.95,56.49c0.27,16.22 -25.14,30.81 -24.32,10c0.81,-20.81 -2.97,-35.68 -9.73,-43.24c-6.76,-7.57 2.7,-23.51 2.97,-23.51c0.27,0 8.65,-2.16 15.14,0.27l-0.01,-0.01z" fill="#000000" fill-opacity="null" id="svg_13" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m718.07,160.14l-2.21,-7.72c0,0 -8.83,-7.45 -15.72,-12.97c-6.9,-5.52 -43.79,-46.9 -87.59,-44.48c-43.79,2.41 -64.14,17.59 -81.38,49.31c-17.24,31.72 -53.1,40 -72.07,39.31c-18.97,-0.69 -94.83,-20.69 -142.41,-21.03c-47.59,-0.34 -113.79,-1.72 -129.31,-1.38c-15.52,0.34 -55.17,0.69 -55.31,0.55c-0.14,-0.14 -1.93,2.55 -1.59,2.55c0.34,0 3.79,8.28 3.79,23.79c0,15.52 -2.72,25.12 -1.86,25.83c0.86,0.7 1.07,1.76 2.6,1.62c1.53,-0.14 44.09,-20.54 79.6,-25.02c35.52,-4.48 101.03,-0.34 127.59,1.72c26.55,2.07 72.41,14.48 104.83,17.24c32.41,2.76 45.52,0 66.55,-11.38c21.03,-11.38 43.1,-69.66 85.17,-70c42.07,-0.34 52.07,12.76 64.14,21.38c12.07,8.62 27.93,29.66 27.79,29.52c-0.14,-0.14 7.38,6 7.24,5.86c-0.14,-0.14 2.19,-10.55 7.16,-16.19c4.96,-5.64 12.98,-8.5 12.98,-8.5l0.01,0l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_15" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m707.03,227.03c0.34,0 26.55,-11.03 26.41,-11.17c-0.14,-0.14 3.59,0.83 4.62,22.55c1.03,21.72 -4.14,30.69 -10,32.41c-5.86,1.72 -15.52,0.34 -19.66,-17.93c-4.14,-18.28 -1.38,-25.86 -1.38,-25.86l0.01,0z" fill="#000000" fill-opacity="null" id="svg_14" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m920.22,682c0.5,0.77 14.23,5.28 7.66,31.04c-6.57,25.76 -30.34,26.5 -28.04,26.11c2.31,-0.38 11.38,7.23 12.15,6.85c0.77,-0.38 19.06,4.2 30.06,-24.8c11,-29 -5.6,-41.82 -8.68,-41.43c-3.08,0.38 -13.66,1.46 -13.16,2.23l0.01,0z" fill="#000000" fill-opacity="null" id="svg_4" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m682.76,583.3c0.27,0.27 8.11,10.27 -0.54,24.59c-8.65,14.32 -28.65,15.68 -28.7,15.62c-0.05,-0.05 8.16,4.38 8.43,4.38c0.27,0 18.65,-2.7 27.03,-17.03c8.38,-14.32 1.62,-24.32 1.57,-24.38c-0.05,-0.05 -7.78,-3.19 -7.78,-3.19l-0.01,0.01z" fill="#000000" fill-opacity="null" id="svg_5" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m697.85,590.19c-1.08,-0.81 5.95,9.73 -2.7,24.05c-8.65,14.32 -25.95,16.49 -23.84,17.24c2.11,0.76 4.38,3.03 6.27,3.3c1.89,0.27 15.95,-3.24 24.32,-17.57c8.38,-14.32 1.62,-24.32 1.57,-24.38c-0.05,-0.05 -4.54,-1.84 -5.62,-2.65l0,0.01z" fill="#000000" fill-opacity="null" id="svg_9" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m710.83,597.76c1.08,1.08 5.41,9.19 -3.24,23.51c-8.65,14.32 -23.24,17.3 -25.46,16.16c-2.22,-1.14 6.54,2.76 9.51,2.49c2.97,-0.27 14.05,-2.16 22.43,-16.49c8.38,-14.32 4.32,-21.89 2.38,-23.3c-1.95,-1.41 -6.7,-3.46 -5.62,-2.38l0,0.01z" fill="#000000" fill-opacity="null" id="svg_10" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m27.89,241.68c2.7,-15.41 5.68,-17.84 8.92,-17.84c3.24,0 6.22,7.57 6.49,22.7c0.27,15.14 -15.41,-4.86 -15.41,-4.86z" fill="#000000" fill-opacity="null" id="svg_20" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m482.52,462.96c-4.78,-3.48 -3.04,-13.04 -3.39,-13.39c-0.35,-0.35 7.3,2.09 15.57,-7.91c8.26,-10 0.87,21.3 0.52,20.96c-0.35,-0.35 -12.7,0.35 -12.7,0.35l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_34" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m475.57,415.57c0,0 15.22,14.35 29.13,22.17c13.91,7.83 30.43,13.91 30.09,13.57c-0.35,-0.35 -3.57,6 -3.91,5.65c-0.35,-0.35 -14,-5.3 -30.09,-14.43c-16.09,-9.13 -29.13,-22.17 -29.48,-22.52c-0.35,-0.35 4.26,-4.43 4.26,-4.43l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_37" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m28.43,264.11c0.54,-16.76 1.35,-30 7.3,-30c5.95,0 5.95,10.81 6.76,21.62c0.81,10.81 -14.05,8.38 -14.05,8.38l-0.01,0z" fill="#000000" fill-opacity="null" id="svg_18" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m581,324c-15,-16 -32,-41 -70,-39c-38,2 -61.39,10.91 -82.09,23.91c-20.7,13 -35.91,26.09 -35.91,26.09c0,0 -1.26,-8 23.3,-28c24.57,-20 41.48,-22.52 57.35,-24.83c15.87,-2.3 49.57,-9.35 80.48,8.7c30.91,18.04 31.52,20.74 38.96,19.43c7.43,-1.3 -3.57,-11.17 -17.13,-20.78c-13.57,-9.61 -44.35,-26.65 -76.26,-27.09c-31.91,-0.43 -56.3,5.04 -76.48,15.74c-20.17,10.7 -34.52,35.17 -49.96,54.52c-15.43,19.35 -29.26,24.3 -47.26,33.3c-18,9 -37,10 -35,10c2,0 -18.83,7.91 7.61,10.61c26.43,2.7 33.13,-0.74 53.13,-11.43c20,-10.7 30.48,-19.87 59.87,-38.17c29.39,-18.3 43.74,-28.22 68.13,-33.83c24.39,-5.61 27.09,-5.57 48.39,-1.91c21.3,3.65 22.48,7.22 37.48,22.22c15,15 15.39,0.52 15.39,0.52z" fill="#000000" fill-opacity="null" id="svg_24" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m648.17,292.96c12.61,10.43 14.78,26.96 9.57,38.7c-5.22,11.74 -15.22,6.52 -22.61,-1.3c-7.39,-7.83 -9.13,-27.39 -9.48,-27.74c-0.35,-0.35 22.52,-9.65 22.52,-9.65l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_38" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m566.43,350.78l12.7,-10.78l15.22,-21.74l10.43,-26.09c0,0 1.65,-33.13 2.09,-33.13c0.43,0 10.87,3.91 23.04,12.17c12.17,8.26 9.57,6.09 14.87,11.83c5.3,5.74 7.74,6.87 5.57,17.74c-2.17,10.87 -5.22,18.7 -16.96,29.57c-11.74,10.87 -20,20 -30.87,29.57c-10.87,9.57 -10.43,12.61 -7.39,23.48" fill="#000000" fill-opacity="null" id="svg_28" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m552.52,356.43c13.91,0.87 20,-3.48 26.96,-10.87c6.96,-7.39 20,-26.96 25.22,-39.57c5.22,-12.61 8.7,-32.17 5.65,-41.74c-3.04,-9.57 -6.96,-2.61 -7.3,-2.96c-0.35,-0.35 -8.35,10.78 -12.7,20.78c-4.35,10 -7.83,13.04 -6.43,19.65c1.39,6.61 0.78,5.57 2.96,6.87c2.17,1.3 7.83,-1.3 7.48,-1.65c-0.35,-0.35 -3.13,6.87 -10.52,17.3c-7.39,10.43 -8.26,17.39 -23.39,21.39" fill="#000000" fill-opacity="null" id="svg_25" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m35.46,242.76c6.22,-0.54 14.05,31.35 16.76,34.32c2.7,2.97 24.86,22.16 38.92,28.11c14.05,5.95 73.51,26.49 96.49,31.35c22.97,4.86 57.3,11.62 77.57,11.89c20.27,0.27 50,-6.22 67.3,-21.89c17.3,-15.68 31.35,-39.73 43.78,-52.16c12.43,-12.43 23.24,-21.89 42.16,-30c18.92,-8.11 31.89,-12.43 46.76,-14.05c14.86,-1.62 24.86,-2.16 32.43,-2.43c7.57,-0.27 14.05,-0.27 20.54,0.27c6.49,0.54 14.32,1.35 21.08,2.7c6.76,1.35 14.32,4.32 17.57,5.14c3.24,0.81 8.92,2.97 23.51,10c14.59,7.03 23.24,15.95 23.19,15.89c-0.05,-0.05 1.41,21.41 -4,32.49c-5.41,11.08 -12.7,8.11 -12.76,8.05c-0.05,-0.05 -14.27,-17.78 -30.22,-24.81c-15.95,-7.03 -35.95,-14.32 -56.49,-14.86c-20.54,-0.54 -59.46,3.24 -76.49,13.51c-17.03,10.27 -37.57,34.05 -48.38,51.08c-10.81,17.03 -37.57,31.35 -51.89,37.3c-14.32,5.95 -38.38,14.86 -61.62,13.51c-23.24,-1.35 -60,-7.3 -88.38,-15.41c-28.38,-8.11 -68.11,-22.43 -90,-32.97c-21.89,-10.54 -46.76,-25.68 -50.81,-29.73c-4.05,-4.05 -3.51,-8.11 -3.51,-10.54c0,-2.43 -2.43,-14.59 -1.08,-25.95c1.35,-11.35 1.35,-20.27 7.57,-20.81z" fill="#000000" fill-opacity="null" id="svg_17" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m117.62,168.43c1.62,-6.22 1.35,-5.14 4.86,-5.68c3.51,-0.54 5.7,3.24 6.78,15.68c1.08,12.43 2.08,40.83 -4.41,42.45c-6.49,1.62 -4.26,-2.72 -4.32,-2.77c-0.05,-0.05 1.68,-0.49 1.62,-0.54c-0.05,-0.05 3.13,-7.29 1.78,-27.83c-1.35,-20.54 -2.27,-21.58 -2.32,-21.63c-0.05,-0.05 -4,0.32 -4,0.32l0.01,0z" fill="#000000" fill-opacity="null" id="svg_21" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m110.25,170.2c1.62,-6.22 1.35,-5.14 4.86,-5.68c3.51,-0.54 5.7,3.41 6.78,15.84c1.08,12.43 2.41,42 -4.08,43.62c-6.49,1.62 -4.59,-4.05 -4.65,-4.11c-0.05,-0.05 1.68,-0.49 1.62,-0.54c-0.05,-0.05 3.3,-5.62 1.95,-26.16c-1.35,-20.54 -2.43,-23.24 -2.49,-23.3c-0.05,-0.05 -4,0.32 -4,0.32l0.01,0.01z" fill="#000000" fill-opacity="null" id="svg_22" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m102.41,172.9c1.62,-6.22 1.18,-5.3 4.86,-6.01c3.68,-0.71 5.36,3.24 7.11,15.34c1.75,12.1 3.41,42.16 -3.08,43.78c-6.49,1.62 -7.93,-0.05 -6.48,-0.44c1.45,-0.39 2.18,-1.15 3.29,-1.54c1.11,-0.39 3.13,-8.29 1.78,-28.83c-1.35,-20.54 -3.43,-22.58 -3.49,-22.63c-0.05,-0.05 -4,0.32 -4,0.32l0.01,0.01z" fill="#000000" fill-opacity="null" id="svg_23" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<path d="m480.78,462.52c0.87,-1.74 6.96,-5.65 10.43,-9.13c3.48,-3.48 7.83,-13.04 7.48,-13.39c-0.35,-0.35 13.83,9.04 13.48,8.7c-0.35,-0.35 -4,9.04 -7.48,12.96c-3.48,3.91 -11.3,9.13 -11.65,8.78c-0.35,-0.35 -13.13,-6.17 -12.26,-7.91l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_36" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m539.64,200.24c15.45,-11.52 21.52,-26.36 27.58,-32.12c6.06,-5.76 15.15,-13.64 31.52,-17.27c16.36,-3.64 34.24,0.3 45.45,6.36c11.21,6.06 19.39,15.15 25.76,23.03c6.36,7.88 13.33,14.85 14.55,22.12c1.21,7.27 0.61,12.42 -1.21,21.52c-1.82,9.09 -10,17.27 -10,1.52c0,-15.76 -3.03,-23.94 -10,-34.55c-6.97,-10.61 -18.79,-19.39 -30,-22.12c-11.21,-2.73 -21.82,-2.73 -35.15,2.73c-13.33,5.45 -26.97,14.24 -38.79,19.09c-11.82,4.85 -53.03,13.33 -60.3,15.15c-7.27,1.82 -25.76,4.55 -27.88,6.67c-2.12,2.12 4.85,12.12 17.27,11.82c12.42,-0.3 33.64,-6.67 43.03,-9.09c9.39,-2.42 24.55,-6.97 41.21,-13.94c16.67,-6.97 33.03,-20 45.45,-19.7c12.42,0.3 18.79,3.33 27.27,12.12c8.48,8.79 12.73,20.91 12.73,31.21c0,10.3 -3.03,18.79 9.39,23.33c12.42,4.55 24.24,-4.24 25.15,-7.88c0.91,-3.64 12.73,-14.24 10,-29.39c-2.73,-15.15 -3.33,-20.3 -8.48,-28.18c-5.15,-7.88 -21.82,-25.15 -29.09,-31.21c-7.27,-6.06 -23.03,-19.39 -34.24,-20.3c-11.21,-0.91 -20,-6.97 -35.76,-2.42c-15.76,4.55 -26.06,8.18 -34.55,16.06c-8.48,7.88 -11.52,16.36 -20,26.67c-8.48,10.3 -18.18,23.33 -28.18,28.48" fill="#000000" fill-opacity="null" id="svg_42" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m519.64,197.21c-3.03,2.12 -16.97,8.48 -18.48,10.61c-1.52,2.12 31.52,-0.91 31.27,-1.15c-0.24,-0.24 23.27,-22.48 14.85,-16.97c-8.42,5.52 -24.61,5.39 -27.64,7.52l0,-0.01z" fill="#000000" fill-opacity="null" id="svg_43" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0"/>
<path d="m691.58,179.26c1.16,-8.84 5.58,-14.42 8.37,-17.44c2.79,-3.02 7.67,-8.84 15.58,-9.3" fill="#000000" fill-opacity="null" id="svg_1" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3"/>
<!-- Device -->
<rect fill="#000000" fill-opacity="null" height="20.48" id="svg_19" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="72.38" x="194.57" y="837.43"/>
<rect fill="#000000" fill-opacity="null" height="114.29" id="svg_26" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="159.05" x="149.81" y="728.38"/>
<path d="m135.52,690.76l-8.38,-7.43c1.2,-1.43 1.66,-2.36 2.86,-3.79l-3.81,-3.83l0,-10.48l6.26,-1.43c1.25,-2.25 -5.02,-6.38 -4.55,-5.9c0.48,0.48 3.33,-8.57 3.05,-8.86c-0.29,-0.29 5.27,1.22 5.52,0.24c0.25,-0.98 -1.9,-4.71 -2.19,-5c-0.29,-0.29 9.33,-6.38 10.29,-5.9c0.95,0.48 8.1,4.76 7.81,4.48c-0.29,-0.29 -16.86,47.9 -16.86,47.9z" fill="#000000" fill-opacity="null" id="svg_27" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<rect fill="#000000" fill-opacity="null" height="9.52" id="svg_12" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)" width="50.48" x="202.67" y="604.1"/>
<path d="m161.51,606.2c0,0 17.62,5.71 66.67,6.19c49.05,0.48 67.14,-7.62 66.86,-7.9c-0.29,-0.29 7.43,9.81 19.81,57.9c12.38,48.1 10.95,61.9 10.67,61.62c-0.29,-0.29 -38.76,19.33 -97.33,19.81c-58.57,0.48 -97.62,-18.57 -97.9,-18.86c-0.29,-0.29 4.57,-42.1 4.76,-40c0.19,2.1 -3.05,-9.71 1.24,-24.48c4.29,-14.76 10.48,-16.67 11.62,-18.38c1.14,-1.71 12.12,-35.4 13.62,-35.9l-0.02,0z" fill="#000000" fill-opacity="null" id="svg_11" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="274.81" cy="695.52" fill="#000000" fill-opacity="null" id="svg_35" rx="28.33" ry="28.57" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="277.9" cy="698.38" fill="#000000" fill-opacity="null" id="svg_39" rx="20.95" ry="20.48" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="269.81" cy="656.48" fill="#000000" fill-opacity="null" id="svg_40" rx="5.71" ry="5.71" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="3" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="228.05" cy="636.71" fill="#000000" id="svg_44" rx="6.44" ry="6.41" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2"/>
<ellipse cx="222.97" cy="642.06" fill="#ff0000" id="svg_45" rx="4.55" ry="4.09" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0"/>
<ellipse cx="233.58" cy="642.06" fill="#ff0000" id="svg_46" rx="4.55" ry="4.09" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<rect fill="#000000" height="13.48" id="svg_47" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" transform="matrix(1, 0, 0, 1, 0, 0)" width="8.03" x="224.03" y="636.61"/>
<ellipse cx="144.79" cy="666.61" fill="#000000" id="svg_49" rx="0.15" ry="0" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="2"/>
<ellipse cx="154.65" cy="666.95" fill="#000000" fill-opacity="null" id="svg_56" rx="10.81" ry="10.68" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5"/>
<ellipse cx="154.51" cy="666.95" fill="#000000" fill-opacity="null" id="svg_55" rx="8.51" ry="8.51" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<ellipse cx="154.38" cy="667.08" fill="#000000" fill-opacity="null" id="svg_54" rx="5.95" ry="5.95" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m155.19,664.65l12.65,-10.32l-19.73,-6.22l-8.92,9.73c0,0 -0.76,17.62 0.32,18.7c1.08,1.08 31.89,11.08 32.92,9.41c1.03,-1.68 -16.7,-15.62 -17.03,-15.68c-0.32,-0.05 -0.22,-5.62 -0.22,-5.62l0.01,0z" fill="#000000" fill-opacity="null" id="svg_57" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m150.39,664.33l2.33,-0.09l3.79,-3.18l-0.15,12.27l-3.79,-3.03l-2.42,-0.3l0.24,-5.67z" fill="#ff0000" id="svg_48" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0"/>
<path fill="#000000" fill-opacity="null" id="svg_53" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0"/>
<ellipse cx="296" cy="647.98" fill="#000000" id="svg_61" rx="4.69" ry="5.06" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null"/>
<rect fill="#000000" fill-opacity="null" height="6.05" id="svg_62" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0" width="10.99" x="290.32" y="641.56"/>
<path d="m293.28,641.8c0,0.86 -0.12,-3.46 2.72,-3.46c2.84,0 2.72,2.72 2.77,3.63c0.05,0.91 0.07,5.14 0.07,6c0,0.86 -0.25,3.09 -2.72,3.09c-2.47,0 -2.84,-1.85 -2.91,-2.79c-0.07,-0.94 0.07,-7.33 0.07,-6.47z" fill="#000000" fill-opacity="null" id="svg_59" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null"/>
<path d="m293.41,645.01l5.36,-0.2l-0.25,4.44l-1.23,1.36l-1.85,0.25l-1.73,-1.11l-0.62,-2.22l0.32,-2.52z" fill="#ff0000" id="svg_60" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="0"/>
<line fill="none" fill-opacity="null" id="svg_63" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" x1="296.12" x2="296.12" y1="652.79" y2="655.88"/>
<line fill="none" fill-opacity="null" id="svg_64" stroke="#ff0000" stroke-dasharray="null" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" x1="293.28" x2="298.84" y1="655.91" y2="655.91"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -2,6 +2,7 @@
* QEMU USB Xbox Live Communicator (XBLC) Device
*
* Copyright (c) 2022 Ryan Wendland
* Copyright (c) 2025 faha223
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -26,8 +27,8 @@
#include "hw/usb.h"
#include "hw/usb/desc.h"
#include "ui/xemu-input.h"
#include "audio/audio.h"
#include "qemu/fifo8.h"
#include "xblc.h"
//#define DEBUG_XBLC
#ifdef DEBUG_XBLC
@ -51,32 +52,32 @@
#define XBLC_MAX_PACKET 48
#define XBLC_FIFO_SIZE (XBLC_MAX_PACKET * 100) //~100 ms worth of audio at 16bit 24kHz
#define NULL_DEFAULT(a, b) (a == NULL ? b : a)
static const uint8_t silence[256] = {0};
static const uint16_t xblc_sample_rates[5] = {
8000, 11025, 16000, 22050, 24000
};
typedef struct XBLCStream {
char *device_name;
SDL_AudioDeviceID voice;
SDL_AudioSpec spec;
uint8_t packet[XBLC_MAX_PACKET];
Fifo8 fifo;
int volume;
int 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[] = {

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

@ -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 <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);
/* 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

@ -0,0 +1 @@
Subproject commit 0f1b62c2b3d0898cbab7aa685c2593303ffdc1a2

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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 <fpng.h>
@ -32,9 +33,11 @@
#include <vector>
#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;

View file

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

View file

@ -41,6 +41,7 @@
#include "../xemu-xbe.h"
#include "../thirdparty/fatx/fatx.h"
#include "../../hw/xbox/xblc.h"
#define DEFAULT_XMU_SIZE 8388608
@ -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");

View file

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