v1.5.0
Fixed
- Scene cue delete button disappearing when the same preset is added as a cue multiple times (egui widget ID collision)
- Crash (index out of bounds) when deleting a scene cue while the timeline is playing
UI
- Right panel restyle: single-row param layout (name/slider/value/M/O), compact 16×14 MIDI/OSC badges, smaller Reset All button, large particle count header with 3px health bar, standalone bordered blend mode badge, compact particle param rows replacing grid, tab-strip obstacle source selector with active state styling, compact obstacle slider rows with separate value labels, audio mapping hover background lift with fixed-width 52px feature names
- Remove redundant activity dots from Settings subsection enable rows; status bar always shows MIDI/OSC/WEB/NDI indicators with three-state dots (active/enabled-idle/disabled)
- Consolidate MIDI, OSC, Web, NDI/Outputs, and Global into single collapsible "Settings" section with subsections — header shows labeled status dots, subsection headers use smaller font/arrow for visual hierarchy, protocol-specific badge colors
- Trigger grids (MIDI + OSC) use fixed-width label columns (52px) for proper badge alignment across rows
- OSC TX settings wrapped in subtle bordered frame; Global settings use fixed-width labels aligned to dropdowns
- Layers panel visual refresh: type color legend (FX/MD/WC), left color strip per card, layer index numbers, type badges, type-colored add buttons (all three on one row), subtle "Clear All" text link with red hover/confirm, footer type breakdown counts, hover-only delete buttons, stronger dimming for disabled layers
- Reorder sidebar panels: Effects → Layers → Presets → Scenes
- Layer badge shows "N/8" capacity format
- Scenes panel visual refresh: 2-column scene grid tiles, two-line cue rows with bottom progress bars (hold elapsed + transition progress with stripes), accent active-cue highlighting, pulsing red LIVE dot, GO button green fill, STOP red outline, hover-reveal delete buttons, hold time always visible, scrollable cue list (up to 6 visible), add-cue bar with separator
- Timeline bar polish: accent-colored active cue, striped from-cue during transitions, two-line target cue label (name + "Morph 58%"), transition-typed playhead colors
- Presets panel: add "Save current state as preset:" label above name input, and make "+ New" button clear all layers to a fresh Phosphor state with two-click confirmation when unsaved changes exist
- Amber dirty-state styling for presets panel: custom header with pulsing dot and inline preset name, amber-tinted dirty bar with Update/Reset buttons, amber selected tile, pulsing card border, and bumped tile height to 26px
- Add effect type indicators to effects panel: left color strip (purple SH / orange PS / teal FB), two-char badge, type legend, and footer breakdown count
- Add hover tooltips to effect type color legend explaining Shader, Particle+Shader, and Feedback categories
- Show Copy and Edit buttons side by side — Copy enabled for visible built-ins only, Edit enabled for user effects only; fixes Copy being clickable on hidden Phosphor branding effect
- Add "msc: N" badge to PARTICLES panel showing
max_scaled_countwith tooltip; merge blend mode and feature badges into single wrapping horizontal row; normalize morph section to 9pt font - Add
EffectTypeenum (Shader, Particle, Feedback) with optionaleffect_typefield in .pfx format — auto-detects from data when absent - Tag 5 feedback effects (Accretion, Array, Chaos, Mycelium, Turing) with explicit
"effect_type": "feedback"in their .pfx files
Audio
- Add
dominant_chromafeature: CPU-side argmax of 12 chroma bins, normalized 0–1, forwarded to both fragment and particle GPU uniforms - Cymatics audio remapping: chroma-driven Chladni mode selection (curated visually-distinct (n,m) pairs per pitch class), mfcc[0] vibrational amplitude, chroma peakedness pattern clarity
- Murmur audio remapping: mfcc[1] flock cohesion (bright=tight, dark=dispersed), mfcc[3] predator aggressiveness, flux speed agitation, dominant_chroma rim/sky hue; fix emitter waves by probing whole screen + inheriting donor heading
- Symbiosis audio remapping: mfcc[4-6] per-species force modulation (timbre shifts ecology), dominant_chroma species color rotation, flux interaction radius expansion, zcr Brownian noise jitter
- Morph audio remapping: dominant_chroma target shape selection (pitch class picks morph target instead of sequential), mfcc[1] transition speed modulation, mfcc[2] mid-transition scatter/explosion strength, chroma-tinted background
- Flux audio remapping: mfcc[1] curl strength (bright=tight spirals, dark=loose), flux-driven turbulence agitation, dominant_chroma emission/background hue anchor
- Add
zcr(zero-crossing rate) to ParticleUniforms for compute shader access
Effects
- Add obstacle collision to all 11 remaining particle sim shaders (phosphor, flux, murmur, raster, tesla, genesis, cymatics, symbiosis, turing, morph, as) — obstacles now work across all 16 effects
Fixes
- Fix preset showing as dirty during ParamMorph scene transitions — morph interpolation now resets param changed flags since it's timeline playback, not user edits
- Clamp particle count to GPU dispatch dimension limit (
max_compute_workgroups_per_dimension × 256) to prevent validation errors on high-count effects - Add per-effect
max_scaled_countfield — quality multiplier won't push past this ceiling; applied to Accretion (60K), Genesis (24K), Murmur (2.4M), and Symbiosis (4M) - Hide Stress test effect from effects panel
- Fix morph slider animating when auto-cycle mode is "Off" — transition progress now only advances when auto-cycling is active
- Remove redundant image source UI (IMG badge, image selector dropdown, Load Image/Video/Webcam buttons) from particle panel — morph targets handle image loading
- Fix morph snapshot/image/text/video filling wrong slot when
target_countdiverges from def count — all handlers now use def vector length for slot selection and pad gaps
Morph Effect (New)
- Shape target morphing: particles store home positions for up to 4 target shapes and morph between them on beat drops with spring physics and turbulence
- Multiple target types: image files, geometry shapes (circle, ring, grid, spiral, heart, star), and random scatter — mixed freely across 4 slots
- 5 transition styles: Spring (default), Explode-reform, Flow (curl noise), Cascade (left-to-right wave), and Direct (pure lerp) — selectable via UI dropdown
- Per-particle stagger: each particle transitions at a slightly different time for organic cascading motion
- Audio reactivity: onset triggers morph to next target, bass drives spring vibration, mid adds turbulence during transition, brilliance creates scatter
- Strided aux buffer: 4x normal aux size interleaves target data (32MB at 500K particles), no bind group changes
- ParticleUniforms extended: 4 new fields (morph_progress, morph_source, morph_dest, morph_flags) — 784→800 bytes, existing effects unaffected
- Morph UI controls: target slot display with click-to-morph buttons, transition progress bar, auto-cycle mode (Off/On Beat/Timed), hold duration slider, transition style dropdown, runtime image and geometry loading into morph slots
- Text morph targets: type any string in the morph panel and particles form the letters — uses fontdue to rasterize Inter-Bold glyphs into a bitmap, then samples through the standard image pipeline. Supports
"text:HELLO"in .pfx files - Video frame morph targets: click "+ Video" to load a video file — evenly-spaced frames fill available morph slots (up to 4), cycling between different video frames on beat
- Snapshot morph targets: click "Snapshot" to freeze current particle positions into a new morph target — GPU readback captures alive particles as a reusable shape
WBOIT (Weighted Blended Order-Independent Transparency)
- Order-independent transparency: new
"blend": "wboit"mode approximates correct alpha compositing in a single unsorted pass — no back-to-front sort needed - Two-pass rendering: particles render into accumulation (Rgba16Float) + revealage (R8Unorm) targets with WBOIT weight function, then a fullscreen composite blends onto the scene
- Automatic compute raster fallback: WBOIT is incompatible with compute rasterization;
"render_mode": "compute"+"wboit"falls back to billboard with a warning, and"auto"mode excludes WBOIT effects - Resize-safe: WBOIT textures recreated on window resize alongside compute raster framebuffer
Symbiosis Effect (New)
- Multi-species particle life simulation: 2–8 species interact via an asymmetric 8×8 force matrix, producing emergent ecosystems, crystals, and predator-prey dynamics
- 6 named presets: Ecosystem, Crystals, Hunters, Membrane, Chaos, Symmetric — smoothly interpolated on switch
- Audio-reactive matrix: bass boosts force scale, mid modulates friction, presence/brilliance expand interaction radius, onset shuffles random matrix entries for emergent disruption
- Toroidal topology: particles wrap around screen edges with correct wrapped-distance neighbor queries
- Force matrix in ParticleUniforms: 8×8 float matrix (256 bytes) appended to uniforms — no new bind groups needed, all existing effects unaffected
Fix: CR scatter bind group crash on particle quality change
- Request adapter-supported buffer limits: device now requests the GPU adapter's actual
max_storage_buffer_binding_sizeandmax_buffer_sizeinstead of wgpu defaults (128MB/256MB), allowing high-VRAM GPUs to use their full capacity - Cap particle count to device limits: after applying quality multiplier, particle count is clamped so the largest buffer (
sorted_particles_buffer= N×9×4 bytes) stays within the device's storage buffer binding limit — prevents invalid bind groups and crash on quality change
Gaussian Area Splat for Compute Rasterizer (Fix)
- Soft Gaussian splat for particles >1.5px: compute raster now matches billboard renderer output for larger particles (e.g., Array at ~2.7px radius). Previously all energy concentrated into a 2×2 bilinear kernel, making dim-colored effects nearly invisible
- Three-tier kernel: single-pixel (≤1px), bilinear 2×2 (1–1.5px), Gaussian area splat (>1.5px, capped at 8px). Weight
col.a × glow²matches billboard'sSrcAlphablend - Multi-tile binning: particles overlapping multiple 16×16 tiles are binned/scattered to all covered tiles (up to 3×3). Sorted buffer sized 9× max_particles accordingly
- Tiled path support: Gaussian loop in shared-memory tiled shader with per-pixel tile boundary clipping
Preset: Particle Image Save/Restore (Fix)
- Particle image path now saved in presets: selecting a different particle image (e.g., skull → phoenix) and updating the preset persists the choice across preset loads
- Mark dirty on particle/obstacle changes: changing particle image, video source, webcam source, video transport settings, or obstacle settings now marks the preset as dirty so the Update button appears
- Backward compatible: old presets without
particle_image_pathload normally (default image from .pfx)
Particle Quality Setting (New)
- Global quality multiplier: Low (0.25x), Medium (0.5x), High (1x), Ultra (2x), Max (4x) — scales both
max_countandemit_rateproportionally so fill time stays consistent - Default: High (1x) — no change for existing users; backward-compatible with old settings.json files
- Combo box in Global panel: same pattern as theme selector; changing quality mid-session reloads the active effect to rebuild GPU buffers
- Built-in effects upgraded: all effects now default to 1–2M particles with compute rasterization as the baseline
Window Size
- Default window size changed to 1920×1080 (was 1280×720)
Compute Rasterization (New)
- Atomic framebuffer rendering: 3-pass compute raster pipeline bypasses vertex→rasterizer→fragment for sub-pixel particles. Eliminates 2×2 fragment quad waste and rasterizer bottleneck for high particle counts
- Fixed-point encoding: 12-bit fractional precision (4096 scale) via
atomicAddon 4 separate i32 storage buffers (R/G/B/A), supporting up to ~1000 overlapping particles per pixel within i32 range - Size-adaptive kernel: single-pixel fast path for particles ≤1.5px, Gaussian kernel footprint for 1.5–5px, billboard fallback for larger particles
- Render mode field:
"render_mode"in .pfx files —"billboard"(default),"compute", or"auto"(auto-selects compute for ≥100K particles with no sprites/trails and size ≤0.005) - Fullscreen resolve: render pass with hardware blend state (additive or alpha) reads decoded framebuffer with Reinhard tonemapping for additive mode
- COMPUTE badge: cyan badge in particle panel when compute rasterization is active
- Tiled shared-memory accumulation: 4-pass bin→prefix-sum→scatter→tile-raster pipeline for ≥50K particles. Accumulates in 4 KB workgroup shared memory per 16×16 tile (~100× faster atomics), then flushes to global framebuffer with plain stores. Direct draw path retained as fallback for low counts. Automatic path selection based on 1-frame-latent alive count
Performance & Build Optimization
- Build profiles: dev builds use opt-level 1 (deps at 2 for faster shader compilation), release uses thin LTO + codegen-units=1
- GPU buffer clears: spatial hash dispatch uses
encoder.clear_buffer()instead of allocating a zeroed Vec per frame (up to 262KB saved per frame at max grid) - Sort params cached: bitonic sort parameters written once at buffer creation via
mapped_at_creationinstead of ~210write_buffercalls per frame - Parallel prefix sum: spatial hash prefix sum upgraded from single-thread sequential to 256-thread Blelloch scan with shared memory (supports up to 65K cells)
- Pipeline cache: compiled shader data persisted to
~/.config/phosphor/pipeline_cache.binfor faster startup on subsequent launches - Device loss handling: GPU device lost callback + uncaptured error handler + graceful render-time detection (no more hard crash on driver reset)
- GPU profiler (
--features profiling): wgpu-profiler integration with egui overlay panel, timestamp query support - Clippy pedantic: workspace-level clippy lints enabled, auto-fixed 500+ warnings, rustfmt config added
- FxHashMap:
rustc-hashfor runtime-only hash maps (MIDI trigger tracking)
Audio Panel Redesign (Improved)
- BPM ring: circular beat-phase arc with orbiting dot replaces static BPM number; flashes white on downbeat
- Chroma wheel: radial 12-segment pie chart with energy-proportional radius replaces linear bars; dominant note displayed in center
- MFCC heatmap: single-row dark blue→cyan→white timbral fingerprint replaces bar graph; selective labels (DC, Slope, Shape, Formant, Det6)
- Spectrum bars: vertical gradient mesh bars (bright top→faded bottom) with peak-hold markers (0.95 decay)
- Dynamics section: 7 labeled rows with thin track bars + percentage values; kick uses boolean dot indicator
- Section headers: self-documenting sections (SPECTRUM 7 bands, DYNAMICS 7 features, CHROMA 12 pitch classes, TIMBRE·MFCC 13 coefficients)
- Tooltips: per-bar spectrum tooltips (name, Hz range, character), per-row dynamics tooltips (feature description), per-cell MFCC tooltips (coefficient meaning), chroma wheel and BPM ring tooltips
- Footer: feature count summary (7 bands · 7 dynamics · 12 chroma · 13 mfcc · 512 fft)
MFCC + Chroma Audio Feature Extraction (New)
- 13 Mel-Frequency Cepstral Coefficients: captures timbral content — different instruments/voices produce distinct MFCC patterns. Computed from 26-band mel filterbank + DCT-II on the existing 4096-pt FFT
- 12 Chroma pitch-class energies: maps spectral energy to musical pitch classes (C through B). Individual notes/chords light up specific bars. Frequency range 20–5000 Hz
- GPU-accessible: new
mfcc(i)andchroma_val(i)WGSL helper functions in both fragment and compute shaders. ShaderUniforms 256→288 bytes, ParticleUniforms 416→528 bytes - Audio panel visualization: chromatic-colored chroma bars (12 pitch classes) and MFCC bar graph in the audio panel overlay
- Smoothed + normalized: 25 new features go through the existing adaptive normalizer and asymmetric EMA smoother (attack 0.03s, release 0.15s)
- Non-breaking: existing effects unchanged — they simply don't read the new fields. NUM_FEATURES 20→45
Spatial Hash Grid (Improved)
- Dynamic grid sizing:
grid_dims(N)computesclamp(sqrt(N/16), 40, 256)— grid scales automatically with particle count instead of fixed 40×40 - Shader constant patching: SH_GRID_W/H constants in particle_lib, count, prefix_sum, and scatter shaders patched at pipeline creation time
- Particle panel slider fix: emit_rate slider max now scales to 10% of max_count for high-count effects (e.g. 100K emit_rate for 1M particles)
Murmur Effect (Upgraded)
- Topological K=7 nearest neighbors: replaced fixed-radius Boids with scale-free K-nearest neighbor queries — flock correlations work from 40K to 1M particles without parameter tuning
- Vicsek noise phase transitions: angular noise (eta parameter) modulated by bass drives order→chaos transitions; proper research-accurate model instead of ad-hoc disorder
- Predator avoidance: Lissajous-drifting predator with onset-triggered strike jumps; exponential falloff repulsion causes realistic flock splits
- Heading-level roost centering: quadratic ramp centering force feeds into heading computation (not just position) — prevents alignment-consensus drift that plagued the old position-only correction
- KNN-based rim lighting: edge detection from neighbor anisotropy (COM displacement / k-radius); color_mode slider (0=silhouette, 1=full rim); audio-reactive brightness (RMS) and color temperature (centroid: cool blue ↔ warm amber)
- Non-accumulating color: base color re-derived from gradient each frame instead of reading previous frame's output — prevents wash-out over time
- Adaptive separation: K-th neighbor distance as interaction scale; density-invariant at any particle count
- Donor-based emission: new particles spawn near existing flock members for organic growth
- 1M particles, 8 params (noise_eta, cohesion, color_mode, predator, separation, speed, smoothing, audio_drive), alpha blending, bloom postprocess
Mycelium Effect (New)
- Chain-based growth system: 2,500 pre-allocated chains (80 segments max each, 200K particles total) — leader particles at tips follow curl noise flow fields while depositing follower particles that form spring-connected tendrils
- Self-activation architecture: dead particles detect when they should activate by reading chain state from the input buffer — no cross-thread writes, no race conditions. Growth propagates via time-based segment intervals
- Branching on audio onset: reserve chains (beyond initial 100 leaders) self-activate by branching from random active chain tips; onset boosts branch probability 15×, brilliance widens branch angles
- Spring physics: followers connected by asymmetric springs (strong toward tip, weak toward root) with critical damping; creates organic elastic motion through the network
- Death cascade: roots age faster with spectral flux; when a root dies, death propagates tip-ward with 50ms/segment delay — visual dissolve from root to tip, then chain slot recycles for new branches
- 4 color modes: depth (teal-blue root → forest green → phosphorescent tip), generation (teal/gold/magenta by branch depth), velocity (dim blue → bright green), age (bright green → dark brown)
- Beat-phase traveling wave: periodic brightness pulse travels root→tip synced to detected beat, modulated by RMS
- Bioluminescent feedback trails: background shader with differential RGB decay (green persists longest), organic noise warp for living feel, HDR clamp
- Audio integration: bass→growth speed, onset→branching, mid→curl intensity, brilliance→branch angle, flux→death rate, beat_phase→traveling glow, rms→brightness, centroid→hue shift
- 200K particles, 8 params, additive blending, bloom postprocess, obstacle collision
Chaos Effect (New)
- Strange attractor dynamics: 5 classic attractors (Lorenz, Rössler, Halvorsen, Thomas, Chen) with RK4 integration — particles trace chaotic trajectories that reveal butterfly, torus, and knot-like shapes through accumulated feedback trails
- Attractor morphing: spectral centroid smoothly blends between adjacent attractor types; slider + audio drive crossfade the derivative functions (all attractors normalized to [-1,1] space)
- Audio-reactive bifurcation: bass shifts the bifurcation parameter across chaos boundaries (e.g. Lorenz ρ through ~24.74), onset provides impulse push past critical points for dramatic order→chaos transitions
- RK4 integrator: 4th-order Runge-Kutta for accurate trajectory integration; brilliance modulates time step for faster/more chaotic motion at high frequencies
- 4 color modes: velocity (blue→gold speed ramp), z-depth (cool→warm gradient), wing (cyan vs magenta by sign of attractor x), age (bright warm→dim cool fade)
- Feedback trail rendering: background shader with centripetal UV warp, differential RGB decay (red fades fastest → cool aging trails), HDR clamp
- 3D projection: slow constant-speed Y-axis rotation, perspective depth scaling via projection param, depth-based size and brightness modulation
- Divergence guard: NaN/out-of-bounds particles reset to random position near origin; soft clamp to normalized space prevents runaway
- 50K particles, 8 params, additive blending, bloom postprocess, obstacle collision
Turing Effect (New)
- Gray-Scott reaction-diffusion: full 2D R-D simulation on ping-pong compute textures, running N steps/frame (default 8, audio-modulated up to 32)
- Hybrid R-D + particles: 200K particles sample the R-D field for gradient-based chemotactic forces, flowing toward high-concentration pattern regions
- Particle coloring from chemistry: color, size, and alpha derived from local B concentration — particles are visible in pattern regions, transparent in substrate
- Audio-reactive chemistry: bass→feed rate (2.5× boost), mid→kill rate (2.5× boost), brilliance→diffuse_b, onset injects 4 randomized B drops per beat (PCG hash, 0.35 strength), beat doubles sim steps
- Particle audio reactivity: onset pulses gradient force (3× surge), bass pumps particle size, beat kicks velocity, RMS brightness (0.8 coeff), centroid shifts palette (0.5 coeff)
- Gray-Scott parameter space: feed/kill rate sliders traverse spots → worms → mitosis → coral → chaos regimes
- R-D infrastructure: new
ReactionDiffusionDefin particle types,RDUniformsstruct, group 4 bind group for particle compute,create_rd_resources()factory - 512×512 R-D grid, 8 params (gradient_strength, drag, feed_rate, kill_rate, diffuse_b, sim_speed, drop_size, brightness)
Accretion Effect (New)
- Tiled N-body gravity: O(N²) gravitational simulation using workgroup shared-memory tiling (GPU Gems 3 Ch. 31) — every particle attracts every other, forming orbital systems, accretion discs, and slingshot ejections
- Angular-momentum-preserving damping: only radial velocity is damped, preserving tangential (orbital) velocity for stable long-lived disc structure
- Audio seed injection: beat onsets inject "seed" particles (local attractors within the disc); seed mass scales with
mid, gravity strength modulated bybass - Spiral feedback trails: background shader warps trails with inward pull + rotation, creating galaxy-like spiral arms; differential RGB decay shifts aging trails from warm gold to cool blue
- 4 init patterns: disc (tangential orbits), ring (near-circular), two-body (binary system), collapse (zero-velocity infall)
- 3 color modes: velocity (blue→gold), proximity to seeds (blue→orange), orbital energy (blue=bound, red=escaping)
- Audio reactivity: onset brightness flash (warm-tinted), bass particle size breathing, RMS glow, bass/mid drive feedback warp strength
- 30K particles, 8 params, additive blending, central pressure support, obstacle collision
Array Effect (New)
- Audio-band speaker emitters: 5 toroidal ring emitters, one per frequency band (sub-bass through brilliance), each firing disc-shaped particle rings outward like speaker cones pushing air
- Two layout modes: vertical speaker stack (default) and concentric rings — crossfade with
layoutparam - Per-band audio reactivity: each emitter responds to its own frequency band for emission density, speed, and glow
- Beat-phase breathing: ring radii modulate with beat phase for rhythmic pulsing
- ParticleUniforms expanded 400→416 bytes: added
low_mid,upper_mid,presence,brillianceaudio fields to compute shaders (all 7 bands now available) - 300K particles, 8 params (trail_decay, ring_radius, spread, layout, color_mode, speed_mult, beat_pulse, emitter_glow)
Obstacle Refinements
- Contain mode: new collision mode that traps particles inside the obstacle shape — inverted collision test, binary search, and normals
- Flow Around improvement: redirects approach energy into tangential direction instead of stripping it, preventing pile-ups
- Video obstacles: luminance-to-alpha conversion so video frames work as obstacles (bright = solid, dark = passable); UI now shows "Video: filename"
- Webcam cleanup: Clear and source-switching now properly stop webcam/depth when no longer needed
- Tooltips: all obstacle panel controls have descriptive hover text
Monocular Depth Estimation (New)
- Feature-gated
depth:cargo run --features depth,webcamfor MiDaS v2.1 small monocular depth estimation - Webcam frames → background
phosphor-depththread → ONNX Runtime (ort crate) → 256×256 depth map → obstacle texture - Particles collide with person's depth silhouette — nearby objects block, far objects pass through
- Model download from HuggingFace (~66 MB), user-initiated via UI button with progress indicator
- Model cached at
~/.config/phosphor/models/midas_v21_small_256.onnx - 1-2 frame latency depth pipeline: bounded(1) channels, try_send drops stale frames
- UI: "Depth" button in Obstacle panel (auto-shows "Download" if model missing, progress % during download)
- Preset save/load:
obstacle_depthfield with backward-compatible serde defaults - 7 new tests (downscale, model path, download progress states, preset serde) — 352 total
Obstacle Collision (New)
- Image-based 2D obstacle textures for particle collision — load PNG/JPEG/WebP with alpha channel
- 3 collision modes: Bounce (reflect velocity along surface normal, scale by elasticity), Stick (zero velocity, hold at surface), Flow Around (tangent projection, remove normal component)
- Obstacle texture extends compute group 1 bind group (bindings 2+3) alongside existing flow field
- WGSL collision helpers in
particle_lib.wgsl:obstacle_alpha(),obstacle_normal()(central-difference gradient),apply_obstacle_collision()— any*_sim.wgslcan call them with 3 lines ParticleUniformsextended 384→400 bytes: obstacle_enabled, obstacle_threshold, obstacle_mode, obstacle_elasticity- Collapsible "Obstacle" UI panel in right sidebar: enable toggle, Load Image (rfd), Clear, mode dropdown, threshold/elasticity sliders
- Integrated into Cascade effect; same 3-line pattern (
prev_pos, integrate,apply_obstacle_collision) works in any particle sim - Preset save/load with backward-compatible serde defaults
- Opaque image support: images without alpha (JPEG, opaque PNG) auto-detect and use luminance as collision mask — dark areas are passthrough, bright areas are solid
- Anti-strobe: binary search finds exact surface contact point along integration step (4 iterations), places particle just outside surface; velocity reflection only when moving into obstacle
- Texture format
Rgba8Unorm(not sRGB) for raw data sampling; UV Y-flip for correct clip→texture mapping - 6 new tests (ObstacleMode conversions, preset serde, backward compat, alpha preprocessing) — 351 total
Cascade Effect (Fix)
- Fixed beat flash strobe: replaced binary
u.beatfull-screen flash with smoothbeat_phaseenvelope (fast 25%-of-beat quadratic fade), reduced amplitude from 0.05 to 0.03
Cascade Effect (New)
- Cascade: solid walls of particles emit inward from all 4 screen edges, audio-segmented by frequency band
- Pixel-perfect wall emission: particle index maps deterministically to perimeter pixels (1920 top/bottom + 1080 left/right at 1080p), guaranteeing gap-free solid walls that scale with resolution
- Band-to-edge mapping: bottom=bass+sub_bass (red-orange), left=mid (teal), right=mid (blue-violet), top=centroid (white-cyan)
- Audio directly drives wall penetration depth: band energy controls particle speed, so walls extend/retract with the music like Aurora's ribbons
- Continuous audio push force during simulation keeps accelerating particles when their band is active
- Curl noise turbulence for organic lateral drift, beat-triggered velocity pulse, onset jitter
- 3 color modes: band colors (4 distinct per edge), monochrome white-blue, speed/energy gradient
- Background shader: feedback trails with directional inward UV warp, per-band audio-reactive edge glow strips (width scales with audio), beat flash
- Functions as both standalone effect and layerable edge frame via convergence param + brightness alpha compositing
- 8 params: trail_decay, inward_speed, spread, curl_strength, color_mode, edge_glow, convergence, beat_sync
Genesis Effect (Retuned)
- Genesis (Particle Lenia): retuned from reference implementation (znah.net/lenia) for proper self-organizing behavior
- Ring kernel calibrated to reference ratios: peak at 30% of R, width 23% of R (proper ring with falloff)
- Kernel weight W_K scaled from 0.003 → 0.02 for correct field density at particle count
- Interaction radius narrowed to [0.05, 0.20] from [0.10, 0.50] — manageable search radius
- Removed curl rotation (was compensating for over-smooth density field at 20K)
- Stronger growth forces (step×0.10) and higher speed cap (MAX_SPEED=0.25) for VJ-paced dynamics
- Soft radial containment spring past screen edge + hard clamp safety net (replaces square boundary)
- Beat-triggered seed drops: ~3% of particles teleport to a random screen location on each beat, creating fresh organisms that self-organize from the music
- 9 initial seed clusters in 3×3 grid (fills screen), zero-mean paired-hash noise (no drift bias)
- Stronger bloom (threshold 0.15, intensity 0.55) for visual presence
Tesla Effect (New)
- Tesla (magnetic field): 200K charged particles follow magnetic field lines as a flow field, creating interweaving helical trajectories
- Magnetic monopole flow field: particles follow superposition of dipole field directions; charge sign determines direction along field lines (+charge follows B, -charge follows -B)
- 4 dipole arrangements via
dipole_modeparam: parallel (same polarity), antiparallel (bar magnet, default), ring (4 alternating), quadrupole (4 + oscillating center) - Orbital correction near poles: smoothly blends field-following to tangential orbit, preventing convergence at sink poles
- Helical oscillation: per-particle perpendicular sine wave with random phase, controlled by
helix_tightness - Proximity dimming: particles near dipoles render dim, brightening as they arc away
- Charge-based coloring: cyan for positive, magenta for negative;
charge_ratiocontrols mix - Audio-reactive: bass drives flow speed, onset triggers velocity jitter, beat triggers per-particle polarity flips (cyan↔magenta)
- 3 color modes: charge-based (cyan/magenta), speed-based (cool→hot), lifetime gradient (cool→warm)
- Feedback trails with electric UV shimmer, dim background field line visualization
- 8 params: trail_decay, field_strength, charge_ratio, dipole_mode, field_rotation, color_mode, helix_tightness, flip_sensitivity
AoS → SoA Particle Buffer Refactor
- Structure of Arrays layout: split single 64-byte
Particlestorage buffer into 4 separate 16-byte SoA buffers (pos_life,vel_size,color,flags) — position-only reads (spatial hash, sort keygen) now load 16 instead of 64 bytes per particle (4× bandwidth savings) - 13-entry compute bind group: bindings 1-4 read, 5-8 write, 9 counters, 10 aux, 11 dead, 12 alive indices
- 6-entry render bind group: 4 SoA read buffers + uniforms + alive indices
read_particle()/write_particle()helpers in particle_lib.wgsl — effect compute shaders use convenience functions, hot-path neighbor reads (murmur boids) access individual arrays directly- Spatial hash and sort keygen optimized: count/scatter shaders bind only
pos_life, sort keygen binds onlyvel_size - Request
max_storage_buffers_per_shader_stage: 16(all desktop GPUs support this) - Total memory identical (64 bytes/particle), just reorganized for better GPU cache line utilization
Raster Shard Flex
- Flexible shard motion: Voronoi shards deform organically instead of moving as rigid blocks — distance-dependent rotation twist, per-particle noise within shards, and wider burst spread with outer particles flying further
Raster Grid Artifact Fix
- Jittered grid sampling: ±0.4×step deterministic offset per particle breaks up visible row/column banding in smooth gradient areas
- Bilinear color interpolation: fractional pixel positions sample from 4 surrounding pixels, eliminating color stairstepping in gradients
- Gradient-based size modulation: luminance gradient magnitude stored in
home.w; smooth areas get 30% larger particles (fill gaps), edges stay neutral - Sparkle boost: bright pixels at high-gradient locations (stars, glints) get audio-reactive size pulsing — per-particle phase offset creates independent twinkling driven by onset and mids
- New unit tests for jitter, bilinear sampling, and gradient helpers
Raster Image Loading Fixes
- Fix 2048x2048 image loading:
upload_aux_data()no longer shrinks aux buffer belowmax_particlessize — subsequentupdate_aux_in_place()calls for larger images were silently dropped - Fix image selector not updating: switching built-in images now updates
ps.def.emitter.imageso the ComboBox reflects the active selection - Fix particle slider clamping: slider ranges now dynamically extend to include the current value — prevents Raster's
emit_rate=100K/lifetime=999from being silently clamped to slider defaults, which corrupted the effect every frame the panel was drawn - Built-in effects are runtime-only: particle slider changes no longer write back to
effect_loaderfor built-in effects — use presets to persist tweaks - Warning on aux buffer overflow:
update_aux_in_place()now logs a warning when a write is skipped due to buffer size - Raster particle cap raised from 500K to 2M (covers 2048² images at grid step=1)
- Raster default particle size reduced from 0.004 to 0.002
Particle Video/Webcam Source + Transitions
- Video as particle source: Raster (and any image-emitter effect) can now use video files as particle source — particles update home positions per-frame tracking video content
- Webcam as particle source: live webcam feed drives particle positions in real-time (feature-gated
webcam) - Source transitions: smooth 0.5s interpolation of particle home positions when switching between image/video/webcam sources (no hard snap)
- Per-frame aux updates: pre-allocated aux buffer at
max_particlessize enableswrite_bufferupdates without recreating GPU bind groups (8MB DMA transfer for 500K particles) sample_rgba_buffer(): new function samples raw RGBA byte data directly, skipping file I/O — used for video frame and webcam frame particle sampling- UI source controls: particle panel shows source type badge (IMG/VIDEO/CAM), source name, Load Video / Webcam buttons (feature-gated), video transport (play/pause, speed, loop, seek)
- Preset save/load: particle video path, speed, looping, and webcam source persisted in
LayerPresetwith serde defaults for backward compat - Load Image button: users can load custom PNG/JPEG/WebP images as particle source (replaces default image)
- Animated GIF as particle source: GIFs with multiple frames auto-detect as animated source with per-frame particle position updates (same as video, no feature gate required)
- Background loading: all particle source loading (image, GIF, video) happens on a background thread via
ParticleSourceLoader— no UI freeze during decode - EmitterDef
videofield:.pfxfiles can specify"video": "clip.mp4"or"video": "webcam"for built-in video/webcam source at effect load time - Video decode reuses existing
media/video.rsinfrastructure (ffmpeg pre-decode to RAM, 60s max) - Built-in image selector: combo box in particle panel for quick-switching between bundled raster images (skull, phoenix, jellyfish, hand, samurai_mask) without file dialog
- Morph-safe preset loading: when a preset layer has the same effect already loaded, skip the full effect reload — keeps particle systems alive so morph transitions interpolate params smoothly instead of destroying and rebuilding all particles
- 12 new tests:
sample_rgba_buffer(5),ParticleImageSource(2),SourceTransition(3),LayerPresetserde (3),EmitterDefserde with video (2)
Raster Effect (New)
- Raster (video wall): 500K particles map to image pixel positions, colored by source image
- Voronoi shard fragmentation: 32 seed points create irregular fragments with per-shard rotation, translation, and depth variation
- Frayed edges: particles near Voronoi cell boundaries break free with individual jitter
- Three bass displacement modes via
bass_modeparam: shards (default), tangential swirl, radial push - 2D sinusoidal wave displacement from mids, onset-driven per-particle scatter from highs
- Spring-return physics pull particles back to displaced home positions (sustained audio holds displacement)
- Beat onset impulse with shard-coherent burst direction
- Luminance-based particle sizing: brighter pixels render larger for visual depth
- 8 exposed params: trail_decay, spring_k, bass_strength, mid_wave, high_scatter, burst_force, depth_scale, bass_mode
- Optional feedback trails via trail_decay for motion blur during scatter
- Image source
MAX_DIMraised from 512 to 2048 for higher-resolution sampling
Particle Effects Overhaul
- Delete 4 effects: removed Coral, Helix, Nova, Vortex (low quality, never worked well)
- Murmuration → Murmur: renamed, removed feedback trails (root cause of "sperm" look), brighter sky for contrast, larger/darker/more opaque particles (0.014 size, 0.9 alpha), reduced count 70K→40K
- Ribbons fix: lower feedback clamp 0.12→0.06, stronger vignette, halved particle brightness and alpha to prevent additive washout
- Veil fix: lower feedback cap 0.4→0.15, stronger decay dampening, halved particle brightness and alpha, minimum dampening floor even without audio
- Cymatics enhancement: 25K→50K particles, emit rate 1700→3500, burst 100→500, 3 new params (rotation, symmetry, glow), more vivid color gradient, stronger nodal line contrast
- Swarm → Spirograph: complete reimagine — hypotrochoid parametric curves with 5 arms of different petal-count patterns, drifting centers, audio-reactive ratio morphing. 6 params: trail_decay, draw_speed, pattern_scale, complexity, drift, color_spread
- Spirograph (new): multi-pattern hypotrochoid curves with 5 arms of distinct petal-count patterns, drifting centers, audio-reactive ratio morphing. Replaces Swarm.
- Compute shader
param()access: particle compute shaders can now access effect params 0-7 viaparam(i)helper, forwarded from ParamStore througheffect_paramsfields in ParticleUniforms (repurposed padding bytes, no size change) - Updated Crucible and Spectral Eye presets (Swarm→Spirograph)
- Effect count: 15 curated effects (was 12 before particle additions, peaked at 19, now curated down to 15)
Particle System Hardening for 1M Support
- GPU-side buffer zero-init: replaced CPU
vec![0u8; 128MB]+write_bufferwithencoder.clear_buffer()— eliminates 128MB+ CPU allocation at 1M particles - Device limits validation:
max_countclamped to devicemax_storage_buffer_binding_sizewith log warning - Bitonic sort auto-cap: depth sort auto-disabled above 65K particles (would require 210 dispatches/frame at 1M)
- Trail buffer safety: trails disabled above 500K particles; trail length capped to fit device binding limit
Parameter Persistence
- Slider values persist to .pfx: adjusting parameters in the Parameters panel now updates the
defaultvalues in the .pfx file for user effects, so values survive effect reload and app restart - Debounced 500ms save — writes only after slider stops moving, not on every frame
- Editor's Effect tab stays in sync when params are saved from UI
Shader Editor
- Fix: Save now persists both tabs: Save (Ctrl+S) writes both the active and paired file when either has unsaved changes — previously only saved the currently active tab, losing edits to the other file
Effect Browser
- Particle badge: small accent-colored dot in top-right corner of effect buttons that use particles
- Particle count in hover: hover text shows particle count (e.g. "70K particles", "1M particles")
Veil Effect
- Fix feedback blowout on loud audio: particle brightness now decreases with volume, stronger loudness dampening on alpha, audio-reactive decay, lowered feedback clamp
Helix Effect Overhaul
- Dipole field-guided particles: replaced uniform B_z Lorentz force with magnetic dipole field — particles follow curved field lines from emitter, creating visible force field pattern through trails
- Helical oscillation: perpendicular oscillation around field lines (charge-dependent CW/CCW), bass tightens helix frequency
- Feedback warps along field: particle trails flow along magnetic field direction with chromatic aberration, creating persistent field line visualization
- Dark background: replaced overpowering painted field lines with near-black background + very subtle dipole hints; particles are the main visual
- Bright particles: brightness 0.22→0.70, alpha 0.30→0.55, particle size 0.010→0.018; emit rate 100→200 for dense field coverage
- Beat burst: speed pulse along field lines on beat detection; onset scatters particles laterally off field lines
- Tuned post-processing: bloom threshold 0.50→0.35, bloom intensity 0.45→0.60 for force-field glow; brighter saturated color gradient
Chaos Effect Overhaul
- Visible Lorenz butterfly: fixed base rho from 22 (below chaos threshold) to 28 (canonical chaotic regime); particles now trace the iconic butterfly attractor pattern
- XZ butterfly projection: replaced Y-axis rotation with XZ plane view (the classic Lorenz butterfly orientation) plus gentle ±8° tilt oscillation for 3D depth feel
- Stable attractor dynamics: reduced audio modulation of sigma/rho/beta to subtle range (was dramatically reshaping the attractor every frame); fixed
time * audio-varyingrotation bug that caused violent camera jumps - Trail-based visualization: dim particles (alpha × 0.2) with high trail persistence (decay 0.92) accumulate into visible trajectory lines; clean feedback without twist distortion preserves tight attractor lines
- Density increase: 20K particles at 500/s emit (was 6K at 80/s) for dense attractor coverage
- Removed dead params: attractor_mix, zoom, speed inputs were not wired to compute shader (compute shaders cannot access
param()) - Brightness fixes: removed × 0.18 fallback color dim, raised initial alpha 0.3→1.0, unclamped color from 0.5→1.0 max
Particle Effect Review & Enhancement
- Murmuration (hero effect): full Boids flocking model with separation, cohesion, and alignment; angular heading smoothing eliminates jitter; alpha blend for opaque bird silhouettes; twilight sky gradient background; depth-based sizing; 15K particles; audio: beat→cohesion spike, bass→disorder, onset→flock split
- Coral (fix): replaced numerical central-difference gradient with analytical derivative of Turing cosine waves; added soft exponential boundary repulsion (no more hard clamp collapse); FBM curl noise for organic diffusion; reduced attraction_strength 2.5→1.2; aspect ratio correction; color gradient and size curve
- Chaos (fix): adaptive sub-stepping (4 steps) prevents divergence; NaN/divergence detection auto-kills bad particles; proper Rossler scaling (native scale then rescale); centered perspective projection around attractor center; color gradient for temperature-based trajectory coloring; size/opacity curves
- Helix (fix): increased E-field upward from 0.05→0.20; reduced B_z from 3.0→1.5 base (wider spirals that actually rise); beat adds upward levitation pulse; moved emitter from y=-0.6 to y=-0.4; RMS-reactive upward drift; two-strand DNA color gradient; size/opacity curves
- Nova (overhaul): color gradient lifecycle (white-hot→vivid→orange→dim red); size/opacity lifetime curves; ground bounce for trailing sparks (restitution 0.3); speed/size/life variance for natural variation; 15→85% shell/spark ratio; staggered burst emission; stronger ground glow with beat pulse
- Phosphor (polish): reduced sparkle overbright (mix 0.55→0.3, mult 1.6→1.2); tightened feedback decay 0.88→0.84; stronger beat inward pull 0.02→0.06; size/opacity curves for crisp particle falloff; reduced bloom intensity 1.2→1.1
- Veil (bloom fix): raised background cap 0.2→0.6 for richer fabric; lowered bloom_threshold 0.9→0.7
- Ribbons (bloom fix): raised bloom_threshold 0.4→0.60; reduced bloom_intensity 0.6→0.40; reduced trail_width 0.008→0.005; capped feedback at 0.8
- Cymatics (polish): analytical Chladni gradient (no numerical eps); smooth bilinear mode crossfade between integer modes; raised background cap 0.5→0.85; sharper nodal line glow (exp factor 12→18); soft boundary repulsion; color gradient and size curve
- Vortex: chromatic aberration in gravitational lensing (R/G/B channel separation); accretion glow flash near event horizon on beat; temperature-based color gradient for disk coloring
- Swarm: wider orbit radius 0.3→0.5; reduced radial damping 8.0→4.5; stronger beat orbit expansion
- Flux: sparkle system (8% of particles get 2.5x brightness with flicker); opacity curve for gentle fade-out; raised background cap 0.5→0.8
Advanced Particle Forces
- ParticleUniforms extended from 192 to 384 bytes with new force, emitter, curve, and sort fields
- FBM noise forces: configurable octaves, lacunarity, persistence with turbulence and curl noise modes
- Wind force: constant directional force via
wind: [x, y] - Vortex field: rotational force with center, strength, and falloff radius
- Ground bounce: configurable y-level and restitution coefficient
- New emitter shapes: disc (uniform area fill), cone (directional spread with angle control)
- Emitter velocity inheritance: particles inherit emitter motion for trailing effects
- Speed, lifetime, and size variance: per-particle randomization (0-1 range)
- Lifetime curves: 8-point LUT for size and opacity over particle lifetime
- Color gradient: up to 8 hex colors interpolated over lifetime
- Particle spin: per-particle rotation with configurable speed, rendered via vertex rotation
apply_builtin_forces()helper inparticle_lib.wgsl: single call applies all forces (gravity, wind, drag, noise, attraction, vortex, flow field)- Curve/gradient evaluation helpers:
eval_size_curve(),eval_opacity_curve(),eval_color_gradient() - Bitonic depth sort: optional GPU merge-sort on alive indices for correct alpha-blended rendering
parse_hex_color()utility for#RRGGBB/#RRGGBBAAto packed u32- All new fields default to zero/disabled — existing .pfx files and custom shaders work unchanged
PFX Hot-Reload + Editor Integration
.pfxfile hot-reload: editing effect metadata (params, postprocess, passes, particles) live-updates without restarting- Differential updates: param-only or postprocess-only changes skip GPU pipeline rebuilds
merge_from_defson ParamStore preserves slider positions when param definitions change- Editor tab switching: Shader and Effect tabs in code editor for
.wgsland.pfxside by side - JSON syntax highlighting for
.pfxediting - "Edit Shader" → "Edit Effect", "Copy Shader" → "Copy Effect" button labels
- 12 new unit tests for PfxDiff and merge_from_defs
Bug Fixes
- Fix BPM detection reporting double tempo (~290 instead of ~145 BPM): remove cascading octave-up correction that fought multi-ratio disambiguation, tighten tempo prior σ from 1.5 to 1.0, widen frame time clamp to tolerate real audio thread timing, reduce Kalman snap escape from 50 to 30 frames
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 Phosphor.app to Applications (or run directly). Signed and notarized.
Linux / Windows: Extract, run phosphor 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 for network video output
- Video playback (optional):
ffmpegon PATH for media layers
NDI® is a registered trademark of Vizrt NDI AB.