App 2.43.4: the Halloween pumpkin's light follows the music

The bass from Android's audio analyser drives the glow and brightens the
carving on the beat, only while the style is on screen. Yellow and green and
the embers keep their meaning; without a signal, as with the radio, the light
stays as it was. Levels only. Tried on the emulator. Not tried on the car.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-11-01 10:07:01 +01:00
co-authored by Claude Opus 5.5
parent ea9763e1de
commit fd38c37ebe
9 changed files with 252 additions and 15 deletions
@@ -0,0 +1,38 @@
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.
*/
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;
private float average, peak, 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) {
average = 0f;
peak = 0f;
} else {
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)));
}
pulse += (target - pulse) * share(seconds, target > pulse ? RISE_S : FALL_S);
if (pulse < 0.005f) pulse = 0f;
return pulse;
}
private static float share(float seconds, float timeConstant) {
return Math.min(1f, Math.max(0f, seconds) / timeConstant);
}
}