App 2.44.0: the pumpkin light follows the kick drum, behind a switch
MusicPulse reads 40-120 Hz and lights only on jumps of the bass, so held notes and bass lines stay dark. Settings gets "Pumpkin light follows the music" (Halloween only, off by default); off, the audio analyser never starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -2,32 +2,45 @@ package com.evsuite.abrp;
|
||||
|
||||
/**
|
||||
* How strongly the Halloween pumpkin's light answers the music at this moment, from 0 to 1, out of
|
||||
* the bass the audio analyser reports. What counts is the beat, not the loudness: each reading is
|
||||
* placed between the bass's recent average and its recent peak, so quiet and loud songs both
|
||||
* pulse and a steady hum settles at nothing. It rises fast and falls slowly, as a flame does. No
|
||||
* reading or silence falls back to 0, so without music the light stays as it is. Android-free and
|
||||
* unit-tested.
|
||||
* the kick drum's band the audio analyser reports. What counts is the hit, not the loudness: only
|
||||
* a jump of the bass since the last reading lights it, measured against the biggest jumps of the
|
||||
* last seconds, so quiet and loud songs both flash, and a held bass note or a steady hum does not.
|
||||
* Jumps that are small against the bass's own level (a swelling note, noise) are ignored. It
|
||||
* flares at once and dies down within a beat, as a flame does. No reading or silence falls back to
|
||||
* 0, so without music the light stays as it is. Android-free and unit-tested.
|
||||
*/
|
||||
final class MusicPulse {
|
||||
|
||||
/** Below this the bass counts as silence. */
|
||||
static final float SILENT = 0.005f;
|
||||
private static final float RISE_S = 0.03f, FALL_S = 0.25f, AVERAGE_S = 2f, PEAK_S = 4f;
|
||||
/** The kick drum's band, in Hz, that {@link MusicTap} reads. */
|
||||
static final float FROM_HZ = 40f, TO_HZ = 120f;
|
||||
private static final float FALL_S = 0.2f, AVERAGE_S = 2f, PEAK_S = 4f;
|
||||
/** A jump below this share of the recent biggest one lights nothing. */
|
||||
private static final float THRESHOLD = 0.2f;
|
||||
/** A jump has to be at least this share of the bass's average level to count at all. */
|
||||
private static final float LEVEL_SHARE = 0.5f;
|
||||
|
||||
private float average, peak, pulse;
|
||||
private float last, average, peakJump, pulse;
|
||||
|
||||
/** One reading, {@code seconds} after the last; null when it failed. Returns the new pulse. */
|
||||
float update(Float bass, float seconds) {
|
||||
float target = 0f;
|
||||
if (bass == null || bass.isNaN() || bass < SILENT) {
|
||||
last = 0f;
|
||||
average = 0f;
|
||||
peak = 0f;
|
||||
peakJump = 0f;
|
||||
} else {
|
||||
float jump = Math.max(0f, bass - last);
|
||||
last = bass;
|
||||
average = average == 0f ? bass : average + (bass - average) * share(seconds, AVERAGE_S);
|
||||
peak = Math.max(bass, peak - peak * share(seconds, PEAK_S));
|
||||
if (peak > average) target = Math.max(0f, Math.min(1f, (bass - average) / (peak - average)));
|
||||
peakJump = Math.max(jump, peakJump - peakJump * share(seconds, PEAK_S));
|
||||
float reference = Math.max(peakJump, average * LEVEL_SHARE);
|
||||
if (reference > 0f) {
|
||||
target = Math.max(0f, Math.min(1f, (jump / reference - THRESHOLD) / (1f - THRESHOLD)));
|
||||
}
|
||||
}
|
||||
pulse += (target - pulse) * share(seconds, target > pulse ? RISE_S : FALL_S);
|
||||
pulse = target > pulse ? target : pulse + (target - pulse) * share(seconds, FALL_S);
|
||||
if (pulse < 0.005f) pulse = 0f;
|
||||
return pulse;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user