package com.evsuite.abrp; /** * How strongly the Halloween pumpkin's light answers the music at this moment, from 0 to 1, out of * 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; /** 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 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; peakJump = 0f; } else { float jump = Math.max(0f, bass - last); last = bass; average = average == 0f ? bass : average + (bass - average) * share(seconds, AVERAGE_S); 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 ? target : pulse + (target - pulse) * share(seconds, 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); } }