Add live capture diagnostic logging to live.log

- Logs device name, audio format (bit depth, channels, sample rate)
- Logs dataAvailable callbacks (every 100th): bytes, samples, buffer size
- Logs FFT ticks (every 50th): melody energy, freq, intensity A/B
- Warning if data arrives but buffer doesn't fill
- Logs totals + any exception on stop
- Writes to live.log next to .exe, cleared on each start
This commit is contained in:
2026-08-14 09:54:47 +00:00
parent 496cc5a6b2
commit 1ce20544a4
+49 -2
View File
@@ -1,6 +1,7 @@
using FftSharp; using FftSharp;
using NAudio.CoreAudioApi; using NAudio.CoreAudioApi;
using NAudio.Wave; using NAudio.Wave;
using System.IO;
namespace Substation; namespace Substation;
@@ -21,6 +22,23 @@ public class LiveCapture : IDisposable
int _fftIndexInTick; int _fftIndexInTick;
readonly DrumDetector _drums = new(); readonly DrumDetector _drums = new();
int _dataAvailableCount;
int _totalSamplesReceived;
int _fftCount;
int _tickCount;
static readonly string LogPath = Path.Combine(AppContext.BaseDirectory, "live.log");
static readonly object LogLock = new();
static void Log(string msg)
{
var line = $"{DateTime.Now:HH:mm:ss.fff} {msg}";
lock (LogLock)
{
try { File.AppendAllText(LogPath, line + Environment.NewLine); } catch { }
}
}
public event Action? Stopped; public event Action? Stopped;
public LiveCapture(State state, MMDevice device) public LiveCapture(State state, MMDevice device)
@@ -36,6 +54,12 @@ public class LiveCapture : IDisposable
_sampleRate = _capture.WaveFormat.SampleRate; _sampleRate = _capture.WaveFormat.SampleRate;
_fftsPerTick = Math.Max(1, (int)Math.Round(MusicAnalyzer.TickDuration * _sampleRate / MusicAnalyzer.HopSize)); _fftsPerTick = Math.Max(1, (int)Math.Round(MusicAnalyzer.TickDuration * _sampleRate / MusicAnalyzer.HopSize));
try { File.WriteAllText(LogPath, ""); } catch { }
Log($"[live] device: {_device.FriendlyName}");
Log($"[live] format: {_capture.WaveFormat} ({_capture.WaveFormat.BitsPerSample}bit, {_capture.WaveFormat.Channels}ch, {_sampleRate}Hz)");
Log($"[live] fftsPerTick: {_fftsPerTick}");
_capture.DataAvailable += OnDataAvailable; _capture.DataAvailable += OnDataAvailable;
_capture.RecordingStopped += OnRecordingStopped; _capture.RecordingStopped += OnRecordingStopped;
@@ -43,7 +67,8 @@ public class LiveCapture : IDisposable
_processThread.Start(); _processThread.Start();
_capture.StartRecording(); _capture.StartRecording();
Console.WriteLine($"[live] capture started: {_device.FriendlyName} ({_sampleRate}Hz, {_capture.WaveFormat.Channels}ch)"); Log($"[live] capture started");
Log($"[live] log file: {LogPath}");
} }
public void Stop() public void Stop()
@@ -59,6 +84,12 @@ public class LiveCapture : IDisposable
int frameSize = channels * bytesPerSample; int frameSize = channels * bytesPerSample;
int sampleCount = e.BytesRecorded / frameSize; int sampleCount = e.BytesRecorded / frameSize;
_dataAvailableCount++;
_totalSamplesReceived += sampleCount;
if (_dataAvailableCount % 100 == 1)
Log($"[live] dataAvailable #{_dataAvailableCount}: {e.BytesRecorded} bytes, {sampleCount} samples, total={_totalSamplesReceived}, buffer={_sampleBuffer.Count}");
lock (_bufferLock) lock (_bufferLock)
{ {
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
@@ -81,7 +112,9 @@ public class LiveCapture : IDisposable
{ {
_running = false; _running = false;
Stopped?.Invoke(); Stopped?.Invoke();
Console.WriteLine("[live] capture stopped"); Log($"[live] capture stopped. dataAvailable={_dataAvailableCount}, totalSamples={_totalSamplesReceived}, ffts={_fftCount}, ticks={_tickCount}");
if (e?.Exception != null)
Log($"[live] ERROR stop exception: {e.Exception.Message}");
} }
void ProcessLoop() void ProcessLoop()
@@ -91,6 +124,8 @@ public class LiveCapture : IDisposable
var tf = new MusicAnalyzer.TickFeature(); var tf = new MusicAnalyzer.TickFeature();
double[]? overlap = null; // last HopSize samples from previous window double[]? overlap = null; // last HopSize samples from previous window
Log("[live] process thread started");
while (_running) while (_running)
{ {
double[]? windowData = null; double[]? windowData = null;
@@ -120,6 +155,8 @@ public class LiveCapture : IDisposable
if (windowData == null) if (windowData == null)
{ {
if (_fftCount == 0 && _dataAvailableCount > 0 && _dataAvailableCount % 200 == 0)
Log($"[live] WARNING: data available ({_dataAvailableCount} callbacks, {_totalSamplesReceived} samples) but buffer has only {_sampleBuffer.Count} samples (need {MusicAnalyzer.WindowSize})");
Thread.Sleep(5); Thread.Sleep(5);
continue; continue;
} }
@@ -128,6 +165,8 @@ public class LiveCapture : IDisposable
var spectrum = FFT.Forward(windowData); var spectrum = FFT.Forward(windowData);
var mag = FFT.Magnitude(spectrum); var mag = FFT.Magnitude(spectrum);
_fftCount++;
var (melodyEnergy, melodyFreq) = MusicAnalyzer.ExtractFeatures(mag, _sampleRate); var (melodyEnergy, melodyFreq) = MusicAnalyzer.ExtractFeatures(mag, _sampleRate);
// Adaptive normalization: running max with slow decay // Adaptive normalization: running max with slow decay
@@ -151,6 +190,14 @@ public class LiveCapture : IDisposable
_state.EnqueueStream('A', new[] { frameA }); _state.EnqueueStream('A', new[] { frameA });
_state.EnqueueStream('B', new[] { frameB }); _state.EnqueueStream('B', new[] { frameB });
_tickCount++;
if (_tickCount % 50 == 1)
{
int intA = (frameA.Intensity[0] + frameA.Intensity[1] + frameA.Intensity[2] + frameA.Intensity[3]) / 4;
int intB = (frameB.Intensity[0] + frameB.Intensity[1] + frameB.Intensity[2] + frameB.Intensity[3]) / 4;
Log($"[live] tick #{_tickCount}: ffts={_fftCount}, melodyEnergy={melodyEnergy:F4}, maxMelody={_liveMaxMelodyEnergy:F4}, freq={melodyFreq:F0}Hz, intA={intA}, intB={intB}, freqA={frameA.Freq[0]}, freqB={frameB.Freq[0]}");
}
tf = new MusicAnalyzer.TickFeature(); tf = new MusicAnalyzer.TickFeature();
_fftIndexInTick = 0; _fftIndexInTick = 0;
} }