Add Primary Channel (select using Ctrl+Click on a selected channel) functionality, tie Global PTT and tones to Primary Channel rather than every selected channel

pull/1/head
Steven Jennison 1 year ago
parent 145da26184
commit d2bfd46a39

@ -9,6 +9,7 @@
* *
* Copyright (C) 2025 Caleb, K4PHP * Copyright (C) 2025 Caleb, K4PHP
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Steven Jennison, KD8RHO
* *
*/ */
@ -29,6 +30,8 @@ namespace dvmconsole.Controls
/// </summary> /// </summary>
public partial class ChannelBox : UserControl, INotifyPropertyChanged public partial class ChannelBox : UserControl, INotifyPropertyChanged
{ {
public readonly static Border BORDER_DEFAULT;
public readonly static Border BORDER_GREEN;
public readonly static LinearGradientBrush GRAY_GRADIENT; public readonly static LinearGradientBrush GRAY_GRADIENT;
public readonly static LinearGradientBrush DARK_GRAY_GRADIENT; // Delected/Disconnected Color public readonly static LinearGradientBrush DARK_GRAY_GRADIENT; // Delected/Disconnected Color
public readonly static LinearGradientBrush BLUE_GRADIENT; // Selected Channel Color public readonly static LinearGradientBrush BLUE_GRADIENT; // Selected Channel Color
@ -265,6 +268,17 @@ namespace dvmconsole.Controls
} }
} }
private bool isPrimary = false;
public bool IsPrimary
{
get => isPrimary;
set
{
isPrimary = value;
UpdateBackground();
}
}
/// <summary> /// <summary>
/// Current volume for this channel. /// Current volume for this channel.
/// </summary> /// </summary>
@ -364,6 +378,19 @@ namespace dvmconsole.Controls
ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0FFAF00"), 0.485)); ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0FFAF00"), 0.485));
ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0C68700"), 0.517)); ORANGE_GRADIENT.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#F0C68700"), 0.517));
BORDER_DEFAULT = new Border
{
BorderBrush = new SolidColorBrush(Colors.LightGray),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8)
};
BORDER_GREEN = new Border
{
BorderBrush = new SolidColorBrush(Colors.Green),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(8)
};
} }
/// <summary> /// <summary>
@ -510,6 +537,13 @@ namespace dvmconsole.Controls
} }
ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT; ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
if (IsSelected)
if (IsPrimary)
ControlBorder.BorderBrush = BORDER_GREEN.BorderBrush;
else
ControlBorder.BorderBrush = BORDER_DEFAULT.BorderBrush;
else
ControlBorder.BorderBrush = BORDER_DEFAULT.BorderBrush;
} }
/// <summary> /// <summary>
@ -530,6 +564,28 @@ namespace dvmconsole.Controls
/// <param name="e"></param> /// <param name="e"></param>
private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ {
if (IsSelected)
{
// Check if either CTRL key is down, if so toggle PRIMARY state instead of deselecting
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
// If current channel is PRIMARY, clear it, otherwise set current to primary
if (selectedChannelsManager.PrimaryChannel == this)
{
selectedChannelsManager.ClearPrimaryChannel();
IsPrimary = false;
}
else
{
selectedChannelsManager.SetPrimaryChannel(this);
IsPrimary = true;
}
// Shortcut return, do not run the rest of this method
return;
}
}
IsSelected = !IsSelected; IsSelected = !IsSelected;
ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT; ControlBorder.Background = IsSelected ? BLUE_GRADIENT : DARK_GRAY_GRADIENT;
@ -544,7 +600,7 @@ namespace dvmconsole.Controls
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
private void PttButton_Click(object sender, RoutedEventArgs e) public void PttButton_Click(object sender, RoutedEventArgs e)
{ {
if (!IsSelected) if (!IsSelected)
return; return;

@ -10,7 +10,7 @@
* Copyright (C) 2024-2025 Caleb, K4PHP * Copyright (C) 2024-2025 Caleb, K4PHP
* Copyright (C) 2025 J. Dean * Copyright (C) 2025 J. Dean
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL * Copyright (C) 2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2025 Steven Jennison, KD8RHO * Copyright (C) 2025 Steven Jennison, KD8RHO
* *
*/ */
@ -46,8 +46,8 @@ namespace dvmconsole
public class ChannelPosition public class ChannelPosition
{ {
/* /*
** Properties ** Properties
*/ */
/// <summary> /// <summary>
/// X /// X
@ -120,8 +120,8 @@ namespace dvmconsole
private bool selectAll = false; private bool selectAll = false;
/* /*
** Properties ** Properties
*/ */
/// <summary> /// <summary>
/// Codeplug /// Codeplug
@ -129,8 +129,8 @@ namespace dvmconsole
public Codeplug Codeplug { get; set; } public Codeplug Codeplug { get; set; }
/* /*
** Methods ** Methods
*/ */
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MainWindow"/> class. /// Initializes a new instance of the <see cref="MainWindow"/> class.
@ -168,10 +168,23 @@ namespace dvmconsole
btnGlobalPtt.MouseRightButtonDown += btnGlobalPtt_MouseRightButtonDown; btnGlobalPtt.MouseRightButtonDown += btnGlobalPtt_MouseRightButtonDown;
selectedChannelsManager.SelectedChannelsChanged += SelectedChannelsChanged; selectedChannelsManager.SelectedChannelsChanged += SelectedChannelsChanged;
selectedChannelsManager.PrimaryChannelChanged += PrimaryChannelChanged;
SizeChanged += MainWindow_SizeChanged; SizeChanged += MainWindow_SizeChanged;
Loaded += MainWindow_Loaded; Loaded += MainWindow_Loaded;
} }
private void PrimaryChannelChanged()
{
var primaryChannel = selectedChannelsManager.PrimaryChannel;
foreach (UIElement element in channelsCanvas.Children)
{
if (element is ChannelBox box)
{
box.IsPrimary = box == primaryChannel;
}
}
}
/// <summary> /// <summary>
/// Helper to enable menu controls for Commands submenu. /// Helper to enable menu controls for Commands submenu.
/// </summary> /// </summary>
@ -631,119 +644,122 @@ namespace dvmconsole
{ {
try try
{ {
foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels()) var channel = selectedChannelsManager.PrimaryChannel;
if (channel == null)
{ {
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG) return;
continue; }
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
if (system == null) return;
{
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel);
continue;
}
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
if (cpgChannel == null) if (system == null)
{ {
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
channel.IsSelected = false; channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel); selectedChannelsManager.RemoveSelectedChannel(channel);
continue; return;
} }
PeerSystem fne = fneSystemManager.GetFneSystem(system.Name); Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (fne == null) if (cpgChannel == null)
{ {
Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
channel.IsSelected = false; channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel); selectedChannelsManager.RemoveSelectedChannel(channel);
continue; return;
} }
// PeerSystem fne = fneSystemManager.GetFneSystem(system.Name);
if (channel.PageState || (forHold && channel.HoldState)) if (fne == null)
{ {
byte[] pcmData; Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel);
return;
}
Task.Run(async () => { //
using (var waveReader = new WaveFileReader(filePath)) if (channel.PageState || (forHold && channel.HoldState))
{ {
if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm || byte[] pcmData;
waveReader.WaveFormat.SampleRate != 8000 ||
waveReader.WaveFormat.BitsPerSample != 16 ||
waveReader.WaveFormat.Channels != 1)
{
MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
using (MemoryStream ms = new MemoryStream()) Task.Run(async () => {
{ using (var waveReader = new WaveFileReader(filePath))
waveReader.CopyTo(ms); {
pcmData = ms.ToArray(); if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
} waveReader.WaveFormat.SampleRate != 8000 ||
waveReader.WaveFormat.BitsPerSample != 16 ||
waveReader.WaveFormat.Channels != 1)
{
MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
} }
int chunkSize = 1600; using (MemoryStream ms = new MemoryStream())
int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
if (pcmData.Length % chunkSize != 0)
{ {
byte[] paddedData = new byte[totalChunks * chunkSize]; waveReader.CopyTo(ms);
Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length); pcmData = ms.ToArray();
pcmData = paddedData;
} }
}
Task.Run(() => int chunkSize = 1600;
{ int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
}); if (pcmData.Length % chunkSize != 0)
{
byte[] paddedData = new byte[totalChunks * chunkSize];
Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length);
pcmData = paddedData;
}
DateTime startTime = DateTime.UtcNow; Task.Run(() =>
{
audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
});
for (int i = 0; i < totalChunks; i++) DateTime startTime = DateTime.UtcNow;
{
int offset = i * chunkSize;
byte[] chunk = new byte[chunkSize];
Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
channel.chunkedPCM = AudioConverter.SplitToChunks(chunk); for (int i = 0; i < totalChunks; i++)
{
int offset = i * chunkSize;
byte[] chunk = new byte[chunkSize];
Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
foreach (byte[] audioChunk in channel.chunkedPCM) channel.chunkedPCM = AudioConverter.SplitToChunks(chunk);
foreach (byte[] audioChunk in channel.chunkedPCM)
{
if (audioChunk.Length == PCM_SAMPLES_LENGTH)
{ {
if (audioChunk.Length == PCM_SAMPLES_LENGTH) if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.P25)
{ P25EncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.P25) else if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.DMR)
P25EncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system); DMREncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
else if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.DMR)
DMREncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
}
} }
}
DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100); DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
TimeSpan waitTime = nextPacketTime - DateTime.UtcNow; TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
if (waitTime.TotalMilliseconds > 0) if (waitTime.TotalMilliseconds > 0)
await Task.Delay(waitTime); await Task.Delay(waitTime);
} }
double totalDurationMs = ((double)pcmData.Length / 16000) + 250; double totalDurationMs = ((double)pcmData.Length / 16000) + 250;
await Task.Delay((int)totalDurationMs + 3000); await Task.Delay((int)totalDurationMs + 3000);
fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false); fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
if (forHold) if (forHold)
channel.PttButton.Background = ChannelBox.GRAY_GRADIENT; channel.PttButton.Background = ChannelBox.GRAY_GRADIENT;
else else
channel.PageState = false; channel.PageState = false;
});
}); });
} });
} }
} }
catch (Exception ex) catch (Exception ex)
@ -1917,61 +1933,12 @@ namespace dvmconsole
{ {
if (globalPttState) if (globalPttState)
await Task.Delay(500); await Task.Delay(500);
ChannelBox channel = selectedChannelsManager.PrimaryChannel;
foreach (ChannelBox channel in selectedChannelsManager.GetSelectedChannels()) if (channel == null)
{ {
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG) return;
continue;
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
if (system == null)
{
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}.");
channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel);
continue;
}
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
if (cpgChannel == null)
{
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}.");
channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel);
continue;
}
PeerSystem fne = fneSystemManager.GetFneSystem(system.Name);
if (fne == null)
{
Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}.");
channel.IsSelected = false;
selectedChannelsManager.RemoveSelectedChannel(channel);
continue;
}
channel.TxStreamId = fne.NewStreamId();
if (globalPttState)
{
Dispatcher.Invoke(() =>
{
btnGlobalPtt.Background = ChannelBox.RED_GRADIENT;
channel.PttState = true;
});
fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), true);
}
else
{
Dispatcher.Invoke(() =>
{
btnGlobalPtt.Background = ChannelBox.GRAY_GRADIENT;
channel.PttState = false;
});
fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
}
} }
channel.PttButton_Click(sender,e);
} }
/// <summary> /// <summary>
@ -2982,122 +2949,122 @@ namespace dvmconsole
switch (e.DUID) switch (e.DUID)
{ {
case P25DUID.LDU1: case P25DUID.LDU1:
{
// The '62', '63', '64', '65', '66', '67', '68', '69', '6A' records are LDU1
if ((data[0U] == 0x62U) && (data[22U] == 0x63U) &&
(data[36U] == 0x64U) && (data[53U] == 0x65U) &&
(data[70U] == 0x66U) && (data[87U] == 0x67U) &&
(data[104U] == 0x68U) && (data[121U] == 0x69U) &&
(data[138U] == 0x6AU))
{ {
// The '62', '63', '64', '65', '66', '67', '68', '69', '6A' records are LDU1 // The '62' record - IMBE Voice 1
if ((data[0U] == 0x62U) && (data[22U] == 0x63U) && Buffer.BlockCopy(data, count, channel.netLDU1, 0, 22);
(data[36U] == 0x64U) && (data[53U] == 0x65U) && count += 22;
(data[70U] == 0x66U) && (data[87U] == 0x67U) &&
(data[104U] == 0x68U) && (data[121U] == 0x69U) && // The '63' record - IMBE Voice 2
(data[138U] == 0x6AU)) Buffer.BlockCopy(data, count, channel.netLDU1, 25, 14);
{ count += 14;
// The '62' record - IMBE Voice 1
Buffer.BlockCopy(data, count, channel.netLDU1, 0, 22); // The '64' record - IMBE Voice 3 + Link Control
count += 22; Buffer.BlockCopy(data, count, channel.netLDU1, 50, 17);
byte serviceOptions = data[count + 3];
// The '63' record - IMBE Voice 2 isEmergency = (serviceOptions & 0x80) == 0x80;
Buffer.BlockCopy(data, count, channel.netLDU1, 25, 14); count += 17;
count += 14;
// The '65' record - IMBE Voice 4 + Link Control
// The '64' record - IMBE Voice 3 + Link Control Buffer.BlockCopy(data, count, channel.netLDU1, 75, 17);
Buffer.BlockCopy(data, count, channel.netLDU1, 50, 17); count += 17;
byte serviceOptions = data[count + 3];
isEmergency = (serviceOptions & 0x80) == 0x80; // The '66' record - IMBE Voice 5 + Link Control
count += 17; Buffer.BlockCopy(data, count, channel.netLDU1, 100, 17);
count += 17;
// The '65' record - IMBE Voice 4 + Link Control
Buffer.BlockCopy(data, count, channel.netLDU1, 75, 17); // The '67' record - IMBE Voice 6 + Link Control
count += 17; Buffer.BlockCopy(data, count, channel.netLDU1, 125, 17);
count += 17;
// The '66' record - IMBE Voice 5 + Link Control
Buffer.BlockCopy(data, count, channel.netLDU1, 100, 17); // The '68' record - IMBE Voice 7 + Link Control
count += 17; Buffer.BlockCopy(data, count, channel.netLDU1, 150, 17);
count += 17;
// The '67' record - IMBE Voice 6 + Link Control
Buffer.BlockCopy(data, count, channel.netLDU1, 125, 17); // The '69' record - IMBE Voice 8 + Link Control
count += 17; Buffer.BlockCopy(data, count, channel.netLDU1, 175, 17);
count += 17;
// The '68' record - IMBE Voice 7 + Link Control
Buffer.BlockCopy(data, count, channel.netLDU1, 150, 17); // The '6A' record - IMBE Voice 9 + Low Speed Data
count += 17; Buffer.BlockCopy(data, count, channel.netLDU1, 200, 16);
count += 16;
// The '69' record - IMBE Voice 8 + Link Control
Buffer.BlockCopy(data, count, channel.netLDU1, 175, 17); // decode 9 IMBE codewords into PCM samples
count += 17; P25DecodeAudioFrame(channel.netLDU1, e, handler, channel, isEmergency);
// The '6A' record - IMBE Voice 9 + Low Speed Data
Buffer.BlockCopy(data, count, channel.netLDU1, 200, 16);
count += 16;
// decode 9 IMBE codewords into PCM samples
P25DecodeAudioFrame(channel.netLDU1, e, handler, channel, isEmergency);
}
} }
}
break; break;
case P25DUID.LDU2: case P25DUID.LDU2:
{
// The '6B', '6C', '6D', '6E', '6F', '70', '71', '72', '73' records are LDU2
if ((data[0U] == 0x6BU) && (data[22U] == 0x6CU) &&
(data[36U] == 0x6DU) && (data[53U] == 0x6EU) &&
(data[70U] == 0x6FU) && (data[87U] == 0x70U) &&
(data[104U] == 0x71U) && (data[121U] == 0x72U) &&
(data[138U] == 0x73U))
{ {
// The '6B', '6C', '6D', '6E', '6F', '70', '71', '72', '73' records are LDU2 // The '6B' record - IMBE Voice 10
if ((data[0U] == 0x6BU) && (data[22U] == 0x6CU) && Buffer.BlockCopy(data, count, channel.netLDU2, 0, 22);
(data[36U] == 0x6DU) && (data[53U] == 0x6EU) && count += 22;
(data[70U] == 0x6FU) && (data[87U] == 0x70U) &&
(data[104U] == 0x71U) && (data[121U] == 0x72U) && // The '6C' record - IMBE Voice 11
(data[138U] == 0x73U)) Buffer.BlockCopy(data, count, channel.netLDU2, 25, 14);
{ count += 14;
// The '6B' record - IMBE Voice 10
Buffer.BlockCopy(data, count, channel.netLDU2, 0, 22); // The '6D' record - IMBE Voice 12 + Encryption Sync
count += 22; Buffer.BlockCopy(data, count, channel.netLDU2, 50, 17);
newMI[0] = data[count + 1];
// The '6C' record - IMBE Voice 11 newMI[1] = data[count + 2];
Buffer.BlockCopy(data, count, channel.netLDU2, 25, 14); newMI[2] = data[count + 3];
count += 14; count += 17;
// The '6D' record - IMBE Voice 12 + Encryption Sync // The '6E' record - IMBE Voice 13 + Encryption Sync
Buffer.BlockCopy(data, count, channel.netLDU2, 50, 17); Buffer.BlockCopy(data, count, channel.netLDU2, 75, 17);
newMI[0] = data[count + 1]; newMI[3] = data[count + 1];
newMI[1] = data[count + 2]; newMI[4] = data[count + 2];
newMI[2] = data[count + 3]; newMI[5] = data[count + 3];
count += 17; count += 17;
// The '6E' record - IMBE Voice 13 + Encryption Sync // The '6F' record - IMBE Voice 14 + Encryption Sync
Buffer.BlockCopy(data, count, channel.netLDU2, 75, 17); Buffer.BlockCopy(data, count, channel.netLDU2, 100, 17);
newMI[3] = data[count + 1]; newMI[6] = data[count + 1];
newMI[4] = data[count + 2]; newMI[7] = data[count + 2];
newMI[5] = data[count + 3]; newMI[8] = data[count + 3];
count += 17; count += 17;
// The '6F' record - IMBE Voice 14 + Encryption Sync // The '70' record - IMBE Voice 15 + Encryption Sync
Buffer.BlockCopy(data, count, channel.netLDU2, 100, 17); Buffer.BlockCopy(data, count, channel.netLDU2, 125, 17);
newMI[6] = data[count + 1]; channel.algId = data[count + 1]; // Algorithm ID
newMI[7] = data[count + 2]; channel.kId = (ushort)((data[count + 2] << 8) | data[count + 3]); // Key ID
newMI[8] = data[count + 3]; count += 17;
count += 17;
// The '71' record - IMBE Voice 16 + Encryption Sync
// The '70' record - IMBE Voice 15 + Encryption Sync Buffer.BlockCopy(data, count, channel.netLDU2, 150, 17);
Buffer.BlockCopy(data, count, channel.netLDU2, 125, 17); count += 17;
channel.algId = data[count + 1]; // Algorithm ID
channel.kId = (ushort)((data[count + 2] << 8) | data[count + 3]); // Key ID // The '72' record - IMBE Voice 17 + Encryption Sync
count += 17; Buffer.BlockCopy(data, count, channel.netLDU2, 175, 17);
count += 17;
// The '71' record - IMBE Voice 16 + Encryption Sync
Buffer.BlockCopy(data, count, channel.netLDU2, 150, 17); // The '73' record - IMBE Voice 18 + Low Speed Data
count += 17; Buffer.BlockCopy(data, count, channel.netLDU2, 200, 16);
count += 16;
// The '72' record - IMBE Voice 17 + Encryption Sync
Buffer.BlockCopy(data, count, channel.netLDU2, 175, 17); if (channel.p25Errs > 0) // temp, need to actually get errors I guess
count += 17; P25Crypto.CycleP25Lfsr(channel.mi);
else
// The '73' record - IMBE Voice 18 + Low Speed Data Array.Copy(newMI, channel.mi, P25Defines.P25_MI_LENGTH);
Buffer.BlockCopy(data, count, channel.netLDU2, 200, 16);
count += 16; // decode 9 IMBE codewords into PCM samples
P25DecodeAudioFrame(channel.netLDU2, e, handler, channel, isEmergency, P25DUID.LDU2);
if (channel.p25Errs > 0) // temp, need to actually get errors I guess
P25Crypto.CycleP25Lfsr(channel.mi);
else
Array.Copy(newMI, channel.mi, P25Defines.P25_MI_LENGTH);
// decode 9 IMBE codewords into PCM samples
P25DecodeAudioFrame(channel.netLDU2, e, handler, channel, isEmergency, P25DUID.LDU2);
}
} }
}
break; break;
} }
@ -3114,4 +3081,4 @@ namespace dvmconsole
}); });
} }
} // public partial class MainWindow : Window } // public partial class MainWindow : Window
} // namespace dvmconsole } // namespace dvmconsole

@ -8,6 +8,7 @@
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
* *
* Copyright (C) 2024 Caleb, K4PHP * Copyright (C) 2024 Caleb, K4PHP
* Copyright (C) 2025 Steven Jennison, KD8RHO
* *
*/ */
@ -20,8 +21,11 @@ namespace dvmconsole
/// </summary> /// </summary>
public class SelectedChannelsManager public class SelectedChannelsManager
{ {
private ChannelBox primaryChannel;
private readonly HashSet<ChannelBox> selectedChannels; private readonly HashSet<ChannelBox> selectedChannels;
public ChannelBox PrimaryChannel => primaryChannel;
public IReadOnlyCollection<ChannelBox> GetSelectedChannels() => selectedChannels; public IReadOnlyCollection<ChannelBox> GetSelectedChannels() => selectedChannels;
/* /*
@ -33,6 +37,10 @@ namespace dvmconsole
/// </summary> /// </summary>
public event Action SelectedChannelsChanged; public event Action SelectedChannelsChanged;
/// <summary>
/// Triggered when primary channel is changed
/// </summary>
public event Action PrimaryChannelChanged;
/* /*
** Methods ** Methods
*/ */
@ -54,7 +62,7 @@ namespace dvmconsole
if (selectedChannels.Add(channel)) if (selectedChannels.Add(channel))
{ {
channel.IsSelected = true; channel.IsSelected = true;
SelectedChannelsChanged.Invoke(); SelectedChannelsChanged?.Invoke();
} }
} }
@ -66,8 +74,13 @@ namespace dvmconsole
{ {
if (selectedChannels.Remove(channel)) if (selectedChannels.Remove(channel))
{ {
if (primaryChannel == channel)
{
ClearPrimaryChannel();
}
channel.IsPrimary = false;
channel.IsSelected = false; channel.IsSelected = false;
SelectedChannelsChanged.Invoke(); SelectedChannelsChanged?.Invoke();
} }
} }
@ -80,7 +93,27 @@ namespace dvmconsole
channel.IsSelected = false; channel.IsSelected = false;
selectedChannels.Clear(); selectedChannels.Clear();
SelectedChannelsChanged.Invoke(); SelectedChannelsChanged?.Invoke();
}
/// <summary>
/// Sets primary channel to the passed ChannelBox
/// </summary>
/// <param name="channel"></param>
public void SetPrimaryChannel(ChannelBox channel)
{
Log.WriteLine($"Setting primary channel to {channel.ChannelName}");
primaryChannel = channel;
PrimaryChannelChanged?.Invoke();
}
/// <summary>
/// Clears the primary channel selection, setting it to null
/// </summary>
public void ClearPrimaryChannel()
{
primaryChannel = null;
PrimaryChannelChanged?.Invoke();
} }
} // public class SelectedChannelsManager } // public class SelectedChannelsManager
} // namespace dvmconsole } // namespace dvmconsole

Loading…
Cancel
Save

Powered by TurnKey Linux.