Files
2024-01-02 14:11:31 +01:00

122 lines
3.6 KiB
C#

using NAudio.Dsp;
using NAudio.Wave;
using System;
using System.Linq;
namespace BeatDetector.AudioProcessing;
internal class AudioBeatDetector : IDisposable
{
public event EventHandler<BeatDetectorEventArgs>? OnBeat;
public event EventHandler<BeatDetectorEventArgs>? OnNoBeat;
private readonly IWaveIn waveIn;
private readonly SampleAggregator sampleAggregator;
private readonly TimeSpan beatDetectionDelay;
private readonly int fftLength;
private readonly int chunkCount;
private readonly BeatChunk[] beatChunks;
public AudioBeatDetector(IWaveIn waveIn, TimeSpan beatDetectionDelay, int fftLength, int chunkCount)
{
this.waveIn = waveIn;
this.beatDetectionDelay = beatDetectionDelay;
this.chunkCount = chunkCount;
this.fftLength = fftLength;
beatChunks = new BeatChunk[chunkCount];
sampleAggregator = new SampleAggregator(fftLength);
for (int i = 0; i < chunkCount; i++)
{
beatChunks[i] = new BeatChunk();
beatChunks[i].StopWatch.Start();
}
sampleAggregator.FftCalculated += new EventHandler<FftEventArgs>(FftCalculated);
sampleAggregator.PerformFFT = true;
waveIn.DataAvailable += OnDataAvailable;
}
public void Dispose()
{
waveIn.DataAvailable -= OnDataAvailable;
}
private void OnDataAvailable(object? sender, WaveInEventArgs e)
{
byte[] buffer = e.Buffer;
int bytesRecorded = e.BytesRecorded;
int bufferIncrement = waveIn.WaveFormat.BlockAlign;
for (int index = 0; index < bytesRecorded; index += bufferIncrement)
{
float sample32 = BitConverter.ToSingle(buffer, index);
sampleAggregator.Add(sample32);
}
}
private void FftCalculated(object? sender, FftEventArgs e)
{
Complex[][] chunks = e.Result.Chunk(fftLength / chunkCount).ToArray();
for (int i = 0; i < chunkCount; i++)
{
CalculateBeat(chunks[i], beatChunks[i], i);
}
}
private void CalculateBeat(Complex[] values, BeatChunk beatChunk, int chunkIndex)
{
double energyLevel = 0;
foreach (Complex value in values)
{
energyLevel += Math.Pow(Math.Abs(value.Y), 2);
}
double averageEnergyLevel = beatChunk.EnergyHistory.Count > 0 ? beatChunk.EnergyHistory.Average() : double.MaxValue;
if (beatChunk.StopWatch.Elapsed >= beatDetectionDelay || beatDetectionDelay <= TimeSpan.Zero)
{
double difference = energyLevel - averageEnergyLevel;
if (difference > beatChunk.AverageDifference / 2)
{
beatChunk.AverageDifference = (difference + beatChunk.AverageDifference) / 2d;
OnBeat?.Invoke(this, new BeatDetectorEventArgs(chunkIndex, difference));
}
else
{
//if (difference > 0)
//{
// beatChunk.AverageDifference = (difference + beatChunk.AverageDifference) / 2.00001;
//}
OnNoBeat?.Invoke(this, new BeatDetectorEventArgs(chunkIndex, 0));
}
beatChunk.StopWatch.Restart();
}
beatChunk.EnergyHistory.Add(energyLevel);
if (beatChunk.EnergyHistory.Count > 43)
{
beatChunk.EnergyHistory.RemoveAt(0);
}
}
}
public class BeatDetectorEventArgs : EventArgs
{
public int ChunkIndex { get; }
public double DetectedValue { get; }
public BeatDetectorEventArgs(int chunkIndex, double detectedValue)
{
ChunkIndex = chunkIndex;
DetectedValue = detectedValue;
}
}