dualSense emulation beta ready (works with Sunshine-Foundation)

This commit is contained in:
True Zhuanjia committed 2026-08-21 18:01:39 +08:00
1 parent e63e5f2120
commit 089515443d
22 files changed
+1290 -510

No files matched your search

+65 -66
View File
@@ -264,7 +264,7 @@
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="LGh-Lc-Odp" userLabel="emulatedControllerTypeStack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="LGh-Lc-Odp" userLabel="emulatedControllerTypeStack">
<rect key="frame" x="440" y="973" width="380" height="56.5"/> <rect key="frame" x="440" y="924" width="380" height="56.5"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Emulated Controller Type" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JTl-un-QJJ" userLabel="emulatedControllerTypeLabel"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Emulated Controller Type" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JTl-un-QJJ" userLabel="emulatedControllerTypeLabel">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
@@ -272,12 +272,13 @@
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="JaY-XA-1V6" userLabel="emulatedControllerTypeSelector"> <segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="JaY-XA-1V6" userLabel="emulatedControllerTypeSelector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/> <rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments> <segments>
<segment title="Xbox 360"/> <segment title="Xbox 360"/>
<segment title="DS4"/> <segment title="PS"/>
<segment title="Both"/> <segment title="PS (Enhanced Haptics)"/>
<segment title="Xbox + PS"/>
</segments> </segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/> <color key="selectedSegmentTintColor" systemColor="linkColor"/>
@@ -414,37 +415,37 @@
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-StK" userLabel="interpolationLevel Stack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-StK" userLabel="interpolationLevel Stack">
<rect key="frame" x="440" y="1350" width="380" height="56.5"/> <rect key="frame" x="440" y="1350" width="380" height="56.5"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Lbl"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Sel" userLabel="Interpolation Level Slider"> <slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Sel" userLabel="Interpolation Level Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/> <rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider> </slider>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="width" constant="380" id="FIp-Lv-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIp-Lv-Hgt"/> <constraint firstAttribute="height" constant="56.5" id="FIp-Lv-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIp-Lv-Wid"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-StK" userLabel="streamDimensionScale Stack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-StK" userLabel="streamDimensionScale Stack">
<rect key="frame" x="440" y="1411" width="380" height="56.5"/> <rect key="frame" x="440" y="1411" width="380" height="56.5"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Lbl"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Sld" userLabel="Stream Dimension Scale Slider"> <slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/> <rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider> </slider>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="width" constant="380" id="FIp-Sc-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIp-Sc-Hgt"/> <constraint firstAttribute="height" constant="56.5" id="FIp-Sc-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIp-Sc-Wid"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="hcl-DT-peO" userLabel="redirectMicStack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="hcl-DT-peO" userLabel="redirectMicStack">
@@ -1078,48 +1079,8 @@
<constraint firstAttribute="width" constant="380" id="qOk-ee-a8A"/> <constraint firstAttribute="width" constant="380" id="qOk-ee-a8A"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="A31-WC-As7">
<rect key="frame" x="441" y="1363" width="280" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Timebase" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cpg-mq-kYj" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="StG-nI-WpK">
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="lFV-eP-bfD"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="seO-0F-yIw"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="e0Q-XV-ydj">
<rect key="frame" x="441" y="1402" width="322" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Dequeue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X0u-7f-F86" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="172" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="auR-cw-77D">
<rect key="frame" x="192" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="mZn-6A-4OO"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="UK8-ck-1zc"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="ydX-NT-gG0"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="ydX-NT-gG0">
<rect key="frame" x="441" y="1441" width="383" height="31"/> <rect key="frame" x="437" y="1480" width="383" height="31"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WWl-77-twB" userLabel="uiLabel"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WWl-77-twB" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/> <rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
@@ -2280,6 +2241,44 @@
<constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/> <constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="e0Q-XV-ydj">
<rect key="frame" x="852" y="1300" width="322" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Dequeue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X0u-7f-F86" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="172" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="auR-cw-77D">
<rect key="frame" x="192" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="mZn-6A-4OO"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="UK8-ck-1zc"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="xrc-Fe-ETh" userLabel="dualSenseTransientStack">
<rect key="frame" x="440" y="998" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="DualSense Haptic Intensity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UpM-yi-yc9">
<rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="2" translatesAutoresizingMaskIntoConstraints="NO" id="ZOE-dg-g4w" userLabel="dualSenseTransientSlider">
<rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="56.5" id="aRM-XQ-Xxd"/>
<constraint firstAttribute="width" constant="380" id="k8R-Co-bxO"/>
</constraints>
</stackView>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view> </view>
@@ -2324,12 +2323,14 @@
<outlet property="disablePencilSlideGestureSwitch" destination="lcb-Iq-B5C" id="NZY-fW-4Yd"/> <outlet property="disablePencilSlideGestureSwitch" destination="lcb-Iq-B5C" id="NZY-fW-4Yd"/>
<outlet property="doubleTapShortcutStack" destination="prr-Do-Nna" id="sK3-va-U5W"/> <outlet property="doubleTapShortcutStack" destination="prr-Do-Nna" id="sK3-va-U5W"/>
<outlet property="doubleTapShortcutSwitch" destination="cw7-Jc-7bZ" id="bRy-9x-0Tl"/> <outlet property="doubleTapShortcutSwitch" destination="cw7-Jc-7bZ" id="bRy-9x-0Tl"/>
<outlet property="dualSenseTransientSlider" destination="ZOE-dg-g4w" id="J8t-pc-pBo"/>
<outlet property="dualSenseTransientStack" destination="xrc-Fe-ETh" id="bAL-at-fHy"/>
<outlet property="duckOtherAppStack" destination="gBG-8F-6F8" id="T9P-ys-pHB"/> <outlet property="duckOtherAppStack" destination="gBG-8F-6F8" id="T9P-ys-pHB"/>
<outlet property="duckOtherAppSwitch" destination="YEv-1B-97I" id="o3Q-yt-74a"/> <outlet property="duckOtherAppSwitch" destination="YEv-1B-97I" id="o3Q-yt-74a"/>
<outlet property="edgeSlidingSensitivitySlider" destination="41s-BD-8rZ" id="ytk-Y5-Alq"/> <outlet property="edgeSlidingSensitivitySlider" destination="41s-BD-8rZ" id="ytk-Y5-Alq"/>
<outlet property="edgeSlidingSensitivityStack" destination="CVS-fB-e0q" id="tGR-K2-Jwq"/> <outlet property="edgeSlidingSensitivityStack" destination="CVS-fB-e0q" id="tGR-K2-Jwq"/>
<outlet property="emulatedControllerTypeSelector" destination="JaY-XA-1V6" id="aU2-ps-esq"/> <outlet property="emulatedControllerTypeSelector" destination="JaY-XA-1V6" id="aU2-ps-esq"/>
<outlet property="emulatedControllerTypeStack" destination="LGh-Lc-Odp" id="Fny-rC-9zP"/> <outlet property="emulatedControllerTypeStack" destination="LGh-Lc-Odp" id="MHo-66-ltO"/>
<outlet property="enableGraphsSwitch" destination="mhH-ZA-Kqg" id="R0X-hj-NgM"/> <outlet property="enableGraphsSwitch" destination="mhH-ZA-Kqg" id="R0X-hj-NgM"/>
<outlet property="enableOswForNativeTouchSwitch" destination="Zmq-Kv-Nmp" id="f6j-0V-ldT"/> <outlet property="enableOswForNativeTouchSwitch" destination="Zmq-Kv-Nmp" id="f6j-0V-ldT"/>
<outlet property="enableOswSwitchStack" destination="SkS-ft-peB" id="n3M-EK-U74"/> <outlet property="enableOswSwitchStack" destination="SkS-ft-peB" id="n3M-EK-U74"/>
@@ -2338,15 +2339,9 @@
<outlet property="fpsStack" destination="TuR-H8-e2H" id="zGQ-gR-P1i"/> <outlet property="fpsStack" destination="TuR-H8-e2H" id="zGQ-gR-P1i"/>
<outlet property="framePacingModeSelector" destination="2Dp-Qg-chi" id="h8A-YF-SZP"/> <outlet property="framePacingModeSelector" destination="2Dp-Qg-chi" id="h8A-YF-SZP"/>
<outlet property="framePacingStack" destination="oZG-5Y-pw5" id="vGd-hD-zTr"/> <outlet property="framePacingStack" destination="oZG-5Y-pw5" id="vGd-hD-zTr"/>
<outlet property="interpolationLevelSlider" destination="FIp-Lv-Sel" id="FIp-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIp-Lv-StK" id="FIp-Out-StK"/>
<outlet property="streamDimensionScaleSlider" destination="FIp-Sc-Sld" id="FIp-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIp-Sc-StK" id="FIp-Out-ScK"/>
<outlet property="frameQueueSizeLabel" destination="Ce3-FM-vIh" id="90D-Tq-OGd"/> <outlet property="frameQueueSizeLabel" destination="Ce3-FM-vIh" id="90D-Tq-OGd"/>
<outlet property="frameQueueSizeSlider" destination="oZa-8Y-icz" id="Jo1-Fx-UMw"/> <outlet property="frameQueueSizeSlider" destination="oZa-8Y-icz" id="Jo1-Fx-UMw"/>
<outlet property="frameQueueSizeStack" destination="MML-c1-D5j" id="82B-r7-Zbh"/> <outlet property="frameQueueSizeStack" destination="MML-c1-D5j" id="82B-r7-Zbh"/>
<outlet property="frameTimebaseStack" destination="A31-WC-As7" id="vcp-SE-PuI"/>
<outlet property="frameTimebaseSwitch" destination="StG-nI-WpK" id="yYY-DO-U1D"/>
<outlet property="framerateSelector" destination="8hF-kb-2uV" id="dfT-hb-csE"/> <outlet property="framerateSelector" destination="8hF-kb-2uV" id="dfT-hb-csE"/>
<outlet property="fullColorRangeStack" destination="iPd-Rg-St7" id="iPd-Out-Let"/> <outlet property="fullColorRangeStack" destination="iPd-Rg-St7" id="iPd-Out-Let"/>
<outlet property="fullColorRangeSwitch" destination="iPd-Wt-Ch9" id="iPd-Out-Swt"/> <outlet property="fullColorRangeSwitch" destination="iPd-Wt-Ch9" id="iPd-Out-Swt"/>
@@ -2367,6 +2362,8 @@
<outlet property="hapticEngineStack" destination="MUp-Hc-VgH" id="XuW-ww-cmv"/> <outlet property="hapticEngineStack" destination="MUp-Hc-VgH" id="XuW-ww-cmv"/>
<outlet property="hdrStack" destination="dH7-Sy-EdJ" id="cJm-bj-Aqo"/> <outlet property="hdrStack" destination="dH7-Sy-EdJ" id="cJm-bj-Aqo"/>
<outlet property="hdrSwitch" destination="wIq-fF-X8l" id="Ozz-h0-nJL"/> <outlet property="hdrSwitch" destination="wIq-fF-X8l" id="Ozz-h0-nJL"/>
<outlet property="interpolationLevelSlider" destination="FIp-Lv-Sel" id="FIp-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIp-Lv-StK" id="FIp-Out-StK"/>
<outlet property="leftClickDelaySlider" destination="Mh4-1P-bSv" id="Tbj-Y0-t1x"/> <outlet property="leftClickDelaySlider" destination="Mh4-1P-bSv" id="Tbj-Y0-t1x"/>
<outlet property="leftClickDelayStack" destination="q0S-CP-V6v" id="0lz-MN-OEb"/> <outlet property="leftClickDelayStack" destination="q0S-CP-V6v" id="0lz-MN-OEb"/>
<outlet property="leftStickMinOffsetSlider" destination="KtJ-qH-KJX" id="vIV-4l-gbA"/> <outlet property="leftStickMinOffsetSlider" destination="KtJ-qH-KJX" id="vIV-4l-gbA"/>
@@ -2468,6 +2465,8 @@
<outlet property="squeezeShortcutSwitch" destination="VCg-Es-PQV" id="kgj-FT-fcZ"/> <outlet property="squeezeShortcutSwitch" destination="VCg-Es-PQV" id="kgj-FT-fcZ"/>
<outlet property="statsOverlaySelector" destination="NLH-qN-qCo" id="EF8-JF-O3S"/> <outlet property="statsOverlaySelector" destination="NLH-qN-qCo" id="EF8-JF-O3S"/>
<outlet property="statsOverlayStack" destination="UcP-0b-nJD" id="UdI-NS-O6k"/> <outlet property="statsOverlayStack" destination="UcP-0b-nJD" id="UdI-NS-O6k"/>
<outlet property="streamDimensionScaleSlider" destination="FIp-Sc-Sld" id="FIp-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIp-Sc-StK" id="FIp-Out-ScK"/>
<outlet property="streamingRadialMenuDelaySlider" destination="qZX-tG-Pwf" id="V5Z-kn-irq"/> <outlet property="streamingRadialMenuDelaySlider" destination="qZX-tG-Pwf" id="V5Z-kn-irq"/>
<outlet property="streamingRadialMenuDelayStack" destination="xXr-dB-QWZ" id="I9M-3Y-HGg"/> <outlet property="streamingRadialMenuDelayStack" destination="xXr-dB-QWZ" id="I9M-3Y-HGg"/>
<outlet property="swapAbxyStack" destination="2rP-Ui-wVU" id="gUR-Vz-ORP"/> <outlet property="swapAbxyStack" destination="2rP-Ui-wVU" id="gUR-Vz-ORP"/>
@@ -3978,19 +3977,19 @@
<image name="tray.full.fill" catalog="system" width="128" height="88"/> <image name="tray.full.fill" catalog="system" width="128" height="88"/>
<image name="video.and.waveform" catalog="system" width="128" height="94"/> <image name="video.and.waveform" catalog="system" width="128" height="94"/>
<systemColor name="linkColor"> <systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
<systemColor name="systemBackgroundColor"> <systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor> </systemColor>
<systemColor name="systemBrownColor"> <systemColor name="systemBrownColor">
<color red="0.67450980392156867" green="0.49803921568627452" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
<systemColor name="systemGray3Color"> <systemColor name="systemGray3Color">
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
<systemColor name="systemTealColor"> <systemColor name="systemTealColor">
<color red="0.0" green="0.76470588235294112" blue="0.81568627450980391" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor> </systemColor>
</resources> </resources>
</document> </document>
+57 -56
View File
@@ -2405,12 +2405,13 @@
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="q6v-Jb-ysy" userLabel="emulatedControllerTypeSelector"> <segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="q6v-Jb-ysy" userLabel="emulatedControllerTypeSelector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/> <rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments> <segments>
<segment title="Xbox 360"/> <segment title="Xbox 360"/>
<segment title="DS4"/> <segment title="PS"/>
<segment title="Both"/> <segment title="PS (Enhanced Haptics)"/>
<segment title="Xbox + PS"/>
</segments> </segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/> <color key="selectedSegmentTintColor" systemColor="linkColor"/>
@@ -2551,37 +2552,37 @@
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-StK" userLabel="interpolationLevel Stack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-StK" userLabel="interpolationLevel Stack">
<rect key="frame" x="456" y="2232" width="380" height="56.5"/> <rect key="frame" x="456" y="2232" width="380" height="56.5"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Lbl"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Sel" userLabel="Interpolation Level Slider"> <slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Sel" userLabel="Interpolation Level Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/> <rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
</slider> </slider>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="width" constant="380" id="FIi-Lv-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIi-Lv-Hgt"/> <constraint firstAttribute="height" constant="56.5" id="FIi-Lv-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIi-Lv-Wid"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-StK" userLabel="streamDimensionScale Stack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-StK" userLabel="streamDimensionScale Stack">
<rect key="frame" x="456" y="2293" width="380" height="56.5"/> <rect key="frame" x="456" y="2293" width="380" height="56.5"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Lbl"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Sld" userLabel="Stream Dimension Scale Slider"> <slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/> <rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
</slider> </slider>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="width" constant="380" id="FIi-Sc-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIi-Sc-Hgt"/> <constraint firstAttribute="height" constant="56.5" id="FIi-Sc-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIi-Sc-Wid"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CKa-yk-GSi" userLabel="redirectMicStack"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CKa-yk-GSi" userLabel="redirectMicStack">
@@ -3127,28 +3128,8 @@
<constraint firstAttribute="height" constant="56.5" id="MNC-Cn-lnb"/> <constraint firstAttribute="height" constant="56.5" id="MNC-Cn-lnb"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="HCc-SK-NBk">
<rect key="frame" x="459" y="2252" width="280" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Timebase" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P4N-i6-1zD" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bwf-wJ-EC2">
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="KUb-Mq-m9s"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="qzI-PN-1ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dZ3-os-MKX"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dZ3-os-MKX">
<rect key="frame" x="459" y="2291" width="312" height="31"/> <rect key="frame" x="455" y="2380" width="312" height="31"/>
<subviews> <subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Deueue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1n-pw-04X" userLabel="uiLabel"> <label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Deueue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1n-pw-04X" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="162" height="31"/> <rect key="frame" x="0.0" y="0.0" width="162" height="31"/>
@@ -3167,26 +3148,6 @@
<constraint firstAttribute="height" constant="31" id="O8n-y9-TrQ"/> <constraint firstAttribute="height" constant="31" id="O8n-y9-TrQ"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Rw1-5R-Jjf" userLabel="SDR Performance Workaround Stack">
<rect key="frame" x="459" y="2330" width="383" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F8d-OQ-PsH" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y5l-G9-PyG">
<rect key="frame" x="253" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="Qwc-Ko-5ZE"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="abg-h5-3Ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="fqe-49-wG1"> <stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="fqe-49-wG1">
<rect key="frame" x="15" y="1643" width="309.5" height="31"/> <rect key="frame" x="15" y="1643" width="309.5" height="31"/>
<subviews> <subviews>
@@ -3551,6 +3512,46 @@
<constraint firstAttribute="width" constant="380" id="tSR-LD-qKK"/> <constraint firstAttribute="width" constant="380" id="tSR-LD-qKK"/>
</constraints> </constraints>
</stackView> </stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Rw1-5R-Jjf" userLabel="SDR Performance Workaround Stack">
<rect key="frame" x="734" y="2380" width="383" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F8d-OQ-PsH" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y5l-G9-PyG">
<rect key="frame" x="253" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="Qwc-Ko-5ZE"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="abg-h5-3Ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="7Ye-hW-K31" userLabel="dualSenseTransientStack">
<rect key="frame" x="437" y="1540" width="380" height="55.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="DualSense Haptic Intensity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vak-fg-iAQ">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="2" translatesAutoresizingMaskIntoConstraints="NO" id="8fE-0o-N0V" userLabel="dualSenseTransientSlider">
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
<color key="tintColor" systemColor="linkColor"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="55.5" id="8g8-0d-mPf"/>
<constraint firstAttribute="width" constant="380" id="WTF-gm-sAY"/>
</constraints>
</stackView>
</subviews> </subviews>
<viewLayoutGuide key="safeArea" id="0sf-mG-PCk"/> <viewLayoutGuide key="safeArea" id="0sf-mG-PCk"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -3591,6 +3592,8 @@
<outlet property="customResolutionSwitch" destination="kRO-HO-zKO" id="ZUP-Yd-QDY"/> <outlet property="customResolutionSwitch" destination="kRO-HO-zKO" id="ZUP-Yd-QDY"/>
<outlet property="delayLeftClickStack" destination="fjb-oT-r1E" id="0Tb-f9-Pym"/> <outlet property="delayLeftClickStack" destination="fjb-oT-r1E" id="0Tb-f9-Pym"/>
<outlet property="delayLeftClickSwitch" destination="YB5-xe-Zdx" id="FEK-Lb-f0r"/> <outlet property="delayLeftClickSwitch" destination="YB5-xe-Zdx" id="FEK-Lb-f0r"/>
<outlet property="dualSenseTransientSlider" destination="8fE-0o-N0V" id="ECI-p9-Fn4"/>
<outlet property="dualSenseTransientStack" destination="7Ye-hW-K31" id="IaM-oQ-DVA"/>
<outlet property="duckOtherAppStack" destination="FkE-Sc-6kA" id="YUO-j1-Bdv"/> <outlet property="duckOtherAppStack" destination="FkE-Sc-6kA" id="YUO-j1-Bdv"/>
<outlet property="duckOtherAppSwitch" destination="71J-Gt-A3D" id="3iT-jh-hWx"/> <outlet property="duckOtherAppSwitch" destination="71J-Gt-A3D" id="3iT-jh-hWx"/>
<outlet property="edgeSlidingSensitivitySlider" destination="KRf-2B-Reb" id="UaG-GM-wkO"/> <outlet property="edgeSlidingSensitivitySlider" destination="KRf-2B-Reb" id="UaG-GM-wkO"/>
@@ -3605,15 +3608,9 @@
<outlet property="fpsStack" destination="aMv-7Q-yYg" id="JxU-wq-2Za"/> <outlet property="fpsStack" destination="aMv-7Q-yYg" id="JxU-wq-2Za"/>
<outlet property="framePacingModeSelector" destination="CC6-pu-Uoh" id="HTB-6I-QgB"/> <outlet property="framePacingModeSelector" destination="CC6-pu-Uoh" id="HTB-6I-QgB"/>
<outlet property="framePacingStack" destination="U0H-kh-QLS" id="M8w-wy-HI8"/> <outlet property="framePacingStack" destination="U0H-kh-QLS" id="M8w-wy-HI8"/>
<outlet property="interpolationLevelSlider" destination="FIi-Lv-Sel" id="FIi-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIi-Lv-StK" id="FIi-Out-StK"/>
<outlet property="streamDimensionScaleSlider" destination="FIi-Sc-Sld" id="FIi-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIi-Sc-StK" id="FIi-Out-ScK"/>
<outlet property="frameQueueSizeLabel" destination="r8X-jt-JfH" id="Tr9-eb-bIx"/> <outlet property="frameQueueSizeLabel" destination="r8X-jt-JfH" id="Tr9-eb-bIx"/>
<outlet property="frameQueueSizeSlider" destination="thv-JE-onX" id="gKD-6c-5tw"/> <outlet property="frameQueueSizeSlider" destination="thv-JE-onX" id="gKD-6c-5tw"/>
<outlet property="frameQueueSizeStack" destination="IHx-FO-ctr" id="gob-Ea-Avh"/> <outlet property="frameQueueSizeStack" destination="IHx-FO-ctr" id="gob-Ea-Avh"/>
<outlet property="frameTimebaseStack" destination="HCc-SK-NBk" id="xts-bc-f0C"/>
<outlet property="frameTimebaseSwitch" destination="bwf-wJ-EC2" id="fwl-GA-bQ6"/>
<outlet property="framerateSelector" destination="dAN-Gy-Eg7" id="tMv-Va-PhO"/> <outlet property="framerateSelector" destination="dAN-Gy-Eg7" id="tMv-Va-PhO"/>
<outlet property="fullColorRangeStack" destination="fRg-Rn-St7" id="fRg-Out-Let"/> <outlet property="fullColorRangeStack" destination="fRg-Rn-St7" id="fRg-Out-Let"/>
<outlet property="fullColorRangeSwitch" destination="fRs-Wt-Ch9" id="fRs-Out-Let"/> <outlet property="fullColorRangeSwitch" destination="fRs-Wt-Ch9" id="fRs-Out-Let"/>
@@ -3634,6 +3631,8 @@
<outlet property="hapticEngineStack" destination="kiR-q0-FQX" id="zj5-eD-N5a"/> <outlet property="hapticEngineStack" destination="kiR-q0-FQX" id="zj5-eD-N5a"/>
<outlet property="hdrStack" destination="kFw-d7-BTn" id="3aT-Gh-bDO"/> <outlet property="hdrStack" destination="kFw-d7-BTn" id="3aT-Gh-bDO"/>
<outlet property="hdrSwitch" destination="Kgb-YU-AFW" id="ksG-A4-HhS"/> <outlet property="hdrSwitch" destination="Kgb-YU-AFW" id="ksG-A4-HhS"/>
<outlet property="interpolationLevelSlider" destination="FIi-Lv-Sel" id="FIi-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIi-Lv-StK" id="FIi-Out-StK"/>
<outlet property="leftClickDelaySlider" destination="HNu-vj-Q0t" id="X9f-ya-wVv"/> <outlet property="leftClickDelaySlider" destination="HNu-vj-Q0t" id="X9f-ya-wVv"/>
<outlet property="leftClickDelayStack" destination="muF-2y-dHu" id="rmd-Pm-nfV"/> <outlet property="leftClickDelayStack" destination="muF-2y-dHu" id="rmd-Pm-nfV"/>
<outlet property="leftStickMinOffsetSlider" destination="vOQ-zv-JOQ" id="kJb-ey-xMh"/> <outlet property="leftStickMinOffsetSlider" destination="vOQ-zv-JOQ" id="kJb-ey-xMh"/>
@@ -3720,6 +3719,8 @@
<outlet property="softKeyboardToolbarSwitch" destination="c4W-2p-R3A" id="gf6-pq-L8o"/> <outlet property="softKeyboardToolbarSwitch" destination="c4W-2p-R3A" id="gf6-pq-L8o"/>
<outlet property="statsOverlaySelector" destination="Bda-3L-Wtg" id="9kj-wY-VjF"/> <outlet property="statsOverlaySelector" destination="Bda-3L-Wtg" id="9kj-wY-VjF"/>
<outlet property="statsOverlayStack" destination="dby-k3-aiH" id="1Ha-Y3-su4"/> <outlet property="statsOverlayStack" destination="dby-k3-aiH" id="1Ha-Y3-su4"/>
<outlet property="streamDimensionScaleSlider" destination="FIi-Sc-Sld" id="FIi-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIi-Sc-StK" id="FIi-Out-ScK"/>
<outlet property="streamingRadialMenuDelaySlider" destination="Tgh-nL-w5K" id="91f-Q5-IIl"/> <outlet property="streamingRadialMenuDelaySlider" destination="Tgh-nL-w5K" id="91f-Q5-IIl"/>
<outlet property="streamingRadialMenuDelayStack" destination="ySL-A5-fcn" id="HrE-C7-y0g"/> <outlet property="streamingRadialMenuDelayStack" destination="ySL-A5-fcn" id="HrE-C7-y0g"/>
<outlet property="swapAbxyStack" destination="reE-vr-Mdk" id="y3v-ac-LHy"/> <outlet property="swapAbxyStack" destination="reE-vr-Mdk" id="y3v-ac-LHy"/>
+9
View File
@@ -63,6 +63,15 @@
rightSharpness:(float)rightSharpness rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds; delaySeconds:(double)delaySeconds;
-(BOOL) hasDualSenseController:(uint16_t)controllerNumber;
-(void) renderDeviceDualSenseHaptics:(uint16_t)controllerNumber
leftAmplitude:(float)leftAmplitude
leftSharpness:(float)leftSharpness
leftTransient:(float)leftTransient
rightAmplitude:(float)rightAmplitude
rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds;
-(void) cancelScheduledDualSenseHaptics; -(void) cancelScheduledDualSenseHaptics;
-(void) updateTimerStateForOsc; -(void) updateTimerStateForOsc;
+381 -235
View File
@@ -184,9 +184,11 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
case RumbleDevice: case RumbleDevice:
[_oscController.lowFreqMotor setMotorAmplitude:lowFreqMotor]; [_oscController.lowFreqMotor setMotorAmplitude:lowFreqMotor];
[_oscController.highFreqMotor setMotorAmplitude:highFreqMotor]; [_oscController.highFreqMotor setMotorAmplitude:highFreqMotor];
break;
case LeftRightSwapped: case LeftRightSwapped:
[voidController.lowFreqMotor setMotorAmplitude:highFreqMotor]; [voidController.lowFreqMotor setMotorAmplitude:highFreqMotor];
[voidController.highFreqMotor setMotorAmplitude:lowFreqMotor]; [voidController.highFreqMotor setMotorAmplitude:lowFreqMotor];
break;
case RumbleOff: case RumbleOff:
break; break;
default: default:
@@ -212,250 +214,317 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
return CGRectGetWidth([[UIScreen mainScreen]bounds]) > CGRectGetHeight([[UIScreen mainScreen]bounds]); return CGRectGetWidth([[UIScreen mainScreen]bounds]) > CGRectGetHeight([[UIScreen mainScreen]bounds]);
} }
-(void)updateTimerStateForController:(VoidController* )voidController{ #if !TARGET_OS_TV
// if (@available(iOS 14.0, tvOS 14.0, *)) { - (UIInterfaceOrientation)currentInterfaceOrientation
if (true) { {
if(_gyroMode == GyroModeOff){ if (@available(iOS 13.0, *)) {
[self stopTimerForController:voidController]; for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
return; if (![scene isKindOfClass:UIWindowScene.class]) {
} continue;
}
if(voidController.gamepad.motion.hasAttitudeAndRotationRate) [voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
if (@available(iOS 14.0, *)) if(voidController.gamepad.motion.hasRotationRate) [voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)];
for(NSNumber* motionTypeObj in voidController.motionTypes){ UIWindowScene *windowScene = (UIWindowScene *)scene;
uint8_t motionType = motionTypeObj.intValue; if (windowScene.activationState != UISceneActivationStateForegroundActive) {
continue;
}
if (windowScene.interfaceOrientation != UIInterfaceOrientationUnknown) {
return windowScene.interfaceOrientation;
}
}
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (windowScene.activationState == UISceneActivationStateUnattached) {
continue;
}
if (windowScene.interfaceOrientation != UIInterfaceOrientationUnknown) {
return windowScene.interfaceOrientation;
}
}
}
return UIApplication.sharedApplication.statusBarOrientation;
}
#endif
-(void)updateTimerStateForController:(VoidController* )voidController{
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateTimerStateForController:voidController];
});
return;
}
if (voidController == nil || voidController.reportRateHz == 0) {
[self stopTimerForController:voidController];
return;
}
// if (@available(iOS 14.0, tvOS 14.0, *)) {
if(_gyroMode == GyroModeOff){
[self stopTimerForController:voidController];
return;
}
if([ControllerUtil hasControllerAccelerometer:voidController.gamepad]) {
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
}
if (@available(iOS 14.0, *)) if(voidController.gamepad.motion.hasRotationRate){
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)];
}
for(NSNumber* motionTypeObj in voidController.motionTypes){
uint8_t motionType = motionTypeObj.intValue;
#if !TARGET_OS_TV //tvOS has no device motion #if !TARGET_OS_TV //tvOS has no device motion
if(voidController == _oscController){ if(voidController == _oscController){
//Player has no controller *or* no motion for controller 1 *or* wants to override controller 1 motion with device motion //Player has no controller *or* no motion for controller 1 *or* wants to override controller 1 motion with device motion
if(!voidController.motionManager) { if(!voidController.motionManager) {
voidController.motionManager = [[CMMotionManager alloc] init]; voidController.motionManager = [[CMMotionManager alloc] init];
} }
switch (motionType) { switch (motionType) {
case LI_MOTION_TYPE_ACCEL: case LI_MOTION_TYPE_ACCEL: {
[voidController.accelTimer invalidate]; [voidController.accelTimer invalidate];
voidController.accelTimer = nil; voidController.accelTimer = nil;
// Reset the last motion sample // Reset the last motion sample
CMAcceleration emptyDeviceAccelSample = {}; CMAcceleration emptyDeviceAccelSample = {};
voidController.lastDeviceAccelSample = emptyDeviceAccelSample; voidController.lastDeviceAccelSample = emptyDeviceAccelSample;
voidController.motionManager.deviceMotionUpdateInterval = 1.0 / voidController.reportRateHz;
{dispatch_async(dispatch_get_main_queue(), ^{ [voidController.motionManager startDeviceMotionUpdates];
NSLog(@"setup device built-in gyro accelTimer"); {NSLog(@"setup device built-in accelTimer");
voidController.hasAccelerometer = YES; voidController.hasAccelerometer = YES;
voidController.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) { voidController.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMAcceleration lastDeviceAccelSample = voidController.lastDeviceAccelSample;
CMAcceleration deviceAccelSample = voidController.motionManager.deviceMotion.userAcceleration; CMAcceleration deviceAccelSample = voidController.motionManager.deviceMotion.userAcceleration;
//userAcceleration does not contain gravity, add gravity to x, y and z values: //userAcceleration does not contain gravity, add gravity to x, y and z values:
deviceAccelSample.x += voidController.motionManager.deviceMotion.gravity.x * self->_gyroSensitivity; deviceAccelSample.x += voidController.motionManager.deviceMotion.gravity.x;
deviceAccelSample.y += voidController.motionManager.deviceMotion.gravity.y * self->_gyroSensitivity; deviceAccelSample.y += voidController.motionManager.deviceMotion.gravity.y;
deviceAccelSample.z += voidController.motionManager.deviceMotion.gravity.z * self->_gyroSensitivity; deviceAccelSample.z += voidController.motionManager.deviceMotion.gravity.z;
NSLog(@"sending device accel %f", CACurrentMediaTime());
if (memcmp(&deviceAccelSample, &lastDeviceAccelSample, sizeof(deviceAccelSample)) == 0) {
return; UIInterfaceOrientation interfaceOrientation = [self currentInterfaceOrientation];
} CMAcceleration mappedDeviceAccelSample = {};
voidController.lastDeviceAccelSample = deviceAccelSample;
// Convert g to m/s^2
if (@available(iOS 13.0, *)) {
if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){ //check for landscape left or landscape right
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * -9.80665f * self->_gyroSensitivity);
}
else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * +9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * +9.80665f * self->_gyroSensitivity);
}
}
else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * -9.80665f * self->_gyroSensitivity);
}
}];
});}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
// Reset the last motion sample
CMRotationRate emptyDeviceGyroSample = {};
voidController.lastDeviceGyroSample = emptyDeviceGyroSample;
[voidController.motionManager startDeviceMotionUpdates];
NSLog(@"setup device built-in gyro gyroTimer");
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMRotationRate lastDeviceGyroSample = voidController.lastDeviceGyroSample;
CMRotationRate deviceGyroSample = voidController.motionManager.deviceMotion.rotationRate;
if (memcmp(&deviceGyroSample, &lastDeviceGyroSample, sizeof(deviceGyroSample)) == 0) {
return;
}
voidController.lastDeviceGyroSample = deviceGyroSample;
// Convert rad/s to deg/s
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
if (@available(iOS 13.0, *)) {
interfaceOrientation = UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation;
} else {
interfaceOrientation = UIApplication.sharedApplication.statusBarOrientation;
}
switch (interfaceOrientation) { switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: case UIInterfaceOrientationLandscapeLeft:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, mappedDeviceAccelSample.x = deviceAccelSample.y * -9.80665f;
LI_MOTION_TYPE_GYRO, mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0, mappedDeviceAccelSample.z = deviceAccelSample.x * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
break; break;
case UIInterfaceOrientationLandscapeRight: case UIInterfaceOrientationLandscapeRight:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, mappedDeviceAccelSample.x = deviceAccelSample.y * 9.80665f;
LI_MOTION_TYPE_GYRO, mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0, mappedDeviceAccelSample.z = deviceAccelSample.x * 9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
break; break;
case UIInterfaceOrientationPortrait: case UIInterfaceOrientationPortrait:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, mappedDeviceAccelSample.x = deviceAccelSample.x * -9.80665f;
LI_MOTION_TYPE_GYRO, mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0, mappedDeviceAccelSample.z = deviceAccelSample.y * 9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
break; break;
case UIInterfaceOrientationPortraitUpsideDown: case UIInterfaceOrientationPortraitUpsideDown:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, mappedDeviceAccelSample.x = deviceAccelSample.x * 9.80665f;
LI_MOTION_TYPE_GYRO, mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0, mappedDeviceAccelSample.z = deviceAccelSample.y * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0);
break; break;
default: default:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, mappedDeviceAccelSample.x = deviceAccelSample.y * -9.80665f;
LI_MOTION_TYPE_GYRO, mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0, mappedDeviceAccelSample.z = deviceAccelSample.x * -9.80665f;
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
break; break;
} }
// Don't send duplicate samples after orientation remapping.
CMAcceleration lastDeviceAccelSample = voidController.lastDeviceAccelSample;
if (memcmp(&mappedDeviceAccelSample, &lastDeviceAccelSample, sizeof(mappedDeviceAccelSample)) == 0) {
return;
}
voidController.lastDeviceAccelSample = mappedDeviceAccelSample;
/* // Convert g to m/s^2
if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){//check for landscape left or landscape right LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
mappedDeviceAccelSample.x,
mappedDeviceAccelSample.y,
mappedDeviceAccelSample.z);
}];}
break;
}
case LI_MOTION_TYPE_GYRO: {
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
// Reset the last motion sample
CMRotationRate emptyDeviceGyroSample = {};
voidController.lastDeviceGyroSample = emptyDeviceGyroSample;
[voidController.motionManager startDeviceMotionUpdates];
NSLog(@"setup device built-in gyro gyroTimer");
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMRotationRate lastDeviceGyroSample = voidController.lastDeviceGyroSample;
CMRotationRate deviceGyroSample = voidController.motionManager.deviceMotion.rotationRate;
if (memcmp(&deviceGyroSample, &lastDeviceGyroSample, sizeof(deviceGyroSample)) == 0) {
return;
}
voidController.lastDeviceGyroSample = deviceGyroSample;
// Convert rad/s to deg/s
UIInterfaceOrientation interfaceOrientation = [self currentInterfaceOrientation];
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO, LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0, self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0, self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0); self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
} break;
else{ case UIInterfaceOrientationLandscapeRight:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO, LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0, self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0, self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0); self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
} break;
*/ case UIInterfaceOrientationPortrait:
}]; LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
break; LI_MOTION_TYPE_GYRO,
} self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
} self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
#endif break;
else{ case UIInterfaceOrientationPortraitUpsideDown:
NSLog(@"controller obj timer update: controller timer "); LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
if (@available(iOS 14.0, *)) { self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0,
switch (motionType) { self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
case LI_MOTION_TYPE_ACCEL: self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0);
[voidController.accelTimer invalidate]; break;
voidController.accelTimer = nil; default:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
if (voidController.reportRateHz && voidController.gamepad.motion.hasGravityAndUserAcceleration) { LI_MOTION_TYPE_GYRO,
// Reset the last motion sample self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
GCAcceleration emptyAccelSample = {}; self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
voidController.lastAccelSample = emptyAccelSample; self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
NSLog(@"setup controller gyro accelTimer"); break;
dispatch_sync(dispatch_get_main_queue(), ^{ }
voidController.hasAccelerometer = YES;
voidController.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) { /*
// Don't send duplicate samples if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){//check for landscape left or landscape right
GCAcceleration lastAccelSample = voidController.lastAccelSample; LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
GCAcceleration accelSample = voidController.gamepad.motion.acceleration; LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) { self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
return; self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
} }
voidController.lastAccelSample = accelSample; else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
// Convert g to m/s^2 LI_MOTION_TYPE_GYRO,
//NSLog(@"sending controller gyro data, accelSample data 00: %f, playerIndex: %ld, obj: %@",accelSample.x, (long)voidController.gamepad.playerIndex, voidController); self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0,
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber, self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
LI_MOTION_TYPE_ACCEL, self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
accelSample.x * -9.80665f * self->_gyroSensitivity, }
accelSample.y * -9.80665f * self->_gyroSensitivity, */
accelSample.z * -9.80665f * self->_gyroSensitivity); }];
}]; break;
});
}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
if (voidController.reportRateHz && voidController.gamepad.motion.hasRotationRate) {
// Reset the last motion sample
GCRotationRate emptyGyroSample = {};
voidController.lastGyroSample = emptyGyroSample;
//dispatch_sync(dispatch_get_main_queue(), ^{
{dispatch_async(dispatch_get_main_queue(), ^{
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
GCRotationRate lastGyroSample = voidController.lastGyroSample;
GCRotationRate gyroSample = voidController.gamepad.motion.rotationRate;
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
return;
}
voidController.lastGyroSample = gyroSample;
// Convert rad/s to deg/s
// NSLog(@"sending controller gyro data, gyroSample data 00: %f, playerIndex: %ld, obj: %@",gyroSample.x, (long)voidController.gamepad.playerIndex, voidController);
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? gyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
}];
// });
});}
}
break;
}
} }
} }
} }
NSLog(@"controller obj timer, motionTypes: %lu", (unsigned long)voidController.motionTypes.count); #endif
else{
// Set the motion sensor state if they require manual activation // NSLog(@"controller obj timer update: controller timer ");
[self updateSensorSateForController:voidController]; if (@available(iOS 14.0, *)) {
// NSLog(@"sensor active: %d", voidController.gamepad.motion.sensorsActive); switch (motionType) {
case LI_MOTION_TYPE_ACCEL:
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
// if (voidController.reportRateHz && voidController.gamepad.motion.hasGravityAndUserAcceleration) {
if (voidController.reportRateHz) {
// Reset the last motion sample
GCAcceleration emptyAccelSample = {};
voidController.lastAccelSample = emptyAccelSample;
void (^setupAccelTimer)(void) = ^{
voidController.hasAccelerometer = YES;
voidController.accelTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz
repeats:YES
block:^(NSTimer *timer) {
GCAcceleration lastAccelSample = voidController.lastAccelSample;
GCAcceleration accelSample = voidController.gamepad.motion.acceleration;
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) {
return;
}
voidController.lastAccelSample = accelSample;
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
accelSample.x * -9.80665f,
accelSample.y * -9.80665f,
accelSample.z * -9.80665f);
}];
};
if ([NSThread isMainThread]) {
setupAccelTimer();
} else {
dispatch_async(dispatch_get_main_queue(), setupAccelTimer);
}
}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
if (voidController.reportRateHz && voidController.gamepad.motion.hasRotationRate) {
// Reset the last motion sample
GCRotationRate emptyGyroSample = {};
voidController.lastGyroSample = emptyGyroSample;
//dispatch_sync(dispatch_get_main_queue(), ^{
void (^setupGyroTimer)(void) = ^{
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
GCRotationRate lastGyroSample = voidController.lastGyroSample;
GCRotationRate gyroSample = voidController.gamepad.motion.rotationRate;
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
return;
}
voidController.lastGyroSample = gyroSample;
// Convert rad/s to deg/s
// NSLog(@"sending controller gyro data, gyroSample data 00: %f, playerIndex: %ld, obj: %@",gyroSample.x, (long)voidController.gamepad.playerIndex, voidController);
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? gyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
}];
};
if ([NSThread isMainThread]) setupGyroTimer();
else dispatch_async(dispatch_get_main_queue(), setupGyroTimer);
}
break;
}
}
}
} }
NSLog(@"controller obj timer, motionTypes: %lu", (unsigned long)voidController.motionTypes.count);
// Set the motion sensor state if they require manual activation
[self updateSensorSateForController:voidController];
// NSLog(@"sensor active: %d", voidController.gamepad.motion.sensorsActive);
} }
- (void)updateSensorSateForController:(VoidController* )voidController{ - (void)updateSensorSateForController:(VoidController* )voidController{
@@ -472,6 +541,13 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
} }
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz { - (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
});
return;
}
//if (@available(iOS 14.0, tvOS 14.0, *)) { //if (@available(iOS 14.0, tvOS 14.0, *)) {
NSLog(@"gyroMode: %ld", (long)_gyroMode); NSLog(@"gyroMode: %ld", (long)_gyroMode);
@@ -483,16 +559,26 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
if(!voidController.motionTypes){ if(!voidController.motionTypes){
voidController.motionTypes = [[NSMutableSet alloc] init]; voidController.motionTypes = [[NSMutableSet alloc] init];
} }
[voidController.motionTypes addObject:@(motionType)]; if (reportRateHz == 0) {
[voidController.motionTypes removeObject:@(motionType)];
}
else {
[voidController.motionTypes addObject:@(motionType)];
}
voidController.hasGyroscope = NO; voidController.hasGyroscope = NO;
voidController.hasAccelerometer = NO; voidController.hasAccelerometer = NO;
voidController.reportRateHz = reportRateHz;
} }
voidController.reportRateHz = reportRateHz;
voidController.controllerNumber = controllerNumber; voidController.controllerNumber = controllerNumber;
if(voidController == _oscController) [self updateTimerStateForController:voidController]; if (reportRateHz == 0) {
[self stopTimerForController:voidController];
}
else if(voidController == _oscController) {
[self updateTimerStateForController:voidController];
}
//} //}
} }
@@ -605,6 +691,62 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
} }
} }
-(BOOL) hasDualSenseController:(uint16_t)controllerNumber {
__block BOOL result = NO;
dispatch_block_t checkBlock = ^{
if (@available(iOS 14.5, tvOS 14.5, *)) {
VoidController* controller = [self->_voidControllers objectForKey:@(controllerNumber)];
result = controller != nil &&
[controller.gamepad.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]];
}
};
if ([NSThread isMainThread]) {
checkBlock();
}
else {
dispatch_sync(dispatch_get_main_queue(), checkBlock);
}
return result;
}
-(void) renderDeviceDualSenseHaptics:(uint16_t)controllerNumber
leftAmplitude:(float)leftAmplitude
leftSharpness:(float)leftSharpness
leftTransient:(float)leftTransient
rightAmplitude:(float)rightAmplitude
rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds {
__block uint64_t generation;
@synchronized(self) {
generation = _authoredHapticsGeneration;
}
dispatch_block_t renderBlock = ^{
@synchronized(self) {
if (generation != self->_authoredHapticsGeneration) {
return;
}
}
if (@available(iOS 14.0, tvOS 14.0, *)) {
[self->_oscController.lowFreqMotor setAuthoredAmplitude:leftAmplitude
sharpness:leftSharpness
transientStrength:leftTransient];
[self->_oscController.highFreqMotor setAuthoredAmplitude:rightAmplitude
sharpness:rightSharpness
transientStrength:rightTransient];
}
};
if (delaySeconds > 0.0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC)),
dispatch_get_main_queue(), renderBlock);
}
else {
dispatch_async(dispatch_get_main_queue(), renderBlock);
}
}
-(void) cancelScheduledDualSenseHaptics { -(void) cancelScheduledDualSenseHaptics {
@synchronized(self) { @synchronized(self) {
_authoredHapticsGeneration++; _authoredHapticsGeneration++;
@@ -1020,7 +1162,8 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
// LI_CTYPE_UNKNOWN for option "Both" // LI_CTYPE_UNKNOWN for option "Both"
if(voidController.playerIndex == 0){ if(voidController.playerIndex == 0){
type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType; type = _streamConfig.emulatedControllerType == ControllerEmulationXboxAndPs ? LI_CTYPE_PS : _streamConfig.emulatedControllerType;
if(type == ControllerEmulationPsEnhancedHaptic) type = ControllerEmulationPs;
} }
if(voidController.playerIndex == 1 && _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN){ if(voidController.playerIndex == 1 && _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN){
@@ -1038,15 +1181,10 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
if (@available(iOS 14.5, tvOS 14.5, *)) { if (@available(iOS 14.5, tvOS 14.5, *)) {
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) { if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
type = LI_CTYPE_PS; type = LI_CTYPE_PS;
capabilities |= LI_CCAP_DS5_HAPTICS_PCM;
} }
} }
} }
if (@available(iOS 14.5, tvOS 14.5, *)) {
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
capabilities |= LI_CCAP_DS5_HAPTICS_PCM;
}
}
// Detect supported haptics localities // Detect supported haptics localities
if (controller.haptics) { if (controller.haptics) {
@@ -1081,7 +1219,8 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
} }
bool controllerLacksGyro = (capabilities & LI_CCAP_GYRO) == 0; bool controllerLacksGyro = (capabilities & LI_CCAP_GYRO) == 0;
if(_streamConfig.emulatedControllerType == LI_CTYPE_PS && (_gyroMode == AlwaysDevice || (_gyroMode == GyroModeAuto && controllerLacksGyro))) if((_streamConfig.emulatedControllerType == ControllerEmulationPs || _streamConfig.emulatedControllerType == ControllerEmulationPsEnhancedHaptic)
&& (_gyroMode == AlwaysDevice || (_gyroMode == GyroModeAuto && controllerLacksGyro)))
{ {
type = LI_CTYPE_PS; type = LI_CTYPE_PS;
capabilities |= LI_CCAP_GYRO | LI_CCAP_ACCEL; capabilities |= LI_CCAP_GYRO | LI_CCAP_ACCEL;
@@ -1092,7 +1231,7 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
// This is a virtual controller corresponding to our OSC // This is a virtual controller corresponding to our OSC
// set osc to PS to utilize built-in gyro when "both" is selected // set osc to PS to utilize built-in gyro when "both" is selected
type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType; type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType;
if(type == ControllerEmulationPsEnhancedHaptic) type = LI_CTYPE_PS;
/* /*
if (_streamConfig.gyroMode != GyroModeOff) { if (_streamConfig.gyroMode != GyroModeOff) {
type = LI_CTYPE_PS; type = LI_CTYPE_PS;
@@ -1180,8 +1319,7 @@ double rc_expo(double x, double expo) {
- (bool)useMotionHandler{ - (bool)useMotionHandler{
return self->tempSettings.emulatedControllerType.intValue!=LI_CTYPE_PS return true;
||self->tempSettings.gyroMode.intValue==GyroModeOff;
} }
- (void)switchMotionControlOnOffByControllerButton{ - (void)switchMotionControlOnOffByControllerButton{
@@ -1190,7 +1328,7 @@ double rc_expo(double x, double expo) {
if(self->_gyroEnabledFlag) [self->motionHandler startMotionControlByControllerButton]; if(self->_gyroEnabledFlag) [self->motionHandler startMotionControlByControllerButton];
else [self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false]; else [self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false];
} }
else self->_gyroEnabledFlag = self->_gyroEnabledFlag || !self->_controllerGyroSwitchEnabled; self->_gyroEnabledFlag = self->_gyroEnabledFlag || !self->_controllerGyroSwitchEnabled;
} }
- (void)sendNavigationButtonPress { - (void)sendNavigationButtonPress {
@@ -1330,6 +1468,7 @@ double rc_expo(double x, double expo) {
*/ */
if([self useMotionHandler] if([self useMotionHandler]
&& self->motionHandler.motionControlStarted
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick && self->oscProfile.mapGyroTo==mapGyroToControllerStick
&& self->oscProfile.yawPitchToRightStick && self->oscProfile.yawPitchToRightStick
&& self->_gyroEnabledFlag && self->_gyroEnabledFlag
@@ -1339,6 +1478,7 @@ double rc_expo(double x, double expo) {
else [self updateRightStick: voidController.playerIndex==0?self->_oscController:voidController x:rightStickX y:rightStickY]; else [self updateRightStick: voidController.playerIndex==0?self->_oscController:voidController x:rightStickX y:rightStickY];
if([self useMotionHandler] if([self useMotionHandler]
&& self->motionHandler.motionControlStarted
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick && self->oscProfile.mapGyroTo==mapGyroToControllerStick
&& self->oscProfile.rollToLeftStick && self->oscProfile.rollToLeftStick
&& self->_gyroEnabledFlag && self->_gyroEnabledFlag
@@ -1611,7 +1751,7 @@ double rc_expo(double x, double expo) {
voidController.hasGyroscope = NO; voidController.hasGyroscope = NO;
if(voidController.gamepad.motion.hasAttitudeAndRotationRate){ if([ControllerUtil hasControllerAccelerometer:voidController.gamepad]){
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)]; [voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
voidController.hasAccelerometer = YES; voidController.hasAccelerometer = YES;
voidController.reportRateHz = 120; voidController.reportRateHz = 120;
@@ -1848,7 +1988,7 @@ double rc_expo(double x, double expo) {
NSLog(@"controller obj in dict: %@", controller); NSLog(@"controller obj in dict: %@", controller);
} }
if([self gamepadGyroEnabledInSetting]) [self updateFinished:_oscController]; if([self psGyroEnabledInSetting]) [self updateFinished:_oscController];
} }
-(id)initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate -(id)initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate
@@ -2038,8 +2178,12 @@ double rc_expo(double x, double expo) {
return false; return false;
} }
-(bool)gamepadGyroEnabledInSetting { -(bool)psGyroEnabledInSetting {
return (tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS && tempSettings.gyroMode.intValue != GyroModeOff); uint8_t emulation = tempSettings.emulatedControllerType.intValue;
return (emulation == ControllerEmulationPs
|| emulation == ControllerEmulationPsEnhancedHaptic
|| emulation == ControllerEmulationXboxAndPs)
&& tempSettings.gyroMode.intValue != GyroModeOff;
} }
-(void)connectionEstablished { -(void)connectionEstablished {
@@ -2049,7 +2193,7 @@ double rc_expo(double x, double expo) {
//if (_oscEnabled //if (_oscEnabled
// || (tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS && tempSettings.gyroMode != GyroModeOff)) { // || (tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS && tempSettings.gyroMode != GyroModeOff)) {
if ([self gamepadGyroEnabledInSetting]) { if ([self psGyroEnabledInSetting]) {
[self setButtonFlag:self->_oscController flags:A_FLAG]; [self setButtonFlag:self->_oscController flags:A_FLAG];
[self updateFinished:self->_oscController]; [self updateFinished:self->_oscController];
[self clearButtonFlag:self->_oscController flags:A_FLAG]; [self clearButtonFlag:self->_oscController flags:A_FLAG];
@@ -2068,14 +2212,14 @@ double rc_expo(double x, double expo) {
[self resetGyroInputForController:voidController]; [self resetGyroInputForController:voidController];
if (@available(iOS 14.0, *)) { if (@available(iOS 14.0, *)) {
//NSLog(@"stop controller obj: %@, hasAcc %d, hasGyro %d", voidController, voidController.hasAccelerometer, voidController.hasGyroscope); //NSLog(@"stop controller obj: %@, hasAcc %d, hasGyro %d", voidController, voidController.hasAccelerometer, voidController.hasGyroscope);
if(voidController.hasAccelerometer){ // if(voidController.hasAccelerometer){
[voidController.accelTimer invalidate]; [voidController.accelTimer invalidate];
voidController.accelTimer = nil; voidController.accelTimer = nil;
} // }
if(voidController.hasGyroscope){ // if(voidController.hasGyroscope){
[voidController.gyroTimer invalidate]; [voidController.gyroTimer invalidate];
voidController.gyroTimer = nil; voidController.gyroTimer = nil;
} // }
} }
} }
@@ -2083,11 +2227,12 @@ double rc_expo(double x, double expo) {
// Stop all timers to ensure a clean slate before applying the new setting. // Stop all timers to ensure a clean slate before applying the new setting.
[self stopTimerForAllControllers]; [self stopTimerForAllControllers];
_gyroEnabledFlag = _gyroMode != GyroModeOff && tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS; _gyroEnabledFlag = [self psGyroEnabledInSetting];
switch(_gyroMode) { switch(_gyroMode) {
case AlwaysController: case AlwaysController:
// Activate timers only for physical controllers. // Activate timers only for physical controllers.
for (VoidController* voidController in _voidControllers.allValues) { for (VoidController* voidController in _voidControllers.allValues) {
if(voidController == _oscController) break;
[self updateTimerStateForController:voidController]; [self updateTimerStateForController:voidController];
} }
break; break;
@@ -2170,7 +2315,8 @@ double rc_expo(double x, double expo) {
} }
} }
-(void) dealloc
- (void) dealloc
{ {
if (VLSharedControllerSupport == self) { if (VLSharedControllerSupport == self) {
VLSharedControllerSupport = nil; VLSharedControllerSupport = nil;
+478 -44
View File
@@ -260,6 +260,13 @@ import UIKit
} }
} }
@objc enum ControllerEmulation: UInt8 {
case xbox = 0x01 // LI_CTYPE_XBOX
case ps = 0x02 // LI_CTYPE_PS
case psEnhancedHaptic = 0xFE
case xboxAndPs = 0x00 // LI_CTYPE_UNKNOWN
}
@objc protocol ControllerUtilDelegate: AnyObject { @objc protocol ControllerUtilDelegate: AnyObject {
func isStreaming() -> Bool func isStreaming() -> Bool
func isInAppView() -> Bool func isInAppView() -> Bool
@@ -269,6 +276,38 @@ import UIKit
@objc static weak var delegate: ControllerUtilDelegate? @objc static weak var delegate: ControllerUtilDelegate?
#if !VOIDLINK_PREVIEW #if !VOIDLINK_PREVIEW
@objc static var hasDualSenseController: Bool {
if #available(iOS 14.5, *) {
for gcController in GCController.controllers() {
if gcController.extendedGamepad is GCDualSenseGamepad {return true}
}
return false
}
else {return false}
}
@objc static func hasControllerAccelerometer(_ controller: GCController) -> Bool {
guard let motion = controller.motion else { return false }
if #available(iOS 14.0, tvOS 14.0, *) {
if motion.hasGravityAndUserAcceleration {
return true
}
if controller.extendedGamepad is GCDualShockGamepad {
return true
}
if #available(iOS 14.5, tvOS 14.5, *) {
if controller.extendedGamepad is GCDualSenseGamepad {
return true
}
}
}
return false
}
private struct DualSensePCMChunk { private struct DualSensePCMChunk {
let controllerNumber: UInt16 let controllerNumber: UInt16
let flags: UInt8 let flags: UInt8
@@ -310,17 +349,57 @@ import UIKit
} }
} }
private final class AuthoredIrRenderState {
var renderHostAnchorUs: UInt64?
var renderLocalAnchor = 0.0
var diagnosticFrameCounter: UInt32 = 0
func renderDelay(for timestampUs: UInt64, resetTimeline: Bool) -> TimeInterval {
let now = ProcessInfo.processInfo.systemUptime
if resetTimeline || renderHostAnchorUs == nil {
renderHostAnchorUs = timestampUs
renderLocalAnchor = now + 0.002
}
let anchor = renderHostAnchorUs ?? timestampUs
let hostDelta = timestampUs >= anchor ? Double(timestampUs - anchor) / 1_000_000.0 : 0
return min(max(renderLocalAnchor + hostDelta - now, 0), 0.025)
}
}
private static let dualSenseHapticsLock = NSLock() private static let dualSenseHapticsLock = NSLock()
private static let dualSenseHapticsWorker = DispatchQueue( private static let dualSenseHapticsWorker = DispatchQueue(
label: "com.voidlink.dualsense-authored-haptics", label: "com.voidlink.dualsense-authored-haptics",
qos: .userInteractive qos: .userInteractive
) )
private static let maxQueuedDualSensePCMChunks = 8 private static let maxQueuedDualSensePCMChunks = 8
private static let defaultDualSenseHapticsEQ: [Float] = [0.78, 0.98, 1.59, 0.175, 1.2] private static let defaultDualSenseControllerHapticsEQ: [Float] = [0.78, 0.98, 1.59, 0.175, 1.2]
private static let defaultDeviceDualSenseHapticsEQ: [Float] = [18, 0.98, 1.59, 0.9, 1.2]
private static let dualSenseControllerAmplitudeScale: Float = 1.15
private static let dualSenseControllerTransientScale: Float = 1.0
private static let dualSenseControllerSharpnessScale: Float = 1.15
private static let dualSenseControllerMirrorMixedHandles = false
private static var dualSensePCMQueue = Deque<DualSensePCMChunk>() private static var dualSensePCMQueue = Deque<DualSensePCMChunk>()
private static var dualSenseWorkerScheduled = false private static var dualSenseWorkerScheduled = false
private static var dualSenseForcedDiscontinuities = Set<UInt16>() private static var dualSenseForcedDiscontinuities = Set<UInt16>()
private static var dualSenseAuthoredEngines = [UInt16: AuthoredEngineState]() private static var dualSenseAuthoredEngines = [UInt16: AuthoredEngineState]()
private static var dualSenseIrRenderStates = [UInt16: AuthoredIrRenderState]()
private static let authoredHapticsRouteLock = NSLock()
private static var authoredHapticsPreference = AuthoredHapticsPreference.auto
private static var authoredHapticsLastRoutedToDualSense = false
private static var authoredHapticsLastRoute = AuthoredHapticsRoute.off
private enum AuthoredHapticsPreference: Int {
case auto = 0
case device = 1
case swapped = 2
case off = 3
}
private enum AuthoredHapticsRoute {
case dualSense
case device
case off
}
#if os(iOS) #if os(iOS)
private static var dualSenseHapticsEQOverlayWindow: UIWindow? private static var dualSenseHapticsEQOverlayWindow: UIWindow?
@@ -329,11 +408,70 @@ import UIKit
#endif #endif
fileprivate static func dualSenseHapticsEQSnapshot() -> [Float] { fileprivate static func dualSenseHapticsEQSnapshot() -> [Float] {
defaultDualSenseHapticsEQ defaultDeviceDualSenseHapticsEQ
} }
fileprivate static func defaultDualSenseHapticsEQSnapshot() -> [Float] { fileprivate static func defaultDualSenseHapticsEQSnapshot() -> [Float] {
defaultDualSenseHapticsEQ defaultDeviceDualSenseHapticsEQ
}
private static func dualSenseHapticsEQSnapshot(for route: AuthoredHapticsRoute) -> [Float] {
switch route {
case .dualSense:
return defaultDualSenseControllerHapticsEQ
case .device, .off:
return defaultDeviceDualSenseHapticsEQ
}
}
private static func compensatedDualSenseControllerValues(
_ values: (amplitude: Float, sharpness: Float, transient: Float),
highBandRatio: Float
) -> (amplitude: Float, sharpness: Float, transient: Float) {
let lowFrequencyTrim = 0.82 + 0.18 * min(max(highBandRatio, 0), 1)
let amplitude = min(max(
pow(values.amplitude, 0.50) * dualSenseControllerAmplitudeScale * lowFrequencyTrim,
0
), 1)
let sharpness = min(max(
pow(values.sharpness, 0.8) * dualSenseControllerSharpnessScale,
0
), 1)
let transient = min(max(
pow(values.transient, 0.9) * dualSenseControllerTransientScale,
0
), 1)
return (amplitude, sharpness, transient)
}
private static func authoredHighBandRatio(for lane: AhAuthoredLaneFrame?) -> Float {
1 - min(max(lane?.low_band_ratio ?? 1, 0), 1)
}
private static func mixedDualSenseControllerHandleValues(
left: (amplitude: Float, sharpness: Float, transient: Float),
right: (amplitude: Float, sharpness: Float, transient: Float)
) -> (
left: (amplitude: Float, sharpness: Float, transient: Float),
right: (amplitude: Float, sharpness: Float, transient: Float)
) {
guard dualSenseControllerMirrorMixedHandles else {
return (left, right)
}
let amplitude = min(max(sqrt(
(left.amplitude * left.amplitude + right.amplitude * right.amplitude) * 0.5
), 0), 1)
let amplitudeSum = left.amplitude + right.amplitude
let sharpness = amplitudeSum > 0.000_001
? min(max(
(left.sharpness * left.amplitude + right.sharpness * right.amplitude) / amplitudeSum,
0
), 1)
: min(max(max(left.sharpness, right.sharpness), 0), 1)
let transient = min(max(max(left.transient, right.transient), 0), 1)
let mixed = (amplitude: amplitude, sharpness: sharpness, transient: transient)
return (mixed, mixed)
} }
fileprivate static func previewDualSenseHapticsEQ(_ values: [Float]) { fileprivate static func previewDualSenseHapticsEQ(_ values: [Float]) {
@@ -342,6 +480,86 @@ import UIKit
fileprivate static func saveDualSenseHapticsEQ(_ values: [Float]) { fileprivate static func saveDualSenseHapticsEQ(_ values: [Float]) {
} }
private static func refreshAuthoredHapticsPreference() -> AuthoredHapticsPreference {
let rawValue = Int(DataManager().getSettings().hapticEngine.intValue)
let preference = AuthoredHapticsPreference(rawValue: rawValue) ?? .auto
authoredHapticsRouteLock.lock()
authoredHapticsPreference = preference
authoredHapticsRouteLock.unlock()
return preference
}
private static func selectAuthoredHapticsRoute(
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) -> AuthoredHapticsRoute {
switch refreshAuthoredHapticsPreference() {
case .off:
return .off
case .device:
return .device
case .auto, .swapped:
let hasDualSense = controllerSupport.hasDualSenseController(controllerNumber)
authoredHapticsRouteLock.lock()
authoredHapticsLastRoutedToDualSense = hasDualSense
authoredHapticsRouteLock.unlock()
return hasDualSense ? .dualSense : .device
}
}
private static func stopAuthoredHapticsRoute(
_ route: AuthoredHapticsRoute,
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) {
switch route {
case .dualSense:
controllerSupport.renderDualSenseHaptics(
controllerNumber,
leftAmplitude: 0,
leftSharpness: 0,
leftTransient: 0,
rightAmplitude: 0,
rightSharpness: 0,
rightTransient: 0,
delaySeconds: 0
)
case .device:
controllerSupport.renderDeviceDualSenseHaptics(
controllerNumber,
leftAmplitude: 0,
leftSharpness: 0,
leftTransient: 0,
rightAmplitude: 0,
rightSharpness: 0,
rightTransient: 0,
delaySeconds: 0
)
case .off:
break
}
}
private static func updateAuthoredHapticsRouteTransition(
to route: AuthoredHapticsRoute,
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) {
authoredHapticsRouteLock.lock()
let previousRoute = authoredHapticsLastRoute
authoredHapticsLastRoute = route
authoredHapticsLastRoutedToDualSense = route == .dualSense
authoredHapticsRouteLock.unlock()
if previousRoute != route {
stopAuthoredHapticsRoute(
previousRoute,
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
}
}
#if os(iOS) #if os(iOS)
@available(iOS 14.0, *) @available(iOS 14.0, *)
@objc static func presentDualSenseHapticsEQIfNeeded() { @objc static func presentDualSenseHapticsEQIfNeeded() {
@@ -425,6 +643,51 @@ import UIKit
} }
} }
@objc(enqueueDualSenseHapticsIRV2WithControllerNumber:flags:sourceSequenceNumber:timestampUs:sourceFrameCount:leftRms:leftPeak:leftTransient:leftLowRatio:leftZeroCrossHz:rightRms:rightPeak:rightTransient:rightLowRatio:rightZeroCrossHz:laneCorrelation:)
static func enqueueDualSenseHapticsIRV2(
controllerNumber: UInt16,
flags: UInt8,
sourceSequenceNumber: UInt32,
timestampUs: UInt64,
sourceFrameCount: UInt32,
leftRms: Float,
leftPeak: Float,
leftTransient: Float,
leftLowRatio: Float,
leftZeroCrossHz: Float,
rightRms: Float,
rightPeak: Float,
rightTransient: Float,
rightLowRatio: Float,
rightZeroCrossHz: Float,
laneCorrelation: Float
) {
dualSenseHapticsWorker.async {
processDualSenseIRV2Frame(
controllerNumber: controllerNumber,
flags: flags,
sourceSequenceNumber: sourceSequenceNumber,
timestampUs: timestampUs,
sourceFrameCount: sourceFrameCount,
left: makeAuthoredLaneFrame(
rmsAmplitude: leftRms,
peakAmplitude: leftPeak,
transientStrength: leftTransient,
lowBandRatio: leftLowRatio,
zeroCrossingRateHz: leftZeroCrossHz
),
right: makeAuthoredLaneFrame(
rmsAmplitude: rightRms,
peakAmplitude: rightPeak,
transientStrength: rightTransient,
lowBandRatio: rightLowRatio,
zeroCrossingRateHz: rightZeroCrossHz
),
laneCorrelation: laneCorrelation
)
}
}
@objc static func stopAllDualSenseHaptics() { @objc static func stopAllDualSenseHaptics() {
ControllerSupport.sharedInstance()?.cancelScheduledDualSenseHaptics() ControllerSupport.sharedInstance()?.cancelScheduledDualSenseHaptics()
@@ -434,14 +697,79 @@ import UIKit
dualSenseHapticsLock.unlock() dualSenseHapticsLock.unlock()
dualSenseHapticsWorker.async { dualSenseHapticsWorker.async {
let controllerNumbers = Array(dualSenseAuthoredEngines.keys) let controllerNumbers = Array(
Set(dualSenseAuthoredEngines.keys).union(dualSenseIrRenderStates.keys)
)
dualSenseAuthoredEngines.removeAll(keepingCapacity: false) dualSenseAuthoredEngines.removeAll(keepingCapacity: false)
dualSenseIrRenderStates.removeAll(keepingCapacity: false)
for controllerNumber in controllerNumbers { for controllerNumber in controllerNumbers {
renderDualSenseHaptics(controllerNumber: controllerNumber, left: nil, right: nil, delay: 0) renderDualSenseHaptics(controllerNumber: controllerNumber, left: nil, right: nil, delay: 0)
} }
} }
} }
private static func makeAuthoredLaneFrame(
rmsAmplitude: Float,
peakAmplitude: Float,
transientStrength: Float,
lowBandRatio: Float,
zeroCrossingRateHz: Float
) -> AhAuthoredLaneFrame {
var lane = AhAuthoredLaneFrame()
lane.rms_amplitude = rmsAmplitude
lane.peak_amplitude = peakAmplitude
lane.transient_strength = transientStrength
lane.low_band_ratio = lowBandRatio
lane.zero_crossing_rate_hz = zeroCrossingRateHz
return lane
}
private static func processDualSenseIRV2Frame(
controllerNumber: UInt16,
flags: UInt8,
sourceSequenceNumber: UInt32,
timestampUs: UInt64,
sourceFrameCount: UInt32,
left: AhAuthoredLaneFrame,
right: AhAuthoredLaneFrame,
laneCorrelation: Float
) {
let isDiscontinuity = (flags & 0x01) != 0
let isStreamEnd = (flags & 0x04) != 0
let isSilent = (flags & 0x08) != 0
let state = dualSenseIrRenderStates[controllerNumber] ?? AuthoredIrRenderState()
dualSenseIrRenderStates[controllerNumber] = state
let delay = state.renderDelay(
for: timestampUs,
resetTimeline: isDiscontinuity || state.diagnosticFrameCounter == 0
)
state.diagnosticFrameCounter &+= 1
renderDualSenseHaptics(
controllerNumber: controllerNumber,
left: isSilent ? nil : left,
right: isSilent ? nil : right,
laneCorrelation: laneCorrelation,
logDiagnostics: state.diagnosticFrameCounter % 100 == 0,
appliesEQ: false,
delay: delay
)
if isStreamEnd {
renderDualSenseHaptics(
controllerNumber: controllerNumber,
left: nil,
right: nil,
appliesEQ: false,
delay: delay + 0.005
)
dualSenseIrRenderStates.removeValue(forKey: controllerNumber)
}
_ = sourceSequenceNumber
_ = sourceFrameCount
}
private static func drainDualSensePCMQueue() { private static func drainDualSensePCMQueue() {
while true { while true {
dualSenseHapticsLock.lock() dualSenseHapticsLock.lock()
@@ -544,6 +872,7 @@ import UIKit
right: silent ? nil : frame.lanes.1, right: silent ? nil : frame.lanes.1,
laneCorrelation: frame.lane_correlation, laneCorrelation: frame.lane_correlation,
logDiagnostics: state.diagnosticFrameCounter % 100 == 0, logDiagnostics: state.diagnosticFrameCounter % 100 == 0,
appliesEQ: false,
delay: delay delay: delay
) )
} }
@@ -560,26 +889,52 @@ import UIKit
} }
} }
@objc static var dualSenseHapticTransient: Float = 1.0
private static func renderDualSenseHaptics( private static func renderDualSenseHaptics(
controllerNumber: UInt16, controllerNumber: UInt16,
left: AhAuthoredLaneFrame?, left: AhAuthoredLaneFrame?,
right: AhAuthoredLaneFrame?, right: AhAuthoredLaneFrame?,
laneCorrelation: Float = 0, laneCorrelation: Float = 0,
logDiagnostics: Bool = false, logDiagnostics: Bool = false,
appliesEQ: Bool = true,
delay: TimeInterval delay: TimeInterval
) { ) {
guard let controllerSupport = ControllerSupport.sharedInstance() else { guard let controllerSupport = ControllerSupport.sharedInstance() else {
return return
} }
let eq = dualSenseHapticsEQSnapshot() let route = selectAuthoredHapticsRoute(
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
updateAuthoredHapticsRouteTransition(
to: route,
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
guard route != .off else {
return
}
let eq = appliesEQ ? dualSenseHapticsEQSnapshot(for: route) : [1, 1, 1, 1, 1] as [Float]
let masterGain = eq[0] let masterGain = eq[0]
let lowBandGain = eq[1] let lowBandGain = eq[1]
let highBandGain = eq[2] let highBandGain = eq[2]
let transientGain = eq[3] let transientGain = eq[3]
let sharpnessGain = eq[4] let sharpnessGain = eq[4]
func renderValues(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) { func rawIrValues(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
guard let lane else {
return (0, 0, 0)
}
let amplitude = min(max(lane.rms_amplitude, 0), 1)
let sharpness = min(max(1 - lane.low_band_ratio, 0), 1)
let transient = min(max(lane.transient_strength, 0), 1)
return (amplitude, sharpness, transient)
}
func renderValues1(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
let lowBandRatio = min(max(lane?.low_band_ratio ?? 1, 0), 1) let lowBandRatio = min(max(lane?.low_band_ratio ?? 1, 0), 1)
let highBandRatio = 1 - lowBandRatio let highBandRatio = 1 - lowBandRatio
let effectiveLowBandGain = pow(lowBandGain, 1.6) let effectiveLowBandGain = pow(lowBandGain, 1.6)
@@ -604,10 +959,12 @@ import UIKit
: immediateSharpness : immediateSharpness
let sourceTransient = min(max(lane?.transient_strength ?? 0, 0), 1) let sourceTransient = min(max(lane?.transient_strength ?? 0, 0), 1)
let rmsAmplitude = min(max(lane?.rms_amplitude ?? 0, 0), 1) let rmsAmplitude = min(max(lane?.rms_amplitude ?? 0, 0), 1)
let perceptualBandTrim = 0.55 + 0.55 * equalizedHighBandRatio let transientBandTrim = 0.55 + 0.35 * lowBandRatio + 0.10 * equalizedHighBandRatio
let lowContinuousTrim = 0.78 + 0.22 * lowBandRatio
let highContinuousTrim = 0.62 - 0.30 * equalizedHighBandRatio
let renderedTransient = min(max( let renderedTransient = min(max(
pow(sourceTransient, 0.45) * sqrt(masterGain) * transientGain * pow(sourceTransient, 0.45) * sqrt(masterGain) * transientGain *
bandGain * perceptualBandTrim * 1.35, bandGain * transientBandTrim * 1.35,
0 0
), 1) ), 1)
let continuousShare = 1 - 0.85 * min( let continuousShare = 1 - 0.85 * min(
@@ -617,11 +974,11 @@ import UIKit
let lowBody = pow(rmsAmplitude, 1.85) * let lowBody = pow(rmsAmplitude, 1.85) *
(1 - highBandRatio) * effectiveLowBandGain * 0.55 (1 - highBandRatio) * effectiveLowBandGain * 0.55
let highTexture = pow(rmsAmplitude, 0.55) * let highTexture = pow(rmsAmplitude, 0.55) *
pow(highBandRatio, 1.15) * effectiveHighBandGain * 0.22 pow(highBandRatio, 1.35) * effectiveHighBandGain * 0.09
let midBodyTrim = 1 - 0.55 * min(max((representativeFrequency - 90) / 140, 0), 1) * let midBodyTrim = 1 - 0.55 * min(max((representativeFrequency - 90) / 140, 0), 1) *
(1 - highBandRatio) (1 - highBandRatio)
let continuousAmplitude = (lowBody + highTexture) * masterGain * let continuousAmplitude = (lowBody * lowContinuousTrim + highTexture * highContinuousTrim) *
perceptualBandTrim * midBodyTrim * continuousShare masterGain * midBodyTrim * continuousShare
return ( return (
min(max(continuousAmplitude, 0), 1), min(max(continuousAmplitude, 0), 1),
min(max(synthesizedSharpness * sharpnessGain, 0), 1), min(max(synthesizedSharpness * sharpnessGain, 0), 1),
@@ -629,36 +986,83 @@ import UIKit
) )
} }
var leftValues = renderValues(for: left) func renderValues2(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
var rightValues = renderValues(for: right) guard let lane else {
return (0, 0, 0)
}
let positiveCorrelation = min(max(laneCorrelation, 0), 1) let useBuiltinHaptic = route == .device
let centerBlend = min(max((positiveCorrelation - 0.1) / 0.9, 0), 1) * 0.85
if centerBlend > 0 { let rmsAmplitude = min(max(lane.rms_amplitude, 0), 1)
let commonAmplitude = sqrt(
(leftValues.amplitude * leftValues.amplitude + let lowBandRatio = min(max(lane.low_band_ratio, 0), 1)
rightValues.amplitude * rightValues.amplitude) * 0.5 let highBandRatio = 1 - lowBandRatio
let amplitudeLowBandGain: Float = 1.0
let amplitudeHighBandGain: Float = 1.0
let bandGain = lowBandRatio * amplitudeLowBandGain + highBandRatio * amplitudeHighBandGain
let sourceSharpness = min(max(highBandRatio, 0), 1)
let sharpnessLowGain: Float = 0.62
let sharpnessMidGain: Float = 0.82
let sharpnessHighGain: Float = 1.2
let sharpnessLowWeight = min(max((0.50 - sourceSharpness) / 0.50, 0), 1)
let sharpnessHighWeight = min(max((sourceSharpness - 0.50) / 0.50, 0), 1)
let sharpnessMidWeight = 1 - max(sharpnessLowWeight, sharpnessHighWeight)
let sharpnessGain = sharpnessLowWeight * sharpnessLowGain +
sharpnessMidWeight * sharpnessMidGain +
sharpnessHighWeight * sharpnessHighGain
let sharpness = min(max(sourceSharpness * sharpnessGain, 0), 1)
let transient = min(max(lane.transient_strength, 0), 1)
let amplitudeCeiling: Float = 1.0
let transientGain: Float = useBuiltinHaptic ? 1.03 : 0.343
let transientCeiling: Float = useBuiltinHaptic ? 1.03 : 0.6
let transientBottom: Float = useBuiltinHaptic ? 0.14 : 0.0
return (
min(rmsAmplitude * bandGain, amplitudeCeiling),
sharpness,
min(max(transientBottom, transient * transientGain * 1.6666666667 * dualSenseHapticTransient), transientCeiling)
) )
let amplitudeSum = leftValues.amplitude + rightValues.amplitude
let commonSharpness = amplitudeSum > 0.000_001
? (leftValues.sharpness * leftValues.amplitude +
rightValues.sharpness * rightValues.amplitude) / amplitudeSum
: max(leftValues.sharpness, rightValues.sharpness)
let commonTransient = max(leftValues.transient, rightValues.transient)
leftValues.amplitude += (commonAmplitude - leftValues.amplitude) * centerBlend
rightValues.amplitude += (commonAmplitude - rightValues.amplitude) * centerBlend
leftValues.sharpness += (commonSharpness - leftValues.sharpness) * centerBlend
rightValues.sharpness += (commonSharpness - rightValues.sharpness) * centerBlend
leftValues.transient += (commonTransient - leftValues.transient) * centerBlend
rightValues.transient += (commonTransient - rightValues.transient) * centerBlend
} }
var leftValues = appliesEQ ? renderValues1(for: left) : renderValues2(for: left)
var rightValues = appliesEQ ? renderValues1(for: right) : renderValues2(for: right)
if appliesEQ {
let positiveCorrelation = min(max(laneCorrelation, 0), 1)
let commonHighBandRatio = (
authoredHighBandRatio(for: left) + authoredHighBandRatio(for: right)
) * 0.5
let dualSenseCenterBlendScale = 0.10 + 0.55 * commonHighBandRatio
let centerBlendScale: Float = route == .dualSense ? dualSenseCenterBlendScale : 0.85
let centerBlend = min(max((positiveCorrelation - 0.1) / 0.9, 0), 1) * centerBlendScale
if centerBlend > 0 {
let commonAmplitude = sqrt(
(leftValues.amplitude * leftValues.amplitude +
rightValues.amplitude * rightValues.amplitude) * 0.5
)
let amplitudeSum = leftValues.amplitude + rightValues.amplitude
let commonSharpness = amplitudeSum > 0.000_001
? (leftValues.sharpness * leftValues.amplitude +
rightValues.sharpness * rightValues.amplitude) / amplitudeSum
: max(leftValues.sharpness, rightValues.sharpness)
let commonTransient = max(leftValues.transient, rightValues.transient)
leftValues.amplitude += (commonAmplitude - leftValues.amplitude) * centerBlend
rightValues.amplitude += (commonAmplitude - rightValues.amplitude) * centerBlend
leftValues.sharpness += (commonSharpness - leftValues.sharpness) * centerBlend
rightValues.sharpness += (commonSharpness - rightValues.sharpness) * centerBlend
leftValues.transient += (commonTransient - leftValues.transient) * centerBlend
rightValues.transient += (commonTransient - rightValues.transient) * centerBlend
}
}
/* /*
if logDiagnostics { if logDiagnostics {
NSLog( NSLog(
"DS5 authored: c=%u srcL[rms=%.3f high=%.3f hz=%.0f] srcR[rms=%.3f high=%.3f hz=%.0f] corr=%.2f outL[a=%.3f s=%.3f t=%.3f] outR[a=%.3f s=%.3f t=%.3f]", "DS5 authored: c=%u route=%@ srcL[rms=%.3f high=%.3f hz=%.0f] srcR[rms=%.3f high=%.3f hz=%.0f] corr=%.2f outL[a=%.3f s=%.3f t=%.3f] outR[a=%.3f s=%.3f t=%.3f]",
controllerNumber, controllerNumber,
route == .dualSense ? "dualsense" : "device",
Double(left?.rms_amplitude ?? 0), Double(left?.rms_amplitude ?? 0),
Double(1 - min(max(left?.low_band_ratio ?? 1, 0), 1)), Double(1 - min(max(left?.low_band_ratio ?? 1, 0), 1)),
Double(min(max((left?.zero_crossing_rate_hz ?? 0) * 0.5, 0), 400)), Double(min(max((left?.zero_crossing_rate_hz ?? 0) * 0.5, 0), 400)),
@@ -674,18 +1078,47 @@ import UIKit
Double(rightValues.transient) Double(rightValues.transient)
) )
} }
*/ */
controllerSupport.renderDualSenseHaptics( switch route {
controllerNumber, case .dualSense:
leftAmplitude: leftValues.amplitude, if appliesEQ {
leftSharpness: leftValues.sharpness, leftValues = compensatedDualSenseControllerValues(
leftTransient: leftValues.transient, leftValues,
rightAmplitude: rightValues.amplitude, highBandRatio: authoredHighBandRatio(for: left)
rightSharpness: rightValues.sharpness, )
rightTransient: rightValues.transient, rightValues = compensatedDualSenseControllerValues(
delaySeconds: delay rightValues,
) highBandRatio: authoredHighBandRatio(for: right)
)
let mixedValues = mixedDualSenseControllerHandleValues(left: leftValues, right: rightValues)
leftValues = mixedValues.left
rightValues = mixedValues.right
}
controllerSupport.renderDualSenseHaptics(
controllerNumber,
leftAmplitude: leftValues.amplitude,
leftSharpness: leftValues.sharpness,
leftTransient: leftValues.transient,
rightAmplitude: rightValues.amplitude,
rightSharpness: rightValues.sharpness,
rightTransient: rightValues.transient,
delaySeconds: delay
)
case .device:
controllerSupport.renderDeviceDualSenseHaptics(
controllerNumber,
leftAmplitude: leftValues.amplitude,
leftSharpness: leftValues.sharpness,
leftTransient: leftValues.transient,
rightAmplitude: rightValues.amplitude,
rightSharpness: rightValues.sharpness,
rightTransient: rightValues.transient,
delaySeconds: delay
)
case .off:
break
}
} }
#endif #endif
@@ -711,6 +1144,7 @@ import UIKit
let elementDict = tempMap as NSDictionary let elementDict = tempMap as NSDictionary
// 单一 gamepad.valueChangedHandler // 单一 gamepad.valueChangedHandler
gamepad.valueChangedHandler = { gamepad, element in gamepad.valueChangedHandler = { gamepad, element in
handler(elementDict, gamepad, element) handler(elementDict, gamepad, element)
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
+1
View File
@@ -65,6 +65,7 @@ typedef NS_ENUM(NSInteger, PencilAndHoverMode) {
@property (nonatomic, assign) CGFloat touchPointerVelocityFactor; @property (nonatomic, assign) CGFloat touchPointerVelocityFactor;
@property (nonatomic, assign) CGPoint normalizedStreamViewOffset; @property (nonatomic, assign) CGPoint normalizedStreamViewOffset;
@property (nonatomic, assign) CGFloat streamViewScale; @property (nonatomic, assign) CGFloat streamViewScale;
@property (nonatomic, assign) CGFloat dualSenseTransient;
@property (nonatomic, assign) bool gamepadOverlayEnabled; @property (nonatomic, assign) bool gamepadOverlayEnabled;
+3 -1
View File
@@ -58,6 +58,7 @@
[encoder encodeFloat:self.touchPointerVelocityFactor forKey:@"touchPointerVelocityFactor"]; [encoder encodeFloat:self.touchPointerVelocityFactor forKey:@"touchPointerVelocityFactor"];
[encoder encodeCGPoint:self.normalizedStreamViewOffset forKey:@"normalizedStreamViewOffset"]; [encoder encodeCGPoint:self.normalizedStreamViewOffset forKey:@"normalizedStreamViewOffset"];
[encoder encodeFloat:self.streamViewScale forKey:@"streamViewScale"]; [encoder encodeFloat:self.streamViewScale forKey:@"streamViewScale"];
[encoder encodeFloat:self.dualSenseTransient forKey:@"dualSenseTransient"];
[encoder encodeBool:self.gamepadOverlayEnabled forKey:@"gamepadOverlayEnabled"]; [encoder encodeBool:self.gamepadOverlayEnabled forKey:@"gamepadOverlayEnabled"];
[encoder encodeObject:self.pressureCurvePoints forKey:@"pressureCurvePoints"]; [encoder encodeObject:self.pressureCurvePoints forKey:@"pressureCurvePoints"];
@@ -132,7 +133,8 @@
self.normalizedStreamViewOffset = [decoder containsValueForKey:@"normalizedStreamViewOffset"] ? [decoder decodeCGPointForKey:@"normalizedStreamViewOffset"] : CGPointZero; self.normalizedStreamViewOffset = [decoder containsValueForKey:@"normalizedStreamViewOffset"] ? [decoder decodeCGPointForKey:@"normalizedStreamViewOffset"] : CGPointZero;
self.streamViewScale = [decoder containsValueForKey:@"streamViewScale"] ? [decoder decodeFloatForKey:@"streamViewScale"] : 1.0; self.streamViewScale = [decoder containsValueForKey:@"streamViewScale"] ? [decoder decodeFloatForKey:@"streamViewScale"] : 1.0;
self.dualSenseTransient = [decoder containsValueForKey:@"dualSenseTransient"] ? [decoder decodeFloatForKey:@"dualSenseTransient"] : 1.0;
self.gamepadOverlayEnabled = [decoder containsValueForKey:@"gamepadOverlayEnabled"] ? [decoder decodeBoolForKey:@"gamepadOverlayEnabled"] : false; self.gamepadOverlayEnabled = [decoder containsValueForKey:@"gamepadOverlayEnabled"] ? [decoder decodeBoolForKey:@"gamepadOverlayEnabled"] : false;
self.pressureCurvePoints = self.pressureCurvePoints =
+48 -34
View File
@@ -18,14 +18,20 @@
@implementation HapticContext { @implementation HapticContext {
GCControllerPlayerIndex _playerIndex; GCControllerPlayerIndex _playerIndex;
CHHapticEngine* _hapticEngine API_AVAILABLE(ios(13.0), tvos(14.0)); CHHapticEngine* _hapticEngine API_AVAILABLE(ios(13.0), tvos(14.0));
id<CHHapticPatternPlayer> _hapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0)); id<CHHapticPatternPlayer> _motorHapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
BOOL _playing; id<CHHapticPatternPlayer> _authoredHapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
BOOL _motorPlaying;
BOOL _authoredPlaying;
} }
-(void)cleanup API_AVAILABLE(ios(14.0), tvos(14.0)) { -(void)cleanup API_AVAILABLE(ios(14.0), tvos(14.0)) {
if (_hapticPlayer != nil) { if (_motorHapticPlayer != nil) {
[_hapticPlayer cancelAndReturnError:nil]; [_motorHapticPlayer cancelAndReturnError:nil];
_hapticPlayer = nil; _motorHapticPlayer = nil;
}
if (_authoredHapticPlayer != nil) {
[_authoredHapticPlayer cancelAndReturnError:nil];
_authoredHapticPlayer = nil;
} }
if (_hapticEngine != nil) { if (_hapticEngine != nil) {
[_hapticEngine stopWithCompletionHandler:nil]; [_hapticEngine stopWithCompletionHandler:nil];
@@ -43,15 +49,15 @@
// Stop the effect entirely if the amplitude is 0 // Stop the effect entirely if the amplitude is 0
if (amplitude == 0) { if (amplitude == 0) {
if (_playing) { if (_motorPlaying) {
[_hapticPlayer stopAtTime:0 error:&error]; [_motorHapticPlayer stopAtTime:0 error:&error];
_playing = NO; _motorPlaying = NO;
} }
return; return;
} }
if (_hapticPlayer == nil) { if (_motorHapticPlayer == nil) {
// We must initialize the intensity to 1.0f because the dynamic parameters are multiplied by this value before being applied // We must initialize the intensity to 1.0f because the dynamic parameters are multiplied by this value before being applied
CHHapticEventParameter* intensityParameter = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f]; CHHapticEventParameter* intensityParameter = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
CHHapticEvent* hapticEvent = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObject:intensityParameter] relativeTime:0 duration:GCHapticDurationInfinite]; CHHapticEvent* hapticEvent = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObject:intensityParameter] relativeTime:0 duration:GCHapticDurationInfinite];
@@ -61,7 +67,7 @@
return; return;
} }
_hapticPlayer = [_hapticEngine createPlayerWithPattern:hapticPattern error:&error]; _motorHapticPlayer = [_hapticEngine createPlayerWithPattern:hapticPattern error:&error];
if (error != nil) { if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player creation failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Haptic player creation failed: %@", _playerIndex, error);
return; return;
@@ -69,21 +75,21 @@
} }
CHHapticDynamicParameter* intensityParameter = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:amplitude / 65535.0f relativeTime:0]; CHHapticDynamicParameter* intensityParameter = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:amplitude / 65535.0f relativeTime:0];
[_hapticPlayer sendParameters:[NSArray arrayWithObject:intensityParameter] atTime:CHHapticTimeImmediate error:&error]; [_motorHapticPlayer sendParameters:[NSArray arrayWithObject:intensityParameter] atTime:CHHapticTimeImmediate error:&error];
if (error != nil) { if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player parameter update failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Haptic player parameter update failed: %@", _playerIndex, error);
return; return;
} }
if (!_playing) { if (!_motorPlaying) {
[_hapticPlayer startAtTime:0 error:&error]; [_motorHapticPlayer startAtTime:0 error:&error];
if (error != nil) { if (error != nil) {
_hapticPlayer = nil; _motorHapticPlayer = nil;
Log(LOG_W, @"Controller %d: Haptic playback start failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Haptic playback start failed: %@", _playerIndex, error);
return; return;
} }
_playing = YES; _motorPlaying = YES;
} }
} }
@@ -100,13 +106,13 @@
NSError* error = nil; NSError* error = nil;
if (amplitude == 0.0f) { if (amplitude == 0.0f) {
if (_playing) { if (_authoredPlaying) {
[_hapticPlayer stopAtTime:CHHapticTimeImmediate error:&error]; [_authoredHapticPlayer stopAtTime:CHHapticTimeImmediate error:&error];
_playing = NO; _authoredPlaying = NO;
} }
} }
else { else {
if (_hapticPlayer == nil) { if (_authoredHapticPlayer == nil) {
CHHapticEventParameter* intensity = [[CHHapticEventParameter alloc] CHHapticEventParameter* intensity = [[CHHapticEventParameter alloc]
initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f]; initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
CHHapticEventParameter* eventSharpness = [[CHHapticEventParameter alloc] CHHapticEventParameter* eventSharpness = [[CHHapticEventParameter alloc]
@@ -122,8 +128,8 @@
Log(LOG_W, @"Controller %d: Authored haptic pattern creation failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Authored haptic pattern creation failed: %@", _playerIndex, error);
return; return;
} }
_hapticPlayer = [_hapticEngine createPlayerWithPattern:pattern error:&error]; _authoredHapticPlayer = [_hapticEngine createPlayerWithPattern:pattern error:&error];
if (_hapticPlayer == nil || error != nil) { if (_authoredHapticPlayer == nil || error != nil) {
Log(LOG_W, @"Controller %d: Authored haptic player creation failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Authored haptic player creation failed: %@", _playerIndex, error);
return; return;
} }
@@ -137,19 +143,19 @@
initWithParameterID:CHHapticDynamicParameterIDHapticSharpnessControl initWithParameterID:CHHapticDynamicParameterIDHapticSharpnessControl
value:sharpness relativeTime:0] value:sharpness relativeTime:0]
]; ];
[_hapticPlayer sendParameters:parameters atTime:CHHapticTimeImmediate error:&error]; [_authoredHapticPlayer sendParameters:parameters atTime:CHHapticTimeImmediate error:&error];
if (error != nil) { if (error != nil) {
Log(LOG_W, @"Controller %d: Authored haptic parameter update failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Authored haptic parameter update failed: %@", _playerIndex, error);
return; return;
} }
if (!_playing) { if (!_authoredPlaying) {
[_hapticPlayer startAtTime:CHHapticTimeImmediate error:&error]; [_authoredHapticPlayer startAtTime:CHHapticTimeImmediate error:&error];
if (error != nil) { if (error != nil) {
_hapticPlayer = nil; _authoredHapticPlayer = nil;
Log(LOG_W, @"Controller %d: Authored haptic playback start failed: %@", _playerIndex, error); Log(LOG_W, @"Controller %d: Authored haptic playback start failed: %@", _playerIndex, error);
return; return;
} }
_playing = YES; _authoredPlaying = YES;
} }
} }
@@ -197,9 +203,11 @@
} }
Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason); Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason);
me->_hapticPlayer = nil; me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_hapticEngine = nil; me->_hapticEngine = nil;
me->_playing = NO; me->_motorPlaying = NO;
me->_authoredPlaying = NO;
}; };
_hapticEngine.resetHandler = ^{ _hapticEngine.resetHandler = ^{
HapticContext* me = weakSelf; HapticContext* me = weakSelf;
@@ -208,8 +216,10 @@
} }
Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex); Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex);
me->_hapticPlayer = nil; me->_motorHapticPlayer = nil;
me->_playing = NO; me->_authoredHapticPlayer = nil;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
[me->_hapticEngine startAndReturnError:nil]; [me->_hapticEngine startAndReturnError:nil];
}; };
@@ -258,9 +268,11 @@
} }
Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason); Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason);
me->_hapticPlayer = nil; me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_hapticEngine = nil; me->_hapticEngine = nil;
me->_playing = NO; me->_motorPlaying = NO;
me->_authoredPlaying = NO;
}; };
_hapticEngine.resetHandler = ^{ _hapticEngine.resetHandler = ^{
HapticContext* me = weakSelf; HapticContext* me = weakSelf;
@@ -269,8 +281,10 @@
} }
Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex); Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex);
me->_hapticPlayer = nil; me->_motorHapticPlayer = nil;
me->_playing = NO; me->_authoredHapticPlayer = nil;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
[me->_hapticEngine startAndReturnError:nil]; [me->_hapticEngine startAndReturnError:nil];
}; };
+18 -7
View File
@@ -179,6 +179,9 @@ import CoreMotion
if useBuiltinGyro { if useBuiltinGyro {
if motionManager.isGyroAvailable { if motionManager.isGyroAvailable {
if rollToLeftStick {
leftStickMotion = leftStickTouchInputX
}
motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motionData, _ in motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motionData, _ in
guard let self = self, let data = motionData else { return } guard let self = self, let data = motionData else { return }
self.handleMotionData(deviceMotion: data) self.handleMotionData(deviceMotion: data)
@@ -229,7 +232,7 @@ import CoreMotion
} }
/// 停止更新 /// 停止更新
private var interruptNoneGyroInput:Bool = false // private var interruptNoneGyroInput:Bool = false
@objc public func stopMotionUpdate(interruptNoneGyroInput:Bool=false) { @objc public func stopMotionUpdate(interruptNoneGyroInput:Bool=false) {
motionControlStarted = false motionControlStarted = false
@@ -362,7 +365,7 @@ import CoreMotion
} }
if !motionControlStarted { if !motionControlStarted {
self.clearGyroInput(interruptNonGyroInput: interruptNoneGyroInput) self.clearGyroInput(interruptNonGyroInput: false)
// print("Gyro: stopped") // print("Gyro: stopped")
return return
} }
@@ -394,12 +397,12 @@ import CoreMotion
if rollToLeftStick { if rollToLeftStick {
roll = gyroInputToStickInput(input:rollSource*sensitvityRoll*widgetRollFactor*0.2) roll = gyroInputToStickInput(input:rollSource*sensitvityRoll*widgetRollFactor*0.2)
leftStickMotion = useBuiltinGyro ? -stickMaxOffset*(gravityYOffset/Double.pi)*3*sensitvityRoll*widgetRollFactor : leftStickMotion + roll leftStickMotion = self.clampStickInput(input: useBuiltinGyro ? -stickMaxOffset*(gravityYOffset/Double.pi)*3*sensitvityRoll*widgetRollFactor : leftStickMotion + roll)
let mixedLeftStickOffsetX = self.clampStickInput(input: leftStickMotion+leftStickTouchInputX+leftStickPhysicalInputX) let mixedLeftStickOffsetX = self.clampStickInput(input: leftStickMotion+leftStickTouchInputX+leftStickPhysicalInputX)
let mixedLeftStickOffsetY = self.clampStickInput(input: leftStickTouchInputY+leftStickPhysicalInputY) let mixedLeftStickOffsetY = self.clampStickInput(input: leftStickTouchInputY+leftStickPhysicalInputY)
let offsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: mixedLeftStickOffsetX, dy: mixedLeftStickOffsetY), minOffset: gyroToStickMinOffset) let offsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: mixedLeftStickOffsetX, dy: mixedLeftStickOffsetY), minOffset: gyroToStickMinOffset, circulate: true)
onScreenControls?.sendLeftStickTouchPadEvent(offsetVector.dx, offsetVector.dy) onScreenControls?.sendLeftStickTouchPadEvent(offsetVector.dx, offsetVector.dy)
} }
@@ -410,11 +413,19 @@ import CoreMotion
guard ControllerUtil.gamepadArrivalReported else { return } guard ControllerUtil.gamepadArrivalReported else { return }
if yawPitchToRightStick{ if yawPitchToRightStick{
onScreenControls?.sendRightStickTouchPadEvent(rightStickPhysicalInputX+rightStickTouchInputX-yawBias, rightStickPhysicalInputY+rightStickTouchInputY-pitchBias) let clearX = rightStickPhysicalInputX+rightStickTouchInputX-yawBias
let clearY = rightStickPhysicalInputY+rightStickTouchInputY-pitchBias
let clearVector = ControllerUtil.circulated(offsetVector: CGVector(dx: clearX, dy: clearY))
onScreenControls?.sendRightStickTouchPadEvent(clearVector.dx, clearVector.dy)
} }
if rollToLeftStick{ if rollToLeftStick{
leftStickMotion = 0 leftStickMotion = leftStickTouchInputX
onScreenControls?.sendLeftStickTouchPadEvent(leftStickPhysicalInputX+leftStickTouchInputX-rollBias,leftStickPhysicalInputY+leftStickTouchInputY) let clearX = leftStickPhysicalInputX+leftStickTouchInputX-rollBias
let clearY = leftStickPhysicalInputY+leftStickTouchInputY
let clearVector = ControllerUtil.circulated(offsetVector: CGVector(dx: clearX, dy: clearY))
onScreenControls?.sendLeftStickTouchPadEvent(clearVector.dx,clearVector.dy)
} }
if(interruptNonGyroInput){ if(interruptNonGyroInput){
onScreenControls?.clearLeftStickTouchPadFlag() onScreenControls?.clearLeftStickTouchPadFlag()
+8 -6
View File
@@ -2000,12 +2000,13 @@ import ObjectiveC.runtime
let mixRightStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick let mixRightStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick
&& oscProfile.yawPitchToRightStick) && oscProfile.yawPitchToRightStick)
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
if !mixRightStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) { if !mixRightStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) {
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
self.onScreenControls?.sendRightStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy) self.onScreenControls?.sendRightStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy)
} }
self.motionHandler?.mixOnScreenRightStickAndGyroInput(x: targetX, y: targetY) self.motionHandler?.mixOnScreenRightStickAndGyroInput(x: stickOffsetVector.dx, y: stickOffsetVector.dy)
if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true} if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true}
} }
@@ -2015,12 +2016,13 @@ import ObjectiveC.runtime
let mixLeftStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick let mixLeftStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick
&& oscProfile.rollToLeftStick) && oscProfile.rollToLeftStick)
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
if !mixLeftStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) { if !mixLeftStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) {
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
self.onScreenControls?.sendLeftStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy) self.onScreenControls?.sendLeftStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy)
} }
self.motionHandler?.mixOnScreenLeftStickAndGyroInput(x: targetX, y: targetY) self.motionHandler?.mixOnScreenLeftStickAndGyroInput(x: stickOffsetVector.dx, y: stickOffsetVector.dy)
if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true} if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true}
} }
+1
View File
@@ -714,6 +714,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
OnScreenWidgetView.buttonVisualFeedbackEnabled = self->settings.buttonVisualFeedback; OnScreenWidgetView.buttonVisualFeedbackEnabled = self->settings.buttonVisualFeedback;
OnScreenWidgetView.gamepadOverlayFLag = profile.gamepadOverlayEnabled; OnScreenWidgetView.gamepadOverlayFLag = profile.gamepadOverlayEnabled;
ControllerUtil.dualSenseHapticTransient = profile.dualSenseTransient;
bool hasLegacyWidget = false; bool hasLegacyWidget = false;
if(reloadWidgets && !OnScreenWidgetView.editMode){ if(reloadWidgets && !OnScreenWidgetView.editMode){
+13 -12
View File
@@ -8212,25 +8212,25 @@
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Sunshine emulates game controller via VigemBus, which support DS4 & Xbox360. \n\n- Xbox360 does not support gyro.\n\n- When “both” is selected, the app will map built-in gyro as player0 & external game controller as player1" "value" : "Sunshine emulates game controller via VigemBus, which support DS4 & Xbox360. \n- Xbox360 does not support gyro.\n- When “both” is selected, the app will map built-in gyro as player0 & external game controller as player1\n\nWhen DualSense sidecar is running on Sunshine-Foundation, VigemBus will be taken over, left DualSense to be the emulated Controller on your host. In this case:\n- The option “PS (Enhanced Haptic)” provides better haptic feedback for a real DualSense controller.\n- The option “PS” provides DualSense haptic feedback translation to traditional rumbling for all controllers.\n- Enhanced haptics are also available with iPhone motor by setting “Haptic Engine” to “Device”.\n"
} }
}, },
"zh-Hans" : { "zh-Hans" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Sunshine 通过 VigemBus 模拟游戏手柄,支持 DS4 和 Xbox 360。\n\n- Xbox 360 不支持陀螺仪。\n\n- 当选择“同时映射”时,app会将内置陀螺仪映射为玩家0,外接手柄映射为玩家1。" "value" : "Sunshine 通过 VigemBus 模拟游戏手柄,支持 DS4 和 Xbox 360。\n- Xbox 360 不支持陀螺仪。\n- 当选择“同时映射”时,app会将内置陀螺仪映射为玩家0,外接手柄映射为玩家1。\n\n当 DualSense 组件在【Sunshine 基地版】上运行时,它会接管 ViGEmBus,并使 DualSense 成为主机端的模拟手柄。在这种情况下:\n- 选择“PS(触觉增强)”将为真实的DualSense 手柄提供更好的触觉反馈。\n- 选择“PS”:将 DualSense 触觉反馈转换为传统震动,适用于所有手柄。\n- 将 “振动引擎” 设置为 “内置”,也可以通过 iPhone 马达获得增强触觉反馈。\n\nDualSense的自适应板机、触觉反馈仅与Sunshine基地版配合使用时有效。"
} }
}, },
"zh-Hant" : { "zh-Hant" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n\n- Xbox 360 不支援陀螺儀。\n\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。" "value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n- Xbox 360 不支援陀螺儀。\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。\n\n當 DualSense 元件在 「Sunshine 基地版」上執行時,它會接管 ViGEmBus,並使 DualSense 成為主機端的模擬手把。在這種情況下:\n- 選擇「PS(觸覺增強)」將為真實的 DualSense 手把提供更好的觸覺回饋。\n- 選擇「PS」:將 DualSense 觸覺回饋轉換為傳統震動,適用於所有手把。\n- 將「振動引擎」設定為「內建」,也可以透過 iPhone 馬達獲得增強觸覺回饋。\n\nDualSense 的自適應扳機、觸覺回饋僅在與 Sunshine 基地版配合使用時有效。"
} }
}, },
"zh-HK" : { "zh-HK" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n\n- Xbox 360 不支援陀螺儀。\n\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。" "value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n- Xbox 360 不支援陀螺儀。\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。\n\n當 DualSense 元件在 「Sunshine 基地版」上執行時,它會接管 ViGEmBus,並使 DualSense 成為主機端的模擬手把。在這種情況下:\n- 選擇「PS(觸覺增強)」將為真實的 DualSense 手把提供更好的觸覺回饋。\n- 選擇「PS」:將 DualSense 觸覺回饋轉換為傳統震動,適用於所有手把。\n- 將「振動引擎」設定為「內建」,也可以透過 iPhone 馬達獲得增強觸覺回饋。\n\nDualSense 的自適應扳機、觸覺回饋僅在與 Sunshine 基地版配合使用時有效。"
} }
} }
} }
@@ -8256,7 +8256,8 @@
"value" : "啟用 Metal 渲染?" "value" : "啟用 Metal 渲染?"
} }
} }
} },
"shouldTranslate" : false
}, },
"enableOswForNativeTouchSwitchTip" : { "enableOswForNativeTouchSwitchTip" : {
"extractionState" : "manual", "extractionState" : "manual",
@@ -9890,25 +9891,25 @@
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "This setting is saved in [Game Profile]. To open game profile selector:\n- Tap the gamepad icon on navigation bar\n- While streaming, swipe inward from the %@ edge of the screen by %@ of screen width, open [Switch Game Profile] in the toolbox menu" "value" : "This setting is saved in [Game Profile]. To open game profile selector:\n- Tap the gamepad icon on navigation bar\n- While streaming, swipe inward from the %@ edge of the screen by %@ of screen width, open [Switch Game Profile] in the toolbox menu\n- Also available by controller radial menu -> [more options]"
} }
}, },
"zh-Hans" : { "zh-Hans" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "needs_review",
"value" : "该设置保存在 [游戏配置] 中. 游戏配置切换入口:\n- 点击导航栏的手柄图标\n- 串流时,由屏幕 %@ 边缘向内滑动 %@ 屏宽,在工具箱菜单中打开 [切换游戏配置]" "value" : "该设置保存在 [游戏配置] 中. 游戏配置切换入口:\n- 点击导航栏的手柄图标\n- 串流时,由屏幕 %@ 边缘向内滑动 %@ 屏宽,在工具箱菜单中打开 [切换游戏配置]\n- 手柄用户请在环形菜单 -> [更多] 中打开"
} }
}, },
"zh-Hant" : { "zh-Hant" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "needs_review",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]" "value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]\n- 手把使用者請在環形選單 -> [更多] 中開啟"
} }
}, },
"zh-HK" : { "zh-HK" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "needs_review",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]" "value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]\n- 手把使用者請在環形選單 -> [更多] 中開啟"
} }
} }
} }
+5
View File
@@ -21,6 +21,7 @@
@interface Connection : NSOperation <NSStreamDelegate> @interface Connection : NSOperation <NSStreamDelegate>
@property (class, nonatomic, assign) bool muteInBackground; @property (class, nonatomic, assign) bool muteInBackground;
@property (class, nonatomic, assign) bool useSystemAudioEngine; @property (class, nonatomic, assign) bool useSystemAudioEngine;
@property (class, nonatomic, assign) bool useDualSenseHapticsIrV2;
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks; -(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;
-(void) terminate; -(void) terminate;
@@ -32,4 +33,8 @@
+ (void)setVolume:(float)newVolume; + (void)setVolume:(float)newVolume;
+ (void)resetSysAudioPlayback; + (void)resetSysAudioPlayback;
+ (bool)useDualSenseAuthoredPCM;
+ (void)setuseDualSenseAuthoredPCM:(bool)use;
@end @end
+41 -2
View File
@@ -61,6 +61,7 @@ static AVAudioFormat *audioFormat;
static bool muteInBackground; static bool muteInBackground;
static bool fullColorRange; static bool fullColorRange;
static bool request10BitCodec; static bool request10BitCodec;
static bool useDualSenseAuthoredPCM = false;
static VideoDecoderRenderer* renderer; static VideoDecoderRenderer* renderer;
@@ -357,6 +358,14 @@ void ArCleanup(void)
useSystemAudioEngine = useSysAudioEngine; useSystemAudioEngine = useSysAudioEngine;
} }
+ (bool)useDualSenseAuthoredPCM {
return useDualSenseAuthoredPCM;
}
+ (void)setuseDualSenseAuthoredPCM:(bool)use {
useDualSenseAuthoredPCM = use;
}
void AudioEngineInit(int sampleRate, int channelCount) { void AudioEngineInit(int sampleRate, int channelCount) {
audioEngine = [[AVAudioEngine alloc] init]; audioEngine = [[AVAudioEngine alloc] init];
@@ -604,6 +613,30 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
pcmData:pcmData]; pcmData:pcmData];
} }
void ClDs5HapticsIrV2(const LI_DS5_HAPTICS_IR_FRAME_V2* frame)
{
if (frame == NULL) {
return;
}
[ControllerUtil enqueueDualSenseHapticsIRV2WithControllerNumber:frame->controllerNumber
flags:frame->flags
sourceSequenceNumber:frame->sourceSequenceNumber
timestampUs:frame->timestampUs
sourceFrameCount:frame->sourceFrameCount
leftRms:frame->lanes[0].rmsAmplitude
leftPeak:frame->lanes[0].peakAmplitude
leftTransient:frame->lanes[0].transientStrength
leftLowRatio:frame->lanes[0].lowBandRatio
leftZeroCrossHz:frame->lanes[0].zeroCrossingRateHz
rightRms:frame->lanes[1].rmsAmplitude
rightPeak:frame->lanes[1].peakAmplitude
rightTransient:frame->lanes[1].transientStrength
rightLowRatio:frame->lanes[1].lowBandRatio
rightZeroCrossHz:frame->lanes[1].zeroCrossingRateHz
laneCorrelation:frame->laneCorrelation];
}
-(void) terminate -(void) terminate
{ {
// Interrupt any action blocking LiStartConnection(). This is // Interrupt any action blocking LiStartConnection(). This is
@@ -706,6 +739,7 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
_streamConfig.audioConfiguration = config.audioConfiguration; _streamConfig.audioConfiguration = config.audioConfiguration;
_streamConfig.redirectMic = config.redirectMic && [MicHandler permissionGranted]; _streamConfig.redirectMic = config.redirectMic && [MicHandler permissionGranted];
[Connection setVolume:config.localVolume]; [Connection setVolume:config.localVolume];
// Since we require iOS 12 or above, we're guaranteed to be running // Since we require iOS 12 or above, we're guaranteed to be running
// on a 64-bit device with ARMv8 crypto instructions, so we don't // on a 64-bit device with ARMv8 crypto instructions, so we don't
// need to check for that here. // need to check for that here.
@@ -767,8 +801,13 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
_clCallbacks.setMotionEventState = ClSetMotionEventState; _clCallbacks.setMotionEventState = ClSetMotionEventState;
_clCallbacks.setControllerLED = ClSetControllerLED; _clCallbacks.setControllerLED = ClSetControllerLED;
_clCallbacks.setAdaptiveTriggers = ClSetAdaptiveTriggers; _clCallbacks.setAdaptiveTriggers = ClSetAdaptiveTriggers;
_clCallbacks.ds5HapticsPcm = ClDs5HapticsPcm; useDualSenseAuthoredPCM = (config.emulatedControllerType == ControllerEmulationPsEnhancedHaptic
&& ControllerUtil.hasDualSenseController) || config.hapticEngine == RumbleDevice;
if (useDualSenseAuthoredPCM) {
_clCallbacks.ds5HapticsPcm = ClDs5HapticsPcm;
// _clCallbacks.ds5HapticsIrV2 = ClDs5HapticsIrV2;
}
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAudioSessionInterruption:) selector:@selector(handleAudioSessionInterruption:)
name:AVAudioSessionInterruptionNotification name:AVAudioSessionInterruptionNotification
+1
View File
@@ -40,6 +40,7 @@
@property BOOL asyncNativeTouchPriority; @property BOOL asyncNativeTouchPriority;
@property int gyroMode; @property int gyroMode;
@property int emulatedControllerType; @property int emulatedControllerType;
@property int hapticEngine;
@property int audioConfiguration; @property int audioConfiguration;
@property int supportedVideoFormats; @property int supportedVideoFormats;
@property BOOL multiController; @property BOOL multiController;
+1 -1
View File
@@ -9,5 +9,5 @@
#import "StreamConfiguration.h" #import "StreamConfiguration.h"
@implementation StreamConfiguration @implementation StreamConfiguration
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, buttonVisualFeedback, gyroMode, emulatedControllerType, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444, enablePIP, fullColorRange, enableHdr, sdrPerformanceWorkaround, localVolume; @synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, buttonVisualFeedback, gyroMode, emulatedControllerType, hapticEngine, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444, enablePIP, fullColorRange, enableHdr, sdrPerformanceWorkaround, localVolume;
@end @end
+18
View File
@@ -117,6 +117,24 @@ import UIKit
return true return true
} }
@objc public static func handleControllerEmulationTip(in vc: UIViewController?) {
let key = "hasShownControllerEmulationTip2"
guard !UserDefaults.standard.bool(forKey: key) else {
return
}
UserDefaults.standard.set(true, forKey: key)
AlertControllerUtil.showAlert(
in: vc,
title: LocalizationHelper.localizedString(forKey: "Tips"),
message: LocalizationHelper.localizedString(forKey: "emulatedControllerTypeStackTip"),
withCancel: false,
buttonTitle: LocalizationHelper.localizedString(forKey: "Got it!"),
countdown: 7
)
}
@objc public static func needUpdateDefaultSettings() -> Bool { @objc public static func needUpdateDefaultSettings() -> Bool {
// let key = "needUpdateDefaultSettings20260226-1" // let key = "needUpdateDefaultSettings20260226-1"
@@ -882,6 +882,7 @@ static NSMutableSet* hostList;
_streamConfig.asyncNativeTouchPriority = streamSettings.asyncNativeTouchPriority; // new streamConfig segment _streamConfig.asyncNativeTouchPriority = streamSettings.asyncNativeTouchPriority; // new streamConfig segment
_streamConfig.gyroMode = [streamSettings.gyroMode intValue]; _streamConfig.gyroMode = [streamSettings.gyroMode intValue];
_streamConfig.emulatedControllerType = streamSettings.emulatedControllerType.intValue; _streamConfig.emulatedControllerType = streamSettings.emulatedControllerType.intValue;
_streamConfig.hapticEngine = streamSettings.hapticEngine.intValue;
//NSLog(@"gyroMode from settings: %ld", _streamConfig.gyroMode); //NSLog(@"gyroMode from settings: %ld", _streamConfig.gyroMode);
// multiController must be set before calling getConnectedGamepadMask // multiController must be set before calling getConnectedGamepadMask
@@ -268,9 +268,6 @@
@property (strong, nonatomic) IBOutlet UIStackView *pressureCurveStack; @property (strong, nonatomic) IBOutlet UIStackView *pressureCurveStack;
@property (strong, nonatomic) IBOutlet UISwitch *pressureCurveSwitch; @property (strong, nonatomic) IBOutlet UISwitch *pressureCurveSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *frameTimebaseStack;
@property (weak, nonatomic) IBOutlet UISwitch *frameTimebaseSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *asyncFrameDequeueStack; @property (weak, nonatomic) IBOutlet UIStackView *asyncFrameDequeueStack;
@property (weak, nonatomic) IBOutlet UISwitch *asyncFrameDequeueSwitch; @property (weak, nonatomic) IBOutlet UISwitch *asyncFrameDequeueSwitch;
@@ -283,6 +280,9 @@
@property (weak, nonatomic) IBOutlet UIStackView *globeAsEscapeStack; @property (weak, nonatomic) IBOutlet UIStackView *globeAsEscapeStack;
@property (weak, nonatomic) IBOutlet UISwitch *globeAsEscapeSwitch; @property (weak, nonatomic) IBOutlet UISwitch *globeAsEscapeSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *dualSenseTransientStack;
@property (weak, nonatomic) IBOutlet UISlider *dualSenseTransientSlider;
@property (strong, nonatomic) IBOutlet UIStackView *testStack; @property (strong, nonatomic) IBOutlet UIStackView *testStack;
@property (nonatomic, strong) MicHandler *micHandler; @property (nonatomic, strong) MicHandler *micHandler;
@@ -211,15 +211,21 @@ CMVideoDimensions resolutionTable[RESOLUTION_TABLE_SIZE];
-(uint16_t)controllerTypeToSegmentIndex:(uint16_t)type{ -(uint16_t)controllerTypeToSegmentIndex:(uint16_t)type{
uint16_t index; uint16_t index;
switch (type) { switch (type) {
case LI_CTYPE_XBOX: case ControllerEmulationXbox:
index = 0; index = 0;
break; break;
case LI_CTYPE_PS: case ControllerEmulationPs:
index = 1; index = 1;
break; break;
default: case ControllerEmulationPsEnhancedHaptic:
index = 2; index = 2;
break; break;
case ControllerEmulationXboxAndPs:
index = 3;
break;
default:
index = 3;
break;
} }
return index; return index;
} }
@@ -228,10 +234,16 @@ CMVideoDimensions resolutionTable[RESOLUTION_TABLE_SIZE];
uint16_t type; uint16_t type;
switch (index) { switch (index) {
case 0: case 0:
type = LI_CTYPE_XBOX; type = ControllerEmulationXbox;
break; break;
case 1: case 1:
type = LI_CTYPE_PS; type = ControllerEmulationPs;
break;
case 2:
type = ControllerEmulationPsEnhancedHaptic;
break;
case 3:
type = ControllerEmulationXboxAndPs;
break; break;
default: default:
type = LI_CTYPE_UNKNOWN; type = LI_CTYPE_UNKNOWN;
@@ -428,6 +440,9 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.synthPhysicalInputSwitch setOn:oscProfile.synthesizePhysicalStick]; [self.synthPhysicalInputSwitch setOn:oscProfile.synthesizePhysicalStick];
[self.dualSenseTransientSlider setValue:oscProfile.dualSenseTransient];
[self.dualSenseTransientSlider sendActionsForControlEvents:UIControlEventValueChanged];
[self.pressureCurveSwitch setOn:oscProfile.pressureCurveEnabled]; [self.pressureCurveSwitch setOn:oscProfile.pressureCurveEnabled];
[self.doubleTapShortcutSwitch setOn:oscProfile.doubleTapShorcutEnabled]; [self.doubleTapShortcutSwitch setOn:oscProfile.doubleTapShorcutEnabled];
[self.squeezeShortcutSwitch setOn:oscProfile.squeezeShorcutEnabled]; [self.squeezeShortcutSwitch setOn:oscProfile.squeezeShorcutEnabled];
@@ -461,6 +476,7 @@ BOOL isCustomResolution(int resolutionSelected) {
&& (int16_t)(oscProfile.gyroSensitivityPitch*100) == (int16_t)pitchSensitivityPercent && (int16_t)(oscProfile.gyroSensitivityPitch*100) == (int16_t)pitchSensitivityPercent
&& (int16_t)(oscProfile.gyroSensitivityRoll*100) == (int16_t)rollSensitivityPercent && (int16_t)(oscProfile.gyroSensitivityRoll*100) == (int16_t)rollSensitivityPercent
&& (int16_t)(oscProfile.gyroToStickMinOffset) == (int16_t)self.gyroToStickMinOffsetSlider.value && (int16_t)(oscProfile.gyroToStickMinOffset) == (int16_t)self.gyroToStickMinOffsetSlider.value
&& (int16_t)(oscProfile.dualSenseTransient*100) == (int16_t)(self.dualSenseTransientSlider.value*100)
&& oscProfile.synthesizePhysicalStick == self.synthPhysicalInputSwitch.isOn && oscProfile.synthesizePhysicalStick == self.synthPhysicalInputSwitch.isOn
&& oscProfile.controllerGyroSwitchMode == self.controllerGyroSwitchButtonSetter.selectedSegmentIndex && oscProfile.controllerGyroSwitchMode == self.controllerGyroSwitchButtonSetter.selectedSegmentIndex
&& oscProfile.reverseGyroHoldButton == self.reverseHoldButtonSwitch.isOn && oscProfile.reverseGyroHoldButton == self.reverseHoldButtonSwitch.isOn
@@ -488,6 +504,7 @@ BOOL isCustomResolution(int resolutionSelected) {
oscProfile.gyroSensitivityPitch = pitchSensitivityPercent/100; oscProfile.gyroSensitivityPitch = pitchSensitivityPercent/100;
oscProfile.gyroSensitivityRoll = rollSensitivityPercent/100; oscProfile.gyroSensitivityRoll = rollSensitivityPercent/100;
oscProfile.gyroToStickMinOffset = (int16_t)self.gyroToStickMinOffsetSlider.value; oscProfile.gyroToStickMinOffset = (int16_t)self.gyroToStickMinOffsetSlider.value;
oscProfile.dualSenseTransient = self.dualSenseTransientSlider.value;
oscProfile.synthesizePhysicalStick = self.synthPhysicalInputSwitch.isOn; oscProfile.synthesizePhysicalStick = self.synthPhysicalInputSwitch.isOn;
oscProfile.controllerGyroSwitchMode = (int)self.controllerGyroSwitchButtonSetter.selectedSegmentIndex; oscProfile.controllerGyroSwitchMode = (int)self.controllerGyroSwitchButtonSetter.selectedSegmentIndex;
oscProfile.reverseGyroHoldButton = self.reverseHoldButtonSwitch.isOn; oscProfile.reverseGyroHoldButton = self.reverseHoldButtonSwitch.isOn;
@@ -996,6 +1013,11 @@ BOOL isCustomResolution(int resolutionSelected) {
self.emulatedControllerTypeStack.hasInfoTag = YES; self.emulatedControllerTypeStack.hasInfoTag = YES;
// self.emulatedControllerTypeStack.isGameProfileSetting = YES; // self.emulatedControllerTypeStack.isGameProfileSetting = YES;
[self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" to:controllerSection]; [self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" to:controllerSection];
self.dualSenseTransientStack.hasInfoTag = YES;
self.dualSenseTransientStack.isGameProfileSetting = YES;
self.dualSenseTransientStack.hasDynamicLabel = YES;
[self addSetting:self.dualSenseTransientStack ofId:@"dualSenseTransientStack" to:controllerSection];
self.gyroModeStack.hasInfoTag = YES; self.gyroModeStack.hasInfoTag = YES;
// self.gyroModeStack.isGameProfileSetting = YES; // self.gyroModeStack.isGameProfileSetting = YES;
@@ -2321,6 +2343,8 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:LeftRightSwapped]; [self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:LeftRightSwapped];
[self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:HapticEngineAuto]; [self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:HapticEngineAuto];
[self.hapticEngineSelector setEnabled:[self isIPhone] forSegmentAtIndex:RumbleDevice]; [self.hapticEngineSelector setEnabled:[self isIPhone] forSegmentAtIndex:RumbleDevice];
[self.dualSenseTransientSlider addTarget:self action:@selector(dualSenseTransientSliderMoved:) forControlEvents:UIControlEventValueChanged];
self.gyroModeSelector.selectedSegmentIndex = self->tempSettings.gyroMode.intValue; self.gyroModeSelector.selectedSegmentIndex = self->tempSettings.gyroMode.intValue;
[self.gyroSensitivitySlider setValue: (uint16_t)(self->tempSettings.gyroSensitivity.floatValue * 100) animated:NO]; // Load old setting. [self.gyroSensitivitySlider setValue: (uint16_t)(self->tempSettings.gyroSensitivity.floatValue * 100) animated:NO]; // Load old setting.
@@ -3425,6 +3449,11 @@ BOOL isCustomResolution(int resolutionSelected) {
} }
} }
- (void)dualSenseTransientSliderMoved:(UISlider* )sender {
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.2f ", sender.value]; // Update label display
ControllerUtil.dualSenseHapticTransient = sender.value;
}
- (void) gyroSensitivitySliderMoved:(UISlider* )sender { - (void) gyroSensitivitySliderMoved:(UISlider* )sender {
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display [self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
} }
@@ -3619,6 +3648,12 @@ BOOL isCustomResolution(int resolutionSelected) {
- (void)emulatedControllerTypeChanged:(UISegmentedControl* )sender{ - (void)emulatedControllerTypeChanged:(UISegmentedControl* )sender{
[self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroModeStack]; [self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroModeStack];
[self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroSensitivityStack]; [self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroSensitivityStack];
[self setHidden:sender.selectedSegmentIndex != 2 forStack:_dualSenseTransientStack];
if(settingsViewJustExpanded) return;
dispatch_async(dispatch_get_main_queue(), ^{
[GenericUtils handleControllerEmulationTipIn:self];
});
} }
+49 -19
View File
@@ -2425,18 +2425,6 @@
} }
} }
}, },
"cpg-mq-kYj.text" : {
"comment" : "Class = \"UILabel\"; text = \"Enable Timebase\"; ObjectID = \"cpg-mq-kYj\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Enable Timebase"
}
}
}
},
"CpY-Sb-BDc.title" : { "CpY-Sb-BDc.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"CpY-Sb-BDc\";", "comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"CpY-Sb-BDc\";",
"extractionState" : "stale", "extractionState" : "stale",
@@ -3999,43 +3987,55 @@
} }
}, },
"JaY-XA-1V6.segmentTitles[1]" : { "JaY-XA-1V6.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[1] = \"DS4\"; ObjectID = \"JaY-XA-1V6\";", "comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[1] = \"PS\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "new",
"value" : "DS4" "value" : "PS"
} }
} }
} }
}, },
"JaY-XA-1V6.segmentTitles[2]" : { "JaY-XA-1V6.segmentTitles[2]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[2] = \"Both\"; ObjectID = \"JaY-XA-1V6\";", "comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[2] = \"PS (Enhanced Haptics)\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "new",
"value" : "Both" "value" : "PS (Enhanced Haptics)"
} }
}, },
"zh-Hans" : { "zh-Hans" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同时映射" "value" : "PS (触觉增强)"
} }
}, },
"zh-Hant" : { "zh-Hant" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同時映射" "value" : "PS(觸覺增強)"
} }
}, },
"zh-HK" : { "zh-HK" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同時映射" "value" : "PS(觸覺增強)"
}
}
}
},
"JaY-XA-1V6.segmentTitles[3]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[3] = \"Xbox + PS\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Xbox + PS"
} }
} }
} }
@@ -6598,6 +6598,36 @@
} }
} }
}, },
"UpM-yi-yc9.text" : {
"comment" : "Class = \"UILabel\"; text = \"DualSense Haptic Intensity\"; ObjectID = \"UpM-yi-yc9\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DualSense Haptic Intensity"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振动强度"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
}
}
},
"V1y-Wk-uHM.text" : { "V1y-Wk-uHM.text" : {
"comment" : "Class = \"UILabel\"; text = \"Height\"; ObjectID = \"V1y-Wk-uHM\";", "comment" : "Class = \"UILabel\"; text = \"Height\"; ObjectID = \"V1y-Wk-uHM\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
+49 -19
View File
@@ -4789,18 +4789,6 @@
} }
} }
}, },
"P4N-i6-1zD.text" : {
"comment" : "Class = \"UILabel\"; text = \"Enable Timebase\"; ObjectID = \"P4N-i6-1zD\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Enable Timebase"
}
}
}
},
"P9b-Th-Ybc.text" : { "P9b-Th-Ybc.text" : {
"comment" : "Class = \"UILabel\"; text = \" Batch move \"; ObjectID = \"P9b-Th-Ybc\";", "comment" : "Class = \"UILabel\"; text = \" Batch move \"; ObjectID = \"P9b-Th-Ybc\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
@@ -5216,43 +5204,55 @@
} }
}, },
"q6v-Jb-ysy.segmentTitles[1]" : { "q6v-Jb-ysy.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[1] = \"DS4\"; ObjectID = \"q6v-Jb-ysy\";", "comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[1] = \"PS\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "new",
"value" : "DS4" "value" : "PS"
} }
} }
} }
}, },
"q6v-Jb-ysy.segmentTitles[2]" : { "q6v-Jb-ysy.segmentTitles[2]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[2] = \"Both\"; ObjectID = \"q6v-Jb-ysy\";", "comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[2] = \"PS (Enhanced Haptics)\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "new",
"value" : "Both" "value" : "PS (Enhanced Haptics)"
} }
}, },
"zh-Hans" : { "zh-Hans" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同时映射" "value" : "PS (触觉增强)"
} }
}, },
"zh-Hant" : { "zh-Hant" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同時映射" "value" : "PS(觸覺增強)"
} }
}, },
"zh-HK" : { "zh-HK" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "同時映射" "value" : "PS(觸覺增強)"
}
}
}
},
"q6v-Jb-ysy.segmentTitles[3]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[3] = \"Xbox + PS\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Xbox + PS"
} }
} }
} }
@@ -6325,6 +6325,36 @@
} }
} }
}, },
"vak-fg-iAQ.text" : {
"comment" : "Class = \"UILabel\"; text = \"DualSense Haptic Intensity\"; ObjectID = \"vak-fg-iAQ\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DualSense Haptic Intensity"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振动强度"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
}
}
},
"VEc-6n-QcS.text" : { "VEc-6n-QcS.text" : {
"comment" : "Class = \"UILabel\"; text = \"Lift Stream View for Local Keyboard \"; ObjectID = \"VEc-6n-QcS\";", "comment" : "Class = \"UILabel\"; text = \"Lift Stream View for Local Keyboard \"; ObjectID = \"VEc-6n-QcS\";",
"extractionState" : "extracted_with_value", "extractionState" : "extracted_with_value",