Compare commits
2
Commits
car-2.43.5
...
car-2.44.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d84dc6d15 | ||
|
|
418883beb0 |
@@ -77,7 +77,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
|
|||||||
|
|
||||||
| Tab | Contents |
|
| 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 |
|
| Consumption / Charging | period totals, daily log, charging sessions and curve |
|
||||||
| Live data | on/off, ABRP API key and user token, upload frequency |
|
| 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 |
|
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ 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
|
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`
|
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).
|
(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.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`).
|
- 2026-11-01 — App 2.43.4 published (`car-2.43.4`).
|
||||||
- 2026-11-01 — App 2.43.4: **the Halloween pumpkin's light follows the music** (`MusicTap`, `MusicPulse`; owner: "make the light in the pumpkin react to the equalizer/sound of the music"). The bass, 25 times a second off the main thread, drives the glow and brightens the carving on the beat; yellow and green still mean drawing and regenerating, the embers still the power, and no signal leaves the light as it was. It listens only with the app in front, on the Overview, in Halloween. The owner reports the sound check worked with Bluetooth and CarPlay; those two reports never reached the server (why not is unexplained), so their levels are unseen and the pulse's feel is untuned for the car. Tested: unit tests, lint, emulator (a tone already playing when the analyser starts: the carving and glow move with it; no sound: unchanged; the analyser is released on another tab and when the app leaves the front; no crash). Not tried on the car; how it looks there, and its effect on the head unit over a long drive, are unknown.
|
- 2026-11-01 — App 2.43.4: **the Halloween pumpkin's light follows the music** (`MusicTap`, `MusicPulse`; owner: "make the light in the pumpkin react to the equalizer/sound of the music"). The bass, 25 times a second off the main thread, drives the glow and brightens the carving on the beat; yellow and green still mean drawing and regenerating, the embers still the power, and no signal leaves the light as it was. It listens only with the app in front, on the Overview, in Halloween. The owner reports the sound check worked with Bluetooth and CarPlay; those two reports never reached the server (why not is unexplained), so their levels are unseen and the pulse's feel is untuned for the car. Tested: unit tests, lint, emulator (a tone already playing when the analyser starts: the carving and glow move with it; no sound: unchanged; the analyser is released on another tab and when the app leaves the front; no crash). Not tried on the car; how it looks there, and its effect on the head unit over a long drive, are unknown.
|
||||||
|
|||||||
@@ -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,
|
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
|
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
|
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
|
this screen). With "Pumpkin light follows the music" on (Settings, shown with this style; off by
|
||||||
brightens on the beat; the radio cannot be heard, so it burns as usual then. The app turns the
|
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.
|
pumpkin's orange with it.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 158
|
versionCode 159
|
||||||
versionName "2.43.5"
|
versionName "2.44.0"
|
||||||
|
|
||||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
// 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.
|
// 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). */
|
/** Whether the Overview hides the navigation (the button in the navigation's header). */
|
||||||
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
|
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. */
|
/** True while the Overview is full screen: the navigation hidden, the edge swipe armed. */
|
||||||
private boolean overviewFull;
|
private boolean overviewFull;
|
||||||
@@ -1223,6 +1225,12 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
bindDriveMode();
|
bindDriveMode();
|
||||||
bindOverviewStyle();
|
bindOverviewStyle();
|
||||||
bindAccentColor();
|
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 = settingsPane.findViewById(R.id.full_screen_switch);
|
||||||
fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs));
|
fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs));
|
||||||
fullScreenSwitch.setOnCheckedChangeListener((btn, checked) -> {
|
fullScreenSwitch.setOnCheckedChangeListener((btn, checked) -> {
|
||||||
@@ -1761,10 +1769,14 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
updateMusicTap();
|
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() {
|
private void updateMusicTap() {
|
||||||
ViewPager2 pager = findViewById(R.id.content);
|
ViewPager2 pager = findViewById(R.id.content);
|
||||||
musicTap.setRunning(inFront && overviewStyle() == OverviewStyle.HALLOWEEN
|
musicTap.setRunning(inFront && overviewStyle() == OverviewStyle.HALLOWEEN
|
||||||
|
&& prefs.getBoolean(PREF_MUSIC_LIGHT, false)
|
||||||
&& pager != null && pager.getCurrentItem() == 0);
|
&& pager != null && pager.getCurrentItem() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1828,6 +1840,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
TextView note = settingsPane.findViewById(R.id.accent_note);
|
TextView note = settingsPane.findViewById(R.id.accent_note);
|
||||||
note.setVisibility(style.usesAccent ? View.GONE : View.VISIBLE);
|
note.setVisibility(style.usesAccent ? View.GONE : View.VISIBLE);
|
||||||
note.setText(getString(R.string.accent_not_used, styleLabels()[style.ordinal()]));
|
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. */
|
/** 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
|
* 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
|
* the kick drum's band the audio analyser reports. What counts is the hit, not the loudness: only
|
||||||
* placed between the bass's recent average and its recent peak, so quiet and loud songs both
|
* a jump of the bass since the last reading lights it, measured against the biggest jumps of the
|
||||||
* pulse and a steady hum settles at nothing. It rises fast and falls slowly, as a flame does. No
|
* last seconds, so quiet and loud songs both flash, and a held bass note or a steady hum does not.
|
||||||
* reading or silence falls back to 0, so without music the light stays as it is. Android-free and
|
* Jumps that are small against the bass's own level (a swelling note, noise) are ignored. It
|
||||||
* unit-tested.
|
* 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 {
|
final class MusicPulse {
|
||||||
|
|
||||||
/** Below this the bass counts as silence. */
|
/** Below this the bass counts as silence. */
|
||||||
static final float SILENT = 0.005f;
|
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. */
|
/** One reading, {@code seconds} after the last; null when it failed. Returns the new pulse. */
|
||||||
float update(Float bass, float seconds) {
|
float update(Float bass, float seconds) {
|
||||||
float target = 0f;
|
float target = 0f;
|
||||||
if (bass == null || bass.isNaN() || bass < SILENT) {
|
if (bass == null || bass.isNaN() || bass < SILENT) {
|
||||||
|
last = 0f;
|
||||||
average = 0f;
|
average = 0f;
|
||||||
peak = 0f;
|
peakJump = 0f;
|
||||||
} else {
|
} else {
|
||||||
|
float jump = Math.max(0f, bass - last);
|
||||||
|
last = bass;
|
||||||
average = average == 0f ? bass : average + (bass - average) * share(seconds, AVERAGE_S);
|
average = average == 0f ? bass : average + (bass - average) * share(seconds, AVERAGE_S);
|
||||||
peak = Math.max(bass, peak - peak * share(seconds, PEAK_S));
|
peakJump = Math.max(jump, peakJump - peakJump * share(seconds, PEAK_S));
|
||||||
if (peak > average) target = Math.max(0f, Math.min(1f, (bass - average) / (peak - average)));
|
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;
|
if (pulse < 0.005f) pulse = 0f;
|
||||||
return pulse;
|
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
|
* 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
|
* {@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.
|
* (the radio) or is not available, the pulse stays 0 and the pumpkin burns as usual.
|
||||||
*/
|
*/
|
||||||
final class MusicTap {
|
final class MusicTap {
|
||||||
@@ -64,7 +64,7 @@ final class MusicTap {
|
|||||||
boolean read = analyser.getFft(fft) == Visualizer.SUCCESS;
|
boolean read = analyser.getFft(fft) == Visualizer.SUCCESS;
|
||||||
failures = read ? 0 : failures + 1;
|
failures = read ? 0 : failures + 1;
|
||||||
long now = System.nanoTime();
|
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;
|
last = now;
|
||||||
if (worker == me) pulse = value;
|
if (worker == me) pulse = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</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" />
|
<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="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">
|
<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" />
|
<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.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>
|
</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" />
|
<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" />
|
<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.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" />
|
<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_scifi">Sci-fi</string>
|
||||||
<string name="overview_style_cartoon">Cartoon</string>
|
<string name="overview_style_cartoon">Cartoon</string>
|
||||||
<string name="overview_style_mg">MG</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="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_enter">Overview full screen: hide the navigation</string>
|
||||||
<string name="overview_fullscreen_exit">Show the navigation again</string>
|
<string name="overview_fullscreen_exit">Show the navigation again</string>
|
||||||
|
|||||||
@@ -36,6 +36,45 @@ public class MusicPulseTest {
|
|||||||
assertEquals(0f, last, 0f);
|
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
|
@Test
|
||||||
public void quietAndLoudSongsPulseAlike() {
|
public void quietAndLoudSongsPulseAlike() {
|
||||||
assertEquals(beatOf(0.5f), beatOf(0.05f), 0.02f);
|
assertEquals(beatOf(0.5f), beatOf(0.05f), 0.02f);
|
||||||
|
|||||||
Reference in New Issue
Block a user