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:
@@ -77,7 +77,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
|
||||
|
||||
| Tab | Contents |
|
||||
|---|---|
|
||||
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Nixie tubes, Chronograph, Grid (`GridView`, neon lines over a moving grid floor, power as a light-cycle trail from `ClusterValues.powerTrail`), Grid with map (the same `GridView` over the roads round the car, top-down: GPS while it is shown, tiles from the server's `/v1/map/roads` through `MapRoads`, cached in `files/map`; `RoadTile`, `MapGeometry`; the map is `MapLayerView`, a hardware layer drawn once and moved and turned by the GPU every display frame), Time circuits (`TimeCircuitsView`, the time machine's LED rows with `Segments14` letters and a flux capacitor), Quest (`QuestView`, a fantasy game interface), Visor (`VisorView`, a helmet display with a motion tracker of the power trail), Empire (`EmpireView`, a strategy game with an isometric world and ages by odometer, `EmpireAge`), Día de Muertos (`MuertosView`, an ofrenda: papel picado, candles, a calavera in a ring of marigolds), Halloween (`HalloweenView`, a jack-o'-lantern, a potion flask, a tombstone on a graveyard hill), both counting with `Tally`, the pumpkin's light following the music on screen (`MusicTap`, `MusicPulse`: Android's audio analyser, levels only; Bluetooth and CarPlay, not the radio); the drawn styles share `ClusterValues`. A style shown in its own palette (Sci-fi, Cartoon, Nixie, Time circuits, Quest, Empire, Día de Muertos, Halloween; Retro, Terminal, Grid and Visor with "Original") gives the whole app its colours (`StylePalette`, `res/values/style_palettes.xml`) instead of the accent colour. A button in the navigation's header hides the navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`); a swipe in from the left edge (`EdgeSwipe`) brings it back; without the app's own full screen the car's sidebar takes that edge, so a tab at the left edge (`edge_tab`) is tapped or swiped instead. Pages change only by the navigation (the pager's own swiping is off) |
|
||||
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Nixie tubes, Chronograph, Grid (`GridView`, neon lines over a moving grid floor, power as a light-cycle trail from `ClusterValues.powerTrail`), Grid with map (the same `GridView` over the roads round the car, top-down: GPS while it is shown, tiles from the server's `/v1/map/roads` through `MapRoads`, cached in `files/map`; `RoadTile`, `MapGeometry`; the map is `MapLayerView`, a hardware layer drawn once and moved and turned by the GPU every display frame), Time circuits (`TimeCircuitsView`, the time machine's LED rows with `Segments14` letters and a flux capacitor), Quest (`QuestView`, a fantasy game interface), Visor (`VisorView`, a helmet display with a motion tracker of the power trail), Empire (`EmpireView`, a strategy game with an isometric world and ages by odometer, `EmpireAge`), Día de Muertos (`MuertosView`, an ofrenda: papel picado, candles, a calavera in a ring of marigolds), Halloween (`HalloweenView`, a jack-o'-lantern, a potion flask, a tombstone on a graveyard hill), both counting with `Tally`, the pumpkin's light flaring on the kick drum while on screen, when "Pumpkin light follows the music" is on (off by default, `PREF_MUSIC_LIGHT`; `MusicTap`, `MusicPulse`: Android's audio analyser, 40–120 Hz, jumps not levels; Bluetooth and CarPlay, not the radio); the drawn styles share `ClusterValues`. A style shown in its own palette (Sci-fi, Cartoon, Nixie, Time circuits, Quest, Empire, Día de Muertos, Halloween; Retro, Terminal, Grid and Visor with "Original") gives the whole app its colours (`StylePalette`, `res/values/style_palettes.xml`) instead of the accent colour. A button in the navigation's header hides the navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`); a swipe in from the left edge (`EdgeSwipe`) brings it back; without the app's own full screen the car's sidebar takes that edge, so a tab at the left edge (`edge_tab`) is tapped or swiped instead. Pages change only by the navigation (the pager's own swiping is off) |
|
||||
| Consumption / Charging | period totals, daily log, charging sessions and curve |
|
||||
| Live data | on/off, ABRP API key and user token, upload frequency |
|
||||
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
|
||||
|
||||
@@ -140,6 +140,7 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
||||
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
||||
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
||||
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
||||
- 2026-11-02 — App 2.44.0: **the pumpkin light follows the bass, and only when switched on** (owner: "more focussed on the bass", "optional via a settings flag"). `MusicPulse` now reads the kick drum's band (40–120 Hz, was 20–250 Hz) and lights only on a jump since the last reading, against the biggest jumps of the last 4 s; a jump under a fifth of that, or under half the bass's average level, lights nothing. So kicks flash, and a held note, a wobbling bass line or a swelling note stay dark (unit tests with made-up patterns). It flares at once and falls in 0.2 s. New switch under the Overview style, shown only for Halloween: "Pumpkin light follows the music" (`PREF_MUSIC_LIGHT`), **off by default**; off, the analyser never starts. Tuned on made-up signals only: no Bluetooth or CarPlay sound report from the car has arrived yet. Next: now playing (title, artist, source) from the media sessions, in its own version. Tested: unit tests, lint, emulator (switch off by default and the analyser thread absent; on, it runs only on the Overview; a throwaway build playing a pulsing test tone: the glow flares with the switch on, and stays exactly still with it off). With Halloween chosen, the Settings card now scrolls a little to reach the tyre unit. Not tried on the car.
|
||||
- 2026-11-01 — App 2.43.5 published (`car-2.43.5`).
|
||||
- 2026-11-01 — App 2.43.5: **update checks log what happens** (`UpdateNetworkCheck`, `UpdateDiagnosis`; owner: updating from the car fails, "Can we log what happens? Maybe my SIM card has no data left"). Each check on Settings writes to the Logs tab: the networks Android sees (type, in use, internet, validated, roaming), whether the server's name resolves, how a failure happened, in words from its type or status only, and how many reports wait. On a failure it also asks `https://www.gstatic.com/generate_204`, the one request to anyone but the car server, to tell no data from a server that is down. A report goes to a USB stick and, when online, the server (`update-check-*.txt`; only the newest waits, so it cannot push out a recording). A check at start logs only a failure. The update log keeps 40 lines, not 10. An out-of-data SIM would also explain why the Bluetooth and CarPlay sound reports did not arrive; not confirmed. Tested: unit tests, lint, emulator with a fake server (online: the name fails, the internet test says it works; airplane mode: both fail, "none in use"; the real build installs and starts, its check not run there, having no server). Not tried on the car.
|
||||
- 2026-11-01 — App 2.43.4 published (`car-2.43.4`).
|
||||
|
||||
@@ -262,8 +262,9 @@ the range ("here lies … km"), and the bats are the range too, one per 50 km; P
|
||||
gravestones, the one engaged lit. The moon holds the clock, a sign hanging from a cobweb has trip,
|
||||
usage, climate and odometer, and the hill along the bottom is the last two minutes of power. While
|
||||
you drive the bats fly, the potion bubbles and the ghost floats (where there is room for it, as on
|
||||
this screen). With Bluetooth or CarPlay music playing, the pumpkin's glow swells and its carving
|
||||
brightens on the beat; the radio cannot be heard, so it burns as usual then. The app turns the
|
||||
this screen). With "Pumpkin light follows the music" on (Settings, shown with this style; off by
|
||||
default) and Bluetooth or CarPlay music playing, the pumpkin's glow flares and its carving
|
||||
brightens on each kick drum; the radio cannot be heard, so it burns as usual then. The app turns the
|
||||
pumpkin's orange with it.
|
||||
|
||||

|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 158
|
||||
versionName "2.43.5"
|
||||
versionCode 159
|
||||
versionName "2.44.0"
|
||||
|
||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||
|
||||
@@ -215,6 +215,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/** Whether the Overview hides the navigation (the button in the navigation's header). */
|
||||
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
|
||||
/** The Halloween pumpkin's light follows the music; off by default, so the analyser stays off. */
|
||||
static final String PREF_MUSIC_LIGHT = "music_light";
|
||||
|
||||
/** True while the Overview is full screen: the navigation hidden, the edge swipe armed. */
|
||||
private boolean overviewFull;
|
||||
@@ -1223,6 +1225,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
bindDriveMode();
|
||||
bindOverviewStyle();
|
||||
bindAccentColor();
|
||||
SwitchMaterial musicLight = settingsPane.findViewById(R.id.music_light_switch);
|
||||
musicLight.setChecked(prefs.getBoolean(PREF_MUSIC_LIGHT, false));
|
||||
musicLight.setOnCheckedChangeListener((btn, checked) -> {
|
||||
prefs.edit().putBoolean(PREF_MUSIC_LIGHT, checked).apply();
|
||||
updateMusicTap();
|
||||
});
|
||||
fullScreenSwitch = settingsPane.findViewById(R.id.full_screen_switch);
|
||||
fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs));
|
||||
fullScreenSwitch.setOnCheckedChangeListener((btn, checked) -> {
|
||||
@@ -1761,10 +1769,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
updateMusicTap();
|
||||
}
|
||||
|
||||
/** The pumpkin listens only while it can be seen: the app in front, on the Overview, in Halloween. */
|
||||
/**
|
||||
* The pumpkin listens only while it can be seen (the app in front, on the Overview, in
|
||||
* Halloween) and only when "Pumpkin light follows the music" is on.
|
||||
*/
|
||||
private void updateMusicTap() {
|
||||
ViewPager2 pager = findViewById(R.id.content);
|
||||
musicTap.setRunning(inFront && overviewStyle() == OverviewStyle.HALLOWEEN
|
||||
&& prefs.getBoolean(PREF_MUSIC_LIGHT, false)
|
||||
&& pager != null && pager.getCurrentItem() == 0);
|
||||
}
|
||||
|
||||
@@ -1828,6 +1840,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
TextView note = settingsPane.findViewById(R.id.accent_note);
|
||||
note.setVisibility(style.usesAccent ? View.GONE : View.VISIBLE);
|
||||
note.setText(getString(R.string.accent_not_used, styleLabels()[style.ordinal()]));
|
||||
int music = style == OverviewStyle.HALLOWEEN ? View.VISIBLE : View.GONE;
|
||||
settingsPane.findViewById(R.id.music_light_switch).setVisibility(music);
|
||||
settingsPane.findViewById(R.id.music_light_hint).setVisibility(music);
|
||||
}
|
||||
|
||||
/** The drop-down's labels, by {@link OverviewStyle} ordinal. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import android.media.audiofx.Visualizer;
|
||||
|
||||
/**
|
||||
* Listens to what the head unit plays for the Halloween pumpkin's light: Android's audio analyser
|
||||
* on the output mix, its bass read 25 times a second on a thread of its own, made into a
|
||||
* on the output mix, its kick-drum band read 25 times a second on a thread of its own, made into a
|
||||
* {@link MusicPulse}. Runs only while MainActivity says so (the style on screen, the app in
|
||||
* front). Levels only: no sound is kept and no microphone used. Where the analyser hears nothing
|
||||
* front, and "Pumpkin light follows the music" on). Levels only: no sound is kept and no microphone used. Where the analyser hears nothing
|
||||
* (the radio) or is not available, the pulse stays 0 and the pumpkin burns as usual.
|
||||
*/
|
||||
final class MusicTap {
|
||||
@@ -64,7 +64,7 @@ final class MusicTap {
|
||||
boolean read = analyser.getFft(fft) == Visualizer.SUCCESS;
|
||||
failures = read ? 0 : failures + 1;
|
||||
long now = System.nanoTime();
|
||||
float value = music.update(read ? SoundLevels.band(fft, rateHz, 20f, 250f) : null, (now - last) / 1e9f);
|
||||
float value = music.update(read ? SoundLevels.band(fft, rateHz, MusicPulse.FROM_HZ, MusicPulse.TO_HZ) : null, (now - last) / 1e9f);
|
||||
last = now;
|
||||
if (worker == me) pulse = value;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/accent_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
|
||||
<!-- Only Halloween listens to the music. -->
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/music_light_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_music_light" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
|
||||
<TextView android:id="@+id/music_light_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_music_light" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false">
|
||||
<LinearLayout android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:orientation="vertical">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="8dp" />
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/accent_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<TextView android:id="@+id/accent_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
|
||||
<!-- Only Halloween listens to the music. -->
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/music_light_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_music_light" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" android:visibility="gone" />
|
||||
<TextView android:id="@+id/music_light_hint" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_music_light" android:textAppearance="@style/Text.EV.Secondary" android:visibility="gone" />
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
|
||||
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.EV.Dropdown" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp">
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView android:id="@+id/tire_unit_choices" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:inputType="none" android:textSize="20sp" app:simpleItemLayout="@layout/dropdown_item" />
|
||||
|
||||
@@ -129,6 +129,8 @@
|
||||
<string name="overview_style_scifi">Sci-fi</string>
|
||||
<string name="overview_style_cartoon">Cartoon</string>
|
||||
<string name="overview_style_mg">MG</string>
|
||||
<string name="label_music_light">Pumpkin light follows the music</string>
|
||||
<string name="hint_music_light">Flares on the kick drum over Bluetooth or CarPlay, not the radio. Levels only, no sound kept.</string>
|
||||
<string name="accent_not_used">%1$s has its own colours, and the app takes them on. To change the accent colour, pick a style that uses it.</string>
|
||||
<string name="overview_fullscreen_enter">Overview full screen: hide the navigation</string>
|
||||
<string name="overview_fullscreen_exit">Show the navigation again</string>
|
||||
|
||||
@@ -36,6 +36,45 @@ public class MusicPulseTest {
|
||||
assertEquals(0f, last, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aHeldBassNoteLightsOnlyItsStart() {
|
||||
MusicPulse p = new MusicPulse();
|
||||
for (int i = 0; i < 50; i++) p.update(0.05f, STEP);
|
||||
assertTrue(p.update(0.4f, STEP) > 0.8f);
|
||||
float last = 1f;
|
||||
for (int i = 0; i < 25; i++) last = p.update(0.4f, STEP);
|
||||
assertEquals(0f, last, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aWobblingBassLineStaysDark() {
|
||||
MusicPulse p = new MusicPulse();
|
||||
for (int i = 0; i < 50; i++) p.update(0.3f, STEP);
|
||||
float top = 0f;
|
||||
for (int i = 0; i < 200; i++) top = Math.max(top, p.update(i % 3 == 0 ? 0.33f : 0.3f, STEP));
|
||||
assertEquals(0f, top, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aSwellingNoteStaysDark() {
|
||||
MusicPulse p = new MusicPulse();
|
||||
for (int i = 0; i < 50; i++) p.update(0.1f, STEP);
|
||||
float top = 0f;
|
||||
for (int i = 0; i < 100; i++) top = Math.max(top, p.update(0.1f + i * 0.004f, STEP));
|
||||
assertEquals(0f, top, 0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aKickOverABassLineLights() {
|
||||
MusicPulse p = new MusicPulse();
|
||||
float top = 0f;
|
||||
for (int i = 0; i < 300; i++) {
|
||||
float v = p.update(i % 12 == 0 ? 0.6f : (i % 3 == 0 ? 0.33f : 0.3f), STEP);
|
||||
if (i > 100) top = Math.max(top, v);
|
||||
}
|
||||
assertTrue("a kick lights it, got " + top, top > 0.8f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void quietAndLoudSongsPulseAlike() {
|
||||
assertEquals(beatOf(0.5f), beatOf(0.05f), 0.02f);
|
||||
|
||||
Reference in New Issue
Block a user