From 4e36580b2293e848015a105013de4157c46cf62f Mon Sep 17 00:00:00 2001 From: Mute Date: Sun, 9 Aug 2026 11:55:16 +0000 Subject: [PATCH] Fix heat map scaling, add per-channel amplifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add per-channel amplifier (0-100, 1.0x to 10.0x) applied to intensity before limiter gates strength - Fix heat map scaling: use LimitScaleA/B (valA/DesiredA ratio) instead of valA/200 — was capping colors at green, never reaching orange - Scale heat map intensity by limiter ratio so colors reflect actual output after limiter - Remove dead LastIntensityA/B field (replaced by heat map scaling) - Amp trackbars: 0=1.0x, 100=10.0x, integer value labels - Close button quits app instead of hiding to tray --- MainForm.cs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++--- State.cs | 43 +++++++++++++++++++++++---- 2 files changed, 119 insertions(+), 10 deletions(-) diff --git a/MainForm.cs b/MainForm.cs index 4969af6..3ab6c35 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -31,10 +31,16 @@ public class MainForm : Form readonly Label _lblRecvB; readonly TrackBar _limitBarA; readonly TrackBar _limitBarB; + readonly TrackBar _ampBarA; + readonly TrackBar _ampBarB; readonly Label _lblLimitA; readonly Label _lblLimitB; readonly Label _lblLimitValA; readonly Label _lblLimitValB; + readonly Label _lblAmpA; + readonly Label _lblAmpB; + readonly Label _lblAmpValA; + readonly Label _lblAmpValB; readonly CheckBox _chkScaleA; readonly CheckBox _chkScaleB; readonly CheckBox _chkSwap; @@ -61,7 +67,7 @@ public class MainForm : Form _loopCts = new CancellationTokenSource(); Text = $"Substation {typeof(MainForm).Assembly.GetName().Version}"; - ClientSize = new Size(420, 450); + ClientSize = new Size(420, 520); FormBorderStyle = FormBorderStyle.FixedSingle; MaximizeBox = false; StartPosition = FormStartPosition.CenterScreen; @@ -291,7 +297,60 @@ public class MainForm : Form _chkScaleB.CheckedChanged += OnLimitChanged; OnLimitChanged(null, EventArgs.Empty); - Controls.AddRange(new Control[] { _lblBle, _lblWs, _lblDeviceName, _txtDeviceName, _btnConnect, _chkSwap, _btnTest, _btnLive, _btnPattern, _btnStop, _lblAudioDev, _cboAudioDev, _lblTrack, _lblSendA, _heatA, _lblSendB, _heatB, _lblRecvA, _gaugeRecvA, _lblRecvB, _gaugeRecvB, _lblLimitA, _limitBarA, _lblLimitValA, _chkScaleA, _lblLimitB, _limitBarB, _lblLimitValB, _chkScaleB }); + _lblAmpA = new Label + { + Text = "Amp A", + Location = new Point(16, 394), + Size = new Size(48, 20), + Font = new Font(Font.FontFamily, 9, FontStyle.Bold) + }; + _ampBarA = new TrackBar + { + Location = new Point(68, 390), + Size = new Size(212, 45), + Minimum = 0, + Maximum = 100, + TickFrequency = 25, + Value = 0 + }; + _ampBarA.ValueChanged += OnAmpChanged; + FixTrackbarKeys(_ampBarA); + _lblAmpValA = new Label + { + Text = "0", + Location = new Point(286, 394), + Size = new Size(32, 20), + TextAlign = ContentAlignment.MiddleRight + }; + + _lblAmpB = new Label + { + Text = "Amp B", + Location = new Point(16, 442), + Size = new Size(48, 20), + Font = new Font(Font.FontFamily, 9, FontStyle.Bold) + }; + _ampBarB = new TrackBar + { + Location = new Point(68, 438), + Size = new Size(212, 45), + Minimum = 0, + Maximum = 100, + TickFrequency = 25, + Value = 0 + }; + _ampBarB.ValueChanged += OnAmpChanged; + FixTrackbarKeys(_ampBarB); + _lblAmpValB = new Label + { + Text = "0", + Location = new Point(286, 442), + Size = new Size(32, 20), + TextAlign = ContentAlignment.MiddleRight + }; + OnAmpChanged(null, EventArgs.Empty); + + Controls.AddRange(new Control[] { _lblBle, _lblWs, _lblDeviceName, _txtDeviceName, _btnConnect, _chkSwap, _btnTest, _btnLive, _btnPattern, _btnStop, _lblAudioDev, _cboAudioDev, _lblTrack, _lblSendA, _heatA, _lblSendB, _heatB, _lblRecvA, _gaugeRecvA, _lblRecvB, _gaugeRecvB, _lblLimitA, _limitBarA, _lblLimitValA, _chkScaleA, _lblLimitB, _limitBarB, _lblLimitValB, _chkScaleB, _lblAmpA, _ampBarA, _lblAmpValA, _lblAmpB, _ampBarB, _lblAmpValB }); // Tray icon — three cached variants: neutral=gray, active=gold, hot=red-orange _iconNeutral = CreateVoltageIcon(Color.Gray); @@ -353,8 +412,17 @@ public class MainForm : Form { BeginInvoke(() => { - _heatA.AddTick(freqA, intA, _state.LastActiveA); - _heatB.AddTick(freqB, intB, _state.LastActiveB); + double aScale = _state.LimitScaleA; + double bScale = _state.LimitScaleB; + var scaledIntA = new byte[4]; + var scaledIntB = new byte[4]; + for (int i = 0; i < 4; i++) + { + scaledIntA[i] = (byte)Math.Round(intA[i] * aScale); + scaledIntB[i] = (byte)Math.Round(intB[i] * bScale); + } + _heatA.AddTick(freqA, scaledIntA, _state.LastActiveA); + _heatB.AddTick(freqB, scaledIntB, _state.LastActiveB); }); } } @@ -555,6 +623,16 @@ public class MainForm : Form _lblLimitValB.Text = $"{maxB}"; } + void OnAmpChanged(object? sender, EventArgs e) + { + var ampA = 1.0 + _ampBarA.Value / 100.0 * 9.0; + var ampB = 1.0 + _ampBarB.Value / 100.0 * 9.0; + _state.SetAmpA(ampA); + _state.SetAmpB(ampB); + _lblAmpValA.Text = $"{_ampBarA.Value}"; + _lblAmpValB.Text = $"{_ampBarB.Value}"; + } + void OnSwapChanged(object? sender, EventArgs e) { _state.SwapChannels = _chkSwap.Checked; diff --git a/State.cs b/State.cs index aa675f5..2eb0199 100644 --- a/State.cs +++ b/State.cs @@ -17,7 +17,7 @@ public class State public int ActualA, ActualB; public int LastSentA, LastSentB; - public int LastIntensityA, LastIntensityB; + public double LimitScaleA, LimitScaleB; public byte[]? LastFreqA, LastIntA, LastFreqB, LastIntB; public bool LastActiveA, LastActiveB; @@ -26,6 +26,8 @@ public class State public LimitMode LimitModeA = LimitMode.Clamp; public LimitMode LimitModeB = LimitMode.Clamp; public bool SwapChannels; + public double AmpA = 1.0; + public double AmpB = 1.0; public void SetLimitA(int max, LimitMode mode) { @@ -47,6 +49,24 @@ public class State } } + public void SetAmpA(double amp) + { + lock (Lock) + { + AmpA = Math.Clamp(amp, 0.1, 10.0); + DirtyA = true; + } + } + + public void SetAmpB(double amp) + { + lock (Lock) + { + AmpB = Math.Clamp(amp, 0.1, 10.0); + DirtyB = true; + } + } + public bool IsSignaling { get @@ -115,20 +135,31 @@ public class State var valB = ApplyLimit(DesiredB, MaxB, LimitModeB); LastSentA = valA; LastSentB = valB; + LimitScaleA = DesiredA > 0 ? (double)valA / DesiredA : 0; + LimitScaleB = DesiredB > 0 ? (double)valB / DesiredB : 0; DirtyA = false; DirtyB = false; bool hasA = !StreamA.IsEmpty || (LoopFreqA != null && LoopIntA != null); bool hasB = !StreamB.IsEmpty || (LoopFreqB != null && LoopIntB != null); - var (freqA, intA) = PopWave(StreamA, LoopFreqA, LoopIntA); - var (freqB, intB) = PopWave(StreamB, LoopFreqB, LoopIntB); + var (freqA, intARaw) = PopWave(StreamA, LoopFreqA, LoopIntA); + var (freqB, intBRaw) = PopWave(StreamB, LoopFreqB, LoopIntB); + + // Apply amplifier to intensity, clamp to 0-100 + var ampA = AmpA; + var ampB = AmpB; + var intA = new byte[4]; + var intB = new byte[4]; + for (int i = 0; i < 4; i++) + { + intA[i] = (byte)Math.Clamp((int)Math.Round(intARaw[i] * ampA), 0, 100); + intB[i] = (byte)Math.Clamp((int)Math.Round(intBRaw[i] * ampB), 0, 100); + } LastActiveA = hasA; LastActiveB = hasB; LastFreqA = freqA; LastIntA = intA; LastFreqB = freqB; LastIntB = intB; - LastIntensityA = hasA ? (intA[0] + intA[1] + intA[2] + intA[3]) / 4 : 0; - LastIntensityB = hasB ? (intB[0] + intB[1] + intB[2] + intB[3]) / 4 : 0; if (SwapChannels) { @@ -140,7 +171,7 @@ public class State (LastFreqA, LastFreqB) = (LastFreqB, LastFreqA); (LastIntA, LastIntB) = (LastIntB, LastIntA); (LastActiveA, LastActiveB) = (LastActiveB, LastActiveA); - (LastIntensityA, LastIntensityB) = (LastIntensityB, LastIntensityA); + (LimitScaleA, LimitScaleB) = (LimitScaleB, LimitScaleA); } return (modeA, valA, modeB, valB, freqA, intA, freqB, intB);