Add Random button (pink noise generator) with counterphase channels
- Voss-McCartney pink noise (1/f spectrum) for intensity and frequency - Separate state per channel, channel B inverted for counterphase - Intensity capped at 80 for comfort, frequency spans full 10-1000ms range - Runs continuously, stopped via Stop All or switching modes - 5th button (68px), all buttons resized to fit one row
This commit is contained in:
+109
-9
@@ -52,11 +52,13 @@ public class MainForm : Form
|
||||
|
||||
readonly Button _btnLive;
|
||||
readonly Button _btnPattern;
|
||||
readonly Button _btnRandom;
|
||||
readonly Label _lblTrack;
|
||||
readonly Label _lblAudioDev;
|
||||
readonly ComboBox _cboAudioDev;
|
||||
bool _isLiveRunning;
|
||||
bool _isPatternRunning;
|
||||
bool _isRandomRunning;
|
||||
LiveCapture? _liveCapture;
|
||||
|
||||
public MainForm(CoyoteDevice device, State state, Server server)
|
||||
@@ -123,31 +125,39 @@ public class MainForm : Form
|
||||
{
|
||||
Text = "Test",
|
||||
Location = new Point(16, 96),
|
||||
Size = new Size(80, 32)
|
||||
Size = new Size(68, 32)
|
||||
};
|
||||
_btnTest.Click += OnTest;
|
||||
|
||||
_btnLive = new Button
|
||||
{
|
||||
Text = "Live",
|
||||
Location = new Point(104, 96),
|
||||
Size = new Size(80, 32)
|
||||
Location = new Point(92, 96),
|
||||
Size = new Size(68, 32)
|
||||
};
|
||||
_btnLive.Click += OnLive;
|
||||
|
||||
_btnPattern = new Button
|
||||
{
|
||||
Text = "Pattern",
|
||||
Location = new Point(192, 96),
|
||||
Size = new Size(80, 32)
|
||||
Location = new Point(168, 96),
|
||||
Size = new Size(68, 32)
|
||||
};
|
||||
_btnPattern.Click += OnPattern;
|
||||
|
||||
_btnRandom = new Button
|
||||
{
|
||||
Text = "Random",
|
||||
Location = new Point(244, 96),
|
||||
Size = new Size(68, 32)
|
||||
};
|
||||
_btnRandom.Click += OnRandom;
|
||||
|
||||
_btnStop = new Button
|
||||
{
|
||||
Text = "Stop All",
|
||||
Location = new Point(280, 96),
|
||||
Size = new Size(80, 32)
|
||||
Location = new Point(320, 96),
|
||||
Size = new Size(76, 32)
|
||||
};
|
||||
_btnStop.Click += OnStop;
|
||||
|
||||
@@ -350,7 +360,7 @@ public class MainForm : Form
|
||||
};
|
||||
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 });
|
||||
Controls.AddRange(new Control[] { _lblBle, _lblWs, _lblDeviceName, _txtDeviceName, _btnConnect, _chkSwap, _btnTest, _btnLive, _btnPattern, _btnRandom, _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);
|
||||
@@ -520,10 +530,11 @@ public class MainForm : Form
|
||||
|
||||
void RefreshButtonStates()
|
||||
{
|
||||
bool busy = IsTestRunning || _isLiveRunning || _isPatternRunning;
|
||||
bool busy = IsTestRunning || _isLiveRunning || _isPatternRunning || _isRandomRunning;
|
||||
_btnTest.Enabled = !_server.HasClient && !busy;
|
||||
_btnLive.Enabled = !_server.HasClient && !busy;
|
||||
_btnPattern.Enabled = !_server.HasClient && !busy;
|
||||
_btnRandom.Enabled = !_server.HasClient && !busy;
|
||||
_btnStop.Enabled = true;
|
||||
}
|
||||
|
||||
@@ -539,6 +550,10 @@ public class MainForm : Form
|
||||
{
|
||||
_lblTrack.Text = $"Pattern: {_patternName}";
|
||||
}
|
||||
else if (_isRandomRunning)
|
||||
{
|
||||
_lblTrack.Text = "Random noise";
|
||||
}
|
||||
|
||||
UpdateTrayIcon();
|
||||
}
|
||||
@@ -605,6 +620,7 @@ public class MainForm : Form
|
||||
{
|
||||
StopLive();
|
||||
StopPattern();
|
||||
StopRandom();
|
||||
_state.SetStrength('A', 0);
|
||||
_state.SetStrength('B', 0);
|
||||
_state.Stop('A');
|
||||
@@ -772,6 +788,90 @@ public class MainForm : Form
|
||||
}
|
||||
}
|
||||
|
||||
CancellationTokenSource? _randomCts;
|
||||
|
||||
void OnRandom(object? sender, EventArgs e)
|
||||
{
|
||||
if (_isRandomRunning || _server.HasClient) return;
|
||||
|
||||
_isRandomRunning = true;
|
||||
_btnRandom.Enabled = false;
|
||||
_lblTrack.Text = "Random noise";
|
||||
|
||||
_state.SetStrength('A', 60);
|
||||
_state.SetStrength('B', 60);
|
||||
_state.Stop('A');
|
||||
_state.Stop('B');
|
||||
|
||||
_randomCts = new CancellationTokenSource();
|
||||
Task.Run(() => RunRandomAsync(_randomCts.Token));
|
||||
}
|
||||
|
||||
async Task RunRandomAsync(CancellationToken ct)
|
||||
{
|
||||
// Pink noise via Voss-McCartney algorithm (1/f spectrum)
|
||||
// Separate state per channel, B inverted for counterphase
|
||||
var rng = new Random();
|
||||
int octaves = 8;
|
||||
var rowsA = new double[octaves];
|
||||
var rowsB = new double[octaves];
|
||||
double pinkA = 0, pinkB = 0;
|
||||
|
||||
while (!ct.IsCancellationRequested && _isRandomRunning)
|
||||
{
|
||||
// Channel A
|
||||
int updateA = rng.Next(octaves);
|
||||
rowsA[updateA] = rng.NextDouble() * 2 - 1;
|
||||
pinkA = 0;
|
||||
for (int i = 0; i < octaves; i++) pinkA += rowsA[i];
|
||||
pinkA /= octaves;
|
||||
|
||||
// Channel B (independent state, inverted for counterphase)
|
||||
int updateB = rng.Next(octaves);
|
||||
rowsB[updateB] = rng.NextDouble() * 2 - 1;
|
||||
pinkB = 0;
|
||||
for (int i = 0; i < octaves; i++) pinkB += rowsB[i];
|
||||
pinkB /= octaves;
|
||||
pinkB = -pinkB; // counterphase
|
||||
|
||||
// Map pink noise (-1..1) to intensity (0..80, capped for comfort)
|
||||
int intA = (int)Math.Clamp((pinkA + 1) * 40, 0, 80);
|
||||
int intB = (int)Math.Clamp((pinkB + 1) * 40, 0, 80);
|
||||
|
||||
// Random frequency: map pink noise to period (10-1000ms)
|
||||
int freqMsA = (int)Math.Clamp((pinkA + 1) * 500, 10, 1000);
|
||||
int freqMsB = (int)Math.Clamp((pinkB + 1) * 500, 10, 1000);
|
||||
|
||||
var frameA = new WaveFrame(
|
||||
Freq.Compress4(new[] { freqMsA, freqMsA, freqMsA, freqMsA }),
|
||||
Intensity.Clamp4(new[] { intA, intA, intA, intA }));
|
||||
var frameB = new WaveFrame(
|
||||
Freq.Compress4(new[] { freqMsB, freqMsB, freqMsB, freqMsB }),
|
||||
Intensity.Clamp4(new[] { intB, intB, intB, intB }));
|
||||
|
||||
_state.EnqueueStream('A', new[] { frameA });
|
||||
_state.EnqueueStream('B', new[] { frameB });
|
||||
|
||||
try { await Task.Delay(100, ct); } catch (OperationCanceledException) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
void StopRandom()
|
||||
{
|
||||
_randomCts?.Cancel();
|
||||
_randomCts = null;
|
||||
|
||||
if (_isRandomRunning)
|
||||
{
|
||||
_isRandomRunning = false;
|
||||
if (!IsDisposed)
|
||||
{
|
||||
_lblTrack.Text = "";
|
||||
RefreshButtonStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string ShowInputDialog(string prompt, string title)
|
||||
{
|
||||
using var dlg = new Form
|
||||
|
||||
Reference in New Issue
Block a user