diff --git a/WhackerLinkConsoleV2/ChannelBox.xaml b/WhackerLinkConsoleV2/ChannelBox.xaml
index 0c5153f..4c5338a 100644
--- a/WhackerLinkConsoleV2/ChannelBox.xaml
+++ b/WhackerLinkConsoleV2/ChannelBox.xaml
@@ -18,6 +18,8 @@
+
diff --git a/WhackerLinkConsoleV2/ChannelBox.xaml.cs b/WhackerLinkConsoleV2/ChannelBox.xaml.cs
index 6c1d6b7..ab810af 100644
--- a/WhackerLinkConsoleV2/ChannelBox.xaml.cs
+++ b/WhackerLinkConsoleV2/ChannelBox.xaml.cs
@@ -30,12 +30,15 @@ namespace WhackerLinkConsoleV2.Controls
{
private readonly SelectedChannelsManager _selectedChannelsManager;
private bool _pttState;
+ private bool _pageState;
private bool _emergency;
private string _lastSrcId = "0";
public FlashingBackgroundManager _flashingBackgroundManager;
public event EventHandler PTTButtonClicked;
+ public event EventHandler PageButtonClicked;
+
public event PropertyChangedEventHandler PropertyChanged;
public string ChannelName { get; set; }
@@ -64,6 +67,16 @@ namespace WhackerLinkConsoleV2.Controls
}
}
+ public bool PageState
+ {
+ get => _pageState;
+ set
+ {
+ _pageState = value;
+ UpdatePageColor();
+ }
+ }
+
public bool Emergency
{
get => _emergency;
@@ -136,6 +149,16 @@ namespace WhackerLinkConsoleV2.Controls
PttButton.Background = new SolidColorBrush(Colors.Green);
}
+ private void UpdatePageColor()
+ {
+ if (IsEditMode) return;
+
+ if (PageState)
+ PageSelectButton.Background = new SolidColorBrush(Colors.Orange);
+ else
+ PageSelectButton.Background = new SolidColorBrush(Colors.Green);
+ }
+
private void UpdateBackground()
{
Background = IsSelected ? Brushes.DodgerBlue : Brushes.DarkGray;
@@ -147,9 +170,15 @@ namespace WhackerLinkConsoleV2.Controls
PTTButtonClicked.Invoke(sender, this);
}
+ private void PageSelectButton_Click(object sender, RoutedEventArgs e)
+ {
+ PageState = !PageState;
+ PageButtonClicked.Invoke(sender, this);
+ }
+
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
+ }
}
}
diff --git a/WhackerLinkConsoleV2/DigitalPageWindow.xaml.cs b/WhackerLinkConsoleV2/DigitalPageWindow.xaml.cs
index 0110573..f1e68e5 100644
--- a/WhackerLinkConsoleV2/DigitalPageWindow.xaml.cs
+++ b/WhackerLinkConsoleV2/DigitalPageWindow.xaml.cs
@@ -1,16 +1,24 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+/*
+* WhackerLink - WhackerLinkConsoleV2
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*
+* Copyright (C) 2024 Caleb, K4PHP
+*
+*/
+
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
using WhackerLinkLib.Models.Radio;
namespace WhackerLinkConsoleV2
diff --git a/WhackerLinkConsoleV2/MainWindow.xaml b/WhackerLinkConsoleV2/MainWindow.xaml
index 96218f7..80190b2 100644
--- a/WhackerLinkConsoleV2/MainWindow.xaml
+++ b/WhackerLinkConsoleV2/MainWindow.xaml
@@ -24,6 +24,7 @@
diff --git a/WhackerLinkConsoleV2/MainWindow.xaml.cs b/WhackerLinkConsoleV2/MainWindow.xaml.cs
index dabfd9f..9500367 100644
--- a/WhackerLinkConsoleV2/MainWindow.xaml.cs
+++ b/WhackerLinkConsoleV2/MainWindow.xaml.cs
@@ -38,6 +38,8 @@ using NAudio.Wave;
using WhackerLinkLib.Interfaces;
using WhackerLinkLib.Models.IOSP;
using Nancy;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;
namespace WhackerLinkConsoleV2
{
@@ -251,6 +253,7 @@ namespace WhackerLinkConsoleV2
}
channelBox.PTTButtonClicked += ChannelBox_PTTButtonClicked;
+ channelBox.PageButtonClicked += ChannelBox_PageButtonClicked;
channelBox.MouseLeftButtonDown += ChannelBox_MouseLeftButtonDown;
channelBox.MouseMove += ChannelBox_MouseMove;
@@ -347,8 +350,23 @@ namespace WhackerLinkConsoleV2
$"Selected Output Device Index: {outputDeviceIndex}",
"Selected Devices");
- _waveIn.DeviceNumber = inputDeviceIndex.Value;
- _waveOut.DeviceNumber = outputDeviceIndex.Value;
+ try
+ {
+ _waveIn.StopRecording();
+ _waveIn.DeviceNumber = inputDeviceIndex.Value;
+ _waveIn.StartRecording();
+
+ _waveOut.Stop();
+ _waveOut.DeviceNumber = outputDeviceIndex.Value;
+ _waveOut.Init(_waveProvider);
+ _waveOut.Play();
+
+ MessageBox.Show("Audio devices updated successfully.", "Success");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Failed to update audio devices: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
}
else
{
@@ -368,6 +386,59 @@ namespace WhackerLinkConsoleV2
}
}
+ private void ManualPage_Click(object sender, RoutedEventArgs e)
+ {
+ QuickCallPage pageWindow = new QuickCallPage();
+ pageWindow.Owner = this;
+ if (pageWindow.ShowDialog() == true)
+ {
+ foreach (ChannelBox channel in _selectedChannelsManager.GetSelectedChannels())
+ {
+ Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
+ IWebSocketHandler handler = _webSocketManager.GetWebSocketHandler(system.Name);
+
+ if (channel.PageState)
+ {
+ ToneGenerator generator = new ToneGenerator();
+
+ byte[] toneA = generator.GenerateTone(Double.Parse(pageWindow.ToneA), 1.0);
+ byte[] toneB = generator.GenerateTone(Double.Parse(pageWindow.ToneB), 3.0);
+
+ byte[] combinedAudio = new byte[toneA.Length + toneB.Length];
+ Buffer.BlockCopy(toneA, 0, combinedAudio, 0, toneA.Length);
+ Buffer.BlockCopy(toneB, 0, combinedAudio, toneA.Length, toneB.Length);
+
+ int chunkSize = 1600;
+ int totalChunks = (combinedAudio.Length + chunkSize - 1) / chunkSize;
+
+ Task.Factory.StartNew(() =>
+ {
+ _waveProvider.ClearBuffer();
+ _waveProvider.AddSamples(combinedAudio, 0, combinedAudio.Length);
+ });
+
+ for (int i = 0; i < totalChunks; i++)
+ {
+ int offset = i * chunkSize;
+ int size = Math.Min(chunkSize, combinedAudio.Length - offset);
+
+ byte[] chunk = new byte[size];
+ Buffer.BlockCopy(combinedAudio, offset, chunk, 0, size);
+
+ handler.SendMessage(PacketFactory.CreateVoicePacket(system.Rid, cpgChannel.Tgid, channel.VoiceChannel, chunk, system.Site));
+ }
+
+ handler.SendMessage(PacketFactory.CreateVoiceChannelRelease(system.Rid, cpgChannel.Tgid, channel.VoiceChannel, system.Site));
+ Dispatcher.Invoke(() =>
+ {
+ channel.PageSelectButton.Background = Brushes.Green;
+ });
+ }
+ }
+ }
+ }
+
private void SelectWidgets_Click(object sender, RoutedEventArgs e)
{
WidgetSelectionWindow widgetSelectionWindow = new WidgetSelectionWindow();
@@ -468,6 +539,9 @@ namespace WhackerLinkConsoleV2
{
channel.Background = new SolidColorBrush(Colors.DarkCyan);
});
+ } else if (channel.PageState && response.Status == (int)ResponseType.GRANT && response.Channel != null && response.SrcId == system.Rid && response.DstId == cpgChannel.Tgid)
+ {
+ channel.VoiceChannel = response.Channel;
}
else
{
@@ -485,6 +559,22 @@ namespace WhackerLinkConsoleV2
}
}
+ private void ChannelBox_PageButtonClicked(object sender, ChannelBox e)
+ {
+ Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
+ Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(e.ChannelName);
+ IWebSocketHandler handler = _webSocketManager.GetWebSocketHandler(system.Name);
+
+ if (e.PageState)
+ handler.SendMessage(PacketFactory.CreateVoiceChannelRequest(system.Rid, cpgChannel.Tgid, system.Site));
+ else
+ {
+ //_stopSending = true;
+ handler.SendMessage(PacketFactory.CreateVoiceChannelRelease(system.Rid, cpgChannel.Tgid, e.VoiceChannel, system.Site));
+ e.VoiceChannel = null;
+ }
+ }
+
private void ChannelBox_PTTButtonClicked(object sender, ChannelBox e)
{
Codeplug.System system = Codeplug.GetSystemForChannel(e.ChannelName);
diff --git a/WhackerLinkConsoleV2/QuickCallPage.xaml b/WhackerLinkConsoleV2/QuickCallPage.xaml
new file mode 100644
index 0000000..e7a4151
--- /dev/null
+++ b/WhackerLinkConsoleV2/QuickCallPage.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/WhackerLinkConsoleV2/QuickCallPage.xaml.cs b/WhackerLinkConsoleV2/QuickCallPage.xaml.cs
new file mode 100644
index 0000000..2bdcb48
--- /dev/null
+++ b/WhackerLinkConsoleV2/QuickCallPage.xaml.cs
@@ -0,0 +1,47 @@
+/*
+* WhackerLink - WhackerLinkConsoleV2
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*
+* Copyright (C) 2024 Caleb, K4PHP
+*
+*/
+
+using System.Windows;
+
+namespace WhackerLinkConsoleV2
+{
+ ///
+ /// Interaction logic for QuickCallPage.xaml
+ ///
+ public partial class QuickCallPage : Window
+ {
+ public string ToneA;
+ public string ToneB;
+
+ public QuickCallPage()
+ {
+ InitializeComponent();
+ }
+
+ private void SendButton_Click(object sender, RoutedEventArgs e)
+ {
+ ToneA = ToneAText.Text;
+ ToneB = ToneBText.Text;
+
+ DialogResult = true;
+ Close();
+ }
+ }
+}
diff --git a/WhackerLinkConsoleV2/ToneGenerator.cs b/WhackerLinkConsoleV2/ToneGenerator.cs
new file mode 100644
index 0000000..1f24136
--- /dev/null
+++ b/WhackerLinkConsoleV2/ToneGenerator.cs
@@ -0,0 +1,100 @@
+/*
+* WhackerLink - WhackerLinkConsoleV2
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*
+* Copyright (C) 2024 Caleb, K4PHP
+*
+*/
+
+using NAudio.Wave;
+
+namespace WhackerLinkConsoleV2
+{
+ ///
+ ///
+ ///
+ public class ToneGenerator
+ {
+ private readonly int _sampleRate = 8000;
+ private readonly int _bitsPerSample = 16;
+ private readonly int _channels = 1;
+ private WaveOutEvent _waveOut;
+ private BufferedWaveProvider _waveProvider;
+
+ ///
+ /// Creates an instance of
+ ///
+ public ToneGenerator()
+ {
+ _waveOut = new WaveOutEvent();
+ _waveProvider = new BufferedWaveProvider(new WaveFormat(_sampleRate, _bitsPerSample, _channels));
+ _waveOut.Init(_waveProvider);
+ }
+
+ ///
+ /// Generate a sine wave tone at the specified frequency and duration.
+ ///
+ /// Frequency in Hz
+ /// Duration in seconds
+ /// PCM data as a byte array
+ public byte[] GenerateTone(double frequency, double durationSeconds)
+ {
+ int sampleCount = (int)(_sampleRate * durationSeconds);
+ byte[] buffer = new byte[sampleCount * (_bitsPerSample / 8)];
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ double time = (double)i / _sampleRate;
+ short sampleValue = (short)(Math.Sin(2 * Math.PI * frequency * time) * short.MaxValue);
+
+ buffer[i * 2] = (byte)(sampleValue & 0xFF);
+ buffer[i * 2 + 1] = (byte)((sampleValue >> 8) & 0xFF);
+ }
+
+ return buffer;
+ }
+
+ ///
+ /// Play the generated tone through the speakers.
+ ///
+ /// Frequency in Hz
+ /// Duration in seconds
+ public void PlayTone(double frequency, double durationSeconds)
+ {
+ byte[] toneData = GenerateTone(frequency, durationSeconds);
+
+ _waveProvider.ClearBuffer();
+ _waveProvider.AddSamples(toneData, 0, toneData.Length);
+
+ _waveOut.Play();
+ }
+
+ ///
+ /// Stop playback.
+ ///
+ public void StopTone()
+ {
+ _waveOut.Stop();
+ }
+
+ ///
+ /// Dispose of resources.
+ ///
+ public void Dispose()
+ {
+ _waveOut.Dispose();
+ }
+ }
+}