mirror of
https://github.com/xemu-project/xemu.git
synced 2025-04-02 11:11:48 -04:00
Merge 6b54b55367
into 57cdee770e
This commit is contained in:
commit
154ab17da5
13 changed files with 1165 additions and 302 deletions
|
@ -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
|
||||
|
|
|
@ -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
BIN
data/xblc_mask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
66
data/xblc_mask.svg
Normal file
66
data/xblc_mask.svg
Normal 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 |
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 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
42
hw/xbox/xblc.h
Normal 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
|
1
subprojects/cpp-httplib
Submodule
1
subprojects/cpp-httplib
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 0f1b62c2b3d0898cbab7aa685c2593303ffdc1a2
|
416
ui/xemu-input.c
416
ui/xemu-input.c
|
@ -33,6 +33,7 @@
|
|||
#include "xemu-settings.h"
|
||||
|
||||
#include "sysemu/blockdev.h"
|
||||
#include "../hw/xbox/xblc.h"
|
||||
|
||||
// #define DEBUG_INPUT
|
||||
|
||||
|
@ -126,6 +127,34 @@ static const char **peripheral_params_settings_map[4][2] = {
|
|||
&g_config.input.peripherals.port4.peripheral_param_1 }
|
||||
};
|
||||
|
||||
static const char **xblc_input_device_map[4] = {
|
||||
&g_config.input.peripherals.port1.xblc1_settings.input_device_name,
|
||||
&g_config.input.peripherals.port2.xblc2_settings.input_device_name,
|
||||
&g_config.input.peripherals.port3.xblc3_settings.input_device_name,
|
||||
&g_config.input.peripherals.port4.xblc4_settings.input_device_name,
|
||||
};
|
||||
|
||||
static float *xblc_input_volume_map[4] = {
|
||||
&g_config.input.peripherals.port1.xblc1_settings.input_device_volume,
|
||||
&g_config.input.peripherals.port2.xblc2_settings.input_device_volume,
|
||||
&g_config.input.peripherals.port3.xblc3_settings.input_device_volume,
|
||||
&g_config.input.peripherals.port4.xblc4_settings.input_device_volume,
|
||||
};
|
||||
|
||||
static const char **xblc_output_device_map[4] = {
|
||||
&g_config.input.peripherals.port1.xblc1_settings.output_device_name,
|
||||
&g_config.input.peripherals.port2.xblc2_settings.output_device_name,
|
||||
&g_config.input.peripherals.port3.xblc3_settings.output_device_name,
|
||||
&g_config.input.peripherals.port4.xblc4_settings.output_device_name,
|
||||
};
|
||||
|
||||
static float *xblc_output_volume_map[4] = {
|
||||
&g_config.input.peripherals.port1.xblc1_settings.output_device_volume,
|
||||
&g_config.input.peripherals.port2.xblc2_settings.output_device_volume,
|
||||
&g_config.input.peripherals.port3.xblc3_settings.output_device_volume,
|
||||
&g_config.input.peripherals.port4.xblc4_settings.output_device_volume,
|
||||
};
|
||||
|
||||
static int sdl_kbd_scancode_map[25];
|
||||
|
||||
static const char *get_bound_driver(int port)
|
||||
|
@ -149,6 +178,12 @@ static const char *get_bound_driver(int port)
|
|||
|
||||
static const int port_map[4] = { 3, 4, 1, 2 };
|
||||
|
||||
const char *peripheral_type_names[3] = {
|
||||
"None",
|
||||
"Memory Unit",
|
||||
"Xbox Live Communicator"
|
||||
};
|
||||
|
||||
void xemu_input_init(void)
|
||||
{
|
||||
if (g_config.input.background_input_capture) {
|
||||
|
@ -217,7 +252,7 @@ void xemu_input_init(void)
|
|||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||
xemu_queue_notification(buf);
|
||||
xemu_input_rebind_xmu(port);
|
||||
xemu_input_rebind_peripherals(port);
|
||||
}
|
||||
|
||||
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
|
||||
|
@ -340,7 +375,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
|||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||
xemu_queue_notification(buf);
|
||||
xemu_input_rebind_xmu(port);
|
||||
xemu_input_rebind_peripherals(port);
|
||||
}
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
|
||||
|
@ -392,6 +427,33 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
|||
}
|
||||
}
|
||||
|
||||
void xemu_input_save_xblc_settings(int port, XblcState *xblc)
|
||||
{
|
||||
const char *default_device_name = "Default";
|
||||
*peripheral_types_settings_map[port][0] = PERIPHERAL_XBLC;
|
||||
*xblc_output_device_map[port] = xblc->output_device_name == NULL ?
|
||||
default_device_name : xblc->output_device_name;
|
||||
*xblc_output_volume_map[port] = xblc->output_device_volume * 100;
|
||||
*xblc_input_device_map[port] = xblc->input_device_name == NULL ?
|
||||
default_device_name : xblc->input_device_name;
|
||||
*xblc_input_volume_map[port] = xblc->input_device_volume * 100;
|
||||
}
|
||||
|
||||
XblcState *xemu_input_load_xblc_settings(int port)
|
||||
{
|
||||
const char *default_device_name = "Default";
|
||||
XblcState *xblc = (XblcState*)g_malloc(sizeof(XblcState));
|
||||
memset(xblc, 0, sizeof(XblcState));
|
||||
xblc->output_device_name = *xblc_output_device_map[port] == NULL ?
|
||||
default_device_name : *xblc_output_device_map[port];
|
||||
xblc->output_device_volume = *xblc_output_volume_map[port] / 100;
|
||||
xblc->input_device_name = *xblc_input_device_map[port] == NULL ?
|
||||
default_device_name : *xblc_input_device_map[port];
|
||||
xblc->input_device_volume = *xblc_input_volume_map[port] / 100;
|
||||
|
||||
return xblc;
|
||||
}
|
||||
|
||||
void xemu_input_update_controller(ControllerState *state)
|
||||
{
|
||||
int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
|
||||
|
@ -519,13 +581,12 @@ void xemu_input_bind(int index, ControllerState *state, int save)
|
|||
assert(bound_controllers[index]->device != NULL);
|
||||
Error *err = NULL;
|
||||
|
||||
// Unbind any XMUs
|
||||
// Unbind any peripherals
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (bound_controllers[index]->peripherals[i]) {
|
||||
// If this was an XMU, unbind the XMU
|
||||
if (bound_controllers[index]->peripheral_types[i] ==
|
||||
PERIPHERAL_XMU)
|
||||
xemu_input_unbind_xmu(index, i);
|
||||
// If a peripheral was bound, unbind the peripheral
|
||||
if (bound_controllers[index]->peripheral_types[i] != PERIPHERAL_NONE)
|
||||
xemu_input_unbind_peripheral(index, i);
|
||||
|
||||
// Free up the XmuState and set the peripheral type to none
|
||||
g_free(bound_controllers[index]->peripherals[i]);
|
||||
|
@ -612,6 +673,28 @@ void xemu_input_bind(int index, ControllerState *state, int save)
|
|||
}
|
||||
}
|
||||
|
||||
static void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
|
||||
{
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
|
||||
|
||||
ControllerState *state = bound_controllers[player_index];
|
||||
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU)
|
||||
return;
|
||||
|
||||
XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index];
|
||||
if (xmu != NULL) {
|
||||
if (xmu->dev != NULL) {
|
||||
qdev_unplug((DeviceState *)xmu->dev, &error_abort);
|
||||
object_unref(OBJECT(xmu->dev));
|
||||
xmu->dev = NULL;
|
||||
}
|
||||
|
||||
g_free((void *)xmu->filename);
|
||||
xmu->filename = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
|
||||
const char *filename, bool is_rebind)
|
||||
{
|
||||
|
@ -716,71 +799,288 @@ bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
|
|||
return true;
|
||||
}
|
||||
|
||||
void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
|
||||
static void xemu_input_rebind_xmu(int port, int expansion_slot_index)
|
||||
{
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
|
||||
enum peripheral_type peripheral_type =
|
||||
(enum peripheral_type)(*peripheral_types_settings_map[port][expansion_slot_index]);
|
||||
|
||||
ControllerState *state = bound_controllers[player_index];
|
||||
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU)
|
||||
// If peripheralType is out of range, change the settings for this
|
||||
// controller and peripheral port to default
|
||||
if (peripheral_type < PERIPHERAL_NONE ||
|
||||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
|
||||
xemu_save_peripheral_settings(port, expansion_slot_index, PERIPHERAL_NONE, NULL);
|
||||
peripheral_type = PERIPHERAL_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index];
|
||||
if (xmu != NULL) {
|
||||
if (xmu->dev != NULL) {
|
||||
qdev_unplug((DeviceState *)xmu->dev, &error_abort);
|
||||
object_unref(OBJECT(xmu->dev));
|
||||
xmu->dev = NULL;
|
||||
const char *param = *peripheral_params_settings_map[port][expansion_slot_index];
|
||||
|
||||
if (peripheral_type == PERIPHERAL_XMU) {
|
||||
if (param != NULL && strlen(param) > 0) {
|
||||
// This is an XMU and needs to be bound to this controller
|
||||
if (qemu_access(param, R_OK | W_OK) == 0) {
|
||||
bound_controllers[port]->peripheral_types[expansion_slot_index] =
|
||||
peripheral_type;
|
||||
bound_controllers[port]->peripherals[expansion_slot_index] =
|
||||
g_malloc(sizeof(XmuState));
|
||||
memset(bound_controllers[port]->peripherals[expansion_slot_index], 0,
|
||||
sizeof(XmuState));
|
||||
bool did_bind = xemu_input_bind_xmu(port, expansion_slot_index, param, true);
|
||||
if (did_bind) {
|
||||
char *buf =
|
||||
g_strdup_printf("Connected Memory Unit %s to Player %d Expansion Slot %c",
|
||||
param, port + 1, 'A' + expansion_slot_index);
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
} else {
|
||||
char *buf =
|
||||
g_strdup_printf("Unable to bind Memory Unit at %s to Player %d Expansion Slot %c",
|
||||
param, port + 1, 'A' + expansion_slot_index);
|
||||
xemu_queue_error_message(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
g_free((void *)xmu->filename);
|
||||
xmu->filename = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void xemu_input_rebind_xmu(int port)
|
||||
static void xemu_input_unbind_xblc(int player_index)
|
||||
{
|
||||
const int expansion_slot_index = 0;
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
|
||||
ControllerState *state = bound_controllers[player_index];
|
||||
if(state == NULL)
|
||||
return;
|
||||
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XBLC)
|
||||
return;
|
||||
|
||||
XblcState *xblc = (XblcState *)state->peripherals[expansion_slot_index];
|
||||
if (xblc != NULL) {
|
||||
if (xblc->dev != NULL) {
|
||||
qdev_unplug((DeviceState *)xblc->dev, &error_abort);
|
||||
object_unref(OBJECT(xblc->dev));
|
||||
xblc->dev = NULL;
|
||||
}
|
||||
|
||||
g_free((void *)xblc->output_device_name);
|
||||
g_free((void *)xblc->input_device_name);
|
||||
xblc->output_device_name = NULL;
|
||||
xblc->input_device_name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool xemu_input_bind_xblc(int player_index, const char *output_device,
|
||||
const char *input_device, bool is_rebind)
|
||||
{
|
||||
// Xbox Live Communicator is keyed such that it can only go into expansion slot 0
|
||||
DPRINTF("Connecting Xbox Live Communicator Headset\n");
|
||||
DPRINTF("XBLC Output Device: %s\n", output_device);
|
||||
DPRINTF("XBLC Input Device: %s\n", input_device);
|
||||
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
|
||||
ControllerState *player = bound_controllers[player_index];
|
||||
if(player == NULL)
|
||||
return false;
|
||||
enum peripheral_type peripheral_type =
|
||||
player->peripheral_types[0];
|
||||
if (peripheral_type != PERIPHERAL_XBLC)
|
||||
return false;
|
||||
|
||||
XblcState *xblc = (XblcState *)player->peripherals[0];
|
||||
|
||||
// Unbind existing XBLC
|
||||
if (xblc->dev != NULL) {
|
||||
xemu_input_unbind_xblc(player_index);
|
||||
}
|
||||
|
||||
// Look for any other XBLCs that are using these devices
|
||||
for (int player_i = 0; player_i < 4; player_i++) {
|
||||
ControllerState *state = bound_controllers[player_i];
|
||||
if (state != NULL) {
|
||||
if (state->peripheral_types[0] == PERIPHERAL_XBLC) {
|
||||
XblcState *xblc_i =
|
||||
(XblcState *)state->peripherals[0];
|
||||
assert(xblc_i);
|
||||
|
||||
if(xblc_i->dev != NULL) {
|
||||
bool already_bound = false;
|
||||
|
||||
if (!((xblc_i->output_device_name == NULL) ^ (output_device == NULL))) {
|
||||
if(output_device ==NULL)
|
||||
already_bound = true;
|
||||
else
|
||||
already_bound = strcmp(xblc_i->output_device_name, output_device) == 0;
|
||||
}
|
||||
|
||||
if (already_bound) {
|
||||
if(output_device == NULL)
|
||||
output_device = "Default";
|
||||
char *buf =
|
||||
g_strdup_printf("Output Device %s is already mounted on "
|
||||
"player %d slot %c\r\n", output_device,
|
||||
player_i + 1, 'A');
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
already_bound = false;
|
||||
|
||||
if (!((xblc_i->input_device_name == NULL) ^ (input_device == NULL))) {
|
||||
if(input_device == NULL)
|
||||
already_bound = true;
|
||||
else
|
||||
already_bound = strcmp(xblc_i->input_device_name, input_device) == 0;
|
||||
}
|
||||
|
||||
if (already_bound) {
|
||||
char *buf =
|
||||
g_strdup_printf("Input Device %s is already mounted on "
|
||||
"player %d slot %c\r\n", input_device,
|
||||
player_i + 1, 'A');
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(xblc->input_device_name != NULL)
|
||||
g_free((void*)xblc->input_device_name);
|
||||
if(input_device == NULL)
|
||||
xblc->input_device_name = NULL;
|
||||
else
|
||||
xblc->input_device_name = g_strdup(input_device);
|
||||
|
||||
if(xblc->output_device_name != NULL)
|
||||
g_free((void*)xblc->output_device_name);
|
||||
if(output_device == NULL)
|
||||
xblc->output_device_name = NULL;
|
||||
else
|
||||
xblc->output_device_name = g_strdup(output_device);
|
||||
|
||||
char *tmp;
|
||||
|
||||
static int id_counter = 0;
|
||||
tmp = g_strdup_printf("xblc_%d", id_counter++);
|
||||
|
||||
// Create the usb-storage device
|
||||
QDict *qdict = qdict_new();
|
||||
|
||||
// Specify device driver
|
||||
qdict_put_str(qdict, "driver", "usb-xblc");
|
||||
|
||||
// Specify index/port
|
||||
tmp = g_strdup_printf("1.%d.2", port_map[player_index]);
|
||||
qdict_put_str(qdict, "port", tmp);
|
||||
g_free(tmp);
|
||||
|
||||
// Create the device
|
||||
QemuOpts *opts =
|
||||
qemu_opts_from_qdict(qemu_find_opts("device"), qdict, &error_abort);
|
||||
|
||||
DeviceState *dev = qdev_device_add(opts, &error_abort);
|
||||
assert(dev);
|
||||
|
||||
xblc->dev = (void *)dev;
|
||||
xblc_audio_stream_set_output_volume(xblc->dev, xblc->output_device_volume);
|
||||
xblc_audio_stream_set_input_volume(xblc->dev, xblc->input_device_volume);
|
||||
|
||||
// Unref for eventual cleanup
|
||||
qobject_unref(qdict);
|
||||
|
||||
if (!is_rebind) {
|
||||
xemu_input_save_xblc_settings(player_index, xblc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void xemu_input_rebind_xblc(int port)
|
||||
{
|
||||
enum peripheral_type peripheral_type =
|
||||
(enum peripheral_type)(*peripheral_types_settings_map[port][0]);
|
||||
|
||||
// If peripheralType is out of range, change the settings for this
|
||||
// controller and peripheral port to default
|
||||
if (peripheral_type < PERIPHERAL_NONE ||
|
||||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
|
||||
xemu_save_peripheral_settings(port, 0, PERIPHERAL_NONE, NULL);
|
||||
peripheral_type = PERIPHERAL_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (peripheral_type == PERIPHERAL_XBLC) {
|
||||
bound_controllers[port]->peripheral_types[0] = peripheral_type;
|
||||
bound_controllers[port]->peripherals[0] = xemu_input_load_xblc_settings(port);
|
||||
XblcState *xblc = (XblcState*)bound_controllers[port]->peripherals[0];
|
||||
|
||||
char *output_temp = xblc->output_device_name == NULL ? NULL : g_strdup(xblc->output_device_name);
|
||||
char *input_temp = xblc->input_device_name == NULL ? NULL : g_strdup(xblc->input_device_name);
|
||||
|
||||
bool did_bind = xemu_input_bind_xblc(port, output_temp, input_temp, true);
|
||||
if (did_bind) {
|
||||
char *buf =
|
||||
g_strdup_printf("Connected Xbox Live Communicator Headset to Player %d Expansion Slot A",
|
||||
port + 1);
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
|
||||
if(output_temp != NULL)
|
||||
g_free(output_temp);
|
||||
if(input_temp != NULL)
|
||||
g_free(input_temp);
|
||||
}
|
||||
}
|
||||
|
||||
void xemu_input_unbind_peripheral(int player_index, int expansion_slot_index)
|
||||
{
|
||||
ControllerState *state = bound_controllers[player_index];
|
||||
if(state != NULL)
|
||||
{
|
||||
switch(state->peripheral_types[expansion_slot_index])
|
||||
{
|
||||
case PERIPHERAL_XMU:
|
||||
xemu_input_unbind_xmu(player_index, expansion_slot_index);
|
||||
break;
|
||||
case PERIPHERAL_XBLC:
|
||||
assert(player_index == 0);
|
||||
xemu_input_unbind_xblc(player_index);
|
||||
break;
|
||||
case PERIPHERAL_NONE:
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xemu_input_rebind_peripherals(int port)
|
||||
{
|
||||
// Try to bind peripherals back to controller
|
||||
for (int i = 0; i < 2; i++) {
|
||||
enum peripheral_type peripheral_type =
|
||||
(enum peripheral_type)(*peripheral_types_settings_map[port][i]);
|
||||
|
||||
// If peripheralType is out of range, change the settings for this
|
||||
// controller and peripheral port to default
|
||||
if (peripheral_type < PERIPHERAL_NONE ||
|
||||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
|
||||
xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL);
|
||||
peripheral_type = PERIPHERAL_NONE;
|
||||
}
|
||||
|
||||
const char *param = *peripheral_params_settings_map[port][i];
|
||||
|
||||
if (peripheral_type == PERIPHERAL_XMU) {
|
||||
if (param != NULL && strlen(param) > 0) {
|
||||
// This is an XMU and needs to be bound to this controller
|
||||
if (qemu_access(param, R_OK | W_OK) == 0) {
|
||||
bound_controllers[port]->peripheral_types[i] =
|
||||
peripheral_type;
|
||||
bound_controllers[port]->peripherals[i] =
|
||||
g_malloc(sizeof(XmuState));
|
||||
memset(bound_controllers[port]->peripherals[i], 0,
|
||||
sizeof(XmuState));
|
||||
bool did_bind = xemu_input_bind_xmu(port, i, param, true);
|
||||
if (did_bind) {
|
||||
char *buf =
|
||||
g_strdup_printf("Connected XMU %s to port %d%c",
|
||||
param, port + 1, 'A' + i);
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
} else {
|
||||
char *buf =
|
||||
g_strdup_printf("Unable to bind XMU at %s to port %d%c",
|
||||
param, port + 1, 'A' + i);
|
||||
xemu_queue_error_message(buf);
|
||||
g_free(buf);
|
||||
switch(peripheral_type)
|
||||
{
|
||||
case PERIPHERAL_XMU:
|
||||
xemu_input_rebind_xmu(port, i);
|
||||
break;
|
||||
case PERIPHERAL_XBLC:
|
||||
if(i != 0) {
|
||||
// An Xbox Live Communicator Headset cannot be plugged into Expansion Slot B
|
||||
xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
xemu_input_rebind_xblc(port);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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");
|
||||
|
|
|
@ -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