Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions DS4Windows/DS4Control/ControlServiceDeviceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,28 @@ public enum MuteLEDMode : ushort
Pulse
}

public enum HapticsMode : ushort
{
Off,
SystemAudio,
RumbleToHaptics,
Mix,
}

public enum AudioOutputRoute : ushort
{
Auto, // headphone jack when plugged in, speaker otherwise
Headphone,
Speaker,
}

public enum AudioLatencyMode : ushort
{
Smooth, // maximum buffering; survives congested links
Balanced,
LowLatency, // minimum buffering; needs a clean link
}

private LEDBarMode ledMode = LEDBarMode.MultipleControllers;
public LEDBarMode LedMode
{
Expand All @@ -255,6 +277,112 @@ public MuteLEDMode MuteLedMode
}
public event EventHandler MuteLedModeChanged;

// Bluetooth audio-haptics streaming settings. Fires a single combined
// change event; the device applies live-safe fields or restarts the
// capture pipeline as needed.
private HapticsMode btHapticsMode = HapticsMode.Off;
public HapticsMode BTHapticsMode
{
get => btHapticsMode;
set
{
if (btHapticsMode == value) return;
btHapticsMode = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private double btHapticsGain = 3.0;
public double BTHapticsGain
{
get => btHapticsGain;
set
{
value = Math.Clamp(value, 0.1, 10.0);
if (btHapticsGain == value) return;
btHapticsGain = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private int btHapticsLowPassHz = 350;
public int BTHapticsLowPassHz
{
get => btHapticsLowPassHz;
set
{
value = Math.Clamp(value, 40, 1000);
if (btHapticsLowPassHz == value) return;
btHapticsLowPassHz = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private string btHapticsAudioDeviceId = string.Empty;
public string BTHapticsAudioDeviceId
{
get => btHapticsAudioDeviceId;
set
{
value ??= string.Empty;
if (btHapticsAudioDeviceId == value) return;
btHapticsAudioDeviceId = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

// Bluetooth listening-audio (headphone jack / speaker) settings.
private bool btAudioEnabled = false;
public bool BTAudioEnabled
{
get => btAudioEnabled;
set
{
if (btAudioEnabled == value) return;
btAudioEnabled = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private AudioOutputRoute btAudioRoute = AudioOutputRoute.Auto;
public AudioOutputRoute BTAudioRoute
{
get => btAudioRoute;
set
{
if (btAudioRoute == value) return;
btAudioRoute = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private int btAudioVolume = 50;
public int BTAudioVolume
{
get => btAudioVolume;
set
{
value = Math.Clamp(value, 0, 100);
if (btAudioVolume == value) return;
btAudioVolume = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

private AudioLatencyMode btAudioLatency = AudioLatencyMode.Smooth;
public AudioLatencyMode BTAudioLatency
{
get => btAudioLatency;
set
{
if (btAudioLatency == value) return;
btAudioLatency = value;
BTHapticsOptionChanged?.Invoke(this, EventArgs.Empty);
}
}

public event EventHandler BTHapticsOptionChanged;

public DualSenseControllerOptions(InputDeviceType deviceType) :
base(deviceType)
{
Expand Down
64 changes: 64 additions & 0 deletions DS4Windows/DS4Control/DTOXml/DualSenseControllerOptsDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,80 @@ public MuteLEDMode MuteLedMode
get; set;
}

[XmlElement("BTHapticsMode")]
public HapticsMode BTHapticsMode
{
get; set;
} = HapticsMode.Off;

[XmlElement("BTHapticsGain")]
public double BTHapticsGain
{
get; set;
} = 3.0;

[XmlElement("BTHapticsLowPassHz")]
public int BTHapticsLowPassHz
{
get; set;
} = 350;

[XmlElement("BTHapticsAudioDeviceId")]
public string BTHapticsAudioDeviceId
{
get; set;
} = string.Empty;

[XmlElement("BTAudioEnabled")]
public bool BTAudioEnabled
{
get; set;
} = false;

[XmlElement("BTAudioRoute")]
public AudioOutputRoute BTAudioRoute
{
get; set;
} = AudioOutputRoute.Auto;

[XmlElement("BTAudioVolume")]
public int BTAudioVolume
{
get; set;
} = 50;

[XmlElement("BTAudioLatency")]
public AudioLatencyMode BTAudioLatency
{
get; set;
} = AudioLatencyMode.Smooth;

public void MapFrom(DualSenseControllerOptions source)
{
LEDMode = source.LedMode;
MuteLedMode = source.MuteLedMode;
BTHapticsMode = source.BTHapticsMode;
BTHapticsGain = source.BTHapticsGain;
BTHapticsLowPassHz = source.BTHapticsLowPassHz;
BTHapticsAudioDeviceId = source.BTHapticsAudioDeviceId;
BTAudioEnabled = source.BTAudioEnabled;
BTAudioRoute = source.BTAudioRoute;
BTAudioVolume = source.BTAudioVolume;
BTAudioLatency = source.BTAudioLatency;
}

public void MapTo(DualSenseControllerOptions destination)
{
destination.LedMode = LEDMode;
destination.MuteLedMode = MuteLedMode;
destination.BTHapticsMode = BTHapticsMode;
destination.BTHapticsGain = BTHapticsGain;
destination.BTHapticsLowPassHz = BTHapticsLowPassHz;
destination.BTHapticsAudioDeviceId = BTHapticsAudioDeviceId;
destination.BTAudioEnabled = BTAudioEnabled;
destination.BTAudioRoute = BTAudioRoute;
destination.BTAudioVolume = BTAudioVolume;
destination.BTAudioLatency = BTAudioLatency;
}
}
}
55 changes: 55 additions & 0 deletions DS4Windows/DS4Forms/ControllerRegisterOptionsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,61 @@
<Label Content="{lex:Loc ControllerRegOptWin.MuteLEDMode}" ContentStringFormat="{}{0}:" />
<ComboBox ItemsSource="{Binding Path=DsMuteLEDModes}" DisplayMemberPath="DisplayName" SelectedValuePath="ChoiceValue" SelectedValue="{Binding Options.MuteLedMode}" Margin="8,0,0,0" />
</StackPanel>

<Separator Margin="0,12,0,4" />
<TextBlock Text="{lex:Loc ControllerRegOptWin.HapticsHeading}" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock TextWrapping="Wrap" Foreground="Gray" Margin="0,2,0,0"
Text="{lex:Loc ControllerRegOptWin.HapticsDescription}" />

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.HapticsMode}" ContentStringFormat="{}{0}:" />
<ComboBox ItemsSource="{Binding Path=DsHapticsModes}" DisplayMemberPath="DisplayName" SelectedValuePath="ChoiceValue" SelectedValue="{Binding Options.BTHapticsMode}" Margin="8,0,0,0" MinWidth="180" />
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.AudioSource}" ContentStringFormat="{}{0}:" />
<ComboBox ItemsSource="{Binding Path=HapticsAudioDevices}" DisplayMemberPath="DisplayName" SelectedValuePath="Id" SelectedValue="{Binding Options.BTHapticsAudioDeviceId}" Margin="8,0,0,0" MinWidth="260" />
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.Intensity}" ContentStringFormat="{}{0}:" />
<Slider Value="{Binding Options.BTHapticsGain}" Minimum="0.5" Maximum="8" TickFrequency="0.5" IsSnapToTickEnabled="True" Width="180" Margin="8,4,0,0" VerticalAlignment="Center" />
<TextBlock Text="{Binding Options.BTHapticsGain}" Margin="8,4,0,0" VerticalAlignment="Center" MinWidth="32" />
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.LowPassFilter}" ContentStringFormat="{}{0}:"
ToolTip="{lex:Loc ControllerRegOptWin.LowPassFilterTip}" />
<Slider Value="{Binding Options.BTHapticsLowPassHz}" Minimum="80" Maximum="800" TickFrequency="10" IsSnapToTickEnabled="True" Width="180" Margin="8,4,0,0" VerticalAlignment="Center" />
<TextBlock Text="{Binding Options.BTHapticsLowPassHz}" Margin="8,4,0,0" VerticalAlignment="Center" MinWidth="32" />
</StackPanel>

<Separator Margin="0,12,0,4" />
<TextBlock Text="{lex:Loc ControllerRegOptWin.BTAudioHeading}" FontWeight="Bold" Margin="0,4,0,0" />
<TextBlock TextWrapping="Wrap" Foreground="Gray" Margin="0,2,0,0"
Text="{lex:Loc ControllerRegOptWin.BTAudioDescription}" />

<CheckBox Content="{lex:Loc ControllerRegOptWin.SendAudioToController}" IsChecked="{Binding Options.BTAudioEnabled}" Margin="0,8,0,0" />

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.AudioOutput}" ContentStringFormat="{}{0}:" />
<ComboBox ItemsSource="{Binding Path=DsAudioRoutes}" DisplayMemberPath="DisplayName" SelectedValuePath="ChoiceValue" SelectedValue="{Binding Options.BTAudioRoute}" Margin="8,0,0,0" MinWidth="220" />
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.Volume}" ContentStringFormat="{}{0}:" />
<Slider Value="{Binding Options.BTAudioVolume}" Minimum="0" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Width="180" Margin="8,4,0,0" VerticalAlignment="Center" />
<TextBlock Text="{Binding Options.BTAudioVolume}" Margin="8,4,0,0" VerticalAlignment="Center" MinWidth="32" />
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<Label Content="{lex:Loc ControllerRegOptWin.Buffering}" ContentStringFormat="{}{0}:"
ToolTip="{lex:Loc ControllerRegOptWin.BufferingTip}" />
<ComboBox ItemsSource="{Binding Path=DsAudioLatencies}" DisplayMemberPath="DisplayName" SelectedValuePath="ChoiceValue" SelectedValue="{Binding Options.BTAudioLatency}" Margin="8,0,0,0" MinWidth="220" />
</StackPanel>

<TextBlock TextWrapping="Wrap" Foreground="Gray" Margin="0,6,0,0"
Text="{lex:Loc ControllerRegOptWin.BTStreamingNote}" />
</StackPanel>
</TabItem>

Expand Down
94 changes: 94 additions & 0 deletions DS4Windows/DS4Forms/ViewModels/ControllerRegDeviceOptsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,106 @@ public class DualSenseControllerOptionsWrapper
};
public List<EnumChoiceSelection<MuteLEDMode>> DsMuteLEDModes { get => dsMuteLEDModes; }

private List<EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>> dsHapticsModes =
new List<EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>>()
{
new EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>(
Translations.Strings.ControllerRegOptWin_HapticsModeOff,
DualSenseControllerOptions.HapticsMode.Off),
new EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>(
Translations.Strings.ControllerRegOptWin_HapticsModeSystemAudio,
DualSenseControllerOptions.HapticsMode.SystemAudio),
new EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>(
Translations.Strings.ControllerRegOptWin_HapticsModeRumble,
DualSenseControllerOptions.HapticsMode.RumbleToHaptics),
new EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>(
Translations.Strings.ControllerRegOptWin_HapticsModeMix,
DualSenseControllerOptions.HapticsMode.Mix),
};
public List<EnumChoiceSelection<DualSenseControllerOptions.HapticsMode>> DsHapticsModes { get => dsHapticsModes; }

private List<HapticsAudioDeviceChoice> hapticsAudioDevices = new List<HapticsAudioDeviceChoice>();
public List<HapticsAudioDeviceChoice> HapticsAudioDevices { get => hapticsAudioDevices; }

private List<EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>> dsAudioRoutes =
new List<EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>>()
{
new EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>(
Translations.Strings.ControllerRegOptWin_AudioRouteAuto,
DualSenseControllerOptions.AudioOutputRoute.Auto),
new EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>(
Translations.Strings.ControllerRegOptWin_AudioRouteHeadphones,
DualSenseControllerOptions.AudioOutputRoute.Headphone),
new EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>(
Translations.Strings.ControllerRegOptWin_AudioRouteSpeaker,
DualSenseControllerOptions.AudioOutputRoute.Speaker),
};
public List<EnumChoiceSelection<DualSenseControllerOptions.AudioOutputRoute>> DsAudioRoutes { get => dsAudioRoutes; }

private List<EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>> dsAudioLatencies =
new List<EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>>()
{
new EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>(
Translations.Strings.ControllerRegOptWin_LatencySmooth,
DualSenseControllerOptions.AudioLatencyMode.Smooth),
new EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>(
Translations.Strings.ControllerRegOptWin_LatencyBalanced,
DualSenseControllerOptions.AudioLatencyMode.Balanced),
new EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>(
Translations.Strings.ControllerRegOptWin_LatencyLow,
DualSenseControllerOptions.AudioLatencyMode.LowLatency),
};
public List<EnumChoiceSelection<DualSenseControllerOptions.AudioLatencyMode>> DsAudioLatencies { get => dsAudioLatencies; }

public DualSenseControllerOptionsWrapper(DualSenseControllerOptions options,
DualSenseDeviceOptions parentOpts)
{
this.options = options;
this.parentOptions = parentOpts;
parentOptions.EnabledChanged += (sender, e) => { VisibleChanged?.Invoke(this, EventArgs.Empty); };

PopulateHapticsAudioDevices();
}

private void PopulateHapticsAudioDevices()
{
hapticsAudioDevices.Add(new HapticsAudioDeviceChoice(
Translations.Strings.ControllerRegOptWin_AudioDeviceDefault, string.Empty));
try
{
using NAudio.CoreAudioApi.MMDeviceEnumerator enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator();
foreach (NAudio.CoreAudioApi.MMDevice dev in enumerator.EnumerateAudioEndPoints(
NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active))
{
hapticsAudioDevices.Add(new HapticsAudioDeviceChoice(dev.FriendlyName, dev.ID));
dev.Dispose();
}
}
catch (Exception)
{
// Endpoint enumeration is best-effort; the default entry always works.
}

// Keep the ComboBox binding valid if the saved endpoint disappeared.
if (!string.IsNullOrEmpty(options.BTHapticsAudioDeviceId) &&
!hapticsAudioDevices.Exists(item => item.Id == options.BTHapticsAudioDeviceId))
{
hapticsAudioDevices.Add(new HapticsAudioDeviceChoice(
Translations.Strings.ControllerRegOptWin_AudioDeviceUnavailable,
options.BTHapticsAudioDeviceId));
}
}
}

public class HapticsAudioDeviceChoice
{
public string DisplayName { get; }
public string Id { get; }

public HapticsAudioDeviceChoice(string displayName, string id)
{
DisplayName = displayName;
Id = id;
}
}

Expand Down
Loading