debugview and loop improvements

This commit is contained in:
vadimwit
2026-03-10 00:21:40 +00:00
parent 3aadf1afbb
commit b989238373
4 changed files with 374 additions and 39 deletions
+45 -1
View File
@@ -81,7 +81,7 @@ function detectBassPC(freqData, sampleRate, fftSize) {
return ((bestMidi % 12) + 12) % 12
}
export default function AudioCapture({ onNote, onChroma, onOnset, isListening, minClarity = 0.80, minVolume = 0.01, onPermissionError }) {
export default function AudioCapture({ onNote, onChroma, onOnset, onWaveform, isListening, minClarity = 0.80, minVolume = 0.01, onPermissionError }) {
const audioCtxRef = useRef(null)
const timeBufRef = useRef(null)
const freqBufRef = useRef(null)
@@ -94,20 +94,24 @@ export default function AudioCapture({ onNote, onChroma, onOnset, isListening, m
const onNoteRef = useRef(onNote)
const onChromaRef = useRef(onChroma)
const onOnsetRef = useRef(onOnset)
const onWaveformRef = useRef(onWaveform)
const onPermissionErrorRef = useRef(onPermissionError)
const minClarityRef = useRef(minClarity)
const minVolumeRef = useRef(minVolume)
const smoothRmsRef = useRef(0)
const lastOnsetRef = useRef(0)
const specPeakRef = useRef(null) // peak-hold spectrum for display lingering
useEffect(() => { onNoteRef.current = onNote }, [onNote])
useEffect(() => { onChromaRef.current = onChroma }, [onChroma])
useEffect(() => { onOnsetRef.current = onOnset }, [onOnset])
useEffect(() => { onWaveformRef.current = onWaveform }, [onWaveform])
useEffect(() => { onPermissionErrorRef.current = onPermissionError }, [onPermissionError])
useEffect(() => { minClarityRef.current = minClarity }, [minClarity])
useEffect(() => { minVolumeRef.current = minVolume }, [minVolume])
const stop = useCallback(() => {
activeRef.current = false
specPeakRef.current = null
if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null }
if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null }
if (audioCtxRef.current) { audioCtxRef.current.close(); audioCtxRef.current = null }
@@ -163,6 +167,46 @@ export default function AudioCapture({ onNote, onChroma, onOnset, isListening, m
onOnsetRef.current?.()
}
// Always fire waveform callback — downsample 4096 → 512 points + log-binned spectrum
if (onWaveformRef.current) {
const stride = 8 // 4096 / 8 = 512 points
const wave = new Float32Array(PITCH_FFT / stride)
for (let i = 0; i < wave.length; i++) wave[i] = timeBuf[i * stride]
// Log-binned frequency spectrum: 256 bins from 40 Hz → 4000 Hz
const LOG_BINS = 256
const F_MIN = 40, F_MAX = 4000
const binHz = ctx.sampleRate / ca.fftSize
const freqBuf = freqBufRef.current
ca.getFloatFrequencyData(freqBuf)
const spectrum = new Float32Array(LOG_BINS)
for (let b = 0; b < LOG_BINS; b++) {
const f = F_MIN * Math.pow(F_MAX / F_MIN, b / (LOG_BINS - 1))
const bin = Math.round(f / binHz)
if (bin < freqBuf.length) {
const db = freqBuf[bin]
spectrum[b] = db < NOISE_FLOOR ? 0 : Math.max(0, (db - NOISE_FLOOR) / (-NOISE_FLOOR))
}
}
// Peak-hold with exponential decay — spectrum rises instantly, falls slowly
if (!specPeakRef.current) specPeakRef.current = new Float32Array(LOG_BINS)
const peak = specPeakRef.current
for (let b = 0; b < LOG_BINS; b++) {
peak[b] = spectrum[b] > peak[b] ? spectrum[b] : peak[b] * 0.92
}
let detectedFreq = null, detectedNote = null
if (rms >= minVolumeRef.current) {
const [f, c] = detectorRef.current.findPitch(timeBuf, ctx.sampleRate)
if (c >= minClarityRef.current && f > 60 && f < 4200) {
detectedFreq = f
detectedNote = NOTES[((Math.round(12 * Math.log2(f / 440) + 69) % 12) + 12) % 12]
}
}
onWaveformRef.current({ wave, rms, detectedFreq, detectedNote, spectrum: peak })
}
if (rms >= minVolumeRef.current) {
const [freq, clarity] = detectorRef.current.findPitch(timeBuf, ctx.sampleRate)
if (clarity >= minClarityRef.current && freq > 60 && freq < 4200) {