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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user