A Frutiger Aero audio visualizer for Java Swing — playlist, transport, and nine Java2D presets — sitting on the Fruitiger Aero LAF glass shell rather than a flat spectrum bar.
Fruity Visualizer is a full Swing audio player/visualizer in the 2008 glass aesthetic. It installs Fruitiger Aero LAF early, opens an AeroFrame, and drives a Java2D scene from real decoded audio — sky, bubbles, glass ribbons, petals, tunnels — not a static EQ strip.
Spelling: the look is Frutiger; package/artifact names keep Fruitiger / Fruity, matching the sibling LAF.
Status: working end-to-end app (0.2.0-SNAPSHOT). MIT for this code; the runnable fat jar also ships LGPL 2.1 decoder jars (see NOTICE / LICENSES/). Java 17+.
What you get
| Area | Reality |
|---|---|
| Formats | WAV (pure Java), MP3 (JLayer), FLAC (jFLAC), Ogg Vorbis (JOrbis) — magic-byte sniffed, seekable |
| Playback | Java Sound SourceDataLine (+ NullPlayback for headless) |
| Analysis | FFT → 64 log bins, beat / track profile; display spectrum in dB vs a following peak (ADR 0003) |
| UI shell | AeroFrame, playlist rail, icon transport, scrub seek, level meter, theme toggle, fullscreen |
| Presets | Nine layer stacks — Aqua Symphony through Warp Tunnel — smoke-verified |
| Graphics | Four quality tiers + optional OpenGL accel (restart); settings in ~/.fruity-visualizer/ |
| Packaging | Self-contained -app fat jar (~1.4 MB) with LAF, decoders, fixtures, logo |
Native decode (JniNativeDecoder) is an intentional inert seam — not required for the four formats above (ADR 0001).
Look
Idle shell on Fruitiger Aero LAF — same chrome, playlist rail, and transport; theme toggle flips Aqua Glass ↔ Midnight Aero without dropping the session.
Aqua Glass (idle)

Midnight Aero (idle)

Architecture
core — pure Java: decode, playback, FFT / Analyzer (no Swing, no LAF, no Java2D)
ui — Swing shell + Java2D scene + Fruitiger Aero LAF
The audio pump feeds the Analyzer; Swing reads the latest Metrics + spectrum via a volatile snapshot. Views (TopBar, TransportBar, PlaylistRail) stay passive; FruityVisualizer wires them to VisualizerEngine.
flowchart LR
file[Audio file] --> registry[DecoderRegistry]
registry --> pcm[PcmStream]
pcm --> player[PlaybackBackend]
pcm --> analyzer[Analyzer]
analyzer --> metrics[Metrics + spectrum]
metrics --> engine[VisualizerEngine]
engine --> canvas[VisualizerCanvas]
canvas --> layers[Scene layers]
shell[FruityVisualizer shell] --> aeroLaf[Fruitiger Aero LAF]
shell --> aeroFrame[AeroFrame]
shell --> engine
Install happens once on the main thread before the tree is built (FruitigerAeroLookAndFeel.install(...)). Theme toggle re-installs Aqua Glass ↔ Midnight Aero live without killing the playlist.
Presets
Each preset is an ordered list of layers — no new renderer per preset:
| Preset | Key | Idea |
|---|---|---|
| Aqua Symphony | aquaSymphony |
Flagship: sky, bubbles, glass ribbon, petals, sparkles, vignette |
| Glass Ribbon | glassRibbon |
Spectrum as one glass tube + sparkles |
| Bubble Field | bubbleField |
Ambient droplets, low motion |
| Petal Bloom | petalBloom |
Radial spectrum only |
| Alchemy | alchemy |
Luminous filaments (WMP-inspired interpretation) |
| Trilogy I / II / III | trilogyOne… |
Wings / parametric figures / layered terrain |
| Warp Tunnel | warpTunnel |
Fly down a tube whose cross-section is the spectrum |
Alchemy and Trilogy are original takes on classic WMP visualization behaviour, not ports of third-party code. Warp Tunnel is original to this project; tempo drives flight colour, beats raise obstacle density (TunnelLayerTest pins the behaviours from pixels).
Aqua Symphony
Flagship stack — bubbles, glass ribbon, radial petals — while a track is playing.

Trilogy III
Layered terrain / wave planes driven by the spectrum.

Warp Tunnel
Fly down a tube whose cross-section is the spectrum.

Spectrum in decibels
Early builds looked bass-heavy because renderers clamped raw FFT magnitudes to [0,1] — most low bins pinned at 1.0 and “moved” while already flat. Analyzer.spectrumInto now maps each bin in dB relative to a slow-following spectral peak (−70…0 dB), the way a hardware analyser does. Absolute magnitudes stay available for band energies. Full case: docs/adr/0003-spectrum-display-scale.md.
Graphics quality
| Tier | Antialias | Particles | Paint scale |
|---|---|---|---|
| Best (default) | on | 100 % | 100 % |
| Better | on | 85 % | 100 % |
| Medium | on | 60 % | 70 % |
| Low | off | 35 % | 50 % |
Hints alone aren’t enough — cost is dominated by full-canvas gradients and particle counts (ADR 0002 / 0004), so tiers also cut particle factors, bin strides, and internal resolution. Tiers apply next paint; hardware acceleration (sun.java2d.opengl) needs a JVM restart and is off by default. Gear button or S opens the dialog.
Acceptance bar: ≥ 55 fps at 100 % and 200 % UI scale (--bench / FrameRateBenchmarkTest). Raising UI scale shrinks the canvas inside a fixed shell size — the bench also reports a maximized scenario for honesty.
Run it
Needs the LAF in ~/.m2 first (sibling install or your Reposilite — see README). Then:
mvn install
mvn package
java -jar ui/target/fruity-visualizer-ui-0.2.0-SNAPSHOT-app.jar
java -jar ui/target/fruity-visualizer-ui-0.2.0-SNAPSHOT-app.jar track.flac
java -Dfruity.preset=warpTunnel -jar ui/target/fruity-visualizer-ui-0.2.0-SNAPSHOT-app.jar track.mp3
java -jar ui/target/fruity-visualizer-ui-0.2.0-SNAPSHOT-app.jar --smoke
java -jar ui/target/fruity-visualizer-ui-0.2.0-SNAPSHOT-app.jar --bench
Keys: Space play/pause · ←/→ ±5s · N/P next/prev · F fullscreen · O open · S settings.
Also understands LAF props (-Daero.theme=midnightAero, -Daero.uiScale=…) and Fruity ones (-Dfruity.preset=…, -Dfruity.quality=medium, --volume=35).
--smoke is the real pipeline check: logo pixels, decode all six bundled fixtures through DecoderRegistry, analyse, paint the loudest frame — not a “file exists” shrug.
License and attribution
App code is MIT. The runnable -app jar also ships third-party libraries that keep their own licenses (see NOTICE / LICENSES/ inside the jar). The MIT grant covers this project’s sources, not the LGPL decoders.
| Library | Maven | Role | License | Links |
|---|---|---|---|---|
| Fruitiger Aero LAF | com.fruitiger:fruitiger-aero-laf 0.1.0-SNAPSHOT |
Swing glass shell (AeroFrame, themes) |
MIT | Project page |
| JLayer | javazoom:jlayer 1.0.1 |
MP3 decode | LGPL 2.1 | Maven Central · SourceForge |
| jFLAC | org.jflac:jflac-codec 1.5.2 |
FLAC decode | LGPL 2.1 | Homepage · Maven Central · GitHub (1.5.2 line) |
| JOrbis | org.jcraft:jorbis 0.0.17 |
Ogg Vorbis decode (+ Ogg framer) | LGPL 2.1 | Homepage · Maven Central |
WAV is pure-Java in-tree (no extra jar). The three LGPL decoders are unmaintained upstream and treated as vendored — fixture-tested via DecoderRegistry. Wider formats (M4A/AIFF/…) would be an additive ffmpeg or JNI decoder behind DecoderRegistry.registerFirst(...).
Where to look
| Area | Start here |
|---|---|
| Shell + CLI | ui/.../ui/FruityVisualizer.java |
| Engine / playlist | ui/.../engine/VisualizerEngine.java |
| Presets / layers | ui/.../scene/Presets.java, ui/.../render/* |
| Decode registry | core/.../audio/DecoderRegistry.java |
| Spectrum scale | core/.../analysis/Analyzer.java, ADR 0003 |
| Frame budget | ADR 0002 |
| Quality tiers | ADR 0004 |
| LAF dependency | Fruitiger Aero LAF |