1ab397767a
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
142 lines
4.4 KiB
C#
142 lines
4.4 KiB
C#
namespace Substation;
|
|
|
|
public class DrumDetector
|
|
{
|
|
const int HistorySize = 50;
|
|
|
|
readonly double[] _lowFluxHistory = new double[HistorySize];
|
|
readonly double[] _midFluxHistory = new double[HistorySize];
|
|
readonly double[] _highFluxHistory = new double[HistorySize];
|
|
int _historyIndex;
|
|
|
|
double[]? _prevLowMag;
|
|
double[]? _prevMidMag;
|
|
double[]? _prevHighMag;
|
|
|
|
readonly bool[] _subKick = new bool[4];
|
|
readonly bool[] _subSnare = new bool[4];
|
|
readonly bool[] _subBrass = new bool[4];
|
|
|
|
public void ProcessWindow(double[] magnitude, int sampleRate, int windowSize, int subTickIndex)
|
|
{
|
|
if (subTickIndex < 0 || subTickIndex > 3) return;
|
|
|
|
double binWidth = (double)sampleRate / windowSize;
|
|
int lowLo = (int)(20 / binWidth), lowHi = (int)(150 / binWidth);
|
|
int midLo = (int)(200 / binWidth), midHi = (int)(500 / binWidth);
|
|
int highLo = (int)(5000 / binWidth), highHi = (int)(15000 / binWidth);
|
|
|
|
var lowMag = ExtractBand(magnitude, lowLo, lowHi);
|
|
var midMag = ExtractBand(magnitude, midLo, midHi);
|
|
var highMag = ExtractBand(magnitude, highLo, highHi);
|
|
|
|
double lowFlux = ComputeFlux(lowMag, _prevLowMag);
|
|
double midFlux = ComputeFlux(midMag, _prevMidMag);
|
|
double highFlux = ComputeFlux(highMag, _prevHighMag);
|
|
|
|
_prevLowMag = lowMag;
|
|
_prevMidMag = midMag;
|
|
_prevHighMag = highMag;
|
|
|
|
// Update history and compute adaptive thresholds
|
|
_lowFluxHistory[_historyIndex] = lowFlux;
|
|
_midFluxHistory[_historyIndex] = midFlux;
|
|
_highFluxHistory[_historyIndex] = highFlux;
|
|
_historyIndex = (_historyIndex + 1) % HistorySize;
|
|
|
|
double lowThresh = AdaptiveThreshold(_lowFluxHistory);
|
|
double midThresh = AdaptiveThreshold(_midFluxHistory);
|
|
double highThresh = AdaptiveThreshold(_highFluxHistory);
|
|
|
|
// Detect onsets
|
|
bool kickOnset = lowFlux > lowThresh && lowFlux > 0.001;
|
|
bool snareOnset = midFlux > midThresh && highFlux > highThresh && midFlux > 0.001;
|
|
bool brassOnset = highFlux > highThresh && !snareOnset && highFlux > 0.001;
|
|
|
|
if (kickOnset) _subKick[subTickIndex] = true;
|
|
if (snareOnset) _subSnare[subTickIndex] = true;
|
|
if (brassOnset) _subBrass[subTickIndex] = true;
|
|
}
|
|
|
|
public WaveFrame BuildFrame()
|
|
{
|
|
var intensity = new byte[4];
|
|
var freqBytes = new byte[4];
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
byte freq;
|
|
if (_subKick[i])
|
|
{
|
|
intensity[i] = 100;
|
|
freq = Freq.Compress(150);
|
|
}
|
|
else if (_subSnare[i])
|
|
{
|
|
intensity[i] = 100;
|
|
freq = Freq.Compress(50);
|
|
}
|
|
else if (_subBrass[i])
|
|
{
|
|
intensity[i] = 100;
|
|
freq = Freq.Compress(10);
|
|
}
|
|
else
|
|
{
|
|
intensity[i] = 0;
|
|
freq = 10;
|
|
}
|
|
|
|
freqBytes[i] = freq;
|
|
}
|
|
|
|
// Reset for next tick
|
|
Array.Clear(_subKick, 0, 4);
|
|
Array.Clear(_subSnare, 0, 4);
|
|
Array.Clear(_subBrass, 0, 4);
|
|
|
|
return new WaveFrame(freqBytes, intensity);
|
|
}
|
|
|
|
static double[] ExtractBand(double[] magnitude, int loBin, int hiBin)
|
|
{
|
|
if (hiBin <= loBin || hiBin >= magnitude.Length)
|
|
return Array.Empty<double>();
|
|
|
|
var band = new double[hiBin - loBin + 1];
|
|
Array.Copy(magnitude, loBin, band, 0, band.Length);
|
|
return band;
|
|
}
|
|
|
|
static double ComputeFlux(double[] current, double[]? previous)
|
|
{
|
|
if (previous == null || current.Length != previous.Length) return 0;
|
|
|
|
double flux = 0;
|
|
for (int i = 0; i < current.Length; i++)
|
|
{
|
|
double diff = current[i] - previous[i];
|
|
if (diff > 0) flux += diff;
|
|
}
|
|
return flux;
|
|
}
|
|
|
|
static double AdaptiveThreshold(double[] history)
|
|
{
|
|
double sum = 0, sumSq = 0;
|
|
int count = 0;
|
|
foreach (var v in history)
|
|
{
|
|
if (v <= 0) continue;
|
|
sum += v;
|
|
sumSq += v * v;
|
|
count++;
|
|
}
|
|
if (count == 0) return double.MaxValue;
|
|
double mean = sum / count;
|
|
double variance = sumSq / count - mean * mean;
|
|
double stddev = variance > 0 ? Math.Sqrt(variance) : 0;
|
|
return mean + 2 * stddev;
|
|
}
|
|
}
|