Skip to content

Releases: futureboard/futureboard-studio

Nightly v2026.8.15-nightly

Pre-release

Choose a tag to compare

@github-actions github-actions released this 15 Aug 05:42
b36f7f9

Automated daily build for Windows, Linux, and macOS.
App version: 2026.8.15-nightly

Pre-release snapshot — may be unstable, not for production use.

Futureboard Studio 2026.8.10 Beta 1.1

Choose a tag to compare

@arizkami arizkami released this 10 Aug 08:36

Futureboard Studio 2026.8.10 Beta 1.1

Point release — 10 August 2026

A one-day follow-up to 2026.8.9 Beta 1. Nineteen commits, mostly in three areas: built-in plug-in editors (MixStation and EQuz8 rebuilt, Windows editors now render on the GPU, and mouse input into them fixed), platform input (macOS MIDI, Linux ALSA capture), and release plumbing that the previous hotfix exposed as broken.

The updater fix matters if you are on 2026.8.9 Beta 1: that build could not see its own channel's releases. It can now.


Highlights

  • Windows plug-in editors render on the GPU — CEF's accelerated off-screen path copies D3D11 shared textures straight into the GPUI atlas, with no CPU readback and no per-frame image allocation
  • Knob drags no longer stick, and trackpads scroll — a drag that left the editor rect was cancelled mid-gesture, and a sub-pixel trackpad swipe rounded to nothing forever
  • macOS MIDI input works — physical keyboards produced nothing, and devices did not appear in the scan at all. Two independent macOS-only defects, both fixed
  • Linux audio capture — ALSA input uses the same multi-period ring geometry as playback, and device lists are readable instead of a wall of dmix/surround aliases
  • MixStation rebuilt as a signal-path rack: drag-reorderable modules, per-stage telemetry, per-module output trim, host analyser overlay
  • EQuz8 rebuilt on the MixStation UI stack — Pro-Q-style graph, Serum-style knobs, searchable preset popover, clearer bypass state
  • The updater can see beta releases again — a moving tag made every release unparseable, so the 8.9 hotfix was invisible to installed builds
  • Signed APAK packages with key verification, staged into distributable builds
  • Accessibility semantics across menu bar, title bar, sidebar, sliders, text inputs, and the welcome screen
  • Two inserts of the same plug-in are now distinguishable in editors that bind by instance

Built-in plug-in editors

Accelerated off-screen rendering on Windows

Built-in editors now render windowless on every platform; the Windows native child window is gone. On Windows, CEF's OnAcceleratedPaint hands the host a callback-scoped D3D11 shared texture, and that texture is copied straight into a GPUI-owned atlas tile — no CPU readback, no per-frame RenderImage allocation, no atlas churn from a new tile every frame.

  • SphereWebView gains an OsrAcceleratedFrameSink trait, accelerated surface construction, popup-plane state propagation, and a failure signal. CEF cannot switch an existing browser from accelerated to OnPaint, so a copy failure — or a GPUI device-loss that cleared the atlas — recreates the browser in software mode rather than leaving a blank page.
  • GPUI gains PlatformAtlas::copy_d3d11_shared_texture, D3D11ExternalImage, and Window::create_d3d11_external_image / paint_d3d11_external_image.
  • The DirectX atlas opens the shared resource, validates format, sample count and source bounds, copies with CopySubresourceRegion, and flushes while CEF's handle is still valid.
  • Fixed an atlas-space leak: tile removal decremented the reference count without ever deallocating the etagere slot, so the atlas could not reuse the space.

Linux, macOS, and the Windows fallback keep the software OnPaint framebuffer path.

Mouse input into off-screen editors

Three defects in the GPUI-to-CEF input translation, all of which get worse now that every platform renders windowless.

  • A drag that left the editor rect was cancelled. A move outside the browser rect was reported to the page as a leave, and Blink treats that as pointercancel on a captured pointer — so dragging a knob past the edge of the editor ended the gesture and left the control stuck. A move is now only flagged as leaving when no button is held, drag moves are never suppressed, and hover state is settled on release instead.
  • A precision trackpad never scrolled. Each wheel delta was rounded on its own, and a trackpad reports a fraction of a line per event, so a slow two-finger swipe produced an endless stream of zero deltas. The sub-pixel remainder is now carried between events and spent once it reaches a whole pixel; a direction change drops the stale carry so a reversal is not swallowed. Deltas are clamped, because Windows reports WHEEL_PAGESCROLL as u32::MAX lines, which would saturate the i32 handed to CEF.
  • Clicks and wheels landed wherever CEF last thought the cursor was. The browser's own pointer position decides what the page hit-tests and scrolls, so the position is now sent before a button press and before a wheel event — the same order a real platform delivers.

Also: SCROLL_LINE_HEIGHT 20 → 40 logical pixels, matching Chromium's kPixelsPerLineStep, so one notch moves an embedded editor as far as it moves the same page in a browser window. click_count is clamped to CEF's 1..=3 contract; GPUI keeps counting past 3 while a fast click repeats in place, and Blink has no defined behaviour above that.

MixStation

Rebuilt as a signal-path rack over the real parameter bridge — drag-reorderable rows, portalled popovers, a factory preset browser.

  • Every curve is drawn from the authoritative parameter values through response.ts, a mirror of dsp.rs, so the graphics describe the processing that actually runs. Locked to the Rust suite's own assertions by test.
  • The host analyser overlay now sits behind the EQ. The spectrum channel and its ~30 Hz publish already existed for every built-in; MixStation's editor simply never listened.
  • DSP: per-module output trim (6 wire params, PARAM_COUNT 33 → 39), keyed by module rather than rack slot, so reordering carries a stage's trim with it. Legacy state loads at unity via serde defaults.
  • Shared bridge BRIDGE_LAYOUT_VERSION 8 → 9: per-stage telemetry added to BuiltinMeterFrame. Built-ins with no user-ordered rack publish zeros, and the native side omits the field entirely, so the meter payload is unchanged for every other editor.
  • Instance binding: the display metadata native already sends is now consumed, so two inserts of the same plug-in are distinguishable. The approved binding is mirrored into a hash route, which never binds on its own — a hash edit or back/forward posts requestSelectInstance and native decides. Editor-local view state resets on rebind.

EQuz8

Rewritten UI and then aligned with the MixStation editor stack: Mona Sans, Tailwind tokens, Phosphor icons, motion, BypassSwitch, and a searchable portalled PresetMenu. Full d3 for scales, monotone curves and area fills; anime.js for intro, band-select pulse, rack flash, knob snaps and bypass tweening. Embedded as a single-file CEF bundle via vite-plugin-singlefile. Parameter bridge and DSP behaviour unchanged.

A powered-off stage is now clearly desaturated and dimmed in CSS, with anime.js limited to a brief brightness flash so it cannot override the steady state. React module resolution in the editor build is fixed.

Transport keys

Space drives play/pause from inside a built-in editor again: the OSR shell and surface claim transport keys through more XKB/IM Space aliases, and MixStation falls back through globalCommand when native capture misses.


Platform input

macOS MIDI

Physical MIDI keyboards did not work on macOS, and devices did not appear in the scan at all. Two independent defects, both macOS-only.

Discovery. shared_client() used std::sync::Once, and call_once marks the Once completed even when the closure returns early. One failed MIDIClientCreate latched CLIENT at 0 for the process, and without a client CoreMIDI reports zero sources no matter what is plugged in. Every recovery path above it was dead code — the resilient scan retries, the two-second startup rescan, the Preferences refresh button, the 500 ms device sync. The first attempt runs on a background scan thread during splash, exactly when MIDIServer is most likely unreachable, so the failure that got latched was the transient one the retries existed to absorb. Creation is now mutex-guarded and retried until it succeeds.

Delivery. MIDIPacket is declared under #pragma pack(4) on every Apple target, so the natural #[repr(C)] layout of { u64, u16, [u8; 256] } was wrong: 8-byte alignment pushed the first packet of a MIDIPacketList from offset 4 to 8. Every field read four bytes late — for a Note On, length landed on the velocity byte and the payload came from past the end of the message, so decoding returned None and the note was dropped in the callback. Offsets now come from the header and are pinned by offset_of! assertions that fail the build if the layout drifts.

Also in the input path:

  • Coalesced packets are split into messages. A CoreMIDI packet is a byte stream, so a chord shares one packet and only the first message was decoded. The splitter handles running status across packet boundaries, interleaved System Real-Time bytes, System Common, and SysEx.
  • An endpoint is never dropped for unreadable metadata. Naming falls back kMIDIPropertyDisplayNamekMIDIPropertyName → manufacturer/model → a synthetic name from the unique ID. Long names no longer fail: CFStringGetCString is all-or-nothing and the 512-byte buffer silently dropped the device.
  • One input port off the shared client instead of a client per device, with ordered teardown.
  • Running status carried in an AtomicU8, since the box is owned by the control thread and touched by CoreMIDI's read thread.
  • Hot-plug notifications only fire on the run loop of the creating thread, which is often a background scan thread whose run loop never runs — endpoint-count comparison is the safety net.
  • The outgoing packet-list buffer is aligned; it was a byte-aligned `[u8;...
Read more

Futureboard Studio 2026.8.9 Beta 1 — Hotfix

Choose a tag to compare

@arizkami arizkami released this 08 Aug 08:37

Futureboard Studio 2026.8.9 Beta 1

Hotfix — 9 August 2026

A one-day hotfix on top of 2026.8.8 Beta 1, the first public beta.

Two problems in that build were serious enough to warrant a same-week release: Professional activation was disabled in every CI-produced binary, and the plug-in scan could never finish, so the plug-in catalog never updated. Both are fixed here, along with plug-in classification, plug-in editor window behaviour, audio device persistence, one allocation on the audio thread, and a round of built-in plug-in work.

If you installed 2026.8.8 Beta 1, you want this build.


Highlights

  • Activation works again — 2026.8.8 Beta 1 shipped with no activation endpoint baked in, so Professional users were told "Activation is not configured for this build"
  • The plug-in scan completes — a hung bundle no longer stalls the whole sweep, so the catalog actually gets written
  • No more duplicate plug-ins — bundle-format plug-ins were listed twice under two ids that dedup could not collapse
  • Correct instrument / effect classification — read from what the plug-in declares instead of guessing from its name
  • Kontakt 8 and other bundles that log to stdout are no longer silently discarded
  • Plug-in editor windows: Space drives the transport, no more always-on-top editor, cleaner teardown
  • Project sample rate is saved and restored; ASIO devices match reliably
  • A latency-graph rebuild no longer allocates on the audio thread
  • EQuz8, EchoSpace and MixStation improvements

Licensing

Professional activation was broken in 2026.8.8 Beta 1.

license.rs resolves the activation service from option_env!("FUTUREBOARD_LICENSE_API_URL"), but build.rs never supplied it — it baked only the account URL and the public key. The gap stayed invisible because a maintainer's shell exports that variable, so local builds baked it anyway. Every build without the export — CI above all — shipped a Professional binary with activation_endpoint() == None, which disables Activate and tells paying customers that activation is not configured for the build. That is what 2026.8.8 Beta 1 shipped.

The endpoint is now read from .env like the account URL, non-HTTPS values are refused at build time (the license key is sent to this endpoint, and license.rs rejects plaintext at runtime anyway), and a build with no endpoint emits a loud cargo:warning instead of silently producing an unactivatable binary.


Plug-in scanning and the catalog

The scan could not finish. run_isolated_bundle_scan waited on the scanner child with no deadline, and soothe3 — plus three Neural DSP / Supertone bundles — never returns from module load. The sweep hung forever, and because the catalog is written only after the sweep completes, it was never updated. Each bundle now gets a 30-second budget; the child is killed at the deadline, recorded as a failure, and the sweep carries on.

  • Both scanner pipes are drained on their own threads. One Waves shell emits 718 classes, far past a pipe buffer.
  • Bundles that log to stdout no longer vanish. Kontakt 8 writes two [info] lines while loading, which sat in front of the scanner's JSON and failed the parse, discarding every class in the bundle. The child now points fd 1 at stderr for the duration of the scan and writes its payload to a saved duplicate behind a sentinel; the parent takes the text after the last marker, and still accepts an unmarked payload.
  • Every bundle-format plug-in was reported twice. A .vst3 bundle directory contains Contents/x86_64-win/<same-name>.vst3, and both walkers matched the inner file as well — two different stable ids, which dedup could not collapse. Bundles are leaves now.
  • Failed modules are failures, not plug-ins. A module that fails to load carries a loadError and is recorded as a scan failure instead of becoming a plug-in row named after its file. 32-bit-only bundles report an architecture mismatch.
  • Stale rows are pruned. write_catalog removes rows the scan did not produce, guarded so that an empty scan keeps the existing catalog — an unplugged drive cannot wipe it.

Measured against a real 972-class library: 1756 rows with 710 duplicate vendor+name+format pairs became 988 rows with none, scanning in 94 seconds with 5 named failures. A second rescan prunes nothing.


Plug-in classification

classify_kind matched the strings bass, drum, generator and piano against the plug-in name. Across the 972 VST3 classes surveyed, that misfiled 43 of them — Waves Fx|Bass, Fx|Drums, Fx|Generator and Ozone 12 Bass Control were all listed as Instruments.

Classification now reads only what the plug-in declares: VST3 subCategories, CLAP features, AU component type. A plug-in that declares nothing is PluginKind::Unknown rather than a guess, and Unknown stays insertable as an effect so an untagged effect is not lost.

  • sub_categories is now persisted — plug-in database schema v2, an additive ALTER TABLE, with reads tolerating its absence so a v1 database still loads read-only. A restart re-derives the same kind and category the scan did.
  • Project format v36 stores each insert's registry-resolved instrument/effect role, so an effect in slot zero is never mistaken for an instrument after a project load. Projects written before v36 keep their positional fallback.

Plug-in windows and editors

  • Space drives the transport from a plug-in editor. The editor shell is a raw Win32 window in the main process, so a key pressed on its chrome reached neither GPUI nor the plug-in host, and Space did nothing.
  • The legacy in-process editor is no longer always-on-top. It dropped WS_EX_TOPMOST — which outranked every other application on the desktop — and is now owned by the app window.
  • Cleaner teardown. Pending editor work is cleaned up during unload, and focus resolves on the foreground UI thread.
  • reset_project closes the Add Insert window, which otherwise survived a close or switch aimed at a track that no longer exists.
  • The plug-in picker gains a Type column, and Enter now closes the picker. It previously inserted the plug-in but left the window showing a closed picker, unlike double-click and Add.
  • The external Plugin Manager is integrated into Settings, and dense typography constants are standardised across that surface.

Audio devices and connections

  • Project sample rate is persisted. Each project stores its nominal sample rate and restores it before loading audio sessions, including the deferred engine reopen path.

Realtime

A latency-graph rebuild no longer allocates on the audio thread.

refresh_runtime_latency_graph runs per block from the audio callback. Its steady-state scan was cheap, but the moment an observed latency differed from the active plan it rebuilt everything: a Vec of observed values, a fresh plan behind a HashMap<String, usize> with a cloned track id per entry, a delay-line resize, and two std::env::var_os calls — allocation, string hashing and the environment lock, all inside the callback.

Plug-ins that report a fixed latency never reached that branch, which is why it went unnoticed. A bridged plug-in whose reported latency tracks something live — a network plug-in following its own jitter buffer — re-enters it as often as its latency moves, and every visit is an xrun.

Latency planning now routes on the resolved index fields the render path already uses instead of hashing ids, so the algorithm runs in place over vectors that are already sized. PDC rings reserve capacity to the next power of two while their length stays exact, so a latency that grows mid-session is a length change inside memory the control thread already reserved. If a plan ever outgrows even that, the realtime refit declines rather than allocating, and the affected tracks pass through uncompensated until the next graph swap.


Built-in plug-ins

EQuz8

Every band opened inactive and flat, so the first gesture on one did nothing audible. Shaping a band — dragging its node, turning a knob, the wheel, arrow keys — now switches it on in the same edit. An explicit active in the patch (chip double-click, rack switch) is still honoured, so turning a band off still turns it off.

EchoSpace

  • Tempo sync. Delay times can follow the project tempo, across 18 note divisions (straight, dotted, triplet), clamped at both ends so a bad tempo cannot produce a zero-length or infinite delay. Rust owns the division table and the conversion.
  • L/R link. An edit on either side mirrors to the other, and enabling link snaps right onto left.
  • Free time_ms values are left untouched while synced, so switching Sync off returns the line to where it was.
  • Built-ins previously had no transport at all. The host producer now publishes the block's tempo into the DSP, and hostStatus carries tempoBpm so the editor prints the length the line is really running at. Status is pushed on instance bind rather than up to a second later.

MixStation

  • Serial-chain editor. The rack was a 3×2 grid, so processing order read across two rows. The chain is now one horizontal row — position on screen is position in the signal path — and the 176px module drawer is gone; an empty position is its own picker. Per-module accent colour carries from the faceplate spine through the position number, bypass switch and knob arcs.
  • Cut filters are 24 dB/oct Butterworth cascades at the correct pole Qs, and drop out of the path entirely when parked at 20 Hz / 20 kHz instead of quietly shifting phase.
  • Sweepable mids get proportional Q, 0.7 at unity to 1.9 at the extremes.
  • Saturation — the bias was multiplied by drive, so at high drive the negativ...
Read more