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:
@@ -1,11 +1,8 @@
|
||||
using System.Numerics;
|
||||
using FftSharp;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace Substation;
|
||||
|
||||
public record MusicPattern(List<WaveFrame> ChannelA, List<WaveFrame> ChannelB, TimeSpan Duration);
|
||||
|
||||
public static class MusicAnalyzer
|
||||
{
|
||||
public const int WindowSize = 2048;
|
||||
@@ -15,79 +12,6 @@ public static class MusicAnalyzer
|
||||
const double RhythmLow = 20, RhythmHigh = 250;
|
||||
const double MelodyLow = 300, MelodyHigh = 4000;
|
||||
|
||||
public static MusicPattern Analyze(string mp3Path, IProgress<int>? progress = null)
|
||||
{
|
||||
const int sampleRate = 44100;
|
||||
var samples = DecodeToMono(mp3Path, sampleRate);
|
||||
var duration = TimeSpan.FromSeconds((double)samples.Length / sampleRate);
|
||||
|
||||
var tickCount = (int)Math.Ceiling((double)samples.Length / sampleRate / TickDuration);
|
||||
var channelA = new List<WaveFrame>(tickCount);
|
||||
var channelB = new List<WaveFrame>(tickCount);
|
||||
|
||||
var window = new FftSharp.Windows.Hanning();
|
||||
var buffer = new double[WindowSize];
|
||||
int fftsPerTick = (int)Math.Round(TickDuration * sampleRate / HopSize);
|
||||
|
||||
double[]? prevRhythmMag = null;
|
||||
double maxFlux = 0;
|
||||
double maxMelodyEnergy = 0;
|
||||
|
||||
// First pass: collect per-tick features
|
||||
var tickFeatures = new List<TickFeature>(tickCount);
|
||||
int pos = 0;
|
||||
int fftIndex = 0;
|
||||
|
||||
while (pos + WindowSize <= samples.Length)
|
||||
{
|
||||
for (int i = 0; i < WindowSize; i++)
|
||||
buffer[i] = samples[pos + i];
|
||||
|
||||
window.ApplyInPlace(buffer);
|
||||
var spectrum = FFT.Forward(buffer);
|
||||
var mag = FFT.Magnitude(spectrum);
|
||||
|
||||
var (rhythmEnergy, rhythmFlux, melodyEnergy, melodyFreq) =
|
||||
ExtractFeatures(mag, prevRhythmMag, sampleRate);
|
||||
|
||||
if (rhythmFlux > maxFlux) maxFlux = rhythmFlux;
|
||||
if (melodyEnergy > maxMelodyEnergy) maxMelodyEnergy = melodyEnergy;
|
||||
|
||||
prevRhythmMag = mag;
|
||||
|
||||
int tickIdx = fftIndex / Math.Max(1, fftsPerTick);
|
||||
while (tickFeatures.Count <= tickIdx)
|
||||
tickFeatures.Add(new TickFeature());
|
||||
|
||||
var tf = tickFeatures[tickIdx];
|
||||
tf.RhythmFlux = Math.Max(tf.RhythmFlux, rhythmFlux);
|
||||
tf.MelodyEnergy += melodyEnergy;
|
||||
tf.MelodyFreqSamples.Add(melodyFreq);
|
||||
tf.MelodyCount++;
|
||||
|
||||
pos += HopSize;
|
||||
fftIndex++;
|
||||
|
||||
if (progress != null && fftIndex % 50 == 0)
|
||||
{
|
||||
var pct = (int)((double)pos / samples.Length * 100);
|
||||
progress.Report(pct);
|
||||
}
|
||||
}
|
||||
|
||||
progress?.Report(100);
|
||||
|
||||
// Second pass: normalize and build WaveFrames
|
||||
foreach (var tf in tickFeatures)
|
||||
{
|
||||
var (frameA, frameB) = BuildWaveFrame(tf, maxFlux, maxMelodyEnergy);
|
||||
channelA.Add(frameA);
|
||||
channelB.Add(frameB);
|
||||
}
|
||||
|
||||
return new MusicPattern(channelA, channelB, duration);
|
||||
}
|
||||
|
||||
public static (double rhythmEnergy, double rhythmFlux, double melodyEnergy, double melodyFreq)
|
||||
ExtractFeatures(double[] magnitude, double[]? prevRhythmMag, int sampleRate)
|
||||
{
|
||||
@@ -147,31 +71,6 @@ public static class MusicAnalyzer
|
||||
return Math.Clamp(ms, 10, 1000);
|
||||
}
|
||||
|
||||
static double[] DecodeToMono(string mp3Path, int sampleRate)
|
||||
{
|
||||
using var reader = new Mp3FileReader(mp3Path);
|
||||
var format = new WaveFormat(sampleRate, 16, 1);
|
||||
using var resampler = new MediaFoundationResampler(reader, format);
|
||||
resampler.ResamplerQuality = 60;
|
||||
|
||||
var sampleList = new List<float>();
|
||||
var buffer = new byte[sampleRate * 2]; // 1s worth of 16-bit mono
|
||||
int read;
|
||||
while ((read = resampler.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
for (int i = 0; i < read; i += 2)
|
||||
{
|
||||
short sample = (short)(buffer[i] | (buffer[i + 1] << 8));
|
||||
sampleList.Add(sample / 32768f);
|
||||
}
|
||||
}
|
||||
|
||||
var result = new double[sampleList.Count];
|
||||
for (int i = 0; i < sampleList.Count; i++)
|
||||
result[i] = sampleList[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
public class TickFeature
|
||||
{
|
||||
public double RhythmFlux;
|
||||
|
||||
Reference in New Issue
Block a user