v1.12.0
kevinraymond/fosforav1.12.0Jul 22, 2026by github-actions[bot]
AI Summary
Introduces five new effects (Splat, Frost, Cleave, Vessel, Tide) and fixes HPSS energy detection.
Key Highlights
- New Splat effect for audio-reactive 3D Gaussian-splat playback.
- New effects: Frost, Cleave, Vessel, and Tide.
- Obstacles now have binding targets and OSC addresses for audio automation.
- Fixed HPSS energies which were reading 0 on real music.
New Features
- Splat effect with 3DGS playback
- Frost, Cleave, Vessel, and Tide effects
- Obstacle binding automation
- HPSS energy detection fix
Full Release Notes
### Added
- **New effect: Splat — audio-reactive 3D Gaussian-splat playback** — load a pre-trained 3DGS capture (`.ply` or `.splat`) and play it back as a living cloud: the scene breathes with the music, shatters radially on the `drop` trigger and re-coalesces through the next phrase, onsets scatter a subset, and the spectral centroid drives a depth-of-field focal plane. Scenes stream and decode on a background thread, so the effect loads instantly and a failed load can never leave a layer half-swapped. Ships at a 2M-splat default — on an RTX 4090 at 1080p, 125 FPS at 1M and 70 at 2M.
- **Splat scene management** — a **Splat Scene** panel section shows the loaded scene, decode progress and errors, with a **Load Scene…** picker and a one-click demo-scene download. The loaded scene path round-trips through presets, re-decoding in the background on restore and warning-and-skipping if the file has moved.
- **Splat renders with real occlusion, matching reference viewers** — the default sorted path depth-sorts splats and alpha-composites front to back the way SuperSplat and PlayCanvas do, rather than averaging every splat along a ray with no occlusion — which washed fine detail to mid-tone and collapsed the tonal range. The order-independent path survives as a cheaper fallback for weak GPUs (`"sort": false`).
- **New effect: Frost — spectral-flatness material dissolution** — the sound's texture becomes the image's texture: pure sustained tones freeze the screen into crisp faceted ice, while noisy, breathy or distorted material erodes it into a drifting sand field with wind-blown dunes. The morph is not a crossfade — an erosion mask crumbles facet patches edge-first, so the crystal visibly *becomes* sand. Hiss fizzes the grain, bass gusts the drift, onsets shatter and glint.
- **New effect: Cleave — percussive/harmonic duet** — react to the drums separately from the melody. Ice-blue shards stab radially outward from a draggable fracture point on every drum hit, threaded through slow warm ribbons that swell and stiffen with pads and melody. A breakbeat reads as shards and a pad bridge as ribbons, shifting exactly as the arrangement does; a `balance` param forces either voice by hand, even mid-breakbeat.
- **New effect: Vessel — body-as-container fill-and-release** — your silhouette (webcam, depth or image obstacle) becomes a vessel that fills with trapped light as a buildup rises, then bursts outward exactly on the drop, each particle carrying its stored velocity. With no obstacle armed, a centered "amphora" takes the body's place. A `liquidity` param morphs it from pooling liquid to weightless drifting fireflies, and a bindable `release` param fires the burst by hand for sets without detector-clean drops.
- **New effect: Tide — flow-around-body water** — a luminous waterfall pours from the top edge and visibly parts, pools and eddies around obstacle silhouettes. Drums break the sheet into whitewater and upward foam spray; pads stiffen the flow so it glides as glass. Water pools on heads and shoulders and parts at the crest. Verified live at ~214K particles above 100 FPS with trails on.
- **Particle sims can read the harmonic/percussive split and the buildup/drop detectors** — `percussive_energy`, `harmonic_energy`, `harmonic_ratio`, `buildup` and `drop` have been computed live since the audio DSP tier landed and were already available to fragment shaders, but particle uniforms had no room for them, so no particle effect could separate drums from melody or follow song structure. Now appended, 832 → 864 bytes with all existing offsets stable.
- **Obstacles can be automated** — the obstacle surface was the only major subsystem with no binding targets and no OSC addresses, so the marquee interactive feature couldn't be cued or driven by audio at all. It now has binding targets (`particle.obstacle_enabled` / `_threshold` / `_elasticity` / `_mode`) and per-layer OSC addresses (`/phosphor/layer/{n}/obstacle/{enabled|mode|threshold|elasticity}`), so threshold can breathe on `audio.rms` and mode can hop on a MIDI knob.
### Fixed
- **HPSS energies were silently zero on real music at normal listening volume** — `percussive_energy` and `harmonic_energy` reached the normalizer as raw power (~1e-7 through a loopback capture), below its span-collapse epsilon, so they read a hard 0 — while full-scale synthetic test signals cleared it and made the pipeline look healthy. Every HPSS-gated visual was therefore largely dormant in live use. The energies are now dB-mapped at the producer, which is volume-invariant at any playback level: an A/B on identical live drum-and-bass read a median of 0.000 before and 0.635 after.
- **Splat: close-range parity with SuperSplat** — the sorted render looked right only at maximum camera distance; filling the screen it degraded to an icy white crust of speckle, with a black square artifact and a waxy low-contrast cast. Five compounding causes, from a projected-radius cap that collapsed every surface splat to 8px through to the global ACES tone curve lifting darks. Effects can now opt into a linear tonemap via `postprocess.tonemap`; every other effect is untouched.
- **Splat: raw 3DGS / SuperSplat exports load right-side-up** — captures use a Y-down convention while the orbit camera is Y-up, so a straight export appeared upside-down and had to be hand-inverted before export. The default rotation is now `[180, 0, 0]`, applied consistently to positions and covariance orientation; still a live slider for per-scene tweaks.
- **Tide no longer white-washes on real music** — the dominant cause was trail coverage: total light scales with trail *length*, which scales with speed, so loud music drove the flow 1.6× faster and multiplied deposited light with colour and alpha held flat. Alpha is now normalized by speed, so faster water reads as longer, thinner, dimmer streaks, and splash gates were retuned above the live sustained-groove floor so spray reads as sparks. Silence is untouched.
- **Frame hitches no longer white-flash particle effects** — the per-frame `dt` was raw wall-clock delta, so any stall (a mouse click's input hitch, a window drag, an effect swap) integrated as one giant step: particles teleported past kill bounds and mass-respawned, and trail ribbons smeared across the whole screen as a white flash. `dt` is now clamped to 50 ms, so a stall renders as momentary slow motion. Normal frame times are unaffected.
- **Obstacle maps are no longer aspect-distorted, and horizontal surfaces bounce** — obstacle UVs ignored aspect entirely, so a 256×256 depth map on a 1920×1080 window was 78% too wide; and normals were returned y-down but consumed y-up, so a floor-shaped obstacle's "outward" normal pointed *into* the floor and particles were swallowed rather than bounced. New per-layer **Fit** mode (Fill default, Fit, Stretch); existing presets restore as Fill, since the old stretch was a defect rather than a choice.
- **Scene transport bindings no longer re-fire every frame** — a binding to `scene.transport.go` / `prev` / `stop` was level-triggered, so holding a MIDI pad, or wiring any source that stays high like a sustained beat feature, advanced the timeline ~60 times per second instead of once per press. Audio-driven cue control was unusable. Transport targets now trigger on the rising edge; every continuous target stays level-driven and unchanged.
- **`layer.{n}.blend` bindings reach all 10 blend modes** — the normalized bus output was fed straight into an integer conversion that only ever yields 0 or 1, so a knob bound to blend mode toggled between Normal and Add and could never reach Screen, Colour Dodge, Multiply, Overlay, Hard Light, Difference, Exclusion or Subtract. The raw-integer OSC path already reached all ten and is unchanged.
- **Preset-scoped binding edits are no longer silently lost** — the debounced auto-save only ever flushed *global* bindings, so a preset-scoped binding you added or edited reached its sidecar only via an explicit preset save; quit or switch presets first and the edit was gone, with nothing in the UI to warn you. Preset-scoped edits now light the "unsaved changes" bar like any param edit. Quit also force-flushes pending global binding edits made inside the 1 s debounce window.
- **Right-panel edits round-trip through presets** — hand-tuned Lattice, particle-sim (emit rate / burst / lifetime / speed / size / drag) and Volumetric parameters were applied to live state but never saved into a preset, so they snapped back to the effect's `.pfx` defaults on the next effect reload or restart. Only `.pfx` `inputs` params used to survive. Old presets load unchanged, and the panel Reset still restores `.pfx` values.
### Changed
- **Splat's order-independent fallback tuned for real captures** — real captures use very low per-splat opacity (median ≈0.03) and extreme thin surfels, which the sort-free path rendered washed-out and translucent. Its depth weight now only reshades the average rather than gating visibility — a near-bias was deleting the far half of the figure — and coverage accumulation was raised so faint splats build a solid surface. This is now the low-end path; the sorted renderer above is the default.
- **Frost `audio_reactivity` default 1.0 → 0.4** — full-scale reactivity slammed the crystal↔sand morph between extremes on typical live material. Presets that saved the old value are unaffected.
- **Binding matrix "+ Add" popup migrated to the egui 0.33 `Popup` builder** — the last user of the deprecated popup APIs. No behavior change.
## Downloads
| Platform | File | Notes |
|----------|------|-------|
| **macOS** | `.dmg` | Universal binary (Apple Silicon + Intel) |
| **Linux x86_64** | `.tar.gz` | glibc 2.35+ (Ubuntu 22.04+, Debian 12+) |
| **Windows x64** | `.zip` | Windows 10+ |
## Getting started
**macOS**: Open the DMG, drag Fosfora.app to Applications (or run directly). Signed and notarized.
**Linux / Windows**: Extract, run `fosfora` from the extracted directory. The `assets/` folder must be next to the binary.
## Requirements
- **GPU**: Vulkan (Linux/Windows) or Metal (macOS)
- **Audio**: Built-in mic or line-in for audio-reactive visuals
- **NDI®** (optional): [NDI® SDK runtime](https://ndi.video) for network video output
- **Video playback** (optional): `ffmpeg` on PATH for media layers
---
NDI® is a registered trademark of Vizrt NDI AB.