Add support for per channel audio control

pull/1/head
firealarmss 11 months ago
parent 3ad8b9b867
commit 2ccd34690f

@ -0,0 +1,4 @@
[*.cs]
# CS4014: Because this call is not awaited, execution of the current method continues before the call is completed
dotnet_diagnostic.CS4014.severity = none

@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WhackerLinkLib", "WhackerLi
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhackerLinkConsoleV2", "WhackerLinkConsoleV2\WhackerLinkConsoleV2.csproj", "{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhackerLinkConsoleV2", "WhackerLinkConsoleV2\WhackerLinkConsoleV2.csproj", "{710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B5A7CF60-CCDE-4B2B-85C1-86AE3A19FB31}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

@ -21,7 +21,6 @@
using NAudio.Wave; using NAudio.Wave;
using NAudio.Wave.SampleProviders; using NAudio.Wave.SampleProviders;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
namespace WhackerLinkConsoleV2 namespace WhackerLinkConsoleV2
{ {
@ -29,7 +28,8 @@ namespace WhackerLinkConsoleV2
{ {
private WaveOutEvent _waveOut; private WaveOutEvent _waveOut;
private MixingSampleProvider _mixer; private MixingSampleProvider _mixer;
private Dictionary<string, BufferedWaveProvider> _talkgroupProviders;
private Dictionary<string, (BufferedWaveProvider buffer, VolumeSampleProvider volumeProvider)> _talkgroupProviders;
/// <summary> /// <summary>
/// Creates an instance of <see cref="AudioManager"/> /// Creates an instance of <see cref="AudioManager"/>
@ -37,7 +37,7 @@ namespace WhackerLinkConsoleV2
public AudioManager() public AudioManager()
{ {
_waveOut = new WaveOutEvent(); _waveOut = new WaveOutEvent();
_talkgroupProviders = new Dictionary<string, BufferedWaveProvider>(); _talkgroupProviders = new Dictionary<string, (BufferedWaveProvider, VolumeSampleProvider)>();
_mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(8000, 1)) _mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(8000, 1))
{ {
ReadFully = true ReadFully = true
@ -55,17 +55,43 @@ namespace WhackerLinkConsoleV2
public void AddTalkgroupStream(string talkgroupId, byte[] audioData) public void AddTalkgroupStream(string talkgroupId, byte[] audioData)
{ {
if (!_talkgroupProviders.ContainsKey(talkgroupId)) if (!_talkgroupProviders.ContainsKey(talkgroupId))
AddTalkgroupStream(talkgroupId);
_talkgroupProviders[talkgroupId].buffer.AddSamples(audioData, 0, audioData.Length);
}
/// <summary>
/// Internal helper to create a talkgroup stream
/// </summary>
/// <param name="talkgroupId"></param>
private void AddTalkgroupStream(string talkgroupId)
{ {
var provider = new BufferedWaveProvider(new WaveFormat(8000, 16, 1)) var bufferProvider = new BufferedWaveProvider(new WaveFormat(8000, 16, 1))
{ {
DiscardOnBufferOverflow = true DiscardOnBufferOverflow = true
}; };
_talkgroupProviders[talkgroupId] = provider; var volumeProvider = new VolumeSampleProvider(bufferProvider.ToSampleProvider())
_mixer.AddMixerInput(provider.ToSampleProvider()); {
Volume = 1.0f
};
_talkgroupProviders[talkgroupId] = (bufferProvider, volumeProvider);
_mixer.AddMixerInput(volumeProvider);
} }
_talkgroupProviders[talkgroupId].AddSamples(audioData, 0, audioData.Length); /// <summary>
/// Adjusts the volume of a specific talkgroup stream
/// </summary>
public void SetTalkgroupVolume(string talkgroupId, float volume)
{
if (_talkgroupProviders.ContainsKey(talkgroupId))
_talkgroupProviders[talkgroupId].volumeProvider.Volume = volume;
else
{
AddTalkgroupStream(talkgroupId);
_talkgroupProviders[talkgroupId].volumeProvider.Volume = volume;
}
} }
/// <summary> /// <summary>

@ -1,7 +1,7 @@
<UserControl x:Class="WhackerLinkConsoleV2.Controls.ChannelBox" <UserControl x:Class="WhackerLinkConsoleV2.Controls.ChannelBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="200" Height="120" Background="{Binding Background, RelativeSource={RelativeSource AncestorType=UserControl}}" Width="200" Height="150" Background="{Binding Background, RelativeSource={RelativeSource AncestorType=UserControl}}"
BorderBrush="Gray" BorderThickness="2"> BorderBrush="Gray" BorderThickness="2">
<Border CornerRadius="5" Background="{Binding Background, RelativeSource={RelativeSource AncestorType=UserControl}}"> <Border CornerRadius="5" Background="{Binding Background, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid Margin="5"> <Grid Margin="5">
@ -10,6 +10,7 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="{Binding ChannelName}" FontWeight="Bold" Foreground="White" FontSize="14" Grid.Row="0"/> <TextBlock Text="{Binding ChannelName}" FontWeight="Bold" Foreground="White" FontSize="14" Grid.Row="0"/>
@ -20,6 +21,9 @@
HorizontalAlignment="Left" Grid.Row="3" Click="PTTButton_Click"/> HorizontalAlignment="Left" Grid.Row="3" Click="PTTButton_Click"/>
<Button Content="Page Select" Width="80" Margin="80,5,0,0" Background="Green" Foreground="White" Name="PageSelectButton" <Button Content="Page Select" Width="80" Margin="80,5,0,0" Background="Green" Foreground="White" Name="PageSelectButton"
HorizontalAlignment="Left" Grid.Row="3" Click="PageSelectButton_Click"/> HorizontalAlignment="Left" Grid.Row="3" Click="PageSelectButton_Click"/>
<Slider Grid.Row="4" Minimum="0" Maximum="1" Value="{Binding Volume, Mode=TwoWay}"
Width="180" Margin="5" Name="VolumeSlider" ValueChanged="VolumeSlider_ValueChanged"/>
</Grid> </Grid>
</Border> </Border>
</UserControl> </UserControl>

@ -29,11 +29,14 @@ namespace WhackerLinkConsoleV2.Controls
public partial class ChannelBox : UserControl, INotifyPropertyChanged public partial class ChannelBox : UserControl, INotifyPropertyChanged
{ {
private readonly SelectedChannelsManager _selectedChannelsManager; private readonly SelectedChannelsManager _selectedChannelsManager;
private readonly AudioManager _audioManager;
private bool _pttState; private bool _pttState;
private bool _pageState; private bool _pageState;
private bool _holdState; private bool _holdState;
private bool _emergency; private bool _emergency;
private string _lastSrcId = "0"; private string _lastSrcId = "0";
private double _volume = 1.0;
public FlashingBackgroundManager _flashingBackgroundManager; public FlashingBackgroundManager _flashingBackgroundManager;
@ -45,6 +48,7 @@ namespace WhackerLinkConsoleV2.Controls
public string ChannelName { get; set; } public string ChannelName { get; set; }
public string SystemName { get; set; } public string SystemName { get; set; }
public string DstId { get; set; }
public string LastSrcId public string LastSrcId
{ {
@ -121,13 +125,29 @@ namespace WhackerLinkConsoleV2.Controls
} }
} }
public ChannelBox(SelectedChannelsManager selectedChannelsManager, string channelName, string systemName) public double Volume
{
get => _volume;
set
{
if (_volume != value)
{
_volume = value;
OnPropertyChanged(nameof(Volume));
_audioManager.SetTalkgroupVolume(DstId, (float)value);
}
}
}
public ChannelBox(SelectedChannelsManager selectedChannelsManager, AudioManager audioManager, string channelName, string systemName, string dstId)
{ {
InitializeComponent(); InitializeComponent();
DataContext = this; DataContext = this;
_selectedChannelsManager = selectedChannelsManager; _selectedChannelsManager = selectedChannelsManager;
_audioManager = audioManager;
_flashingBackgroundManager = new FlashingBackgroundManager(this); _flashingBackgroundManager = new FlashingBackgroundManager(this);
ChannelName = channelName; ChannelName = channelName;
DstId = dstId;
SystemName = $"System: {systemName}"; SystemName = $"System: {systemName}";
LastSrcId = $"Last SRC: {LastSrcId}"; LastSrcId = $"Last SRC: {LastSrcId}";
UpdateBackground(); UpdateBackground();
@ -210,6 +230,11 @@ namespace WhackerLinkConsoleV2.Controls
HoldChannelButtonClicked.Invoke(sender, this); HoldChannelButtonClicked.Invoke(sender, this);
} }
private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Volume = e.NewValue;
}
protected virtual void OnPropertyChanged(string propertyName) protected virtual void OnPropertyChanged(string propertyName)
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

@ -247,7 +247,7 @@ namespace WhackerLinkConsoleV2
{ {
foreach (var channel in zone.Channels) foreach (var channel in zone.Channels)
{ {
var channelBox = new ChannelBox(_selectedChannelsManager, channel.Name, channel.System); var channelBox = new ChannelBox(_selectedChannelsManager, _audioManager, channel.Name, channel.System, channel.Tgid);
if (_settingsManager.ChannelPositions.TryGetValue(channel.Name, out var position)) if (_settingsManager.ChannelPositions.TryGetValue(channel.Name, out var position))
{ {
@ -269,10 +269,11 @@ namespace WhackerLinkConsoleV2
ChannelsCanvas.Children.Add(channelBox); ChannelsCanvas.Children.Add(channelBox);
offsetX += 220; offsetX += 220;
if (offsetX + 200 > ChannelsCanvas.ActualWidth) if (offsetX + 200 > ChannelsCanvas.ActualWidth)
{ {
offsetX = 20; offsetX = 20;
offsetY += 140; offsetY += 170;
} }
} }
} }
@ -425,7 +426,7 @@ namespace WhackerLinkConsoleV2
} }
} }
private void ManualPage_Click(object sender, RoutedEventArgs e) private async void ManualPage_Click(object sender, RoutedEventArgs e)
{ {
QuickCallPage pageWindow = new QuickCallPage(); QuickCallPage pageWindow = new QuickCallPage();
pageWindow.Owner = this; pageWindow.Owner = this;
@ -441,8 +442,11 @@ namespace WhackerLinkConsoleV2
{ {
ToneGenerator generator = new ToneGenerator(); ToneGenerator generator = new ToneGenerator();
byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), 1.0); double toneADuration = 1.0;
byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.ToneB), 3.0); double toneBDuration = 3.0;
byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), toneADuration);
byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.ToneB), toneBDuration);
byte[] combinedAudio = new byte[toneA.Length + toneB.Length]; byte[] combinedAudio = new byte[toneA.Length + toneB.Length];
Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length); Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length);
@ -457,12 +461,14 @@ namespace WhackerLinkConsoleV2
_audioManager.AddTalkgroupStream(cpgChannel.Tgid, combinedAudio); _audioManager.AddTalkgroupStream(cpgChannel.Tgid, combinedAudio);
}); });
await Task.Run(async () =>
{
for (int i = 0; i < totalChunks; i++) for (int i = 0; i < totalChunks; i++)
{ {
int offset = i * chunkSize; int offset = i * chunkSize;
int size = Math.Min(chunkSize, combinedAudio.Length - offset); int size = Math.Min(chunkSize, combinedAudio.Length - offset);
byte[] chunk = new byte[size]; byte[] chunk = new byte[chunkSize];
Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size); Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size);
AudioPacket voicePacket = new AudioPacket AudioPacket voicePacket = new AudioPacket
@ -476,11 +482,18 @@ namespace WhackerLinkConsoleV2
Site = system.Site Site = system.Site
}, },
Site = system.Site, Site = system.Site,
LopServerVocode = true LopServerVocode = false
}; };
Console.WriteLine("sending sample");
handler.SendMessage(voicePacket.GetData()); handler.SendMessage(voicePacket.GetData());
await Task.Delay(20);
} }
});
double totalDurationMs = (toneADuration + toneBDuration) * 1000 + 500;
await Task.Delay((int)totalDurationMs);
GRP_VCH_RLS release = new GRP_VCH_RLS GRP_VCH_RLS release = new GRP_VCH_RLS
{ {
@ -491,6 +504,7 @@ namespace WhackerLinkConsoleV2
}; };
handler.SendMessage(release.GetData()); handler.SendMessage(release.GetData());
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
channel.PageSelectButton.Background = Brushes.Green; channel.PageSelectButton.Background = Brushes.Green;
@ -568,7 +582,7 @@ namespace WhackerLinkConsoleV2
Site = system.Site Site = system.Site
}, },
Site = system.Site, Site = system.Site,
LopServerVocode = true LopServerVocode = false
}; };
handler.SendMessage(voicePacket.GetData()); handler.SendMessage(voicePacket.GetData());

@ -0,0 +1,47 @@
radioWide:
hostVersion: R01.00.00
codeplugVersion: R01.06.00
baseMode: FIVEMRADIO
radioAlias: ''
serialNumber: 123ABC1234
model: CONSOLE
inCarMode: N/A
systems:
- name: System 1
address: localhost
rid: 12345
port: 3000
site:
name: Central Site
controlChannel: 772.74375
voiceChannels: []
location:
x: 757.89
y: 1274.17
z: 360.3
siteID: 1
systemID: 1
range: 1.5
zones:
- name: Zone 1
channels:
- name: Channel 1
system: System 1
tgid: 2001
- name: Channel 2
system: System 1
tgid: 15002
- name: Channel 3
system: System 1
tgid: 15003
- name: Zone 2
channels:
- name: Channel A
system: System 1
tgid: 16001
- name: Channel B
system: System 1
tgid: 16002
- name: Channel C
system: System 1
tgid: 16002
Loading…
Cancel
Save

Powered by TurnKey Linux.