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
@@ -25,10 +25,13 @@ import java.util.Locale;
* tombstone reads the range ("here lies … km"); P R N D are four small gravestones, the one engaged
* lit. The bats are the range too, one per 50 km. The hill along the bottom is the last two minutes
* of power, rising with it, in orange or, where it regenerated, green. Unread values show "—" and
* light nothing.
* light nothing. With music playing, the pumpkin's glow swells and its carving brightens on the
* beat ({@link MusicTap}); colours and the embers keep their meaning, and with no music it burns as
* before.
*
* The night sky is drawn once into a bitmap per size; only the rest goes over it. Bats, bubbles and
* the ghost move (about 30 frames a second) only while the car moves and the view is on screen.
* the ghost move (about 30 frames a second) only while the car moves and the view is on screen;
* the light's pulse keeps it drawing while there is one.
*/
final class HalloweenView extends ClusterCanvasView {
@@ -56,6 +59,7 @@ final class HalloweenView extends ClusterCanvasView {
private int skyW, skyH;
private float time;
private long lastFrame;
private MusicTap music;
HalloweenView(Context context) {
super(context);
@@ -63,6 +67,11 @@ final class HalloweenView extends ClusterCanvasView {
line.setStrokeCap(Paint.Cap.ROUND);
}
/** Where the pumpkin's light hears the music; without it, the light does not follow any. */
void setMusic(MusicTap tap) {
music = tap;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -91,12 +100,13 @@ final class HalloweenView extends ClusterCanvasView {
drawGhost(c, w, u);
drawHill(c, w, h, u);
drawEmbers(c, w, u);
drawPumpkin(c, w, u);
float pulse = music == null ? 0f : music.pulse();
drawPumpkin(c, w, u, pulse);
drawSign(c, u);
drawFlask(c, u);
drawTombstone(c, w, u);
drawGears(c, w, u);
keepAnimating(moving, 33);
keepAnimating(moving || pulse > 0f, 33);
}
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
@@ -289,16 +299,18 @@ final class HalloweenView extends ClusterCanvasView {
// ---------- The jack-o'-lantern: the speed ----------
private void drawPumpkin(Canvas c, int w, float u) {
private void drawPumpkin(Canvas c, int w, float u, float pulse) {
float cx = w / 2f, cy = 348f * u, rx = 200f * u, ry = 158f * u;
boolean regen = regen();
int burn = regen ? FIRE : CANDLE;
// The light it throws, stronger the more power.
// The carving brightens on the beat, gently, and stays yellow or green.
int burn = brighter(regen ? FIRE : CANDLE, pulse * 0.35f);
// The light it throws, stronger the more power, swelling with the music.
float share = v.powerKw == null ? 0f : Math.min(1f, Math.abs(v.powerKw) / 80f);
glow.setShader(new RadialGradient(cx, cy, rx * 1.6f,
new int[] { AccentColor.withAlpha(regen ? FIRE : ORANGE, 0x30 + (int) (share * 0x50)),
float reach = rx * (1.6f + pulse * 0.2f);
glow.setShader(new RadialGradient(cx, cy, reach,
new int[] { AccentColor.withAlpha(regen ? FIRE : ORANGE, 0x30 + (int) (share * 0x50) + (int) (pulse * 0x60)),
AccentColor.withAlpha(regen ? FIRE : ORANGE, 0) }, null, Shader.TileMode.CLAMP));
c.drawCircle(cx, cy, rx * 1.6f, glow);
c.drawCircle(cx, cy, reach, glow);
// The stem.
path.reset();
path.moveTo(cx - 22f * u, cy - ry + 12f * u);
@@ -350,6 +362,13 @@ final class HalloweenView extends ClusterCanvasView {
label(c, power, cx, cy + ry + 44f * u, 34f * u, regen ? FIRE : BONE, face, Paint.Align.CENTER);
}
/** The colour moved {@code share} of the way to white. */
private static int brighter(int colour, float share) {
int r = (colour >> 16) & 0xFF, g = (colour >> 8) & 0xFF, b = colour & 0xFF;
return 0xFF000000 | (r + (int) ((255 - r) * share)) << 16 | (g + (int) ((255 - g) * share)) << 8
| (b + (int) ((255 - b) * share));
}
private void triangle(Canvas c, float x1, float y1, float x2, float y2, float x3, float y3, int burn, float u) {
path.reset();
path.moveTo(x1, y1);