Remove music button and MP3 decode, keep live capture only

- Remove Music button, OnMusic/RunMusicAsync/StopMusic and all music fields
- MusicAnalyzer: remove Analyze, DecodeToMono, MusicPattern record;
  keep ExtractFeatures/MapPitchToPeriod/BuildWaveFrame/TickFeature (used by LiveCapture)
- Remove unused NAudio.Wave and System.Diagnostics imports from MainForm
- Fix LiveCapture overlap: sliding window with 50% overlap (was dropping every other frame)
- Buttons resized to 80px (Test/Live/Pattern/Stop All)
This commit is contained in:
2026-08-08 19:52:56 +00:00
parent bbd9bb6d6b
commit e43b1f30cd
3 changed files with 27 additions and 235 deletions
+10 -131
View File
@@ -1,8 +1,6 @@
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using NAudio.CoreAudioApi;
using NAudio.Wave;
namespace Substation;
@@ -48,17 +46,13 @@ public class MainForm : Form
readonly Icon _iconHot;
string _trayState = "";
readonly Button _btnMusic;
readonly Button _btnLive;
readonly Button _btnPattern;
readonly Label _lblTrack;
readonly Label _lblAudioDev;
readonly ComboBox _cboAudioDev;
bool _isMusicRunning;
bool _isLiveRunning;
bool _isPatternRunning;
WaveOutEvent? _audioOut;
Stopwatch? _musicStopwatch;
LiveCapture? _liveCapture;
public MainForm(CoyoteDevice device, State state, Server server)
@@ -125,39 +119,31 @@ public class MainForm : Form
{
Text = "Test",
Location = new Point(16, 96),
Size = new Size(68, 32)
Size = new Size(80, 32)
};
_btnTest.Click += OnTest;
_btnMusic = new Button
{
Text = "Music",
Location = new Point(92, 96),
Size = new Size(68, 32)
};
_btnMusic.Click += OnMusic;
_btnLive = new Button
{
Text = "Live",
Location = new Point(168, 96),
Size = new Size(68, 32)
Location = new Point(104, 96),
Size = new Size(80, 32)
};
_btnLive.Click += OnLive;
_btnPattern = new Button
{
Text = "Pattern",
Location = new Point(244, 96),
Size = new Size(68, 32)
Location = new Point(192, 96),
Size = new Size(80, 32)
};
_btnPattern.Click += OnPattern;
_btnStop = new Button
{
Text = "Stop All",
Location = new Point(320, 96),
Size = new Size(76, 32)
Location = new Point(280, 96),
Size = new Size(80, 32)
};
_btnStop.Click += OnStop;
@@ -307,7 +293,7 @@ public class MainForm : Form
_chkScaleB.CheckedChanged += OnLimitChanged;
OnLimitChanged(null, EventArgs.Empty);
Controls.AddRange(new Control[] { _lblBle, _lblWs, _lblDeviceName, _txtDeviceName, _btnConnect, _chkSwap, _btnTest, _btnMusic, _btnLive, _btnPattern, _btnStop, _lblAudioDev, _cboAudioDev, _lblTrack, _lblSendA, _heatA, _lblSendB, _heatB, _lblRecvA, _gaugeRecvA, _lblRecvB, _gaugeRecvB, _lblLimitA, _limitBarA, _lblLimitValA, _chkScaleA, _lblLimitB, _limitBarB, _lblLimitValB, _chkScaleB });
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 });
// Tray icon — three cached variants: neutral=gray, active=gold, hot=red-orange
_iconNeutral = CreateVoltageIcon(Color.Gray);
@@ -475,9 +461,8 @@ public class MainForm : Form
void RefreshButtonStates()
{
bool busy = IsTestRunning || _isMusicRunning || _isLiveRunning || _isPatternRunning;
bool busy = IsTestRunning || _isLiveRunning || _isPatternRunning;
_btnTest.Enabled = !_server.HasClient && !busy;
_btnMusic.Enabled = !_server.HasClient && !busy;
_btnLive.Enabled = !_server.HasClient && !busy;
_btnPattern.Enabled = !_server.HasClient && !busy;
_btnStop.Enabled = true;
@@ -487,12 +472,7 @@ public class MainForm : Form
{
if (IsDisposed) return;
if (_isMusicRunning && _musicStopwatch != null)
{
var elapsed = _musicStopwatch.Elapsed;
_lblTrack.Text = $"Playing {elapsed:mm\\:ss} / {_musicTotalTime:mm\\:ss} {Path.GetFileName(_musicFilePath)}";
}
else if (_isLiveRunning)
if (_isLiveRunning)
{
_lblTrack.Text = "Live capture";
}
@@ -564,7 +544,6 @@ public class MainForm : Form
void OnStop(object? sender, EventArgs e)
{
StopMusic();
StopLive();
StopPattern();
_state.SetStrength('A', 0);
@@ -590,106 +569,6 @@ public class MainForm : Form
_state.SwapChannels = _chkSwap.Checked;
}
string _musicFilePath = "";
TimeSpan _musicTotalTime;
void OnMusic(object? sender, EventArgs e)
{
if (_isMusicRunning || _server.HasClient) return;
using var dlg = new OpenFileDialog
{
Filter = "MP3 files (*.mp3)|*.mp3|All files (*.*)|*.*",
Title = "Select music to drive e-stim"
};
if (dlg.ShowDialog() != DialogResult.OK) return;
_musicFilePath = dlg.FileName;
_musicTotalTime = TimeSpan.Zero;
_isMusicRunning = true;
_btnMusic.Enabled = false;
_btnTest.Enabled = false;
_lblTrack.Text = $"Analyzing... {Path.GetFileName(_musicFilePath)}";
var filePath = _musicFilePath;
Task.Run(() => RunMusicAsync(filePath));
}
async Task RunMusicAsync(string filePath)
{
MusicPattern? pattern = null;
try
{
var progress = new Progress<int>(pct =>
{
if (!IsDisposed)
_lblTrack.Text = $"Analyzing... {pct}% {Path.GetFileName(filePath)}";
});
pattern = await Task.Run(() => MusicAnalyzer.Analyze(filePath, (IProgress<int>)progress));
}
catch (Exception ex)
{
BeginInvoke(() =>
{
_lblTrack.Text = $"Analysis failed: {ex.Message}";
_isMusicRunning = false;
RefreshButtonStates();
});
return;
}
_musicTotalTime = pattern.Duration;
// Set a moderate strength ceiling for music
_state.SetStrength('A', 60);
_state.SetStrength('B', 60);
// Clear any existing patterns and enqueue all music frames
_state.Stop('A');
_state.Stop('B');
_state.EnqueueStream('A', pattern.ChannelA);
_state.EnqueueStream('B', pattern.ChannelB);
// Start audio playback + stopwatch simultaneously
try
{
var reader = new AudioFileReader(filePath);
_audioOut = new WaveOutEvent();
_audioOut.Init(reader);
_audioOut.PlaybackStopped += (_, _) => BeginInvoke(StopMusic);
_musicStopwatch = Stopwatch.StartNew();
_audioOut.Play();
}
catch (Exception ex)
{
Console.Error.WriteLine($"[music] playback failed: {ex.Message}");
_musicStopwatch = Stopwatch.StartNew();
}
BeginInvoke(() => _lblTrack.Text = $"Playing 00:00 / {_musicTotalTime:mm\\:ss} {Path.GetFileName(filePath)}");
}
void StopMusic()
{
if (_audioOut != null)
{
try { _audioOut.Stop(); } catch { }
try { _audioOut.Dispose(); } catch { }
_audioOut = null;
}
_musicStopwatch = null;
if (_isMusicRunning)
{
_isMusicRunning = false;
if (!IsDisposed)
{
_lblTrack.Text = "";
RefreshButtonStates();
}
}
}
void PopulateAudioDevices()
{
using var enumerator = new MMDeviceEnumerator();