Always on the latest: the download link tracks every notarized release. Existing installs auto-update from inside the app — no need to re-download.
Download latest
1.13.3 Jun 27, 2026 Layout rename fix

Phasion Live 1.13.3

Fixed

  • Renaming a layout no longer exits edit mode instantly. Double-clicking a layout name (or clicking the ✎ pencil) now starts a rename you can actually type into — previously the first click recalled the layout, and the restore stole window focus, blurring the rename field. Clicking elsewhere on a slot still recalls instantly.

--- Auto-update: open Phasion Live and it will offer 1.13.3 on the next update check.

1.13.2 Jun 27, 2026 Layout z-order, capture & minimize

Phasion Live 1.13.2

Media Float layout fixes (follow-up to 1.13.1).

Fixed

  • Phasion's controls no longer sit on top of your layout. Restoring a layout now reliably raises external apps (Arena, etc.) above Phasion's own window again — fixes the 1.13.1 regression where the controls window covered everything. Heals existing layouts on restore (no re-save needed).
  • Layouts capture the right app window. When an app has a secondary fullscreen output window (e.g. Resolume's "Display"), layouts now capture the main window instead — so a narrow side-by-side Arena layout resizes the composition window correctly. Re-save the affected layout once to pick up the corrected capture.

--- Auto-update: open Phasion Live and it will offer 1.13.2 on the next update check.

1.13.1 Jun 25, 2026 Media Float layout fixes

Phasion Live 1.13.1

Bug-fix release for the Media Float layout system.

Fixed

  • Layouts no longer relaunch apps that are already running. Restoring a layout now unhides + activates a running app instead of re-running open -a — which, when several apps share a name (e.g. multiple "OnlyJams" bundles), could launch the wrong/duplicate copy. Fixes "every time I queue the layout it reloads the app."
  • Phasion no longer covers a full-screen external app. When a layout keeps a near-full-screen app (e.g. Resolume Arena filling the screen), Phasion is no longer forced on top — Arena stays visible instead of getting covered a moment after it appears.
  • Your own app + Terminal/Finder are excluded from layouts. Layouts captured while launching from a terminal no longer drag a Terminal window along, and Phasion never manages itself as an external app.

These fixes self-heal existing layouts on restore — no need to recreate them.

--- Auto-update: open Phasion Live and it will offer 1.13.1 on the next update check.

1.13.0 Jun 9, 2026 CrowdCam

Phasion Live 1.13.0

The CrowdCam release — the audience's phones become live cameras on your screens.

📹 CrowdCam (new)

Turn every phone in the room into a camera feed on your venue displays.

  • One tap to go live — guests scan the same QR and tap "Go on screen"; no app, no signup.
  • You stay in control — approve-before-air lobby, or flip on auto-air for hands-free rotation through the whole room.
  • Up to 9 live tiles — spotlight one, grid the crew, or auto-rotate.
  • Pinch-to-zoom on phones — guests frame the shot from the floor (native zoom where supported, smooth digital zoom everywhere else).
  • Single-pull hub — feeds every output (and Resolume) from one pull, so showing CrowdCam on multiple screens doesn't multiply your bandwidth.
  • Record the night — every angle captured with phone audio, exported as a multicam timeline for Final Cut, Premiere, and DaVinci Resolve (audio-synced).
  • Dedicated phasioncrowdcam Syphon source; idle-throttles to save GPU when no cameras are on air.

🎚 MIDI & OSC

  • New "Blend Mix — All Outputs" control — map one fader to move the A/B blend on every output at once (MIDI-learnable; OSC /mashup/mix/all).
  • (Reminder: Change All / Same All / Fade All are already MIDI + OSC mappable.)

🎞 Recap & social exports

  • Social cards and the recap HTML now rebuild from your published cloud recap, so exports work even after restarting the app — not just mid-session.
  • Venue logo now renders on social cards and the recap page (white-label aware).

✨ Polish

  • CrowdCam control panel decluttered with a clear hero action and a "More options" drawer.
  • Audience QR Code menu moved above Color & Light.
  • "Show on" output selection now persists across restarts.
  • Fixed tooltip clipping at narrow window widths and QR-code top padding.

--- Auto-update: open Phasion Live → it will offer 1.13.0 the next time it checks for updates.

1.12.0 May 29, 2026 Launch-Readiness: Security & Reliability

Phasion Live 1.12.0

The launch-readiness release. A deep security-hardening pass, real reliability feedback for the phone remote, an NDI audio fix, and a sweep of cloud-audience polish — everything needed to put Phasion in front of a paying room with confidence.

Security & privacy

The biggest theme of this release. Phasion's cloud remote and audience surfaces are now hardened end to end.

  • Cloud remote authenticates the operator. Each slug now provisions a high-entropy operator secret; the desktop role on the relay requires it. This closes the hole where anyone who saw the on-screen QR/slug could claim "desktop," reset the passphrase, and drive commands on your show. The migration is lockout-proof — an un-updated app is never locked out of its own session.
  • Remote sessions survive a quiet set. The relay's idle timer now checks for live connections before clearing auth, so a connected-but-idle operator never loses the phone remote mid-show.
  • LAN/tunnel remote requires a password. When the remote is reachable over the network (Cloudflare tunnel or a non-loopback address), the old no-password fallback is gone — it returns 403 until you set a remote password. Local/loopback use stays password-free.
  • Admin dashboard token out of the URL. The private stats dashboard now authenticates via an HttpOnly, Secure, SameSite=Strict cookie and redirects to a clean URL — the secret no longer rides in browser history, proxy logs, or referrer headers.
  • Baseline security headers on every cloud page — including the slugged /r, /join, and /recap URLs your QR codes point at: nosniff, no-referrer, and a clickjacking/plugin/base lockdown (frame-ancestors 'none', object-src 'none', base-uri 'none', X-Frame-Options: DENY).
  • Crash reporting is opt-in and off by default, matching the privacy policy. Enable it in Settings → Account & Privacy if you want to help with diagnostics.
  • Hardened internals: shell-free browser/app launch (no shell interpolation), renderer→main file access scoped to dialog-approved + app paths only, and cloud photo upload hardening (size + MIME checks, fail-closed rate limiting on the costly paths).

Phone remote — real delivery feedback

  • You can finally see whether your computer is connected. A new "🖥 Computer" status pill shows green when your Mac is connected and receiving commands, red when it's asleep/closed/unreachable. Tapping a control while disconnected tells you it didn't land — no more false "✓ done" while nothing happens on the wall.
  • Passcode-by-default sharing. The remote link no longer embeds the 4-digit code by default — you type it in by hand. Uncheck "keep passphrase out of the link" for frictionless one-tap pairing of your own phone.
  • Quality-of-life: the emoji burst is always available, failed commands raise a toast, status pills are tappable, and touch targets are bigger.
  • Moderation: flag badges now show the reason, items show their age, and there's a pause-all control.
  • Fixes: the share link no longer leaks the passphrase when clicked; the feed-source button no longer renders cropped on the first open.

NDI audio — real-receiver gating + silence fix

  • NDI audio output is now gated on a reliable, lsof-derived "is anyone actually receiving?" signal. When no receiver is connected, the renderer stops marshaling audio frames across the process boundary entirely.
  • This also fixes a latent bug: in the default shared-texture render mode, NDI audio could be dropped silently because the old gate trusted an unreliable connection count. The new gate fails open — it never silently silences audio.

Cloud audience

  • phasion.live audience URLs. Audience, remote, and recap links now live on the phasion.live domain.
  • Live "people here" count so you can see the room filling up in real time.
  • Audience deep-link polish — submission entry animations, haptic confirmation, and clearer error/empty states.
  • Post-event recap pages that outlive the event — the URL keeps serving the night's gallery after the app closes.

Onboarding & UI

  • Tier-aware guided tour refinements (the walkthrough adapts to your license tier).
  • Settings polish, accessibility labels on icon-only buttons, and light-mode consistency.

To upgrade

  • Settings carry over automatically. Relaunch to pick up the desktop-side changes.
  • The remote share link now defaults to passcode-by-hand. If you want one-tap pairing for your own device, toggle "keep passphrase out of the link" off in Remote settings.
  • Crash reporting is now off by default. Turn it on in Settings → Account & Privacy if you'd like to send diagnostics.
1.11.0 May 26, 2026 Smart NDI Gate

Phasion Live 1.11.0

The "smart NDI gate" release. Major polish on the cost and clarity of NDI broadcasting, plus a sweep of overlay-sizing fixes and UI consistency.

NDI: real-receiver detection

Phasion now identifies every consumer connecting to its NDI senders and gates CPU work accordingly.

  • lsof-based receiver introspection — process names of every TCP peer on each lane's NDI port. Synesthesia, Resolume, OBS, etc. show up by name in Settings → NDI. Async background poll (4-second cadence) — never blocks the main thread.
  • Phantom subscriber filter — substring match on either process name OR remote IP. Default catches NDI Virtual Input (including the modern macOS system extension). One-click "× ignore" button next to each receiver adds it to the filter.
  • Hardware NDI consumers (KiloView, AJA BRIDGE, NDI Bridge ULL, anything that auto-subscribes on discovery) — identified by LAN IP; click "× ignore" to silence the CPU drain.
  • Smart gate modes in Settings — Auto (skip when no real receivers), Always (legacy), Tally-only (program/preview), Armed (manual + auto).
  • Stale-keepalive correction — when NDI's get_no_connections lingers after a receiver disconnects (~30-60s TCP timeout), Phasion cross-references lsof and trusts the OS over the library. CPU drops within ~4s instead of a full minute.
  • Per-tab status dot — green = real receiver pulling frames, amber = only phantoms (gate skipping work), gray = armed but no receivers, hidden = disarmed.

NDI: publish rate cap (opt-in)

The single most expensive subsystem in Phasion is NDI's BGRA readback + SpeedHQ encode (~80% of one core per 1080p60 lane). New dropdown in Settings:

  • Default: match visualizer rate (no cap, smoothest)
  • 30 fps cap ≈ −35% CPU
  • 24 fps cinema ≈ −45% CPU
  • 15 fps lowest CPU ≈ −55% CPU

Receivers interpolate missing frames; only fast cuts / strobes look juddery below 30. Operators pick the trade-off; nothing is forced.

Overlay sizing — measured + consistent

Found and fixed a "double-amplification" antipattern across all DOM overlays (wrapper zoom × clamp(_, vw, _) font sizing was making text 50% larger at 1080p than at 720p). CDP-measured before and after — every overlay now renders at the same proportional size on any lane resolution.

  • QR overlay — removed wrapper zoom on #qr-overlay-dom; applyQrPosition() is the single resolution-normalizer.
  • Trivia / Poll / Caption / Now Playing / Lyrics — clamp+vw font sizes converted to fixed pixels; wrapper zoom is now the sole proportional scaler.
  • Lyrics overlay — added missing zoom: var(--lane-scale) wrapper that the rest of the audience overlays already had.
  • Poll subviews (donut chart, bignum, columns, legend) — all clamp+vw replaced with fixed-px for uniform behavior.

UI polish

  • Keyboard shortcut cheatsheet (press ?) — labels refreshed to match the actual bindings (FX-bar was reshuffled), added Alt+D (toggle Ops bar), Cmd+D / Cmd+Backspace (duplicate / delete edited scene).
  • NDI Settings panel — per-lane receiver names, IPs, source (lsof vs metadata vs unknown), and a stale-count badge when applicable.
  • One-click phantom filter — every receiver in the detail list has an "× ignore" button.

Server-side (licensing)

  • Lemon Squeezy webhook now reads variant → tier mapping from LS_VARIANT_TIERS Supabase secret so new product variants (Pro Local, Pro Cloud, Venue Local, Venue Cloud) don't require code edits. Falls back to pro for unmapped variants.
  • Tier-specific seat-limit defaults (Venue tiers get 5, others get 2).

Performance

  • Async NDI pollexecFileSync calls replaced with promisified execFile + Promise.all for parallel peer resolution. No more 150-300ms hitches every 4 seconds.
  • Sender-side metadata drain — opportunistic, gated, capped at 64 frames per call. Picks up identifying XML when receivers send it (rare in practice).

Default behavior changes

  • NDI publish FPS cap now defaults to 0 (no cap — match visualizer rate) instead of 30. Operators who actively chose 30 keep their setting; new installs get the smoothest defaults out of the box.
  • NDI phantom filter is on by default and pre-populated with the macOS-extension binary name for NDI Virtual Input.

To upgrade

Settings preferences carry over automatically. If you previously configured a phantom filter list, it's preserved; the new IP-matching is additive.

Existing operators who want the new opt-in NDI cap defaults: open Settings → NDI → set "NDI rate" to "Match visualizer (smoothest, default)".

1.10.0 May 25, 2026 NDI fixed, displays steady

Phasion Live 1.10.0 — NDI fixed, displays steady

1.9.0's Phase D win (per-lane Syphon via Electron useSharedTexture) broke NDI as a side-effect: image.toBitmap() returns an empty buffer in shared-texture mode, so NDI receivers got either a still frame or all-black. 1.10.0 fixes NDI end-to-end and adds operator controls so the CPU cost only happens when a real consumer is actually watching.

Also: a Rust display sidecar for fullscreen on Sequoia (the menubar no longer leaks into your projector output), and a handful of polish fixes that surfaced during the NDI work.

📺 NDI — full rework

The headline regression from 1.9.0 is fixed, and the feature is substantially better than it was before.

Phase D NDI fast path

New native syphon.readbackOffscreenLaneBgra(lane) locks the lane's published IOSurface read-only and memcpys it into a Node Buffer. The IOSurface is MTLStorageModeShared, so this is a straight CPU copy from shared memory — no GPU stall, no Metal command-buffer flush. Cost: ~1.6 MB/frame at 480p, ~8 MB/frame at 1080p — only paid when NDI is enabled.

ndi.publishFrameLaneBgra now accepts a stride parameter so the addon can honor the IOSurface's padded row stride directly via line_stride_in_bytes, no tight-packing memcpy required.

Fix: shear at non-aligned widths

854×480 (the old 480p preset) ships with IOSurfaceBytesPerRow=3456 not the expected 3416 — IOSurface aligns to 64 bytes. NDI's BGRA pipeline skewed the image diagonally when xres didn't match stride/4. Two fixes:

1. The 480p preset is now 848×480 (16-pixel aligned, 0.7% aspect delta from true 16:9 — imperceptible). The custom-resolution dialog snaps widths to the nearest multiple of 16 with a friendly notice explaining why. 2. For any non-aligned widths that slip through, the readback passes paddedWidth = stride/4 to NDI so the receiver sees the visible content cleanly + a small black bar on the right.

Per-output NDI broadcast (opt-in)

The NDI panel now has three checkboxes — Out 1, Out 2, Out 3 — that control which lanes get an NDI sender. All three default to OFF: master Enable starts the framework, you opt into each output explicitly. Disabling an output completely stops that lane's sender (no readback, no publish, no mDNS announce — zero CPU). The receiver count (N) shows in green next to each output when consumers connect.

Receiver-count gate

When a lane has zero NDI receivers (get_no_connections polled every 30 frames), the paint bridge skips the IOSurface readback + publish entirely. With Resolume / OBS / Synesthesia subscribed, the lane publishes at full rate. Result on a 3-output rig with Resolume pulling Out 1 only:

| State | Lane 1 paint cost | Lanes 2/3 paint cost | |---|---|---| | NDI off | 1.3 ms | 1.3 ms | | NDI on, no receivers | 1.3 ms | 1.3 ms | | NDI on, Resolume on Out 1 | ~8 ms | 1.3 ms |

Operator visibility for phantom subscribers

Some apps (NDI Tools' Virtual Input system extension, NDI Studio Monitor) auto-subscribe to any source they discover via mDNS, even when nobody's actively viewing. From NDI's perspective these are legitimate receivers — connection count goes up, frames get encoded.

To make this visible:

  • Receiver count (N) next to each per-output checkbox tells you

immediately how many consumers are subscribed.

  • A ↻ Restart Senders button destroys + recreates all senders

in one shot. Legitimate downstream tools (Resolume, OBS) reconnect within 1-2 s. Passive subscribers may not, so this is a useful diagnostic for "who is keeping CPU high?"

  • The per-output checkbox is the lever: uncheck Out N to fully

stop work on that lane, whether the cause is a phantom or a real consumer you no longer need.

NDI Tally exposed

NDIlib_send_get_tally is now wired into the addon (ndi.getLaneTally(lane) returns {onProgram, onPreview, anyActive, available}). Currently surfaced in diagnostic logs only; not used as the publish gate because most consumers (Synesthesia, OBS in non-broadcast mode) don't set tally. Available for future work.

🖥️ Display sidecar — native fullscreen on Sequoia

Electron 41's setSimpleFullScreen leaves the macOS menubar visible on Sequoia, breaking a dark venue projector setup. The fix is a separate Rust sidecar binary (phasion-display) — winit + wgpu + syphon subscription — that opens an NSWindow in native fullscreen on a chosen display, subscribes to the lane's Syphon source, and paints it edge-to-edge.

In the Output Preview row, ⛶ Send to Display now spawns the sidecar. The Settings → Output Windows panel has per-output display dropdowns: pick a display per output, then ⛶ Send to N Displays spawns sidecars in parallel for everything you assigned.

The sidecar's CPU cost is small (subscribes to an existing Syphon IOSurface, no encoding) and it's bundled in the .app via extraResources so it just works after install.

Fix: stale display assignments inflated the "Send to N" count

If you assigned a display, then unplugged it, the saved assignment lingered — the per-row dropdown correctly showed "(no display)" but the aggregate button counted the stale entry ("Send to 2 Displays" when only one was assignable). Now the cleanup function filters out assignments whose display ID isn't in screen.getAllDisplays() and persists the cleanup back to config.

🎚️ Syphon settings — simplified

The Syphon settings panel had two confusing toggles:

  • Visualizer "Enable" — controlled the legacy single-server

Syphon path that Phase D's per-lane servers replaced. Operator observation: "I disable visualizer and it still outputs anyway" — because per-lane (phasion1/output1, etc.) keeps publishing regardless. The toggle was vestigial.

  • Media Capture "Enable" — duplicated the Enable toggle in the

📹 Media Capture menu, which already starts/stops Syphon automatically via the media-capture lifecycle.

Both toggles are now hidden. The Visualizer subsection shows an explanation of the per-lane auto-publish behavior; the Media Capture subsection keeps the server-name input. Server names persist exactly as before.

🛠️ Other fixes

  • OSRSharedTextureNotReleased warning — gracefully suppressed

during normal operation. The deferred-release ring from 1.9.0 already handles this correctly; the warning was a Chromium dev notice that doesn't affect frame delivery.

  • Per-lane diagnostic counters in the offscreen paint bridge

stats log (gate=N readback=N skipped=N conn=N tally=on/off) — always-on, useful for triaging perf reports.

Kill switches

  • PHASION_SHARED_TEXTURE=0 — fall back to 1.8.x's CPU bitmap path

if Phase D regresses on a future macOS / Electron release.

  • Per-lane NDI checkboxes default OFF on fresh install. Existing

users keep their saved state.

Known limitations

  • NDI Virtual Input extension keeps subscribing to any

discovered NDI source even when not actively used. Phasion can't tell the OS extension apart from a legitimate Resolume layer. The per-output checkboxes are the workaround; long-term users may prefer to disable the extension in System Settings → Privacy & Security → Login Items & Extensions → Camera Extensions.

  • NDI tally isn't used as the publish gate because Synesthesia

and OBS consume NDI streams without setting program/preview tally. Available via diagnostic logs.

1.9.0 May 24, 2026 The Performance Release

Phasion Live 1.9.0 — The Performance Release

The big one. Two architectural fixes — Phase D (lane Syphon via useSharedTexture) and Phase E (overlay Syphon via useSharedTexture) — turn multi-output from a CPU-eating compromise into the default smooth path.

🚀 5× CPU reduction at 3 outputs

Measured live with three 1280×720 lane outputs running butterchurn + scene overlays, downstream consumed by Synesthesia:

| Metric | 1.8.2 (offscreen → bitmap) | 1.9.0 (useSharedTexture) | |---|---|---| | Phasion main-process CPU | 159% | 28% | | Per-lane paint cost | ~5ms | ~2ms (CIImage GPU scale) | | Downstream FPS in Resolume / Synesthesia | 57 (jittered) | 60 (rock solid) | | Resolume idle-headroom | <50fps | 96–118fps |

The 1.8.2 offscreen path still had a CPU bottleneck: every paint event ran image.toBitmap() to copy the GPU texture into a CPU Buffer, then memcpy'd it into our IOSurface, then published. With three lanes at 60fps, that path consumed ~970ms/sec of main-thread time — saturating it.

Phase D switches to Electron 41's webPreferences.offscreen.useSharedTexture. The paint event now hands us an IOSurfaceRef directly from Chromium's GPU context. A new native function (publishOffscreenLaneFromIoSurface) wraps it as a CIImage and renders into our published IOSurface via a Metal-backed CIContext bound to our command queue. Zero CPU bitmap. The "publish" step is sub-millisecond GPU work.

The win compounds across lanes. One output is roughly the same as before (both paths fit comfortably in the frame budget). Three outputs go from "barely manages 30fps on each lane while everything else suffers" to "all three at 60fps, Resolume getting more headroom than it ever had."

🎨 Overlay Syphon — same treatment (Phase E)

Settings → Syphon → Separate Overlay Output was a CPU explosion in 1.8.2: the overlay window was visible-but-positioned-off-screen, captured via ScreenCaptureKit, with a 30fps CPU pixel-readback fallback if SCKit failed to start (the common case if Screen Recording permission wasn't perfectly configured). The fallback read 3.7MB per frame via getImageData and ferried it through IPC. With three lanes already running, this saturated the system.

1.9.0 applies the same Phase D treatment to overlay: offscreen + useSharedTexture, alpha channel preserved, GPU-only publish path. The legacy SCKit overlay-window capture is retired (still available behind PHASION_SHARED_TEXTURE=0 for triage).

Enabling Separate Overlay Output now adds ~2ms of paint cost instead of exploding CPU. Resolume sees identical alpha-preserved overlay frames as before — same source name, just way cheaper to produce.

🛠 Polish

A bunch of operator-visible fixes shipped alongside the perf work:

  • Per-lane DOM overlay scaling. Trivia card, marquee, QR overlay,

poll panel, captions, now-playing card, and scoreboard now scale proportionally to the lane's output resolution. An 854×480 lane no longer renders the marquee font + trivia card at the same absolute pixel size as a 1280×720 lane, which had made the smaller lane's overlays look comically oversized. The new CSS --lane-scale variable is set per-lane in JS from outputHeight / 720 and the major overlays use zoom: var(--lane-scale, 1).

  • Resolution settings actually persist. Fixed two distinct

clobber bugs that made the operator-set output resolution and media capture resolution silently revert to defaults on next Settings open: (1) MAIN_ONLY_KEYS list in main now protects outputWidth, outputHeight, laneResolutions from renderer round-trips, (2) config-sync IPC now updates the media-output-scale DOM dropdown in secondary windows so they don't ferry stale values back to disk.

  • Header FPS pill stops flickering at multi-output. Each lane

fires its own perf-update IPC at 1Hz; the pill now tracks per-lane stats and shows the lowest actual FPS across all lanes (stable text). Dot color reflects worst health ratio (capped lanes at 100% of their cap stay green). Per-lane FPS detail moved to the ops bar at the bottom.

  • Ops bar adds BCH / PNT / IDL. Each lane chip now shows

butterchurn render p50, chromium paint cost, and idle-skip fraction. Diagnostic at a glance; tooltip for the full breakdown.

  • Idle render gate. When audio is silent for >1s and no preset

transitions or operator IPCs are in flight, butterchurn render is skipped AND the offscreen window's frame rate is ramped to 5fps. Cuts CPU during quiet stretches; wakes the instant audio returns or you touch anything in controls. Visible as "i%" in the ops bar.

  • Settings → Privacy & Security clarity. The media-window picker

used to silently show an empty list when macOS Screen Recording permission was denied. Now surfaces a copy-pasteable instruction with the exact System Settings path.

  • Stale-state guards on a bunch of secondary-window quirks. The

Visualizer Syphon toggle now reflects reality (Phase D path was always-on but the legacy isRunning() check only saw the old single-server). The overlay window no longer races the real lane 1 in ops-bar stats (its viz-stats reporter is gated off).

  • Chrome offscreen paint-callback rejection ("Video was requested,

but no video stream was provided") that surfaced as an UNHANDLED REJECTION in the main process when a preview tile's getDisplayMedia raced its prepare-call — now filtered with a once-per-minute info log so other genuine rejections still surface loudly.

🧹 Cleanup

  • RENDER MODE (BETA) UI removed. Now that offscreen + shared-texture

is the default path, the per-lane Window/Offscreen selector that shipped in 1.8.2 is gone. Existing operators who saved 'window' for a specific lane keep that override; new installs go straight to offscreen.

  • "Optimize for Resolume / Mad Mapper" button removed. Was a

one-click panic flip to 480p/30fps/single-output to recover Resolume headroom. Phase D delivers that headroom by default; the panic switch is obsolete.

🔌 Kill switches (in case of trouble)

  • PHASION_SHARED_TEXTURE=0 — both lane AND overlay publish revert to

the legacy bitmap / SCKit paths. Use if a specific preset or system config produces visual artifacts under the new path. Default on.

  • PHASION_ENGINE_NATIVE_OVERLAYS=1 — re-enables the dormant Rust

engine subprocess for the v2.0 work-in-progress native overlay primitives. Engine subprocess otherwise doesn't spawn (it had no useful work to do in shared-texture mode).

  • PHASION_FORCE_VISIBLE_LANE=N — debug helper. Forces a specific

lane number back to visible-window mode (with DevTools accessible) while leaving others on shared-texture. Useful when isolating a visual bug to a specific preset.

---

Phasion Live is by AV Club Visuals. Run a venue. Make it weird.

1.8.2 May 23, 2026 Offscreen render mode (BETA)

Phasion Live 1.8.2 — Offscreen render mode (BETA) + perf

A release dedicated to one big architectural addition and the polish around it: Offscreen Render Mode, a per-lane opt-in that bypasses the macOS compositor entirely and publishes butterchurn directly to Syphon via an IOSurface bridge we own end-to-end. In testing this delivered 100+fps sustained at 1080p where SCKit's window-capture path was struggling to hold 30-60.

🧪 Offscreen Render Mode (BETA)

Settings → Performance → 🧪 RENDER MODE (BETA) — per-lane dropdown to flip between two publish architectures:

  • Visible window (SCKit, stable) — the 1.8.1 path. A real BrowserWindow

renders butterchurn, macOS's ScreenCaptureKit grabs that window's IOSurface, Syphon publishes. Pays the compositor cost for every frame.

  • Offscreen → IOSurface (BETA) — no visible window. Chromium renders the

page offscreen at device pixels; a native publisher memcpys/downscales each paint into an IOSurface we created, and Syphon publishes it. Zero compositor involvement.

What this fixes / unlocks

  • Massive perf headroom when Resolume / Mad Mapper / OBS are subscribed.

The macOS WindowServer compositor cost was the real bottleneck in 1.8.1 — bypassing it lets the lane sustain 60+fps even with downstream consumers doing heavy work.

  • No more "Syphon pauses when I minimize the window" — there's no window

to minimize.

  • Identical Syphon source naming. Resolume sees the same phasion1/output1,

phasion2/output2, phasion3/output3 source names regardless of which mode the lane is using. Switching modes doesn't break downstream patches.

  • Correctly published resolution. Native publisher downscales paint dims

(typically 2× on Retina because Chromium offscreen paints at device pixels) down to exactly the configured target (1280×720, 854×480, etc.) before Syphon publish.

  • Per-lane hot-swap. Toggling the dropdown for lane 1, 2, or 3 closes and

re-opens the lane in the new mode without an app restart (~600ms total). Same Syphon source name is preserved so downstream apps don't need to re-pick.

Beta caveats

  • The controls window's preview tile is blank for offscreen lanes (uses

desktopCapturer which needs a visible window). Resolume / Synesthesia / Syphon Inspector remain the source of truth.

  • Lane 1 mode swaps fully recreate the primary viz window; persisted state

(position, FPS cap, fullscreen display) is restored from config on each rebuild.

Native publishing infrastructure

A clean, reusable native foundation that the offscreen path is built on:

  • StartNativeSpike / StartOffscreenLane in syphon_addon.mm — create

an IOSurfaceRef, wrap it in an MTLTexture zero-copy, and publish via a dedicated SyphonMetalServer. Per-lane slots (lanes 1/2/3) each with their own IOSurface + Metal texture + Syphon server.

  • Fast nearest-neighbor downscaler (fixed-point 16.16) in

publishOffscreenLaneBuffer — accepts any paint dim and writes to the IOSurface at exactly the configured target. ~targetW×targetH memcpy-class ops per frame, fast enough at 60fps/1080p.

  • getMainScreenBackingScale — native getter that reads

[NSScreen mainScreen].backingScaleFactor directly, bypassing Electron's screen.scaleFactor API (which returns 1 under --force-device-scale-factor=1).

DOM overlay sizing fixed in both modes

  • forceLowDpr window-shrink retired. It was a 1.8.1-era SCKit compositor

optimization that shrunk lane windows to half their CSS dimensions, which forced DOM overlays (trivia card, QR code, scrolling text, photo carousel) to render proportionally twice as large in the smaller viewport. The new offscreen mode delivers the perf win without that downside, so the shrink is permanently off. Trivia, QR, fonts, and all overlays now render at correct sizes in both modes.

  • --force-device-scale-factor=1 flag auto-skipped when any lane is

configured for offscreen mode. The flag conflicts with offscreen rendering on Retina, where Chromium paints at the real backing scale regardless of the flag.

Stability / fixes

  • No more duplicate Syphon sources. Lane open path now skips

startServerLane() when the lane is in offscreen mode, eliminating the "two phasion1's, one frozen" bug that appeared during mode swaps.

  • Lane 1 hot-swap properly tears down both SCKit (stopWindowCapture)

and the prior Syphon server (stopServer) before recreating, so downstream consumers see a clean handoff rather than a still-framed prior source.

  • Lane 2/3 hot-swap auto-runs closeOutputLane(n) → 350ms pause →

openOutputLane(n) so the operator only has to flip the dropdown.

Diagnostics

  • Opt-in perf probe (window.__phasionPerf = true in viz devtools) logs

p50/p99/max per render stage every 5 seconds plus a GC-pause counter. Useful for identifying bottlenecks; zero cost when off.

Test runners

  • scripts/offscreen-runner.js — standalone Electron runner that loads the

spike's test page or visualizer.html offscreen and publishes to Syphon. Used during development to validate the architecture without launching the full app.

  • scripts/spike-runner.js — minimal CPU-gradient → IOSurface → Syphon proof

that the publishing pipeline works without any rendering layer involved.

---

Phasion Live is by AV Club Visuals. Run a venue. Make it weird.

1.8.1 May 22, 2026 Resolume Syphon perf + persistent recap

Phasion Live 1.8.1 — Resolume-grade Syphon perf + 48-hour event recap

A polish + performance release focused on two things: making Phasion play nicely with downstream Syphon consumers (Resolume, Mad Mapper, OBS), and shipping the persistent event recap that turns your audience URL into a multi-day asset.

Performance — Syphon to Resolume

The biggest find: SCKit's window-capture stream was uncapped at 120Hz on ProMotion displays regardless of your FPS settings. Resolume was being flooded with frames it didn't need, burning GPU on duplicate work. The fix touches the native addon and every output handler.

  • SCKit FPS cap honored end-to-end. The native addon now caps SCKit delivery

at your chosen rate (default 30 fps). Native code rebuilt.

  • Per-lane FPS cap auto-restarts the Syphon stream. Change a lane's FPS in

Settings → Outputs and the publish rate updates within ~2 s. No app restart.

  • Lane window auto-shrinks to physical pixel size. Eliminates WindowServer

compositor overhead from Retina/HiDPI rendering — was 16× wasted work at DPR=4.

  • Send Fullscreen auto-bumps canvas backing. Crisp 1:1 stage output on the

destination display, then restores Syphon-mode res on exit.

  • 🎛 "Optimize for Resolume / Mad Mapper" button in Settings → Performance.

One click: 30 fps cap on all lanes + 854×480 + single preset + 1× DPR + closes extra outputs + hides preview tile.

  • Spectrum visualizer pauses when controls window is blurred. Reclaims GPU

cycles for Resolume when you're not looking at controls.

  • Misleading "120 fps" ops bar fixed. Counter now reflects actually-rendered

frames, not the display refresh rate.

Event Recap — 48-hour persistent URL

The URL you give the audience now outlives the event. Pro Cloud and Venue Cloud users get a recap that loads photos, word cloud results, and event content for 48 hours after you close the app.

  • Mobile-first recap gallery at phasion.live/recap/<slug> — photo grid,

lightbox with download, find-my-photos by username.

  • Rich End-the-Night recap mirrors to the cloud. The polished slideshow

with stats, emoji bars, top photos, and branding now persists on the cloud — identical to the local copy the operator built.

  • Per-photo consent at upload. "Share in event recap" checkbox. Operators

can export the full event archive as ZIP before the 48-hour expiry.

  • Client-side auto-cull on photos. Blur detection (Laplacian variance) +

darkness detection + perceptual hash for dupe detection. Audience sees an inline warning on bad shots before sending.

  • R2 lifecycle rule enforces the 48-hour deletion at the storage layer —

privacy guarantee comes from Cloudflare, not app cleanup logic.

Tiers + licensing

  • Pro Cloud and Venue Cloud tiers added alongside legacy Pro / Venue.

Same feature set within each family; cloud variants ranked higher (pro-cloud rank 2.5, venue-cloud rank 3.5) for upgrade copy. Legacy pro and venue continue to work unchanged.

  • License token includes tier. Slug-claim derives transport mode (cloud vs

local) from tier — new purchases route correctly on first claim.

Diagnostics

  • phasion.live/venue-test — standalone WiFi diagnostic page operators or

venue staff can run from a phone before an event. Measures latency, jitter, bandwidth, sustained-connection drops. Verdict: "Pro Local will work" / "Use Pro Cloud" / "Network too unreliable."

  • "Test audience reach" button in Settings → Privacy. Same probe inside

the app, tier-aware copy (Pro Cloud users see "Cloud is doing its job" instead of upgrade pitches).

  • End-of-event summary card with conditional upgrade nudges based on

session telemetry (tunnel drops, photo upload failures, laptop sleep, low battery). Silent when nothing warrants a nudge.

  • Toggleable Perf HUD overlay in the viz titlebar — live FPS, Syphon path

(SCKit / PBO), bandwidth estimate, active feature flags. Cropped from Syphon output (sits in the titlebar area, never published to Resolume). Toggle via Settings → Privacy or ⌘⇧P inside the viz window.

Output windows

  • Custom resolution dialog fixed. The old window.prompt() path returns

null silently in Electron renderers — now uses a native HTML <dialog> with W × H inputs, Enter to submit, Escape to cancel. Even-pixel rounding for Syphon chroma compatibility.

  • 480p widescreen preset (854×480) added to the resolution dropdown.
  • Global FPS Cap in Performance is now a "set all lanes" mass action that

updates every per-lane cap. Per-output dropdowns override per-lane. No more silent override race when you set conflicting values.

Recap show on all outputs

The End-the-Night recap slideshow now fans to every open output, not just lane 1. Plus fixed a DPR clamp mismatch that was causing the recap to draw into only the upper-left ~67% of the canvas on DPR=3 displays.

Marketing site (phasion.live)

  • New phasion.live/recap/<slug> recap surface
  • New phasion.live/venue-test diagnostic surface
  • Pricing page reframed with "URL outlives the event" pitch + Pro Local vs

Pro Cloud comparison table

  • Privacy policy updated for 48-hour R2 retention model
  • Footer "Venue WiFi test" link on every page

Other fixes

  • Multi-lane End-the-Night recap (was rendering on lane 1 only)
  • Per-lane FPS counter accurate (was reporting display refresh rate)
  • Popout UX: preview popout state, audience-tab focus handling, focus-popout

IPC for the 🪟 ops chip

  • Photobooth-style audience consent + opt-out at upload

---

Phasion Live is by AV Club Visuals. Run a venue. Make it weird.

1.8.0 May 21, 2026 Pro Local architecture

v1.8.0 — Pro Local architecture (audience direct to your Mac)

The biggest release since v1.7's stable audience URL. Introduces the dual-architecture (Pro Local + Pro Cloud) split, hardens the cloud relay against the Codex audit findings, and gives operators much better visibility into what's happening under the hood.

═══════════════════════════════════════════════════════════════════ NEW: Pro Local architecture (zero cloud hop for audience traffic) ═══════════════════════════════════════════════════════════════════

Each operator can now have a stable hostname <slug>.phasion.live that routes via a Cloudflare named tunnel DIRECTLY to their Mac. No Durable Object hop, no R2 photo buffer, no cloud retention of any audience content. The architecture itself becomes the privacy story.

• New Worker endpoints (provision/deprovision/get-token) that create + manage Cloudflare named tunnels via the CF API • Tunnel tokens encrypted at rest in D1 via AES-GCM with a Worker secret (PHASION_TUNNEL_ENC_KEY) • Idempotent provisioning with auto-recovery from orphan CF resources (codes 1013 + 81053) • Operator app boots, claims slug, reads mode='local', fetches encrypted token, runs cloudflared tunnel run --token <stored> • Cloud transports (WS / SSE / Remote-SSE) skip startup in Pro Local mode — saves DO duration entirely • Audience HTML auto-detects subdomain pattern and uses location.origin for API calls (works in both cloud + local) • Operator local server gains /join route serving audience HTML, plus /state + /api/join/<slug>/<verb> path aliases so the same HTML works whether served from phasion.live/join/<slug> (Worker) or <slug>.phasion.live/join (named tunnel) • Recap pages work in Pro Local mode unchanged (served by local server via tunnel — no Worker involvement) • Daily cron sweeps orphan tunnels (failed teardown / cancelled customer) after 7 days of no heartbeat

═══════════════════════════════════════════════════════════════════ Hardening (driven by Codex pre-launch audit) ═══════════════════════════════════════════════════════════════════

• Slug validation moved upstream of DO instantiation — fake sids can no longer DoS our DO quota • RemoteSession passphrases persist to DO storage (handlePublish now fails closed when no passphrase configured) • Per-slug auth-failure rate limit (10 failed pp attempts in 5min → 429) defends against brute-force on 4-digit passphrases • DO idle alarm reads lastActivityAt from storage (was racing against the constructor's freshly-set value, re-arming forever) • handlePublish now counts WS subscribers before declaring "no subscribers" — fixed a regression where v1.8 WS operators got dropped POST commands • Cached-token revive path now calls _startAllTransports (was only restarting SSE+heartbeat, missing WS+remote — the exact cost regression v1.8 was designed to prevent) • Sleep guards added to all reconnect schedulers so close handlers triggered by suspend don't queue exponential-backoff timers • Bundle ID rename: com.avclubvj.app → live.phasion.app (helper bundle stays at com.avclubvj.app to preserve ShazamKit registration) • R2 cleanup paginates (list() caps at 1000 objects — without pagination, slugs with >1000 photos left an orphaned tail) • Photo filenames now include a per-photo suffix so concurrent submissions in the same second don't collide • Queue caps (PENDING_AUDIENCE_MSG, WC_REVIEW) prevent runaway moderation queues on long-absent operators • Word-cloud session disk writes debounced (500ms) — a flurry of 50 phones submitting in 2s used to do 50 fs.writeFileSync calls • Renderer crash counter is now a 10-min sliding window (was cumulative — 3 GPU hiccups over an 8hr gig used to brick auto- respawn forever) • Delta queue cap (200 items) prevents OOM during stuck WS flushes • NDI audio publishing gated on hasAnyReceivers() • Latency-beacon endpoint rate-limited + slug-validated

═══════════════════════════════════════════════════════════════════ Operator UX ═══════════════════════════════════════════════════════════════════

• Ops bar (⌥D): new TUN dot for tunnel health + 📱 phone counter (live SSE subscribers, amber at 200+ for Pro Local capacity) • Plain-English tooltips on every ops-bar item explaining what the channel is, why you'd care, and what its current state means • Mode-indicator chip in Submission Link section: tells operator whether they're on Pro Local (📡) or Pro Cloud (☁️) with a one- line architecture summary • Periodic transport re-sync (every 10s) self-heals any IPC race where the ops bar got stuck on a stale "disconnected" badge • Diagnostics bundle exports operator state as a single JSON file via Settings → Privacy → "Save bundle…" — includes Pro Local / Pro Cloud architecture summary, current tunnel status, audience phone count, recent breadcrumbs (sensitive fields redacted) • Cloud-status reason cell surfaces human-readable explanations when transports degrade ("Audience cloud is busy — retrying. Local tunnel still works.") • End-the-night cleanup: stops running activities + drains pending moderation queues + wipes R2 audience content at 4am rollover • FX strip pruning: removed SHAKE/ZOOM/CRUSH/BLUR (4 low-impact glitch effects per operator feedback). HUE moved next to FLASH

═══════════════════════════════════════════════════════════════════ Marketing site ═══════════════════════════════════════════════════════════════════

• Pricing page restructured into 4 tiers: Pro Local ($19), Pro Cloud ($49), Event Pro ($99 one-month), Venue ($499/yr) • Privacy page split into per-mode sections — Pro Local now carries the real claim "photos never leave your Mac" • Features page audience section calls out the Pro Local / Pro Cloud distinction with the privacy-by-architecture pitch • Homepage AT YOUR EVENT card mentions both architectures • Pricing page Lemon Squeezy checkout URLs reserved for new SKUs (real product IDs wired in when you finalize prices) • Recap pages, features images (output_routing, qr_code, branding, midi, visuals video) all live

═══════════════════════════════════════════════════════════════════ Migration ═══════════════════════════════════════════════════════════════════

D1 migration 0006_slug_tunnel.sql adds tunnel columns to slug_registry. Apply once with:

npx wrangler d1 execute phasion-trials --file=./migrations/0006_slug_tunnel.sql --remote

New Worker secrets required for Pro Local provisioning: CLOUDFLARE_API_TOKEN, PHASION_LIVE_ACCOUNT_ID, PHASION_LIVE_ZONE_ID, PHASION_TUNNEL_ENC_KEY

Existing customers default to mode='cloud' on first license check post-upgrade. No action required for cloud-mode operators.

1.7.1 May 19, 2026 FX polish + multi-output sync + same-Wi-Fi panel fix

Polish + bugfix release on top of v1.7.0 phasion.live. Also bundles the WebSocket migration of operator state push + remote control commands (was developed against 1.7.0 but never cut as its own release).

WebSocket migration (cost + latency)

  • Operator → AudienceRelay state push moved from POST to WebSocket.
  • Diff-based filter on ephemeral fields (bpm/fps/gpuLoad/EQ) drops

idle traffic to ~0/min.

  • Phone remote control (/r/<slug>) and moderator (/mod) commands

also moved to WebSocket — no more per-click POST to /cmd.

  • Self-healing connection layer: 35s liveness watchdog auto-reconnects

if a Worker redeploy zombies the SSE/WS.

Visual Effects

  • Removed SHAKE / ZOOM / CRUSH / BLUR (4 low-impact glitch effects)

per operator feedback. MIDI mappings to those keys are silently dropped from existing configs (harmless).

  • Keyboard shortcuts renumbered:
  • 1 = SPIN, 2 = SQUEEZE, 3 = RIPPLE, 4 = HUE, 5 = BLACK
  • Was: 1=ZOOM, 2=CRUSH, 3=BLUR, 4=SPIN, 5=HUE
  • HUE moved next to FLASH so the keyboard mirrors the visible row.
  • Multi-output FX sync: every FX message now carries a wall-clock

fireAt timestamp and each visualizer waits until that instant before firing. Eliminates the visible 1-2-3 ripple of FX across multi-output lanes.

FX strip layout

  • VISUAL EFFECTS header height matched to EMOJI APPLAUSE in 2/3-col.
  • All 10 row buttons share a single visual baseline.
  • Non-slider buttons stretch to fill row height instead of leaving

16px empty space below them.

  • Unified 1.5px border-width across all multi-col FX buttons.

Bug fixes

  • Same-WiFi fallback URL box now hides when "Allow same-Wi-Fi access"

toggle is off. Previously the box stayed visible even though the remote server was loopback-only and the LAN IP was unreachable.

1.7.0 May 19, 2026 phasion.live: stable audience URLs + native phone remote

Phasion Live 1.7.0 — phasion.live: stable audience URLs + native phone remote

This is the big one. No more random tunnel URLs that change every show. Every install now gets a stable phasion.live/join/<slug> URL that the audience scans, plus a phasion.live/r/<slug> remote-control URL the operator's phone bookmarks once. Both work without a tunnel, both survive app restarts, and venues get the option to claim a custom vanity URL.

What's new

Stable audience URL — phasion.live/join/<slug>

Every licensed install (Pro / Trial / Venue) auto-claims a 4-character slug on first launch. The audience QR encodes that URL forever — no more re-sharing a new link every night. Same QR poster works in week 1 and week 52.

  • Pro tier: random 4-char slug (e.g. phasion.live/join/k7f2).
  • Venue tier: pick a custom vanity (e.g. phasion.live/join/bluemoon-bar). Settable from the QR menu → More options.
  • Anonymous trial: even without signing up, the app claims a slug bound to your device fingerprint. Anonymous slugs auto-expire after 90 days of no activity.
  • 🔄 New Venue ID button in the QR menu rotates both the tunnel and phasion.live URLs in one click — useful if abuse forces a fresh QR.

The full audience-submit page (messages, photos, AI prompts, emoji applause, word cloud, polls, trivia, photo contests, caption contests, white-label branding) is hosted at phasion.live with feature parity to the previous github.io page. The legacy github.io URL is dropped from the QR menu — phasion.live is the canonical audience URL going forward.

Operator remote — phasion.live/r/<slug>

The same slug your audience scans is reused for your operator phone remote. Bookmark phasion.live/r/<your-slug> on your phone once, and every session-start auto-pairs. No more "find your local IP, type it manually."

  • Auto-generated 4-digit passphrase locks the remote to your operator phone. The QR encodes the passphrase as a query param, so scanning auto-pairs without typing. Passphrase auto-syncs from your Remote password in app settings if you set it to a 4-digit value.
  • Moderator role has its own phasion.live/mod page reached by entering the moderator passphrase instead of the operator one. Same QR — different password → different page. Mod commands are server-side gated against MOD_ALLOWED_CMDS so a leaked mod token can't change scenes, kill audio, etc.
  • Sign out button in the top-right of the remote — clears the cached passphrase so a second phone can pair with a different role.

Anonymous trial — no signup required

If you launch the app without signing in, you still get a fully functional phasion.live/join/<slug> URL bound to your device fingerprint. Watermark stays on (same as before), but every audience-engagement feature works. When you later sign up, the app issues a fresh license-tied slug and the anonymous one frees up.

White-label branding mirrored

Operators on the Venue tier can upload a logo + accent color + custom copy in app settings. That branding now also flows through phasion.live — the audience page renders your venue's look with the logo inlined as a data URL (no extra fetch). Changes propagate in under a second via SSE.

"Change All / Same All / Fade All" buttons relabeled

The multi-output mashup buttons now read Change All, Same All, Fade All instead of just Change, Same, Fade — clearer that they hit every open output.

What's fixed

  • Stale audience URL after sign-out — if you signed out of an account, the persisted slug from that account would linger as "phasion.live/join/onlyjams (offline)" in the QR menu. Fixed — chip now hides when there's no eligible session and a "Sign in to activate" hint appears instead.
  • AI prompt rejected as "invalid text" — early version mapped the wrong body shape; the audience page sends {prompt, name, photoDataUrl?, photoWeight?} and the relay was looking for {text, username}. Fixed.
  • Trivia / poll / WC didn't propagate to audience page — the operator-side SSE was using event names that bypassed the audience page's default onmessage handler. Fixed by stripping the event: header so all events flow through one dispatch path.
  • Emoji react didn't fire — the relay routed to viz directly instead of through the controls window (where lane-pick logic lives), so reactions fanned to every output simultaneously. Now matches the legacy /react route's behavior (one random lane per react).
  • Logo data URL mangled — the audience page's _applyOperatorBranding() prepended the API base to any URL not starting with http. Data URLs got broken into /api/join/<slug>/data:image/png;base64,.... Fixed to recognize data: as already-absolute.
  • Caption-contest photo went to generic moderation — the Worker was dropping the slug and voterId fields from photo submissions. Now preserved end-to-end so contest photos route to the right activity.
  • Operator-state push coalescing was lossy — fast-firing pushSSE events could overwrite each other before reaching the audience. Now type-aware: state events coalesce (latest wins, idempotent), delta events queue separately so wordcloud submissions / vote events / live-updates are never dropped.

Behind the scenes

  • New Cloudflare Worker (marketing-site/phasion-link.js) handles slug claim/refresh/reset, license-signed auth, audience submissions, operator state mirror, and the new AudienceRelay Durable Object that holds the SSE stream per slug.
  • D1 schema migrations 0002–0005: slug_registry table with license_id, machine_fp, email, and license_expired_at columns.
  • Lemon Squeezy webhook now handles subscription_cancelled / subscription_expired / order_refunded events and sets license_expired_at on the slug row so the audience page shows the "subscription expired" UI.
  • Hourly cron sweeps anonymous slugs idle for 90+ days.

Upgrade notes

  • The legacy corunography.github.io/avclubvj/?t=...&w=... audience URL still works for installs already running it, but is no longer surfaced in the QR menu. All new submissions flow through phasion.live.
  • The tunnel-based remote continues to work alongside the new phasion.live remote — you don't have to switch.
  • No D1 schema changes required on your end; everything happens server-side at phasion.live.
1.6.2 May 18, 2026 Per-Output Resolution + Preview Grid Reliability

Phasion Live 1.6.2

Per-output resolution + grid-preview reliability + a top-bar redesign. Each audience display now has its own publish resolution (720p / 1080p / 1440p / 4K / Custom), locked to the configured size. The "Preview: All" grid no longer leaks Output 1's stream as a backdrop behind the other tiles. And the top-bar mashup controls got cleaned up: thinner duration dropdown, a numeric mix readout, removed clutter (Speed / Auto / legacy global resolution dropdown), and a responsive layout that fits all your buttons inline at multi-col widths and reflows cleanly at narrow widths.

What's new

Per-output resolution

Each output now has its own publish + window size, settable independently from Settings → Output Windows. The Output 1 / 2 / 3 rows now show a resolution dropdown next to the display picker and the Windowed / Fullscreen toggle.

  • Quick presets: 720p, 1080p, 1440p, 4K, or Custom (any WIDTH×HEIGHT down to 320×180 — supports ultrawide, portrait, square, anything).
  • Window LOCKED to the configured size. Operators can no longer accidentally drag-resize an output window. The captured-via-Syphon frame matches the configured resolution 1:1 — no scaling, no aspect-ratio drift.
  • Each lane is independent. Output 1 can run at 720p (for a smaller projector), Output 2 at 1080p (main stage), Output 3 at 4K (LED wall) — all simultaneously. Each Syphon server (phasion1, phasion2, phasion3) publishes at its own lane's native resolution.
  • Overlays auto-scale. QR codes, scrolling text, captions, photo carousel, logos, applause emoji, trivia / poll cards — every overlay reads canvas.width for its sizing math, so each lane's overlays render at the right size for that lane's resolution. A 1080p output and a 720p output running the same scene look visually identical, just at different pixel densities.
  • Legacy "Output Resolution" global dropdown removed from Settings — the per-output dropdown inside each Output row is now the single source of truth. No more "did this affect Output 1 or all outputs?" ambiguity.

Why per-output matters

The 1.6.0 multi-output release shipped with Output 2 and Output 3 windows being freely draggable / resizable. That looked harmless, but SCKit window-capture scales the captured frame to the configured publish dims — so dragging Output 2 down to, say, 800×600 left Syphon publishing a 1280×720 stream that was an upscaled blur of the smaller window. The lock here makes the captured frame match the rendered frame exactly.

What's fixed

Preview: All — no more Output 1 bleed-through behind the grid tiles

When "Preview: All" was active, Output 1's single-tile capture stream stayed running in the background, visible as a backdrop behind the per-lane grid tiles. Operators were toggling Preview Active → Preview All to manually clear it. Root cause was an async race between applyVisibility() (which manages the single-tile preview) and _startGridImpl (which manages the grid). Three layers of fix: 1. applyVisibility now reads a gridActive flag and forces the single-tile stream off when grid mode is showing multiple lanes. 2. _startGrid and _stopGrid proactively trigger applyVisibility so the gate evaluates the instant grid state changes (instead of waiting for the next matchMedia / tour event). 3. The async startStream() re-checks gridActive after the getDisplayMedia await resolves and abandons the in-flight stream if grid mode flipped on mid-await — preventing a zombie capture from binding to the hidden mainVid element.

Mix slider — direction unified across every blending pattern

Patterns 3 (H-Split), 4 (V-Split), 6 (Radial), and 9 (Luma key) had their slider direction inverted relative to Crossfade and Noise wipe — at slider left, the user saw preset B instead of preset A. All five patterns now share the same direction:

  • Slider far LEFT → 100% preset A
  • Slider far RIGHT → 100% preset B
  • Slider centered → 50/50 split for the wipe patterns; centered transition threshold for the luma key.

Other fixes

  • Photos Syphon status text now reads "Syphon output disabled" instead of "Not running" — consistent with the AI Visuals panel.
  • Settings → Output Windows scroll now works in Settings popout windows (the multi-col overflow: hidden rule no longer leaks into Settings).
  • Column-width resize handle no longer drags adjacent columns when you adjust the column 2 / column 3 boundary at 3-col widths (CSS specificity fix on the @media (min-width: 1100px) grid template).
  • Preview height resize (⋯ drag) in "Preview: All" mode now scales the entire tile row proportionally instead of clipping the bottom of the tiles.

Top-bar polish

Compact, responsive mashup controls

The Visual Mix top-bar got a layout overhaul to use space more wisely at every width:

  • "Pattern" renamed to "Blending" — clearer semantic for what the dropdown controls.
  • Mix slider now has a numeric readout (e.g. 0.50) on the right edge so you can tell when it's centered without eyeballing.
  • Duration dropdown is now compact (~42px) — was hogging too much row width.
  • Speed slider + Auto-Mix button removed from the top bar — these are less-touched controls and were squeezing the Mix slider down to nothing at narrow widths. Auto-Mix is still available via the existing MIDI / OSC / phone-remote bindings.
  • Multi-col widths: all 13 items (Rand A · Gen A · style · ⇄ · style · Gen B · Rand B · A+B · 🔒 · Change · Same · Fade · 3s) stay inline on a single row — no more wrapping at moderate window widths.
  • Single-column widths: cleaner two-row layout —
  • Row 1: per-lane preset controls (Rand A · Gen A · style · ⇄ · style · Gen B · Rand B · A+B)
  • Row 2: master macros (Change · Same · Fade · 3s) sit just above the Visual Effects section, evenly distributed across the row
  • Lock button auto-hides on the wrapped master row to keep it focused on the 4 macro controls; still accessible at wider widths.

Visual Effects / Setlist / Demo header alignment

  • All three collapsed headers (FX, Setlist, Demo) now render at exactly the same height (~25px) with text vertically centered — was previously a few pixels off, with text looking top-aligned because of residual padding-bottom on the collapsed .section-body.
  • Tightened the gap above the first primary group below the FX/Setlist stack (was 14px, now 4px) so the stack reads as a unified bank.

Hidden Syphon Position Adjust panel

The "📐 Syphon Position Adjust" sub-section in Settings → Output Windows is now hidden by default — the per-lane SCKit crop math has been stable for several releases and the manual offset sliders weren't being touched. Inputs stay in the DOM as stubs so existing handlers don't error; the section can be re-enabled by removing one style="display:none" wrapper if a future SCKit regression makes manual adjust necessary again.

Upgrading

Existing 1.6.x users pick this up automatically on the next "Check for updates". After installing, restart Phasion Live. Your existing single global resolution is preserved as Output 1's default; Outputs 2 / 3 inherit the same dims initially but can now be changed independently via Settings → Output Windows.

Behind the scenes

  • New config.laneResolutions = { "1": {w,h}, "2": {w,h}, "3": {w,h} } storage. Falls back to the legacy global outputWidth/Height for installs without per-lane entries, so existing setups continue to work without re-configuring.
  • New set-lane-resolution(lane, w, h) IPC: resize window → push new resolution to renderer → restart SCKit capture at new dims. Lane 1 also routes through here for consistency, while the legacy set-viz-size IPC keeps working as a shortcut.
  • Each lane window's did-finish-load re-pins setContentSize(w, h + 28) defensively so the captured frame is exactly the configured dims from the first publish (matches how Lane 1 already did it).
  • useContentSize: true + resizable: false on lane construction — both belt-and-suspenders against accidental drift.
  • Master-row layout uses CSS flex-wrap: nowrap at multi-col widths (≥740px) + JS re-parenting at narrow widths (<740px) so the master gang lives in different DOM positions for the two layouts without duplicating any elements.
1.6.1 May 17, 2026 Syphon multi-output hotfix

Phasion Live 1.6.1

Hotfix. Lane 1's Syphon server (phasion1) was publishing Lane 3's content in some multi-output sessions. This release fixes the routing.

What's fixed

Multi-output Syphon routing — phasion1 no longer shows phasion3's content

In 1.6.0, the SCKit window-capture used to feed Lane 1's Syphon server (phasion1) matched windows by substring — searching for any OS window whose title contained "Phasion Live — Output". Lane 2 and Lane 3 use titles "Phasion Live — Output 2" and "Phasion Live — Output 3" — both of which contain that substring.

Depending on the order SCShareableContent returned windows on a given boot, Lane 1's capture could lock onto Lane 3's window instead of Lane 1's. Result: downstream apps (Resolume, Synesthesia, OBS) saw phasion1 publishing whatever Lane 3 was showing — and phasion3 showed the same thing because it correctly bound to Lane 3.

The lane 2/3 path already used exact-match (isEqualToString:) for window lookup; only Lane 1 was using the loose substring match. This release equalizes the matching logic so every lane's Syphon server is guaranteed to publish its own lane's content.

Why this manifested only in the packaged build but not in npm start: pure SCKit window-enumeration ordering luck. The bug was latent in both — in dev, lane 1's window happened to be returned first; in the packaged build, lane 3's was. Same code path, different order.

Upgrading

Existing 1.6.0 users pick this up automatically on the next "Check for updates". After installing, restart Phasion Live and test with Resolume / Synesthesia: each of phasion1, phasion2, phasion3 should now publish its own lane's content.

1.6.0 May 17, 2026 Multi-Output

Phasion Live 1.6.0

Multi-output. One Phasion install can now drive up to three independent audience displays — different scenes, different presets, different feeds — from a single controls window. This is the biggest update since Phasion Live shipped.

What's new

Multi-output engine

  • Up to three independent outputs running in parallel. Each lane is its own visualizer window with its own preset slots, its own scene, its own audio-reactive render loop.
  • Output tab strip at the top of the controls window — click a tab to focus that lane; every per-lane slider, preset, and feed source below the strip retargets to it automatically.
  • Per-lane preview tiles so you can see all three outputs at a glance. Preview: Active (single tile follows the focused lane) and Preview: All (grid of every open lane at 15 fps to save CPU).
  • Per-lane Syphon + ScreenCaptureKit publishing so each output is a separate routable signal for Resolume, Synesthesia, OBS, Mad Mapper, etc.
  • Multi-output macros — three buttons appear in the top bar when 2+ outputs are open:
  • 🔁 Change All — randomize A+B on every output
  • 🎯 Same on All — load the SAME random A+B pair across every output
  • 🌀 Smooth Fade — coordinated fade-to-black transition across every output
  • 🎲 A+B button — randomize both presets on the active output in one click.
  • Per-lane preset mode toggle (Settings → Performance) — flip a lane between Blend A+B (default) and Single Preset. Single-preset mode skips the mashup pass for CPU savings on lanes that don't need variety (e.g. a sponsor-loop background lane).
  • Per-lane mix / pattern / speed memory — top-bar sliders snap to each lane's saved values when you switch tabs.
  • Setlist cue model — each setlist entry is a snapshot of every lane's preset, scene, and feed source. One click recalls a full multi-output state.
  • Scene-target chip row — multi-select which outputs a scene activation lands on. Routes carousel, AI, scrolltext, captions, trivia, crowd play, etc. on a per-lane basis.

License tiers rewritten around output count

  • Home ($60/yr) — 1 audience output. Visual engine + AI Visuals + lyrics.
  • Pro ($19/mo or $190/yr) — 2 audience outputs + full audience-engagement suite (QR, Crowd Play, Poll, Trivia, Applause, Photo Carousel, Scrolling Text, Branding Overlays, Setlist, Demo Mode, Moderation, Scenes, Remote Control, MIDI/OSC).
  • Trial — all features including 3 outputs, watermarked, for evaluation.
  • Venue — same as Pro plus 3 audience outputs, custom branding, white-label domain options. Designed for permanent installs at clubs, breweries, restaurants.

MIDI + OSC bindable across all multi-output controls

  • Lane focus: setActiveLane1/2/3, laneNext, lanePrev (MIDI notes or CCs) and /lane/active <n>, /lane/next, /lane/prev (OSC).
  • Multi-output macros: mashupChangeAll, mashupSameAll, smoothFadeAll, mashupRandomBothActive (MIDI) and /mashup/all/{change,same,fade}, /mashup/random/ab/active (OSC).
  • Color theme + room controls: theme cycle next/prev/off + strength, room brightness + contrast + cap on/off + cap level — all bindable on both surfaces.
  • TouchOSC outbound — Phasion emits /lane/active/state <n> on every focus change so TouchOSC layouts can light up an "active output" LED.

Phone remote — multi-output awareness

The phone-served remote control page (http://<lan-ip>:3131/) now shows:

  • Output 1 / 2 / 3 tab strip at the top, mirroring the desktop. Tapping a tab focuses that lane; every per-lane command below targets it.
  • 🔁 Change All / 🎯 Same All / 🌀 Fade All buttons inside the Visual Mix card. Both controls auto-hide in single-output mode.
  • The desktop tab strip + remote tab strip stay in sync over SSE — switch focus on either surface and the other catches up within ~100ms.

Visual Effects strip — redesign

  • Slim collapsed header (~24px tall) — collapsed by default so the strip doesn't crowd the layout. Click ▸ ⚡ VISUAL EFFECTS to expand. Saves ~86px of vertical space for operators who drive FX via keyboard / MIDI.
  • Single popout button row at multi-col widths — right-aligned next to the strip, no double-rendering.
  • Cleaner strobe/black-out button labels at narrow widths.

Branding Overlays — match scene color

  • New per-image 🎨 Match scene color checkbox + tint-strength slider in both the Branding Overlays panel and the Scene Editor logo overlays.
  • Each lane samples its own butterchurn canvas every ~1.2s and applies a CSS-filter chain — drop-shadow glow + depth + hue-rotate + saturate — so logos glow with the preset's dominant color.
  • Per-frame shortest-arc hue smoothing so transitions glide rather than snap.
  • Scene editor adds bulk "Match scene color (every logo) — All on / All off" buttons.

Guided tour — tier-aware

The onboarding tour now reads body[data-license-tier] and filters its 26 steps to match what the operator can actually do:

  • Home tier sees 11 steps (visual engine + AI Visuals + lyrics).
  • Pro / Trial / Venue see all 26 including Multi-Output Tabs, Change All / Same on All, Audience QR + Crowd Play + Poll + Trivia + Applause, Scrolling Text + Photo Carousel, Branding Overlays, Setlist, Demo Mode, Scenes, Remote Control.
  • Welcome step is tier-specific (🏠 + "three minutes" for Home vs ⚡ + "six minutes" for Pro).

Other workflow wins

  • Emoji applause — each reaction now fires on one random open lane (or chip-mask subset) instead of fanning to every output. Operator bursts use round-robin sweep; audience taps use pure random.
  • Top-bar follows the active lane — change tabs and the mashup sliders, preset names, and scene buttons all retarget without an extra click.
  • Lane open/close lifecycle — restoring saved lanes on app launch now triggers a full UI repaint (preview grid, setlist slots, tab strip) so a 3-lane setup looks correct from the first frame.
  • AI Generate styles — preset generation now picks from style buckets (Cyberpunk, Nature, Cosmic, Geometric, Abstract, Glitch) instead of one global pool, retries on failure, and exposes per-slot dropdowns.

Fixes (since 1.5.4)

A non-exhaustive list of bugs squashed across this release:

  • Preview tile cross-renderer race — the per-tile getDisplayMedia FIFO queue is now per-webContents-id, eliminating "Output 1 shows controls window / desktop" misroutes when the single-tile preview and grid build raced on boot.
  • Settings popout missed lanes opened from Main window — Settings reads vizWindows from main rather than its own stale module state.
  • Single-preset mode state desync — Settings popout's checkbox now broadcasts to Main via IPC so the top bar's B-slot chrome flips on every window in lockstep.
  • First setlist cue click after reload activated only lane 1 — boot lane recovery now exposes a promise that _activateCue awaits before committing its lane filter, eliminating the race.
  • Scene transitions on multi-output — per-lane _pendingFTBConfigByLane Map + lane-tagged midpoint IPC routes each lane's pending config to the correct apply path. Multi-output bursts now honor the configured transition style (random / wipe / dissolve / zoom / glitch) on every lane.
  • Open Mic / scene-swap overlay peek-through_wcClearTestSubmissions no longer fires an unconditional _wcMenuPushLive, fixing the rare flash of Open Mic overlay during a scene-to-scene transition right after activity exit.
  • Stale feed globals during multi-lane burstsactivateFeed() now captures the LANE's previous feed up front and gates its teardown IPCs on that, so a lane-2 burst clearing the global flag doesn't strand a carousel running on lane 1.
  • Preview popout close → grid order wrong — the "Preview: All" grid now auto-rebuilds when the popout closes so tiles return to correct lane order without the operator manually toggling modes.
  • Column-width resize broken at 3-col widths — a CSS specificity collision between the @media (min-width: 740px) and @media (min-width: 1100px) rules caused the 2-col template to win at 3-col widths, sticking col 3 on an auto-generated implicit track. Specificity equalized.
  • Settings window scroll — multi-col layout's overflow: hidden was leaking into Settings windows, clipping the tail of long sections (Branding, Privacy). Settings now correctly scrolls.
  • Trial/auto-play deactivation + lane recovery on controls reload — controls reload re-syncs from main rather than starting from a stale snapshot.

Under the hood

  • New native multi-output substrate: each lane gets its own BrowserWindow, its own SyphonMetalServer slot, and its own SCKit capture pipeline. Server names are phasion1, phasion2, phasion3 so downstream apps (Resolume, etc.) can address them individually.
  • New IPC namespace for lane lifecycle: open-output-lane, close-output-lane, list-open-lanes, output-lane-opened, output-lane-closed.
  • New IPC + handlers for phone-remote multi-output sync: remote-state-active-lane, remote-state-recompute-lanes. Lane focus on the desktop pushes via pushSSE so every connected phone repaints its tab strip within ~100ms.
  • shared.toVizLanes is now a lane-aware fan-out: messages with lane: N route to that lane only; messages without lane fan to every open lane.

Upgrading

Existing 1.5.x users pick this up automatically on the next "Check for updates" via the in-app updater. After installing, restart Phasion Live to load the multi-output substrate.

A note on the engine

The audio-reactive engine itself is still butterchurn. Multi-output runs three independent butterchurn pipelines side by side — each consuming the same audio analyzer, each producing a fully independent render. No shared GPU state between lanes, no preset cross-talk. Your existing presets, custom themes, scenes, setlists, and branding overlays all work without change — they're now just multi-output-aware.

1.5.6 May 13, 2026 Fix trial-expired pricing link

Phasion Live 1.5.6

Patch release — fixes a broken pricing link in the trial-expired modal.

What's fixed

Trial-expired pricing link

When the 10-minute free-trial timer fires in v1.5.5, the modal's "See pricing →" button pointed at phasion-live.pages.dev/pricing.html — a Cloudflare Pages project that was retired shortly after 1.5.5 shipped. Result: trial users clicking through to upgrade hit a 404 instead of the pricing page.

This release re-targets that button at the live marketing site (phasion-live.corunography.workers.dev/pricing.html).

No other behavior changes — same 10-minute trial, same modal, same Quit button, same all-other features as 1.5.5.

Upgrading

Existing v1.5.5 users will pick this up automatically on the next "Check for updates" via the in-app updater.

1.5.5 May 13, 2026 10-min free trial + new pricing flow

Phasion Live 1.5.5

10-minute free-trial timeout and a refreshed pricing flow.

What's new

Free trial now caps each session at 10 minutes

Unlicensed sessions run for 10 minutes from app launch, then freeze. This matches the in-browser trial at https://phasion-live.corunography.workers.dev and gives prospects a clear "you've seen what it does, here's the upgrade" moment instead of a permanently watermarked free product.

  • Wall-clock 10-minute timer armed at launch. Only fires when license state resolves to unlicensed or signin_needed — licensed/grace sessions never see it.
  • Mid-session license activation cancels the timer. Sign in and activate during the window and the session continues uninterrupted.
  • Freeze semantics — when the timer fires, the visualizer's render loop cancels (the last frame stays on-screen, watermark on top), audio capture stops cleanly, and the AudioContext is suspended.
  • Trial-expired modal in the controls window. Undismissable except via:
  • See pricing → opens https://phasion-live.corunography.workers.dev/pricing.html in the system browser.
  • Quit Phasion Live exits the app.
  • Reset on relaunch — quit and reopen for a fresh 10 minutes. No persistent lockout.

Marketing site (corunography.workers.dev)

  • New /pricing.html — Free / Web ($30/yr) / Home ($60/yr) / Pro ($19/mo, $190/yr annual) / Venue tiers, with a Monthly ↔ Annual toggle on Pro.
  • /try/ browser-based trial with the same 10-minute cap as the native app, dual-preset blending, audience-display popout, 1,144 base presets + 14k bonus presets loaded from R2 storage.
  • Keyboard shortcuts in /try/A/B/S for slot randomize + swap, arrow keys for blend + sensitivity, F for fullscreen, ? for the help panel.

Under the hood

  • New IPC channel app:quit so the controls renderer can request a clean shutdown.
  • to-viz channel gets a new message type trial-expired which cancels the rAF chain in visualizer.js and tears down audio capture.
  • Bonus preset library (54 MB) now lives in Cloudflare R2; web /try/ fetches it cross-origin and caches in IndexedDB for return visits.
1.5.4 May 11, 2026 AI Generate styles + boot stability + song-title fix

Highlights

🎨 AI Generate gets curated style buckets

Pick a mood per slot via a tiny inline dropdown next to each ⚡ Gen button: 🧊 Calm · ⚡ Aggressive · 🌀 Psychedelic · 📐 Geometric · 💧 Liquid · 💥 Strobe · 🎭 Hybrid · 🎲 Random. Mashup any two moods (Calm × Strobe!) by setting different styles per slot. Default 🎲 Random rolls a fresh mood each click so generations look distinctly different, not slight variants of each other.

🎭 Hybrid mode — cross-pollinate two real presets

Pulls frame_eqs from one preset and pixel_eqs from another. Real butterchurn JS recombined — always compiles, never the same chimera twice.

✨ AI Generate retry on failure

Now retries up to 3 times if a generated preset fails to compile, and stops the spinner immediately on total failure instead of waiting for the 30s safety fallback. Previously about 20% of clicks would silently no-op.

🎬 Song-title overlay no longer freezes the visual

The backdrop-filter compositor layer is now pre-warmed at parse time (same pattern as the QR overlay). The GPU doesn't rebuild it on each Shazam match — no more visible hitch when a song appears on screen.

🖥 Media-capture boot stability

Defensive stop+drain in startMediaCapture, proper Phase 4 ordering after the boot preset apply, and a guard against double-mediaStart. Eliminates the intermittent SIGSEGV on launch when a preset's auto-restart raced with the async restore IIFE.

🎵 Beat-sync + Photo-counter are now true global preferences

Activating scenes that previously baked beatSyncEnabled:false no longer flips your global toggle off. Live BPM also visible in the header next to FPS while Beat Sync is active. Photo-carousel counter follows the same pattern.

🧹 Duplicate scenes cleaned up

One-time dedupe-by-name on launch keeps your customised originals.

🧰 Small UX polish

  • Window-picker buttons (Refresh / Restart) no longer get clipped in the Media Capture section at narrow widths.
  • Pack editor in Stage Management now separates prompts with blank lines so long prompts don't blur together.

---

Direct download (always latest): Phasion-Live.dmg

1.5.3 May 10, 2026 AI Visuals prompt packs + cross-display media-crop fix

Highlights

🤖 AI Visuals prompt packs

A new PACK dropdown above the category selector in the AI Visuals section. Pick a themed pack to override Roll and Auto-cycle — they'll now draw from the pack's prompts instead of the category engine. Ships with a built-in 🤖 AI Visuals pack of 20 starter prompts (psychedelic / cosmic / surrealist / artist-style), and the Pack creator in Stage Management → Prompt Packs gains a 🤖 AI Visuals option so you can author themed packs for specific events.

🎬 Media-crop startup race — fixed

Crops applied from a saved preset now snap in immediately on launch — no slider nudge required. The root cause was window.devicePixelRatio briefly returning 1 before Electron settled the Controls window on its display, then becoming 2. That meant the first sendCropUpdate after launch shipped half-scaled crop values, and any later nudge shipped double-scaled. Hardcoded the scale so it can't race.

🖥 Cross-display preset switching

Switching a saved preset across monitors mid-session no longer requires a manual Restart Capture. applyMediaCropPreset now auto-cycles the SCKit stream (500ms settle) so the bitmap context re-binds to the new display's frame dimensions. Per-monitor presets stay valid since each one's stored slider values are still calibrated to that monitor's source frame.

✏️ Prompt-pack editor readability

Prompts in the pack editor textarea now have a blank-line separator between them so long prompts no longer blur into a wall of text. Larger font, more line-height, refreshed helper copy.

---

Direct download (always latest): Phasion-Live.dmg

1.5.2 May 10, 2026 Copy-link buttons + audio source auto-switch + persistence fix

What's new

  • 📋 Copy-link buttons next to the URL chips in Audience QR and Remote Control sections. Click the chip to open in browser; click the button to just copy the link. Shows ✓ + "Link copied!" toast confirmation. Uses Electron's main-process clipboard via IPC so it works regardless of focus state.
  • Audio source auto-switch — changing the audio device dropdown now re-fires start-audio immediately when previously connected. Before: dropdown change reset the badge but the old device kept streaming until you clicked Connect again. Fixed.
  • Enable-state persistence fixpreload.setMediaEnabledSync now actually uses ipcRenderer.sendSync. Previously it was async-aliased, so the renderer's quit-time write didn't complete before main flushed state to disk. The "I enabled it but it forgot after restart" bug is fixed.

Media Capture

  • ⟳ Restart button added next to the window picker in Media Capture settings. One click does a clean stop + 500ms settle + start, forcing SCKit to re-bind to the source window's current dimensions. Use it after a monitor switch, source resize, or preset reload if the cropped image doesn't match.

Known limitation

On launch, the crop sometimes doesn't visually apply until you nudge a crop slider by 1 pixel. The ⟳ Restart button is a one-click workaround. We have an open investigation into this — the root cause is in the native syphon addon's binding behavior that we don't have source access to. Tracking for a future release.

Install

Apple-Silicon Macs only. Direct DMG download. Signed and notarized.

1.5.1 May 10, 2026 Media source: Syphon ↔ display alignment + audio auto-switch

Headline fix

Same crop slider values now produce identical framing in both the Phasion Output window AND any Syphon receiver (Synesthesia, Resolume, OBS Syphon, etc.). The on-screen display canvas had a hardcoded "+21 src px top, +23 src px bottom" compensation baked into its render loop; it's been removed. Calibration is once-and-done.

Media source improvements

  • DPR-aware crop scaling. Native SCKit captures at physical pixels on Retina displays — without DPR scaling, the same crop value cropped half as much. Fixed.
  • Aspect-matched output. Output width now derives from the cropped-source aspect ratio instead of a hardcoded 16:9 table, so SCKit's resize step doesn't stretch the cropped frame.
  • Live window-bounds sync. As you drag/resize the source app's window manually, Phasion's X/Y/W/H sliders now follow along (1.5s poll). Saving a preset captures the truth, not stale values from when capture was first configured.
  • Apply-preset ordering. Window resize fires first and awaits the AppleScript completion (plus 150 ms settling) before the crop applies. Eliminates the "black bar at top" from crop landing against stale capture dimensions.

Audio source

  • Auto-switch on dropdown change. Picking a different audio source now re-fires start-audio immediately — before, the badge changed to "disconnected" but the previous device kept streaming until you clicked Connect again.

UI polish

  • Send Fullscreen button is now centered in the Output Preview header bar.

Install

Apple-Silicon Macs only. Direct DMG download. Signed and notarized.

1.5.0 May 9, 2026 Multi-tier licensing + captions polish

Highlights

  • Multi-tier licensing. Engine, Trial, Pro, and Venue tiers now gate features per a single tier-rank table.
  • Trial (signed-out or no license) — full Pro feature access with watermark.
  • Engine — visual core + AI Visuals + Lyrics & Captions only. Audience features locked. Auto-lands on a stripped-down default scene that just shows the title pill + lyrics overlay.
  • Pro — everything except Whitelabel Branding.
  • Venue — everything, including Whitelabel Branding.
  • Click any greyed-out feature to open a styled in-app upgrade modal that links to the pricing page.
  • Hide locked features toggle (Settings → License) collapses inaccessible sections out of the layout. Engine + hide-locked drops to a clean 2-column workspace.

Captions / Shazam fixes

  • Title now paints for songs LRCLIB has no synced lyrics for — instrumentals and rare tracks show the title pill instead of nothing.
  • Toggle off → toggle on while same song plays restores the overlay immediately. Re-enable on a new song no longer gets short-circuited by stale state.
  • Opening Settings no longer kills the on-screen title/lyrics. Fixed a long-standing race where a secondary window's renderCaptionModels call fired lyrics-disable to the shared visualizer.
  • Cached title/lyrics repaint automatically when scene visibility flags flip from suppressed → visible — Shazam recognitions caught during a scene transition aren't lost.

Other

  • Sign-in dialog: removed the in-app "Create account" tab. Accounts are created via the Lemon Squeezy checkout.
  • Output Preview: Send Fullscreen button now centered in its header bar.
  • New default scene: default-engine — minimal preset designed for the Engine tier.

Install

Apple-Silicon Macs only. Download the DMG, drag to Applications. Signed and notarized.

1.4.28 May 8, 2026

v1.4.28

Bug fixes:

  • Fixed demo photos not appearing on audience phones (QR voting page) — photos were missing the image data needed for remote devices
  • Fixed demo assets not included in packaged builds (v1.4.27)
1.4.27 May 8, 2026

v1.4.27

Bug fix:

  • Fixed demo photos (Caption from Crowd, Photo Contest, Pet Photos) not appearing in installed/packaged builds
1.4.26 May 6, 2026

Electron 41, 2-col responsive layout, Metal Syphon preview, photo pipeline overhaul, friendly link labels, clean-shelf launches

1.4.25 May 5, 2026 Clean-shelf launches

Three UX fixes that landed after a stream-of-consciousness pass through the controls window.

Fixes

  • Clean-shelf launches. Every launch now opens with all sections, menu groups, settings categories, and moderation subsections collapsed. The single exception: Output Preview still remembers its last state across launches. Mid-session expand/collapse still works exactly as before — it just doesn't carry over to the next launch. The "open the app, see the room running, expand only what I need" pattern, made literal.
  • Light-mode FX buttons fixed. The visual effect buttons (SHAKE, ZOOM, CRUSH, BLUR, SPIN, HUE, SQUEEZE, RIPPLE, KALEID, PIXEL, WAVE, GLITCH) had multi-col rules slammed to pure black with !important, making them slab against the pale page chrome in light mode. Light-mode overrides now win the cascade with white fill, neutral grey text, and a soft red active-state for the ISF toggle buttons.
  • BLACK strobe button no longer dominates. In light mode it now sits in a mid-grey gradient (#8a8a90 → #6a6a70) — readable as "the dark one" without slabbing pure black against the rest of the row. Press / strobe-flash still goes to true black for the active feedback moment.

Cleanup

The new build also strips legacy persisted collapse-state keys from venue.json on first launch (collapsedSections, collapsedMenuGroups, collapsedModSections, etc.). They're all unused now.

1.4.24 May 5, 2026 Smoother launch (CLS 0.66 → 0.00)

Big perf win on the controls-window launch path.

What changed

A DevTools performance trace caught the controls window racking up ~30 layout shifts and a 0.66 Cumulative Layout Shift score during the first ~1.4 s of every launch. Cause: the section / menu-group / mod-section initializers each apply the .collapsed class to many elements at once, triggering all their max-height / margin-top / opacity transitions to fire simultaneously on a 9k-element DOM.

The fix:

  • <body> boots with a .no-transitions class that disables every transition + animation on every descendant.
  • After the startup IIFE finishes, the class is removed in a double-rAF — so all initial state is applied without animating, then transitions resume normally for any subsequent operator interaction.
  • Two scroll-indicator polls (the controls-window indicator and per-column overflow detectors) early-return while .no-transitions is active, so their forced layouts don't compete with the init burst.

Results

  • CLS: 0.66 → 0.00
  • Visual experience: black → settled state, no chaotic animation in between.
  • CPU usage during init drops noticeably (per operator testing).
  • Forced reflows during init: cleared.

LCP is bounded by the dev/Webpack bundle parse + synchronous init code; further wins would require code splitting, deferred for now.