Cleanup pass: remove dead code, fix bugs, simplify
Dead code removed: - MusicAnalyzer.BuildWaveFrame (replaced by DrumDetector + BuildMelodyFrame) - MusicAnalyzer.ExtractFeatures simplified to melody-only (removed rhythm band) - MusicAnalyzer.RhythmLow/RhythmHigh constants, TickFeature.RhythmFlux - LiveCapture._liveMaxFlux + _prevRhythmMag (computed, never read) - State.ActualA/ActualB, State.LastSentA/LastSentB (set, never read) - Program.cs StrengthChanged handler (only wrote to dead fields) - Command.Mode property (never referenced) - DrumDetector.FreqKick/Snare/Brass/Silent static arrays (unused) Bugs fixed: - DrumDetector.BuildFrame: freqBytes was byte[16], now byte[4] - CoyoteDevice fallback: use nameFilter param instead of hardcoded string Refactoring: - Server.DoStatus calls BuildStatusPush(null) instead of duplicating - HeatMap: removed redundant colW assignment - Removed unused System.Numerics imports from LiveCapture, MusicAnalyzer
This commit is contained in:
+1
-1
@@ -33,7 +33,7 @@ public class CoyoteDevice : IDisposable
|
||||
selector = BluetoothLEDevice.GetDeviceSelectorFromPairingState(false);
|
||||
devices = await DeviceInformation.FindAllAsync(selector);
|
||||
var match = devices.FirstOrDefault(d =>
|
||||
d.Name.Contains("47L121000", StringComparison.OrdinalIgnoreCase) ||
|
||||
d.Name.Contains(nameFilter, StringComparison.OrdinalIgnoreCase) ||
|
||||
d.Name.Contains("DG-LAB", StringComparison.OrdinalIgnoreCase) ||
|
||||
d.Name.Contains("Coyote", StringComparison.OrdinalIgnoreCase));
|
||||
if (match == null)
|
||||
|
||||
+1
-6
@@ -17,11 +17,6 @@ public class DrumDetector
|
||||
readonly bool[] _subSnare = new bool[4];
|
||||
readonly bool[] _subBrass = new bool[4];
|
||||
|
||||
static readonly byte[] FreqKick = Freq.Compress4(new[] { 150, 150, 150, 150 });
|
||||
static readonly byte[] FreqSnare = Freq.Compress4(new[] { 50, 50, 50, 50 });
|
||||
static readonly byte[] FreqBrass = Freq.Compress4(new[] { 10, 10, 10, 10 });
|
||||
static readonly byte[] FreqSilent = Freq.Compress4(new[] { 10, 10, 10, 10 });
|
||||
|
||||
public void ProcessWindow(double[] magnitude, int sampleRate, int windowSize, int subTickIndex)
|
||||
{
|
||||
if (subTickIndex < 0 || subTickIndex > 3) return;
|
||||
@@ -66,7 +61,7 @@ public class DrumDetector
|
||||
public WaveFrame BuildFrame()
|
||||
{
|
||||
var intensity = new byte[4];
|
||||
var freqBytes = new byte[16]; // 4 sub-ticks × 4 bytes (but we use per-sub-tick freq)
|
||||
var freqBytes = new byte[4];
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
|
||||
+1
-2
@@ -37,7 +37,6 @@ public class HeatMap : Panel
|
||||
|
||||
int w = Width;
|
||||
int h = Height;
|
||||
int colW = Math.Max(1, w / _maxTicks);
|
||||
|
||||
// Draw axis labels
|
||||
using var font = new Font(FontFamily.GenericMonospace, 7);
|
||||
@@ -48,7 +47,7 @@ public class HeatMap : Panel
|
||||
int plotX = 36;
|
||||
int plotW = w - plotX - 4;
|
||||
int plotH = h - 4;
|
||||
colW = Math.Max(1, plotW / _maxTicks);
|
||||
int colW = Math.Max(1, plotW / _maxTicks);
|
||||
|
||||
for (int i = 0; i < _history.Count; i++)
|
||||
{
|
||||
|
||||
+1
-8
@@ -1,4 +1,3 @@
|
||||
using System.Numerics;
|
||||
using FftSharp;
|
||||
using NAudio.CoreAudioApi;
|
||||
using NAudio.Wave;
|
||||
@@ -16,8 +15,6 @@ public class LiveCapture : IDisposable
|
||||
readonly Queue<double> _sampleBuffer = new();
|
||||
readonly object _bufferLock = new();
|
||||
|
||||
double[]? _prevRhythmMag;
|
||||
double _liveMaxFlux;
|
||||
double _liveMaxMelodyEnergy;
|
||||
int _sampleRate;
|
||||
int _fftsPerTick;
|
||||
@@ -131,13 +128,9 @@ public class LiveCapture : IDisposable
|
||||
var spectrum = FFT.Forward(windowData);
|
||||
var mag = FFT.Magnitude(spectrum);
|
||||
|
||||
var (rhythmEnergy, rhythmFlux, melodyEnergy, melodyFreq) =
|
||||
MusicAnalyzer.ExtractFeatures(mag, _prevRhythmMag, _sampleRate);
|
||||
|
||||
_prevRhythmMag = mag;
|
||||
var (melodyEnergy, melodyFreq) = MusicAnalyzer.ExtractFeatures(mag, _sampleRate);
|
||||
|
||||
// Adaptive normalization: running max with slow decay
|
||||
_liveMaxFlux = Math.Max(rhythmFlux, _liveMaxFlux * 0.999);
|
||||
_liveMaxMelodyEnergy = Math.Max(melodyEnergy, _liveMaxMelodyEnergy * 0.999);
|
||||
|
||||
// Drum detection: map this FFT window to a sub-tick (0-3)
|
||||
|
||||
+3
-65
@@ -1,4 +1,3 @@
|
||||
using System.Numerics;
|
||||
using FftSharp;
|
||||
|
||||
namespace Substation;
|
||||
@@ -9,35 +8,15 @@ public static class MusicAnalyzer
|
||||
public const int HopSize = 1024;
|
||||
public const double TickDuration = 0.1;
|
||||
|
||||
const double RhythmLow = 20, RhythmHigh = 250;
|
||||
const double MelodyLow = 300, MelodyHigh = 4000;
|
||||
|
||||
public static (double rhythmEnergy, double rhythmFlux, double melodyEnergy, double melodyFreq)
|
||||
ExtractFeatures(double[] magnitude, double[]? prevRhythmMag, int sampleRate)
|
||||
public static (double melodyEnergy, double melodyFreq)
|
||||
ExtractFeatures(double[] magnitude, int sampleRate)
|
||||
{
|
||||
double binWidth = (double)sampleRate / WindowSize;
|
||||
int rhythmLoBin = (int)(RhythmLow / binWidth);
|
||||
int rhythmHiBin = (int)(RhythmHigh / binWidth);
|
||||
int melodyLoBin = (int)(MelodyLow / binWidth);
|
||||
int melodyHiBin = (int)(MelodyHigh / binWidth);
|
||||
|
||||
// Rhythm band energy
|
||||
double rhythmEnergy = 0;
|
||||
for (int i = rhythmLoBin; i <= rhythmHiBin && i < magnitude.Length; i++)
|
||||
rhythmEnergy += magnitude[i] * magnitude[i];
|
||||
rhythmEnergy = Math.Sqrt(rhythmEnergy / (rhythmHiBin - rhythmLoBin + 1));
|
||||
|
||||
// Spectral flux (positive change in rhythm band)
|
||||
double rhythmFlux = 0;
|
||||
if (prevRhythmMag != null)
|
||||
{
|
||||
for (int i = rhythmLoBin; i <= rhythmHiBin && i < magnitude.Length; i++)
|
||||
{
|
||||
double diff = magnitude[i] - prevRhythmMag[i];
|
||||
if (diff > 0) rhythmFlux += diff;
|
||||
}
|
||||
}
|
||||
|
||||
// Melody band: energy + dominant frequency (spectral peak)
|
||||
double melodyEnergy = 0;
|
||||
double peakMag = 0;
|
||||
@@ -55,7 +34,7 @@ public static class MusicAnalyzer
|
||||
melodyEnergy = Math.Sqrt(melodyEnergy / (melodyHiBin - melodyLoBin + 1));
|
||||
double melodyFreq = peakBin * binWidth;
|
||||
|
||||
return (rhythmEnergy, rhythmFlux, melodyEnergy, melodyFreq);
|
||||
return (melodyEnergy, melodyFreq);
|
||||
}
|
||||
|
||||
public static int MapPitchToPeriod(double hz)
|
||||
@@ -73,7 +52,6 @@ public static class MusicAnalyzer
|
||||
|
||||
public class TickFeature
|
||||
{
|
||||
public double RhythmFlux;
|
||||
public double MelodyEnergy;
|
||||
public double MelodyCount;
|
||||
public readonly List<double> MelodyFreqSamples = new();
|
||||
@@ -105,44 +83,4 @@ public static class MusicAnalyzer
|
||||
|
||||
return new WaveFrame(freqB, intB);
|
||||
}
|
||||
|
||||
public static (WaveFrame chA, WaveFrame chB) BuildWaveFrame(TickFeature tf, double maxFlux, double maxMelodyEnergy)
|
||||
{
|
||||
const int rhythmFreqMs = 150;
|
||||
|
||||
double normalizedFlux = maxFlux > 0 ? tf.RhythmFlux / maxFlux : 0;
|
||||
int onsetIntensity = (int)Math.Round(normalizedFlux * 100);
|
||||
var intA = new[]
|
||||
{
|
||||
(byte)Math.Clamp(onsetIntensity, 0, 100),
|
||||
(byte)Math.Clamp(onsetIntensity * 6 / 10, 0, 100),
|
||||
(byte)Math.Clamp(onsetIntensity * 3 / 10, 0, 100),
|
||||
(byte)0
|
||||
};
|
||||
var freqA = Freq.Compress4(new[] { rhythmFreqMs, rhythmFreqMs, rhythmFreqMs, rhythmFreqMs });
|
||||
|
||||
double avgEnergy = tf.MelodyCount > 0 ? tf.MelodyEnergy / tf.MelodyCount : 0;
|
||||
double normalizedEnergy = maxMelodyEnergy > 0 ? avgEnergy / maxMelodyEnergy : 0;
|
||||
int melodyIntensity = (int)Math.Round(normalizedEnergy * 80);
|
||||
melodyIntensity = Math.Clamp(melodyIntensity, 0, 100);
|
||||
|
||||
double weightedFreq = 0;
|
||||
double totalWeight = 0;
|
||||
foreach (var f in tf.MelodyFreqSamples)
|
||||
{
|
||||
weightedFreq += f * f;
|
||||
totalWeight += f;
|
||||
}
|
||||
double avgMelodyHz = totalWeight > 0 ? weightedFreq / totalWeight : 500;
|
||||
int estimsMs = MapPitchToPeriod(avgMelodyHz);
|
||||
|
||||
var intB = new[]
|
||||
{
|
||||
(byte)melodyIntensity, (byte)melodyIntensity,
|
||||
(byte)melodyIntensity, (byte)melodyIntensity
|
||||
};
|
||||
var freqB = Freq.Compress4(new[] { estimsMs, estimsMs, estimsMs, estimsMs });
|
||||
|
||||
return (new WaveFrame(freqA, intA), new WaveFrame(freqB, intB));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@ static class Program
|
||||
var state = new State();
|
||||
var server = new Server(device, state, Port);
|
||||
|
||||
device.StrengthChanged += (a, b) =>
|
||||
{
|
||||
state.ActualA = a;
|
||||
state.ActualB = b;
|
||||
};
|
||||
|
||||
Application.Run(new MainForm(device, state, server));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +105,6 @@ public class Command
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Channel { get; set; }
|
||||
|
||||
[JsonPropertyName("mode")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Mode { get; set; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public int? Value { get; set; }
|
||||
|
||||
@@ -242,12 +242,7 @@ public class Server
|
||||
|
||||
string DoStatus()
|
||||
{
|
||||
return JsonSerializer.Serialize(new StatusResponse
|
||||
{
|
||||
Connected = _device.IsConnected,
|
||||
StrengthA = _device.StrengthA,
|
||||
StrengthB = _device.StrengthB
|
||||
}, JsonOpts);
|
||||
return BuildStatusPush(null);
|
||||
}
|
||||
|
||||
string DoStrength(Command cmd)
|
||||
|
||||
@@ -15,8 +15,6 @@ public class State
|
||||
public readonly ConcurrentQueue<WaveFrame> StreamA = new();
|
||||
public readonly ConcurrentQueue<WaveFrame> StreamB = new();
|
||||
|
||||
public int ActualA, ActualB;
|
||||
public int LastSentA, LastSentB;
|
||||
public double LimitScaleA, LimitScaleB;
|
||||
public byte[]? LastFreqA, LastIntA, LastFreqB, LastIntB;
|
||||
public bool LastActiveA, LastActiveB;
|
||||
@@ -133,8 +131,6 @@ public class State
|
||||
: (byte)Math.Clamp(desired, 0, max);
|
||||
var valA = ApplyLimit(DesiredA, MaxA, LimitModeA);
|
||||
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;
|
||||
@@ -167,7 +163,6 @@ public class State
|
||||
(modeA, modeB) = (modeB, modeA);
|
||||
(freqA, freqB) = (freqB, freqA);
|
||||
(intA, intB) = (intB, intA);
|
||||
(LastSentA, LastSentB) = (LastSentB, LastSentA);
|
||||
(LastFreqA, LastFreqB) = (LastFreqB, LastFreqA);
|
||||
(LastIntA, LastIntB) = (LastIntB, LastIntA);
|
||||
(LastActiveA, LastActiveB) = (LastActiveB, LastActiveA);
|
||||
|
||||
Reference in New Issue
Block a user