Skip to content
This repository has been archived by the owner on Jan 7, 2024. It is now read-only.

Commit

Permalink
Added Volume Control
Browse files Browse the repository at this point in the history
  • Loading branch information
iXab3r committed Nov 8, 2022
1 parent a976467 commit 98c6201
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ namespace MicSwitch.MainWindow.ViewModels;

internal interface IOutputControllerViewModel : IMediaController
{
IHotkeyEditorViewModel HotkeyOutputMute { get; }
IHotkeyEditorViewModel HotkeyOutputVolumeUp { get; }
IHotkeyEditorViewModel HotkeyOutputVolumeDown { get; }
public IHotkeyEditorViewModel HotkeyToggleMute { get; }
public IHotkeyEditorViewModel HotkeyMute { get; }
public IHotkeyEditorViewModel HotkeyUnmute { get; }
public IHotkeyEditorViewModel HotkeyVolumeUp { get; }
public IHotkeyEditorViewModel HotkeyVolumeDown { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ static MicSwitchOverlayViewModel()
.Else(x => default)
.To(x => x.OutputVolume);
Binder.BindIf(x => x.OutputDeviceController != null && x.OutputDeviceController.Mute == true, x => PackIconKind.VolumeMute)
.ElseIf(x => x.OutputVolume == 0, x => PackIconKind.VolumeOff)
.ElseIf(x => x.OutputVolume > 0.25, x => PackIconKind.VolumeLow)
.ElseIf(x => x.OutputVolume > 0.5, x => PackIconKind.VolumeMedium)
.ElseIf(x => x.OutputVolume > 0.75, x => PackIconKind.VolumeHigh)
.ElseIf(x => x.OutputVolume <= 0, x => PackIconKind.VolumeOff)
.ElseIf(x => x.OutputVolume <= 0.33, x => PackIconKind.VolumeLow)
.ElseIf(x => x.OutputVolume <= 0.66, x => PackIconKind.VolumeMedium)
.ElseIf(x => x.OutputVolume > 0.66, x => PackIconKind.VolumeHigh)
.Else(x => PackIconKind.None)
.To(x => x.OutputVolumeKind);
}
Expand Down Expand Up @@ -132,6 +132,16 @@ public MicSwitchOverlayViewModel(
}, Log.HandleUiException)
.AddTo(Anchors);

var showOutputIconSource = this.WhenAnyValue(x => x.OutputDeviceController.VolumePercent, x => x.OutputDeviceController.Mute);

showOutputIconSource.Subscribe(_ => ShowOutputIcon = true).AddTo(Anchors);

Observable.Merge(
showOutputIconSource.Select(x => Observable.Timer(TimeSpan.FromSeconds(2))).Switch().ToUnit(),
this.WhenAnyValue(x => x.MicrophoneMute).ToUnit())
.Subscribe(_ => ShowOutputIcon = false)
.AddTo(Anchors);

Binder.Attach(this).AddTo(Anchors);
}

Expand All @@ -143,6 +153,8 @@ public bool IsEnabled
get => overlayWindowController.IsEnabled;
set => overlayWindowController.IsEnabled = value;
}

public bool ShowOutputIcon { get; private set; }

public bool MicrophoneMute { get; [UsedImplicitly] private set; }

Expand Down
112 changes: 98 additions & 14 deletions Sources/MicSwitch/MainWindow/ViewModels/OutputControllerViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,46 +1,130 @@
using log4net;
using MicSwitch.Modularity;
using MicSwitch.Services;
using PoeShared.Audio.Models;

namespace MicSwitch.MainWindow.ViewModels
{
internal sealed class OutputControllerViewModel : MediaControllerBase<MicSwitchHotkeyConfig>, IOutputControllerViewModel
internal sealed class OutputControllerViewModel : MediaControllerBase<MicSwitchVolumeControlConfig>, IOutputControllerViewModel
{
public OutputControllerViewModel(
IMMRenderDeviceProvider deviceProvider,
IFactory<IMMDeviceControllerEx, IMMDeviceProvider> deviceControllerFactory,
IFactory<IHotkeyTracker> hotkeyTrackerFactory,
IFactory<IHotkeyEditorViewModel> hotkeyEditorFactory,
IConfigProvider<MicSwitchHotkeyConfig> hotkeyConfigProvider,
IConfigProvider<MicSwitchVolumeControlConfig> hotkeyConfigProvider,
[Dependency(WellKnownSchedulers.UI)] IScheduler uiScheduler) : base(deviceProvider, deviceControllerFactory.Create(deviceProvider), hotkeyTrackerFactory, hotkeyEditorFactory, hotkeyConfigProvider, uiScheduler)
{
HotkeyOutputMute = PrepareHotkey("Mute/Un-mute", x => x.HotkeyForOutputMute, (config, hotkeyConfig) => config.HotkeyForOutputMute = hotkeyConfig);
HotkeyOutputVolumeDown = PrepareHotkey("Volume Down", x => x.HotkeyForOutputVolumeDown, (config, hotkeyConfig) => config.HotkeyForOutputVolumeDown = hotkeyConfig);
HotkeyOutputVolumeUp = PrepareHotkey("Volume Up", x => x.HotkeyForOutputVolumeUp, (config, hotkeyConfig) => config.HotkeyForOutputVolumeUp = hotkeyConfig);

hotkeyConfigProvider.ListenTo(x => x.EnableOutputVolumeControl)
HotkeyToggleMute = PrepareHotkey("Mute/Un-mute", x => x.HotkeyForToggle, (config, hotkeyConfig) => config.HotkeyForToggle = hotkeyConfig);
HotkeyMute = PrepareHotkey("Mute", x => x.HotkeyForMute, (config, hotkeyConfig) => config.HotkeyForMute = hotkeyConfig);
HotkeyUnmute = PrepareHotkey("Un-mute", x => x.HotkeyForUnmute, (config, hotkeyConfig) => config.HotkeyForUnmute = hotkeyConfig);
HotkeyVolumeDown = PrepareHotkey("Volume Down", x => x.HotkeyForVolumeDown, (config, hotkeyConfig) => config.HotkeyForVolumeDown = hotkeyConfig);
HotkeyVolumeUp = PrepareHotkey("Volume Up", x => x.HotkeyForVolumeUp, (config, hotkeyConfig) => config.HotkeyForVolumeUp = hotkeyConfig);

hotkeyConfigProvider.ListenTo(x => x.IsEnabled)
.ObserveOn(uiScheduler)
.Subscribe(x => IsEnabled = x)
.AddTo(Anchors);


PrepareTracker(HotkeyMode.Click, HotkeyToggleMute)
.ObservableForProperty(x => x.IsActive, skipInitial: true)
.SubscribeSafe(x =>
{
Log.Debug($"[{x.Sender}] Toggling state: {Controller}");
Controller.Mute = !Controller.Mute;
}, Log.HandleUiException)
.AddTo(Anchors);

PrepareTracker(HotkeyMode.Hold, HotkeyMute)
.ObservableForProperty(x => x.IsActive, skipInitial: true)
.Where(x => x.Value)
.SubscribeSafe(x =>
{
Log.Debug($"[{x.Sender}] Muting: {Controller}");
Controller.Mute = true;
}, Log.HandleUiException)
.AddTo(Anchors);

PrepareTracker(HotkeyMode.Hold, HotkeyUnmute)
.ObservableForProperty(x => x.IsActive, skipInitial: true)
.Where(x => x.Value)
.SubscribeSafe(x =>
{
Log.Debug($"[{x.Sender}] Un-muting: {Controller}");
Controller.Mute = false;
}, Log.HandleUiException)
.AddTo(Anchors);

PrepareTracker(HotkeyMode.Hold, HotkeyVolumeUp)
.ObservableForProperty(x => x.IsActive, skipInitial: true)
.SwitchIf(x => x.Value == true, x => Observable.Interval(TimeSpan.FromMilliseconds(10)), x => Observable.Empty<long>())
.SubscribeSafe(x =>
{
if (Controller.VolumePercent == null)
{
return;
}

Controller.VolumePercent = Math.Min(1, Controller.VolumePercent.Value + 0.01);
}, Log.HandleUiException)
.AddTo(Anchors);

PrepareTracker(HotkeyMode.Hold, HotkeyVolumeDown)
.ObservableForProperty(x => x.IsActive, skipInitial: true)
.SwitchIf(x => x.Value == true, x => Observable.Interval(TimeSpan.FromMilliseconds(10)), x => Observable.Empty<long>())
.SubscribeSafe(x =>
{
if (Controller.VolumePercent == null)
{
return;
}

Controller.VolumePercent = Math.Max(0, Controller.VolumePercent.Value - 0.01);
}, Log.HandleUiException)
.AddTo(Anchors);


Observable.Merge(
hotkeyConfigProvider.ListenTo(x => x.DeviceId).ToUnit(),
Devices.ToObservableChangeSet().ToUnit())
.Select(_ => hotkeyConfigProvider.ActualConfig.DeviceId)
.ObserveOn(uiScheduler)
.SubscribeSafe(configLineId =>
{
Log.Debug($"Device line configuration changed, lineId: {configLineId}, known lines: {Devices.Dump()}");

var line = Devices.FirstOrDefault(line => line.Equals(configLineId));
if (line.IsEmpty)
{
Log.Debug($"Selecting first one of available microphone lines, known lines: {Devices.Dump()}");
line = Devices.FirstOrDefault();
}

DeviceId = line;
MuteMicrophoneCommand.ResetError();
}, Log.HandleUiException)
.AddTo(Anchors);

Observable.Merge(
this.ObservableForProperty(x => x.DeviceId, skipInitial: true).ToUnit(),
this.ObservableForProperty(x => x.IsEnabled, skipInitial: true).ToUnit())
.Throttle(ConfigThrottlingTimeout)
.ObserveOn(uiScheduler)
.SubscribeSafe(() =>
{
var hotkeyConfig = hotkeyConfigProvider.ActualConfig.CloneJson();
hotkeyConfig.EnableOutputVolumeControl = IsEnabled;
hotkeyConfig.IsEnabled = IsEnabled;
hotkeyConfig.DeviceId = DeviceId;
hotkeyConfigProvider.Save(hotkeyConfig);
}, Log.HandleUiException)
.AddTo(Anchors);
}

public IHotkeyEditorViewModel HotkeyOutputMute { get; }

public IHotkeyEditorViewModel HotkeyOutputVolumeUp { get; }
public IHotkeyEditorViewModel HotkeyToggleMute { get; }
public IHotkeyEditorViewModel HotkeyMute { get; }
public IHotkeyEditorViewModel HotkeyUnmute { get; }

public IHotkeyEditorViewModel HotkeyVolumeUp { get; }

public IHotkeyEditorViewModel HotkeyOutputVolumeDown { get; }
public IHotkeyEditorViewModel HotkeyVolumeDown { get; }
}
}
89 changes: 47 additions & 42 deletions Sources/MicSwitch/MainWindow/Views/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -197,48 +197,7 @@
</st:StackPanel>

<Separator Grid.ColumnSpan="2" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Volume control:" />
<CheckBox
HorizontalAlignment="Left"
ToolTip="Enable output devices volume control"
IsChecked="{Binding OutputController.IsEnabled, Mode=TwoWay}" />

<st:AutoGrid RowHeightOverride="Auto"
Margin="0"
ColumnSpan="2"
Columns="120,*" ChildMargin="5" RowHeight="50"
Visibility="{Binding OutputController.IsEnabled, Converter={StaticResource TrueToVisibleFalseToCollapsedConverter}}">
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Output device:" />
<ComboBox st:StackPanel.Fill="Fill"
ItemsSource="{Binding OutputController.Devices}"
SelectedItem="{Binding OutputController.DeviceId}"
DisplayMemberPath="Name"
ToolTip="Output device"/>
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Output volume:" />
<st:StackPanel VerticalAlignment="Center">
<Slider Width="160"
Value="{Binding OutputController.Volume}"
Minimum="0" Maximum="1"
Margin="0,2,5,0"
TickFrequency="0.05"
ToolTip="Volume"/>
<TextBlock
MinWidth="45">
<TextBlock.Text>
<Binding Path="OutputController.Volume" StringFormat="{}{0:F1}%" Converter="{StaticResource DoubleToPercentConverter}" />
</TextBlock.Text>
</TextBlock>
</st:StackPanel>
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Mute:" />
<ContentControl Content="{Binding OutputController.HotkeyOutputMute}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Volume Up:" />
<ContentControl Content="{Binding OutputController.HotkeyOutputVolumeUp}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Volume Down:" />
<ContentControl Content="{Binding OutputController.HotkeyOutputVolumeDown}" />
</st:AutoGrid>

<Separator Grid.ColumnSpan="2" />


<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Show overlay:" />
<st:StackPanel>
<RadioButton
Expand Down Expand Up @@ -372,6 +331,52 @@
<ContentControl Content="{Binding MicrophoneController.HotkeyPushToMute}" />
</st:AutoGrid>

<Separator Grid.ColumnSpan="2" />

<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Volume control:" />
<CheckBox
HorizontalAlignment="Left"
ToolTip="Enable output devices volume control"
IsChecked="{Binding OutputController.IsEnabled, Mode=TwoWay}" />

<st:AutoGrid RowHeightOverride="Auto"
Margin="0"
ColumnSpan="2"
Columns="120,*" ChildMargin="5" RowHeight="50"
Visibility="{Binding OutputController.IsEnabled, Converter={StaticResource TrueToVisibleFalseToCollapsedConverter}}">
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Output device:" />
<ComboBox st:StackPanel.Fill="Fill"
ItemsSource="{Binding OutputController.Devices}"
SelectedItem="{Binding OutputController.DeviceId}"
DisplayMemberPath="Name"
ToolTip="Output device"/>
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Output volume:" />
<st:StackPanel VerticalAlignment="Center">
<Slider Width="160"
Value="{Binding OutputController.Volume}"
Minimum="0" Maximum="1"
Margin="0,2,5,0"
TickFrequency="0.05"
ToolTip="Volume"/>
<TextBlock
MinWidth="45">
<TextBlock.Text>
<Binding Path="OutputController.Volume" StringFormat="{}{0:F1}%" Converter="{StaticResource DoubleToPercentConverter}" />
</TextBlock.Text>
</TextBlock>
</st:StackPanel>
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Toggle:" />
<ContentControl Content="{Binding OutputController.HotkeyToggleMute}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Mute:" />
<ContentControl Content="{Binding OutputController.HotkeyMute}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Un-mute:" />
<ContentControl Content="{Binding OutputController.HotkeyUnmute}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Volume Up:" />
<ContentControl Content="{Binding OutputController.HotkeyVolumeUp}" />
<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="for Volume Down:" />
<ContentControl Content="{Binding OutputController.HotkeyVolumeDown}" />
</st:AutoGrid>

<Separator Grid.ColumnSpan="2" />

<TextBlock Style="{StaticResource SettingsLabelStyle}" Text="Minimize on close:" />
Expand Down
Loading

0 comments on commit 98c6201

Please sign in to comment.