Skip to content

Commit 6c5ba65

Browse files
committed
start work on haptics v2
1 parent aa4180b commit 6c5ba65

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1073
-478
lines changed

src/main/java/dev/isxander/controlify/Controlify.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import dev.isxander.controlify.platform.client.PlatformClientUtil;
3636
import dev.isxander.controlify.platform.main.PlatformMainUtil;
3737
import dev.isxander.controlify.platform.network.SidedNetworkApi;
38-
import dev.isxander.controlify.rumble.RumbleManager;
38+
import dev.isxander.controlify.haptics.rumble.RumbleManager;
3939
import dev.isxander.controlify.screenop.keyboard.KeyboardLayoutManager;
4040
import dev.isxander.controlify.server.*;
4141
import dev.isxander.controlify.screenop.ScreenProcessorProvider;

src/main/java/dev/isxander/controlify/api/ControlifyApi.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import dev.isxander.controlify.Controlify;
44
import dev.isxander.controlify.InputMode;
55
import dev.isxander.controlify.controller.ControllerEntity;
6-
import dev.isxander.controlify.rumble.RumbleEffect;
7-
import dev.isxander.controlify.rumble.RumbleSource;
6+
import dev.isxander.controlify.haptics.rumble.RumbleEffect;
7+
import dev.isxander.controlify.haptics.HapticSource;
88
import org.jetbrains.annotations.NotNull;
99

1010
import java.util.Optional;
@@ -32,10 +32,10 @@ public interface ControlifyApi {
3232
@NotNull InputMode currentInputMode();
3333
boolean setInputMode(@NotNull InputMode mode);
3434

35-
default void playRumbleEffect(@NotNull RumbleSource rumbleSource, @NotNull RumbleEffect rumbleEffect) {
35+
default void playRumbleEffect(@NotNull HapticSource hapticSource, @NotNull RumbleEffect rumbleEffect) {
3636
getCurrentController()
3737
.flatMap(ControllerEntity::rumble)
38-
.ifPresent(r -> r.rumbleManager().play(rumbleSource, rumbleEffect));
38+
.ifPresent(r -> r.rumbleManager().play(hapticSource, rumbleEffect));
3939
}
4040

4141
static ControlifyApi get() {

src/main/java/dev/isxander/controlify/compatibility/simplevoicechat/SimpleVoiceChatCompat.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import dev.isxander.controlify.api.bind.InputBindingSupplier;
99
import dev.isxander.controlify.compatibility.simplevoicechat.mixins.KeyEventsAccessor;
1010
import dev.isxander.controlify.controller.ControllerEntity;
11+
import dev.isxander.controlify.controller.dualsense.DS5Effect;
1112
import dev.isxander.controlify.utils.render.Blit;
1213
import net.minecraft.client.Minecraft;
1314
import net.minecraft.network.chat.CommonComponents;
@@ -84,7 +85,7 @@ public static void init() {
8485
}
8586

8687
controller.dualSense().ifPresent(ds -> {
87-
ds.setMuteLight(ClientManager.getPlayerStateManager().isMuted());
88+
ds.submitEffect(new DS5Effect.MuteLight(ClientManager.getPlayerStateManager().isMuted()));
8889
});
8990
});
9091
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package dev.isxander.controlify.controller.dualsense;
2+
3+
import net.minecraft.util.Mth;
4+
import org.apache.commons.lang3.Validate;
5+
6+
public interface DS5Effect {
7+
void apply(DS5EffectsState state);
8+
9+
record RightTriggerEffect(DS5TriggerEffect effect) implements DS5Effect {
10+
@Override
11+
public void apply(DS5EffectsState state) {
12+
state.ucEnableBits1 |= DS5EffectsState.EnableBitFlags1.ALLOW_RIGHT_TRIGGER_FFB;
13+
state.rgucRightTriggerEffect = effect().createState();
14+
}
15+
}
16+
record LeftTriggerEffect(DS5TriggerEffect effect) implements DS5Effect {
17+
@Override
18+
public void apply(DS5EffectsState state) {
19+
state.ucEnableBits1 |= DS5EffectsState.EnableBitFlags1.ALLOW_LEFT_TRIGGER_FFB;
20+
state.rgucLeftTriggerEffect = effect().createState();
21+
}
22+
}
23+
24+
record HeadphoneVolume(byte volume) implements DS5Effect {
25+
public HeadphoneVolume {
26+
Validate.inclusiveBetween(0, 0x7f, volume);
27+
}
28+
29+
@Override
30+
public void apply(DS5EffectsState state) {
31+
state.ucEnableBits1 |= DS5EffectsState.EnableBitFlags1.ALLOW_HEADPHONE_VOLUME;
32+
state.ucHeadphoneVolume = volume();
33+
}
34+
35+
public static HeadphoneVolume fromPercent(float percent) {
36+
return new HeadphoneVolume((byte) Mth.lerpDiscrete(percent, 0, 0x7f));
37+
}
38+
}
39+
40+
record SpeakerVolume(byte volume) implements DS5Effect {
41+
public SpeakerVolume {
42+
Validate.inclusiveBetween(0x3d, 0x64, volume);
43+
}
44+
45+
@Override
46+
public void apply(DS5EffectsState state) {
47+
state.ucEnableBits1 |= DS5EffectsState.EnableBitFlags1.ALLOW_SPEAKER_VOLUME;
48+
state.ucSpeakerVolume = volume();
49+
}
50+
51+
public static SpeakerVolume fromPercent(float percent) {
52+
return new SpeakerVolume((byte) Mth.lerpDiscrete(percent, 0x3d, 0x64));
53+
}
54+
}
55+
56+
/**
57+
* Adjusts the volume of the built-in microphone
58+
* @param volume not linear, max 64, 0 not fully muted
59+
*/
60+
record MicrophoneVolume(byte volume) implements DS5Effect {
61+
public MicrophoneVolume {
62+
Validate.inclusiveBetween(0, 64, volume);
63+
}
64+
65+
@Override
66+
public void apply(DS5EffectsState state) {
67+
state.ucEnableBits1 |= DS5EffectsState.EnableBitFlags1.ALLOW_MIC_VOLUME;
68+
state.ucMicrophoneVolume = volume;
69+
}
70+
71+
public static MicrophoneVolume fromPercent(float percent) {
72+
return new MicrophoneVolume((byte) Mth.lerpDiscrete(percent, 0, 64));
73+
}
74+
}
75+
76+
record MuteLight(LightState state) implements DS5Effect {
77+
public MuteLight(boolean state) {
78+
this(state ? LightState.ON : LightState.OFF);
79+
}
80+
81+
@Override
82+
public void apply(DS5EffectsState state) {
83+
state.ucEnableBits2 |= DS5EffectsState.EnableBitFlags2.ALLOW_MUTE_LIGHT;
84+
state.ucMicLightMode = switch (state()) {
85+
case OFF -> DS5EffectsState.MuteLightState.OFF;
86+
case ON -> DS5EffectsState.MuteLightState.ON;
87+
case BREATHING -> DS5EffectsState.MuteLightState.BREATHING;
88+
};
89+
}
90+
91+
public enum LightState {
92+
OFF, ON, BREATHING
93+
}
94+
}
95+
96+
record ResetLights() implements DS5Effect {
97+
@Override
98+
public void apply(DS5EffectsState state) {
99+
state.ucEnableBits2 |= DS5EffectsState.EnableBitFlags2.RESET_LIGHTS;
100+
}
101+
}
102+
}

src/main/java/dev/isxander/controlify/driver/sdl/dualsense/DS5EffectsState.java renamed to src/main/java/dev/isxander/controlify/controller/dualsense/DS5EffectsState.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.isxander.controlify.driver.sdl.dualsense;
1+
package dev.isxander.controlify.controller.dualsense;
22

33
import com.sun.jna.Pointer;
44
import com.sun.jna.Structure;
@@ -64,7 +64,7 @@ public static class ByValue extends DS5EffectsState implements Structure.ByValue
6464

6565
@FieldOrder({"effectType", "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"})
6666
public static class TriggerEffect extends Structure {
67-
public static final TriggerEffect OFF = new TriggerEffect(DualsenseTriggerEffectTypes.OFF, new byte[0]);
67+
public static final TriggerEffect OFF = new TriggerEffect(DS5TriggerEffectTypes.OFF, new byte[0]);
6868

6969
public byte effectType;
7070
public byte p0;

src/main/java/dev/isxander/controlify/driver/sdl/dualsense/DualsenseTriggerEffect.java renamed to src/main/java/dev/isxander/controlify/controller/dualsense/DS5TriggerEffect.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
package dev.isxander.controlify.driver.sdl.dualsense;
1+
package dev.isxander.controlify.controller.dualsense;
22

33
import org.apache.commons.lang3.Validate;
44
import org.jetbrains.annotations.NotNull;
55
import org.jetbrains.annotations.Range;
66

7-
public interface DualsenseTriggerEffect {
7+
public interface DS5TriggerEffect {
88
DS5EffectsState.TriggerEffect createState();
99

1010
/**
1111
* Turn the trigger effect off and return the trigger stop to the neutral position.
12+
* <p>
1213
* This is an official effect and is expected to be present in future DualSense firmware versions.
1314
*/
14-
record Off() implements DualsenseTriggerEffect {
15+
record Off() implements DS5TriggerEffect {
1516
@Override
1617
public DS5EffectsState.TriggerEffect createState() {
1718
return DS5EffectsState.TriggerEffect.OFF;
@@ -21,6 +22,7 @@ public DS5EffectsState.TriggerEffect createState() {
2122
/**
2223
* Trigger will resist movement beyond the start position.
2324
* The trigger status nybble will report 0 before the effect and 1 when in the effect.
25+
* <p>
2426
* This is an official effect and is expected to be present in future DualSense firmware versions.
2527
*
2628
* @param position The starting zone of the trigger effect. Must be between 0 and 9 inclusive.
@@ -29,7 +31,7 @@ public DS5EffectsState.TriggerEffect createState() {
2931
record Feedback(
3032
@Range(from = 0, to = 9) byte position,
3133
@Range(from = 0, to = 8) byte strength
32-
) implements DualsenseTriggerEffect {
34+
) implements DS5TriggerEffect {
3335
public Feedback {
3436
Validate.inclusiveBetween(0, 9, position, "Position must be between 0 and 9 inclusive");
3537
Validate.inclusiveBetween(0, 8, strength, "Strength must be between 0 and 8 inclusive");
@@ -46,7 +48,7 @@ public DS5EffectsState.TriggerEffect createState() {
4648
activeZones |= (char) (1 << i);
4749
}
4850

49-
return new DS5EffectsState.TriggerEffect(DualsenseTriggerEffectTypes.FEEDBACK, new byte[]{
51+
return new DS5EffectsState.TriggerEffect(DS5TriggerEffectTypes.FEEDBACK, new byte[]{
5052
(byte) (activeZones & 0xff),
5153
(byte) ((activeZones >> 8) & 0xff),
5254
(byte) (forceZones & 0xff),
@@ -64,6 +66,7 @@ public DS5EffectsState.TriggerEffect createState() {
6466
* Trigger will resist movement beyond the start position until the end position.
6567
* The trigger status nybble will report 0 before the effect and 1 when in the effect,
6668
* and 2 after until again before the start position.
69+
* <p>
6770
* This is an official effect and is expected to be present in future DualSense firmware versions.
6871
*
6972
* @param startPosition The starting zone of the trigger effect. Must be between 2 and 7 inclusive.
@@ -74,7 +77,7 @@ record Weapon(
7477
@Range(from = 2, to = 7) byte startPosition,
7578
@Range(from = 2+1, to = 8) byte endPosition,
7679
@Range(from = 0, to = 8) byte strength
77-
) implements DualsenseTriggerEffect {
80+
) implements DS5TriggerEffect {
7881
public Weapon {
7982
Validate.inclusiveBetween(2, 7, startPosition, "Start position must be between 2 and 7 inclusive");
8083
Validate.inclusiveBetween(startPosition+1, 8, endPosition, "End position must be between start+1 and 8 inclusive");
@@ -87,7 +90,7 @@ public DS5EffectsState.TriggerEffect createState() {
8790
if (strength > 0) {
8891
char startAndStopZones = (char) ((1 << startPosition) | (1 << endPosition));
8992

90-
return new DS5EffectsState.TriggerEffect(DualsenseTriggerEffectTypes.WEAPON, new byte[]{
93+
return new DS5EffectsState.TriggerEffect(DS5TriggerEffectTypes.WEAPON, new byte[]{
9194
(byte) (startAndStopZones & 0xff),
9295
(byte) ((startAndStopZones >> 8) & 0xff),
9396
(byte) (strength - 1), // this is actually packed into 3 bits, but since it's only one why bother with the fancy code?
@@ -101,6 +104,7 @@ public DS5EffectsState.TriggerEffect createState() {
101104
/**
102105
* Trigger will vibrate with the input amplitude and frequency beyond the start position.
103106
* The trigger status nybble will report 0 before the effect and 1 when in the effect.
107+
* <p>
104108
* This is an official effect and is expected to be present in future DualSense firmware versions.
105109
*
106110
* @see VibrationMultiplePosition
@@ -113,7 +117,7 @@ record Vibration(
113117
@Range(from = 0, to = 9) byte position,
114118
@Range(from = 0, to = 8) byte amplitude,
115119
byte frequency
116-
) implements DualsenseTriggerEffect {
120+
) implements DS5TriggerEffect {
117121
public Vibration {
118122
Validate.inclusiveBetween(0, 9, position, "Position must be between 0 and 9 inclusive");
119123
Validate.inclusiveBetween(0, 8, amplitude, "Amplitude must be between 0 and 8 inclusive");
@@ -131,7 +135,7 @@ public DS5EffectsState.TriggerEffect createState() {
131135
activeZones |= (char) (1 << i);
132136
}
133137

134-
return new DS5EffectsState.TriggerEffect(DualsenseTriggerEffectTypes.VIBRATION, new byte[]{
138+
return new DS5EffectsState.TriggerEffect(DS5TriggerEffectTypes.VIBRATION, new byte[]{
135139
(byte) (activeZones & 0xff),
136140
(byte) ((activeZones >> 8) & 0xff),
137141
(byte) (amplitudeZones & 0xff),
@@ -149,6 +153,7 @@ public DS5EffectsState.TriggerEffect createState() {
149153

150154
/**
151155
* Trigger will resist movement at varying strengths in 10 regions.
156+
* <p>
152157
* This is an official effect and is expected to be present in future DualSense firmware versions.
153158
*
154159
* @see Feedback
@@ -158,7 +163,7 @@ public DS5EffectsState.TriggerEffect createState() {
158163
*/
159164
record FeedbackMultiplePosition(
160165
@Range(from = 0, to = 9) byte @NotNull [] strength
161-
) implements DualsenseTriggerEffect {
166+
) implements DS5TriggerEffect {
162167
public FeedbackMultiplePosition {
163168
Validate.notNull(strength, "Strength array must not be null");
164169
Validate.isTrue(strength.length == 10, "Strength array must have 10 elements");
@@ -185,7 +190,7 @@ public DS5EffectsState.TriggerEffect createState() {
185190
}
186191
}
187192

188-
return new DS5EffectsState.TriggerEffect(DualsenseTriggerEffectTypes.FEEDBACK, new byte[]{
193+
return new DS5EffectsState.TriggerEffect(DS5TriggerEffectTypes.FEEDBACK, new byte[]{
189194
(byte) (activeZones & 0xff),
190195
(byte) ((activeZones >> 8) & 0xff),
191196
(byte) (forceZones & 0xff),
@@ -201,6 +206,7 @@ public DS5EffectsState.TriggerEffect createState() {
201206

202207
/**
203208
* Trigger will resist movement at a linear range of strengths.
209+
* <p>
204210
* This is an official effect and is expected to be present in future DualSense firmware versions.
205211
*
206212
* @see Feedback
@@ -216,7 +222,7 @@ record FeedbackSlope(
216222
@Range(from = 1, to = 9) byte endPosition,
217223
@Range(from = 1, to = 8) byte startStrength,
218224
@Range(from = 1, to = 8) byte endStrength
219-
) implements DualsenseTriggerEffect {
225+
) implements DS5TriggerEffect {
220226
public FeedbackSlope {
221227
Validate.inclusiveBetween(0, 8, startPosition, "Start position must be between 0 and 8 inclusive");
222228
Validate.inclusiveBetween(startPosition+1, 9, endPosition, "End position must be between start+1 and 9 inclusive");
@@ -241,6 +247,7 @@ public DS5EffectsState.TriggerEffect createState() {
241247

242248
/**
243249
* Trigger will vibrate movement at varying amplitudes and one frequency in 10 regions.
250+
* <p>
244251
* This is an official effect and is expected to be present in future DualSense firmware versions.
245252
*
246253
* @see Vibration
@@ -251,7 +258,7 @@ public DS5EffectsState.TriggerEffect createState() {
251258
record VibrationMultiplePosition(
252259
byte frequency,
253260
@Range(from = 0, to = 8) byte @NotNull [] amplitude
254-
) implements DualsenseTriggerEffect {
261+
) implements DS5TriggerEffect {
255262
public VibrationMultiplePosition {
256263
Validate.notNull(amplitude, "Amplitude array must not be null");
257264
Validate.isTrue(amplitude.length == 10, "Amplitude array must have 10 elements");
@@ -280,7 +287,7 @@ public DS5EffectsState.TriggerEffect createState() {
280287
}
281288
}
282289

283-
return new DS5EffectsState.TriggerEffect(DualsenseTriggerEffectTypes.VIBRATION, new byte[]{
290+
return new DS5EffectsState.TriggerEffect(DS5TriggerEffectTypes.VIBRATION, new byte[]{
284291
(byte) (activeZones & 0xff),
285292
(byte) ((activeZones >> 8) & 0xff),
286293
(byte) (strengthZones & 0xff),

src/main/java/dev/isxander/controlify/driver/sdl/dualsense/DualsenseTriggerEffectTypes.java renamed to src/main/java/dev/isxander/controlify/controller/dualsense/DS5TriggerEffectTypes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
package dev.isxander.controlify.driver.sdl.dualsense;
1+
package dev.isxander.controlify.controller.dualsense;
22

3-
public final class DualsenseTriggerEffectTypes {
3+
public final class DS5TriggerEffectTypes {
44

5-
private DualsenseTriggerEffectTypes() {
5+
private DS5TriggerEffectTypes() {
66
}
77

88
public static final byte

0 commit comments

Comments
 (0)