Add drum detection for channel A in live capture

- DrumDetector.cs: 3-band onset detection (kick/snare/brass) with adaptive
  thresholds, per-sub-tick timing within 100ms frames
- Kick: low-band onset, 150ms deep thump
- Snare: broadband (mid+high) onset, 50ms mid punch
- Brass: high-only onset, 10ms buzzy
- Priority: kick > snare > brass when multiple hit same sub-tick
- Intensity = max (100) on hit, 0 on silence — user limiter scales down
- MusicAnalyzer: add BuildMelodyFrame for chB only (pitch+energy)
- LiveCapture: chA now from DrumDetector, chB unchanged (melody)
- Sub-tick mapping: FFT window position → 0-3 within each 100ms tick
This commit is contained in:
2026-08-09 11:32:16 +00:00
parent abb5364bb4
commit 4c44d48a02
3 changed files with 182 additions and 2 deletions
+9 -2
View File
@@ -22,6 +22,7 @@ public class LiveCapture : IDisposable
int _sampleRate;
int _fftsPerTick;
int _fftIndexInTick;
readonly DrumDetector _drums = new();
public event Action? Stopped;
@@ -139,7 +140,12 @@ public class LiveCapture : IDisposable
_liveMaxFlux = Math.Max(rhythmFlux, _liveMaxFlux * 0.999);
_liveMaxMelodyEnergy = Math.Max(melodyEnergy, _liveMaxMelodyEnergy * 0.999);
tf.RhythmFlux = Math.Max(tf.RhythmFlux, rhythmFlux);
// Drum detection: map this FFT window to a sub-tick (0-3)
int subTick = _fftsPerTick > 0 ? _fftIndexInTick * 4 / _fftsPerTick : 0;
if (subTick > 3) subTick = 3;
_drums.ProcessWindow(mag, _sampleRate, MusicAnalyzer.WindowSize, subTick);
// Melody accumulation for chB
tf.MelodyEnergy += melodyEnergy;
tf.MelodyFreqSamples.Add(melodyFreq);
tf.MelodyCount++;
@@ -147,7 +153,8 @@ public class LiveCapture : IDisposable
if (_fftIndexInTick >= _fftsPerTick)
{
var (frameA, frameB) = MusicAnalyzer.BuildWaveFrame(tf, _liveMaxFlux, _liveMaxMelodyEnergy);
var frameA = _drums.BuildFrame();
var frameB = MusicAnalyzer.BuildMelodyFrame(tf, _liveMaxMelodyEnergy);
_state.EnqueueStream('A', new[] { frameA });
_state.EnqueueStream('B', new[] { frameB });