fix(player): эквалайзер «в ритм» — перекрытие окон + быстрый спад

Визуализатор отставал/висел: медленный спад (бар держался ~300мс после
удара) и редкие обновления. Теперь FFT с перекрытием 50% (~43 обновл/с),
мгновенный рост на удар и быстрый спад (0.78→0.55), убран искусственный
троттл 33мс. Реакция плотнее попадает в бит.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-04 18:25:26 +03:00
parent 1e00287486
commit 1dfee941a0

View File

@@ -84,7 +84,6 @@ class AudioSpectrumAnalyzer(
private var channelCount = 2 private var channelCount = 2
private var pcm16 = true private var pcm16 = true
private var sampleRate = 44100 private var sampleRate = 44100
private var lastEmit = 0L
// Автогейн: бегущий пик амплитуды — чтобы столбики всегда использовали всю // Автогейн: бегущий пик амплитуды — чтобы столбики всегда использовали всю
// высоту независимо от громкости трека. // высоту независимо от громкости трека.
private var agcPeak = 1e-4f private var agcPeak = 1e-4f
@@ -100,22 +99,22 @@ class AudioSpectrumAnalyzer(
if (!pcm16) return if (!pcm16) return
val b = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN) val b = buffer.duplicate().order(ByteOrder.LITTLE_ENDIAN)
val ch = channelCount val ch = channelCount
val hop = fftSize / 2
while (b.remaining() >= 2 * ch) { while (b.remaining() >= 2 * ch) {
var sum = 0f var sum = 0f
for (c in 0 until ch) sum += b.short.toFloat() for (c in 0 until ch) sum += b.short.toFloat()
sample[filled++] = (sum / ch) / 32768f sample[filled++] = (sum / ch) / 32768f
if (filled >= fftSize) { if (filled >= fftSize) {
compute() compute()
filled = 0 // Перекрытие 50%: оставляем вторую половину — чаще обновляем (~43к/с),
// спектр идёт «впритык» к биту, без рывков.
System.arraycopy(sample, hop, sample, 0, fftSize - hop)
filled = fftSize - hop
} }
} }
} }
private fun compute() { private fun compute() {
val now = System.nanoTime()
if (now - lastEmit < 33_000_000L) return // ~30 кадров/с
lastEmit = now
for (i in 0 until fftSize) { for (i in 0 until fftSize) {
re[i] = sample[i] * window[i] re[i] = sample[i] * window[i]
im[i] = 0f im[i] = 0f
@@ -154,8 +153,8 @@ class AudioSpectrumAnalyzer(
// Нормируем по пику + перцептивный лифт (sqrt), чтобы тихое было видно. // Нормируем по пику + перцептивный лифт (sqrt), чтобы тихое было видно.
val v = sqrt((raw[band] / agcPeak).coerceIn(0f, 1f)) val v = sqrt((raw[band] / agcPeak).coerceIn(0f, 1f))
val prev = smoothed[band] val prev = smoothed[band]
// Быстрый рост, плавный спад — как у настоящего эквалайзера. // Мгновенный рост на удар, быстрый спад — чтобы попадать в ритм, не «висеть».
smoothed[band] = if (v > prev) v else prev * 0.78f + v * 0.22f smoothed[band] = if (v > prev) v else prev * 0.55f + v * 0.45f
out[band] = smoothed[band] out[band] = smoothed[band]
} }
_spectrum.value = out _spectrum.value = out