Changelog
Notable changes per release.
#1.0.1
IcedPlugin::needs_redrawkeeps streaming editors live. The iced editor's idle gate skips repaints when no UI input, param, or meter changed - which left a plugin streaming realtime data to its editor outside that system (a lock-free queue drained inview()) showing it late, only when a stray UI event forced a repaint. A plugin can now returntruefromneeds_redraw()while it has new data, so the editor repaints and drains promptly and idles again when there's nothing new.truce-example-midi-inspectoruses it so live MIDI scrolls in without lag.truce-example-envelopeemits sample-accurate CC. The envelope follower pushed one control-change per block atsample_offset: 0carrying the block's final value; it now emits on each value change at the exact sample offset, so the CC stream stays smooth (no per-block@0bursts or up-to-a-block latency) and tracks level changes within a block.
#1.0.0
#Breaking
- Auto-assigned parameter IDs are now a stable hash of the field name instead of a declaration-order counter. Reordering or inserting parameters - including inside
#[nested]groups - no longer shifts later IDs, so host automation and presets keep mapping to the right parameter across plugin versions. Explicit#[param(id = N)]still wins. Migration: a plugin already shipped with the old order-based IDs must add#[params(id_scheme = "ordinal")]to keep them, or its hosts' saved automation and presets will rebind to the wrong parameters. truce-icedno longer depends on theicedumbrella crate. It uses iced's sub-crates directly (iced_core/iced_widget/iced_renderer/iced_wgpu/iced_runtime/iced_futures), re-exported astruce_iced::iced, which drops the transitiveiced_winitdependency whose desktop-only keyboard API broke the iOS build. Migration: plugins that imported types from theicedcrate import the same ones fromtruce_iced::icedinstead.- Removed the deprecated
selector/param_selectorwidget. Theselector()layout builder and each backend'sparam_selector(egui / iced / vizia / slint), deprecated since 0.56, are gone along with the underlyingSelectorwidget. Migration: usedropdown()/param_dropdown- the same single-choice control, click-to-open. - Removed the deprecated
FloatParam::read_block. The const-Nblock read, deprecated since 0.53, is gone. Migration: useread_into(&mut scratch[..n]), which advances the smoother by exactly the number of samples consumed.
#Other changes
- iced editors run on iOS.
truce-icedrenders through aCAMetalLayer-backedUIViewunder AU v3 - touch input plus a soft keyboard for focusedtext_input- so iced plugins build and run on iOS like the egui backend.gain-iced,gui-zoo-iced, andmidi-inspectorare now iOS examples. - AU v3 factory presets ship on iOS.
cargo truce install --ios/package --iosnow bundle a plugin'spresets/library into the embedded framework'sPresets/, so AU v3 hosts list them on iOS like macOS. iOS frameworks are shallow bundles, so the presets sit in a flatPresets/directory rather thanResources/Presets/(aResources/subdir makes iOS installd reject the framework). - New
truce-example-midi-inspector(iced). An audio effect with MIDI in/out that passes the track's audio through untouched and decodes every event truce can deliver - MIDI 1.0 + 2.0 channel voice, SysEx (with manufacturer id + hex), transport, and param automation - into a live scrolling log (newest first), with a raw line for anything not yet richly interpreted. Forwards MIDI through (toggle with theMIDI Thruparam) and demonstrates streaming structured realtime data fromprocess()to an editor via a lock-free ring carried in a#[skip]params field. #[derive(Params)]supports#[skip]fields. A field marked#[skip]is not a parameter: it's plugin-owned state that the editor reaches through theArc<Params>both sides already hold (e.g. a lock-free queue of audio-thread events). The deriveDefault-initializes it innew()and excludes it from parameter ids, infos, state, and count.- CLAP and LV2 state save/load are now panic-guarded like VST3 / VST2 / AU / AAX: a panic in a user's
save_state/load_statereports failure to the host instead of unwinding across theextern "C"boundary and aborting it. - Rendering performance fixes on Windows. Embedded editors run their frame loop on the host's GUI thread, where the iced / egui backends repainted every tick and blocked on a vsync present - so a heavy editor (GUI Zoo) made the host (REAPER) laggy and could lock out other plugin windows. Editors now skip frames when nothing changed, present non-blocking on Windows, and skip rendering while the host window is hidden or minimized.
- Smoother editor repaints on Windows. Editors no longer render in slow, bursty spurts inside busy DAWs - the frame loop is now driven by a steady high-resolution timer (via the
baseview-truce0.1.1-truce.10 dependency), and the same fix closes a crash that heavy repainting could trigger. Applies to both the iced and egui backends.
#0.64.0
- Parameters can declare a default MIDI mapping (
#[param(..., midi_cc = N)]) - VST3, AU v2, and LV2 expose the binding to the host's MIDI controller assignment; CLAP / VST2 / AAX leave it host-driven. - VST3 accepts channel MIDI controller input - CC, program change, channel pressure, pitch bend, and poly pressure now reach the plugin, decoded from VST3's legacy MIDI controller event forms. (#145, by @Boscop)
- VST3 bridges host-mapped MIDI controllers back into MIDI events. A parameter bound via
#[param(midi_cc = N)]/#[param(midi_source = …)]is the target a VST3 host routes the matching controller to (overIMidiMapping); that parameter change is now also delivered as the correspondingEventBody(pitch bend, CC, channel pressure, program change), so plugins reading MIDI events - not the parameter - respond to the pitch and mod wheels under VST3 as they already do on AU / CLAP. - The synth example responds to the pitch and mod wheels.
truce-example-synthnow bends pitch (+/-2 semitones) and adds mod-wheel (CC1) vibrato. - SysEx input fixes. VST2 and AAX no longer drop a queued input SysEx to the per-block event-list clear; AU now accepts SysEx input (v2 via
MusicDeviceSysEx, v3 via UMP SysEx-7/8). - Plugin-process parameter changes notify the host. A parameter the plugin changes during
process()now updates the host's UI / automation on VST2, VST3, and AU (CLAP already did) - viaaudioMasterAutomate, the VST3 output parameter queue, andAUEventListenerNotifyrespectively. (#147, VST2 by @Boscop)
#0.63.3
cargo truce --shellfinds[profile.shell]at the Cargo workspace root - the preflight no longer rejects plugin crates that are members of a larger workspace. (#148)truce-viziano longer risks messaging a freed parentNSViewon editor teardown - the per-frame macOS re-anchor stops once the editor closes.viziais now documented as not recommended for production due to teardown/resize stability issues internal to vizia.
#0.63.1
- Slint editors receive keyboard input.
truce-slinttranslates native key events into SlintWindowEventkey events, soLineEdit/TextInputandFocusScopework in plugin editors (host focus permitting). - The slint
gui-zooexample demos native Slint widgets - button, checkbox, spinbox, slider, progress bar, image - plus a keyboard section, alongside the truce param widgets. - The vizia
gui-zooexample demos native Vizia widgets - button, checkbox, slider, image - plus a keyboard section, alongside the truce param widgets.
#0.63.0
- iced editors receive keyboard input.
truce-icednow forwards native key events into the iced widget tree (a focusedtext_inputor a custom key-captureWidgetreadingphysical_key/ logical key), andIcedPlugin::subscription()(e.g.iced::keyboard::listen,iced::event::listen_with) now fires via an event pump. Keys arrive when the host grants the editor window focus. (#134) gui-zooexamples (iced + egui) demo native framework widgets - button, checkbox, radio, slider, progress bar, image, etc. - plus a keyboard section, alongside the truce param widgets.
#0.62.0
- AU v3 and standalone are one macOS app. A plugin with a standalone bin ships a single
{name}.appthat is both the AU v3 container and the playable standalone host; the separate Standalone format collapses into it and the installer choice reads "AU3 + Standalone". Plugins without a standalone bin still ship the appex, in an informational stub app. - Fixed macOS standalone resize leaving a white margin around the editor; non-resizable editor windows are now pinned so they can't be zoomed open.
package --suite <name>matches a suite by bundle id or display name and errors on an unknown name instead of silently skipping the suite.
#0.61.0
- Reusable
#[nested]param groups. A nestedParamsstruct's ids are rebased by a per-group base (auto-packed, or pinned with#[nested(base = N)]), so the same group type can be nested more than once without id clashes and nested groups need no per-param ids. - Nested meters are caught at construction. Two
#[nested]groups that each declare a meter would have aliased on one id; the derive now panics at construction instead of corrupting state silently. - Nested params construct through the generated
new(). AParamsstruct that mixes its own params with#[nested]groups now default-initializes the nested fields innew(). (#137, by @jedStevens)
#0.60.1
#[derive(Params)]no longer trips a rust-analyzer error. Parameters with arange(or numericdefault) emitted suffixed literals like60f64, which rust-analyzer 1.96 rejected with "expected Expr" even though the code compiled; the derive now emits plain decimal literals. (#135)
#0.60.0
midi_input/midi_outputcapability flags. New[[plugin]]truce.toml keys let an instrument or audio effect opt into MIDI input/output (or opt out), overriding the category default, consistently across every format.- AU MusicEffect for MIDI-driven effects. An audio effect with
midi_input = truenow registers as anaumfMusicEffect, so AU hosts route MIDI to it instead of anaufx. - VST2 MIDI output fixed on 64-bit. The outbound
VstEventsblock placed its pointer array at the wrong offset, so hosts read a garbage event pointer; plugins now emit MIDI correctly. (#131) - VST3 emits non-note MIDI output. Control change, pitch bend, aftertouch, channel pressure, and program change reach the host now, not just note on/off. (#123)
- CLAP advertises the MIDI note dialect. Raw-MIDI output events (CC, pitch bend, SysEx) are no longer dropped by dialect-routing hosts.
- SysEx payloads under sample-accurate chunking. The chunking layer handed
process()a timing-rebased event list whose SysEx pool was empty, so a plugin reading a SysEx payload would index out of bounds; the rebased list now carries its own copy of the payload.
#0.59.0
- Honor cargo's real target directory.
cargo trucenow reads cargo'starget_directoryviacargo metadatainstead of assuming<plugin>/target, fixing installs when the plugin crate is a member of a larger workspace (artifacts land in the workspace-roottarget/). (#124)
#0.58.4
- Aspect-ratio editor resize fixes. Aspect-locked editors stay on-ratio inside the host window without clipping or juddering across CLAP, VST3, AU, and LV2; corner drags track the ratio smoothly and LV2 honors the aspect ratio and
max_size. - Skip rendering while the editor is occluded or detached (macOS). A minimized or fully-covered window can't present, so every rendered frame queued a GPU drawable that never drained. Editors now bail before rendering when the host window is occluded or has no window attached, across every GUI backend (built-in CPU / GPU, egui, iced, slint). (#126, by @tothepoweroftom)
TRUCE_AZURE_ENDPOINTfor Windows code signing. The Azure Trusted Signing endpoint was hardcoded to East US; set this to sign through another region's endpoint. Defaults to the previous value. (#128, by @tothepoweroftom)- iced enum selector option count. Fixed an off-by-one that left the iced enum selector (pick-list) with the wrong number of options. (#129, by @jedStevens)
#0.58.3
- Installers now ship your factory presets.
cargo truce packagenow carries presets for every format that has them, in both the per-plugin and suite installers, on macOS, Linux, and Windows: CLAP / AU / LV2 ride inside the bundle (sealed under the code signature), and VST3 presets are placed into the OS preset folder — merged in, so a user's own saved presets there are never wiped.
#0.58.2
- Standalone presets. A Presets menu in the standalone host (macOS / Windows): pick a preset from Load, step through them with Previous / Next, and Save / Save As your own. Save (Cmd/Ctrl+S) updates the preset you're editing; Save As (Cmd/Ctrl+Shift+S) names a new one. On a factory preset Save is grayed and points you to Save As, so you never overwrite a stock sound by accident. Presets you save show up in the menu right away, and they land where your DAW reads them. Linux drives it all from the shortcuts. Launch straight into a sound with
--preset <name>. .maximizable(true)to allow window maximize in the standalone. Resizable editors can no longer be maximized by default — maximizing would grow the window past the editor'smax_sizeand leave an empty margin around the clamped GUI. Opt back in with.maximizable(true)(built-in grid, egui, iced, slint) for editors that render at any size; the underlyingEditor::can_maximizehook drives it on Linux / macOS / Windows. When a window does end up bigger than the editor anyway, the GUI now sits centered instead of pinned to a corner, and the freed space is painted black rather than showing glitched leftovers.- LV2 presets now apply in REAPER. REAPER listed presets but selecting it didn't change anything. LV2 presets now carry each parameter as a control-port value (
pset:value), not only an opaquestate:stateblob, so port-based hosts apply them through the control ports — which updates the editor, the host's own parameter display, and the DSP together. State restore also gained a base64 fallback for hosts that hand back the preset'sxsd:base64Binaryliteral undecoded (REAPER) instead of as a raw chunk. - Fixed-size LV2 editors no longer stretch in REAPER. REAPER resizes an embedded LV2 X11 editor to fill its FX window, which bilinear-upscaled a non-resizable GUI (the built-in synth, etc.) into a blurry mess. The built-in editor now renders at its native size, centered, with black letterbox margins instead of stretching, and the wrapper pins the editor to its natural size at open. Resizable editors are unaffected.
- VST3 factory presets rejected on Windows ("doesn't appear to be for this plugin"). Installed
.vstpresetfiles stamped the plugin's class ID as a straight byte-for-byte hex dump. That matches how macOS / Linux hosts read it, but Windows hosts use the SDK'sCOM_COMPATIBLEFUID::toStringordering, which reads the first eight bytes as a little-endian GUID — so REAPER (and other Windows VST3 hosts) decoded a different class ID than the plugin reports and refused to load the preset. Emitted presets now match the host platform's ordering. Linux / macOS were unaffected.
#0.58.1
- Pin
timeto 0.3.47. 0.3.48 was yanked from crates.io.
#0.58.0
- Presets. Drop a
presets/directory of.presetTOML files next to a plugin crate (params keyed by yourParamsstruct's field names, resolved through the derive sidecars; numeric ids also accepted) andcargo truce installships them as factory presets in every format: CLAP (native files + the preset-discovery / preset-load extensions), AU v2/v3 (factory list in Logic / GarageBand), VST3 (.vstpresetin the OS preset locations), LV2 (pset:PresetTTL). Every container wraps the same state envelope as session recall, which also powers the newcargo truce presetCLI:convertbetween any two formats,pullto turn presets saved in your DAW into library files (uuid-stable updates, param-name comments),import/exportfor pack zips, andlist/init. Underneath,truce_core::presets::PresetStoremanages factory / user / pack scopes with uuid identity, so renames never break host references and user copies override factory ones.[plugin.presets]user_diroptionally replaces thetruce/<vendor>/<plugin>part of the user-scope path. cargo truce install --au2 --debuginstalled a stale release dylib. The AU installer now resolves the build profile like every other format.
#0.57.2
- CLAP editor clipped above the host's pane in REAPER. The layout's top edge (header / first row) was drifting off the visible plug-in pane as the canvas grew. Editors now stay pinned to the top of the pane at every size. Applies to every GUI backend (built-in, egui, iced, slint, vizia).
- LV2 editor opened at host's default size instead of natural. Resizable LV2 plug-ins (GUI Zoo, etc.) used to take whatever pane size the host defaulted to. The wrapper now hints the editor's preferred size on open; hosts that honor it (REAPER, Ardour) start the pane at natural. User-drag resize from the host's window still works.
- GPU editors use the device's actual wgpu limits. Every wgpu-backed renderer (built-in GPU, egui, iced, slint) now requests
adapter.limits()instead ofwgpu::Limits::downlevel_defaults(). The downlevel cap of 2048 px per axis was clipping tall Retina canvases and crashing the host; modern GPUs report 8192+ and the layout renders at full size instead. - AU class-name collision warnings cleaned up. Multiple installed Truce AU plug-ins no longer log
TruceAuFixedContainer is implemented in both ...on host launch.
#0.57.1
- AU v2 editor clipped in Ableton (macOS). Ableton was embedding the AU v2 view at a frame smaller than the editor's natural size, which left the editor's top edge (the title header and knob row) clipped off the visible plug-in window. The AU v2 shim now pins the container to the editor's
gui_get_sizeregardless of what the host requests, so the full editor is visible in every host. - AU v3 resize wired up on macOS. Logic Pro's host-driven resize now propagates to the editor (was a no-op before). The editor's first layout pass stays at natural size so opening the plug-in doesn't snap to Logic's full pane; subsequent layouts (real user resize) reflow the editor.
- Host crash on quit (REAPER, Cubase). A Rust panic in the editor's teardown chain (wgpu surface drop,
NSViewclose) previously propagated across the AU FFI boundary as an unhandled Obj-C exception and aborted the host on quit. The AU v2destroy/gui_closecallbacks now swallow panics at the boundary - host stays alive through quit. - Cubase crash with built-in editor. A pixmap allocation failure in the built-in
GridLayouteditor was panicking insiderender, which surfaced as a host crash in Cubase. The allocation path now logs and skips the frame instead; the next frame retries.
#0.57.0
- Resizable editors. Every GUI backend opts in with
.resizable(true).min_size((a, b)).max_size((a, b))on the editor / layout, and the CLAP, VST3, AU, and LV2 wrappers round-trip host requests to it. Logical points everywhere except the built-in grid, which takes(cols, rows)cell counts (it snaps to whole cells anyway). Vizia plugin-formset_sizeis a known gap. - New
Editortrait methods.size_increment() -> Option<(u32, u32)>(interactive-resize granularity in logical points; the X11 standalone maps it ontoPResizeIncso window-edge drags snap),aspect_ratio() -> Option<(u32, u32)>((num, denom); CLAP / VST3 / AU v3 / standalone honour it, VST2 / LV2 / AAX silently ignore), andprefers_pow2() -> bool(renderer wants power-of-two surface sizes). All have defaults so existing plugins compile unchanged. Matching builder methods on every editor type; iOS variants are no-op stubs. truce-vizia: XY pad label moved to bottom. Matches the other backends.truce-icedwidgets:meterandxy_padaccept.width(Length),.height(Length),.fill(). Lets the widget stretch with the parent column / row instead of using a fixed pixel size.truce-viziawidgets:level_meterheight andxy_padw/hnow takeimpl Into<Units>. PassPixels(n)for a fixed size,Stretch(1.0)to fill the parent layout. Breaking for callers passingf32directly: wrap inPixels(_).truce-slintwidgets:MeterandXYPadusepreferred-*+min-*/max-*sizing. A parentVerticalLayout/HorizontalLayoutcan stretch them; the floor sizes keep the widgets usable at the editor'smin_size.baseview-truce 0.1.1-truce.8. Adds the macOSsetFrameSize:Resizedevent + OpenGL drawable resize that host-driven editor resize depends on. To upstream to baseview.- Windows: wgpu editors pin the DX12 shader compiler to FXC. Fixes a blank editor (egui, built-in
GpuEditor) and a host crash (Slint) in Pro Tools. wgpu 29 defaults DX12 to a dynamically-loaded DXC (dxcompiler.dll); when the host process has already loaded its own incompatibledxcompiler.dll(Pro Tools does),DxcCreateInstancereturnsE_NOINTERFACEand the entire DX12 backend fails to initialise - the wgpu instance ends up with zero adapters, so surface creation fails (white editor, or a panic on Slint's.expect). Thetruce-gpu,truce-gui,truce-egui, andtruce-slinteditor instances now requestDx12Compiler::Fxc(d3dcompiler_47.dll, always present on Windows, never conflicts). iced (wgpu 0.19, FXC by default) and vizia (OpenGL) were never affected. The Windows editor instances also narrow toBackends::DX12(the only backend feature compiled in on Windows). Offscreen/headless screenshot paths are unchanged. - AAX: fix Pro Tools scan hang on Windows. AAX registration is now lazy - triggered by the first
truce_aax_get_descriptorquery - instead of running from a.CRT$XCUlibrary-load static initializer. On Windows the AAX shimLoadLibraryAs the cdylib from insideGetEffectDescriptionsduring scanning, so the old initializer ran plugin construction (viahas_editor_static's defaultcreate().editor(), which can touch user32 in editor setup) under the Windows loader lock and hung the scan. Now mirrors how VST3/CLAP register lazily on their first host entry point. No API change.
#0.56.0
truce-vizia:param_knoblayout matches the other backends. Reordered the cell as knob → value → name; previously the name label sat between the knob and the value, which inverted the cross-backend convention.selector/param_selectorwidgets deprecated. Marked#[deprecated(since = "0.56.0")]acrosstruce-gui-types,truce-egui,truce-iced, andtruce-vizia; the slintSelectorelement gets the same notice in its widget library header (slint markup has no attribute equivalent). Use the correspondingdropdown/param_dropdown/Dropdowninstead.
#0.55.0
- AU v3: sample-accurate parameter automation. The Swift shim now decodes
AURenderEvent.parameter/.parameterRampinto per-sampleParamChangeevents with the proper within-block offset, and the chunker subdivides the audio block at each automation point. AU v2 stays block-rate (itsAudioUnitSetParameterAPI carries no sample-offset). - LV2: sample-accurate parameter automation. The TTL now advertises each parameter as a
patch:writablelv2:Parameter, and the wrapper decodes host-emittedpatch:SetObjects from the input atom sequence into per-sampleParamChangeevents (the atom event'stime_framesbecomes the within-blocksample_offset). The legacylv2:ControlPortpath stays so older LV2 hosts still update params at block rate.
#0.54.0
- New
vst3_subcategorytruce.tomlkey. Emits the secondary VST3 "Plugin Type Categories" token (Fx|Delay,Fx|Reverb,Instrument|Synth, …). Without it, Cubase buckets the plugin under "Other". Optional; opt-in per plugin.
#0.53.0
- New
FloatParam::read_into(&mut [f32])smoother API. Slice-based block read; advances the smoother by exactlyout.len(). Same one-atomic-pair amortization asread_block, runtime length. - Deprecated
FloatParam::read_block::<N>(). Always advanced byNregardless of consumed samples, silently stepping the smoothed value at the next block boundary whenever the host's block size wasn't a multiple ofN. Audible as clicks on delay / LFO-rate / any timing-sensitive smoothed param.read_into(&mut scratch[..n])is the same code shape with the hazard removed. - New
truce_simd::math64module. f64 mirror oftruce_simd::math(db_to_linear_block,linear_to_db_block,exp2_block,log2_block,tanh_block).wide::f64x4lanes. eqexample uses SIMD math64. Output stage dB → linear runs throughmath64::db_to_linear_blockinstead of scalarf64::powfper sample.- Examples migrated (
block-gain,block-saturate,eq).
#0.52.0
- New GUI backend:
truce-vizia. Param-bound widgets, headless screenshot. Desktop only (no iOS, no Windows ARM64). - New examples:
truce-example-gain-vizia,truce-example-gui-zoo-vizia. - Sample-accurate parameter automation. Param changes apply at their
sample_offsetinstead of the start of the block; smoothers start ramping at the event sample. On by default. Tune via[automation] min_subblock_samplesintruce.tomlor opt out per-param with#[param(chunk = false)]. truce-iced:with_font(bytes)matches egui / vizia. Family name is now read from the TTF (waswith_font(family, bytes)).cargo truce install/package: dedupes duplicate archive members during macOS bundle link. Fixes theclang -bundleduplicate-symbol failure plugins withskia-bindings(vizia) could hit.
#0.49.23
keyboard-types0.6 -> 0.7 workspace-wide.
#0.49.22
- New example:
truce-example-dasp-bitcrusher. Showcasesdasp_sample::Samplebit-depth round-tripping for 8-bit / 16-bit quantization, with a sample-and-hold downsampler. truce-example-gui-zoo-iced: added a Dropdown section exercising theparam_dropdownalias added in 0.49.21.
#0.49.21
truce-egui: int sliders now snap and show plain integers.param_slideradapts toParamRange::Discrete(plainmin..=max, integer step, integer label) instead of the normalized [0, 1] display.truce-egui: multi-channel meters render past 2 bars. The widget allocated a fixed 16 px width regardless of channel count, so bars beyond ~3 were clipped. Width now grows with channel count.truce-egui: toggle height matches the selector so a row mixing them bottom-anchors labels on the same baseline.truce-iced:param_dropdownalias added forparam_selector, matching the egui / CPU APIs.truce-slint: newDropdownwidget (popup-style, wraps the std-widgetsComboBox). Available viaimport { Dropdown } from "@truce".truce-slint: pinned std-widgets style tofluenton every host OS. The default picked Cupertino on macOS, whoseComboBoxrendered its chevron region with a persistent accent-blue square.truce-slint:Toggleheight bumped 40 → 50 to align labels withSelector/Dropdownin mixed rows.- New examples:
truce-example-gui-zoo-egui/-iced/-slint. Each mirrors the CPUgui-zoo's param set so every renderer's widget surface is exercised against the same shape.
#0.49.20
- GUI: discrete params now snap during drag and wheel.
IntParam/EnumParamslider, knob, and XY-pad drags previously emitted continuous normalized values; storage snapped on writeback but the in-flight edit left UI and audio reads briefly out of phase.ParamSnapshot::snap_normalizednow snaps at emit time. - GUI: dropdown A→B switch now repaints on the CPU renderer. The editor's repaint gate diffs
dropdown_is_open()which staystrueacross a close-then-open inside one click;dropdown_closeandopen_dropdownnow flag the dirty bit explicitly. - Layout: labeled sections now start strictly below the previous section's tallest widget. Section breaks advanced
cursor_rowby 1, which packed the next section alongside a tall (rows = N) widget from the prior section. Now advances pastmax_occupied_row. - New
degunit.#[param(unit = "deg")](or"°") prints e.g.180.0°.ParamUnit::Degreesslots into the existing variant set. - New example:
truce-example-gui-zoo. Passthrough plugin that exercises every built-in widget kind across mixed spans and positions, everyParamUnitvariant, and the discrete-snap path. Lives inexamples/and is wired into desktop + iOS screenshot CI.
#0.49.19
#[param(default = std::f64::consts::*)]no longer tripsclippy::approx_constantin the macro expansion. The 0.49.18 parser resolved the path to itsf64value butquote!re-emitted it as a literal. The derive now embeds the original path tokens verbatim while keeping the resolvedf64for the compile-time range / shape checks.
#0.49.18
#[param(default = ...)]now acceptsstd::f64::consts::*. Alsocore::f64::consts::*and baref64::consts::*. Lets plugins writedefault = std::f64::consts::SQRT_2instead of a literal.
#0.49.17
truce-gui: fixed dropdown menu dirty-tracking bugs. Scroll wheel, touch-drag scroll, and per-option hover highlights mutated popup state without flagging the repaint gate, so on the CPU renderer they only became visible when an unrelated event tripped a repaint. (GPU renderer was unaffected: it re-renders every frame.)
#0.49.16
- macOS packaging: CLAP/VST3/VST2 bundles are now ~half the size. The bundle-bin link path inherited
-all_loadwithout a matching-dead_strip, so every staticlib object survived; AU/AAX cdylib links got-dead_stripfor free via rustc. Added-dead_stripto theclang -bundlestep.
#0.49.15
truce-gui: fixed a dropdown popup positioning bug. The popup no longer mis-anchors relative to its trigger widget.
#0.49.14
- Standalone: added MIDI device + channel selection via a unified Settings menu on macOS/Windows, or
--midi-input/--midi-channel(omnior1-16) on Linux.
#0.49.13
- VST2/AAX: extended the 0.49.9
set_statefix. The same path that dropped GUI-edited custom state in CLAP/VST3/AU was present in VST2 and AAX; they now route editor bytes toload_statelike the others. (LV2 is unaffected: its UI is out-of-process and never touchesset_statedirectly.)
#0.49.12
truce-slint: fixed a panic on HiDPI displays after a resize event. The render buffer kept the pre-resize physical extents while slint's window adopted the new ones, so the next frame tripped slint's buffer-too-small check.
#0.49.11
- Standalone: fixed the macOS device menu — the Input/Output Device submenus were grayed out and unopenable.
- Standalone: added input/output channel selection (mono channel or stereo pair) via the macOS/Windows menus or
--input-channels/--output-channels(the CLI is the picker on Linux).
#0.49.10
- Fixed a use-after-free crash when a host closes the editor without calling
close()(seen in Ableton with several plugins loaded). The macOS frame timer kept firing against the freed editor; every editor backend now cancels its window on drop. - Fixed meters not updating in the built-in (CPU) editor. The repaint gate only watched parameter changes, so a moving meter stayed frozen until an unrelated repaint (e.g. dragging a knob) fired; the editor now repaints when a meter value moves.
#0.49.9
- Fixed
#[derive(State)]custom state not persisting when edited from the GUI. The CLAP, VST3, and AU editorset_statepaths silently dropped GUI edits; they now reachload_statecorrectly.
#0.49.6
truce-standalone: theguifeature no longer pullstruce-gpu. Standalone builds compile the GPU backend only when the plugin opts in viatruce-gui = { …, features = ["gpu"] }.
#0.49.4
- Fixed a macOS stack-overflow crash in the built-in (CPU) GUI / standalone path. Moving the cursor over a freshly-opened editor window aborted the process. Fixed in
baseview-truce 0.1.1-truce.4.
#0.49.2
- Housekeeping: minor README updates and safety fixes.
- Standalone: Disable window resizing on Windows for now (mirrors other OSes).
- Standalone: Wire
windows_iconthrough to the window'sWM_SETICONso the title bar and taskbar show the app icon.
#0.49.0
- Breaking (
PluginLogic): the GUI surface collapses to a singleeditor()method. The oldlayout(),custom_editor(),render(),uses_custom_render(), andhit_test()methods are gone — every plugin now returns its editor from one place:rust fn editor(&self) -> Box<dyn Editor> { /* ... */ }editor()is required (there is no headless auto-fallback). Migration steps below. - Renderer split: the built-in GUI now defaults to CPU (tiny-skia); wgpu is opt-in. The CPU rasterizer moved into a new
truce-cpucrate, a peer oftruce-gpu. A layout-only plugin no longer compiles wgpu and its per-OS graphics backends unless it asks for them — smaller binaries and faster builds out of the box. Opt into GPU rendering withtruce-gui = { version = "0.49", features = ["gpu"] }; thegpupath doesn't pull the CPU dependency tree, and vice-versa. Plaintruce-gui = "0.49"keeps the CPU default with no change. - The
truceumbrella no longer pullstruce-guitransitively. Plugins using the built-in renderer declaretruce-guiexplicitly — newly scaffolded projects (cargo truce new) already do.
Both paths end with .into_editor(...) — a fluent terminal that
replaces the old Box::new(...) wrapper and keeps every editor()
impl looking the same.
#Migrating layout()
Move your GridLayout body into editor() and close it with
.into_editor(&self.params):
- fn layout(&self) -> GridLayout {
- GridLayout::build(vec![widgets(vec![
- knob(P::Gain, "Gain"),
- knob(P::Pan, "Pan"),
- ])])
- .with_title("GAIN")
- }
+ fn editor(&self) -> Box<dyn Editor> {
+ GridLayout::build(vec![widgets(vec![
+ knob(P::Gain, "Gain"),
+ knob(P::Pan, "Pan"),
+ ])])
+ .with_title("GAIN")
+ .into_editor(&self.params)
+ }
.into_editor(¶ms) comes from the truce_gui::IntoLayoutEditor
trait — add use truce_gui::IntoLayoutEditor; to your imports. It
picks the renderer from the truce-gui feature you enabled (cpu
by default, gpu if opted in), so the same editor() body covers
both. If your plugin previously depended only on truce-gui-types,
add truce-gui:
[dependencies]
truce-gui-types = { version = "0.49" }
+truce-gui = { version = "0.49" } # add features = ["gpu"] for wgpu
(truce_gui::default_editor(params, layout) is the equivalent
free function if you'd rather not import the trait.)
#Migrating custom_editor() (egui / iced / slint / hand-rolled)
Rename custom_editor to editor and return the Box<dyn Editor>
directly. The new method is non-optional, so drop the Some(...)
wrapper and finish the builder chain with .into_editor():
- fn custom_editor(&self) -> Option<Box<dyn Editor>> {
- Some(Box::new(
- EguiEditor::new(self.params.clone(), (W, H), gain_ui)
- .with_visuals(truce_egui::theme::dark()),
- ))
- }
+ fn editor(&self) -> Box<dyn Editor> {
+ EguiEditor::new(self.params.clone(), (W, H), gain_ui)
+ .with_visuals(truce_egui::theme::dark())
+ .into_editor()
+ }
The zero-arg .into_editor() is the blanket truce_core::IntoEditor
helper — it's in truce::prelude, so no extra import. The egui /
iced / slint editor constructors are otherwise unchanged. These
backends supply their own renderer, so they don't need truce-gui's
cpu / gpu features — a plugin using one of them can drop the
truce-gui dependency entirely.
#0.48.11
cargo-truce: Fix various install-path bugs on Windows. VST3 now installs to system scope by default — Ableton doesn't scan the per-user VST3 directory.truce-gui&truce-gpu: Minor built-in GUI rendering improvements.
#0.48.10
- Minor housekeeping.
cargo-truce: Replace non-Latin status glyphs incargo truce packageoutput with the same[ OK ]/[FAIL]ASCII tags used bycargo truce doctor. The Unicode✓/✗characters broke rendering in Windows 10 WSL terminals.
#0.48.9
- Examples: Fix blocksize bug in EQ example.
- AAX: Set
UNSAFE_OBJC2_ALLOW_CLASS_OVERRIDEwhen building AAX to prevent same-class-name crashes when multiple AAX plugins load into the same host process (e.g. Pro Tools loading two truce plugins). Details: https://github.com/rust-windowing/raw-window-metal/issues/29 cargo-truce: Make--iospackaging behavior and naming scheme consistent with the other formats / OSes — iterates every plugin in the workspace (no longer errors when more than one is declared) and writes the artifact totarget/dist/<crate>-<version>-ios.ipanext to the macOS.pkg/ Windows.exe/ Linux.tar.gz.
#0.48.8
- truce now fully published to crates.io.
cargo truce newscaffolds with atruce = { version = "0.48" }registry pin by default; the historicalgit = "...", tag = "v0.48.7"form stays available viacargo truce new --githubfor scaffolding against an unreleased checkout. - New
truce-aax-bridgecrate carries the C ABI header socargo-trucedoesn't transitively pull the fulltruce-aaxruntime stack.
#Migrating an existing plugin to the registry pin
In your plugin's Cargo.toml, replace each
truce-* = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7"[, ...] }
line with truce-* = { version = "0.48"[, ...] }. Concretely:
[dependencies]
-truce = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7" }
-truce-gui = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7" }
-truce-clap = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7", optional = true }
-truce-vst3 = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7", optional = true }
-truce-standalone = { git = "https://github.com/truce-audio/truce", tag = "v0.48.7", features = ["gui"], optional = true }
+truce = { version = "0.48" }
+truce-gui = { version = "0.48" }
+truce-clap = { version = "0.48", optional = true }
+truce-vst3 = { version = "0.48", optional = true }
+truce-standalone = { version = "0.48", features = ["gui"], optional = true }
Preserve any optional = true / features = [...] keys that
were already on the line. Workspace-mode plugin Cargo.tomls
(truce-* = { workspace = true }) need no change — only the
workspace root [workspace.dependencies] block flips. Then:
cargo update -p truce
cargo build
Cargo's caret resolver expands "0.48" to >=0.48.0, <0.49.0,
so you'll pick up any future 0.48.x patch release without
re-editing.
#0.48.4
- Standalone: Fix default device selection on Linux (broke after cpal 0.17 update in 0.48.x).
#0.48.3
cargo-truce: Improve iOS screenshot reliability, clear any stale_truce_editor_frame.jsonbefore launch, extend the first-paint poll timeout for cold CI runners, and hard-fail with a diagnostic when the editor never renders (instead of silently proceeding to a misleading "screenshot size mismatch").
#0.48.2
truce-egui: Migrate to egui 0.34 (bumpswgputo 29 transitively).- Breaking (
truce-egui):EditorUi::uiand theEguiEditor::newclosure now receive&mut egui::Uiinstead of&egui::Contextegui 0.34 deprecatesContext::runplus the per-panelshow(ctx, …)entry points in favor ofContext::run_uiandshow_inside(ui, …). - Bump MSRV to 1.92.
- For egui-0.33 parity with
nih-plug, pin truce to0.47.0.
(0.48.0 and 0.48.1 were yanked, install 0.48.2.)
#0.47.0
truce-egui: Migrate to egui 0.33 (bumpswgputo 27 transitively).truce-gpu: Declare wgpu graphics backends per-OS (fixes Linux).
#0.46.0
truce-iced: Migrate to iced 0.14.truce-egui: Addparam_dropdownwidget (stock click-to-open dropdown wrappingegui::ComboBox).truce-egui:param_knobsnaps to discrete steps on enum / discrete params.- Examples: Tremolo refreshed (compact transport line, fractional rate labels, dropdown polish).
#0.45.4
- LV2: Fix install path on Windows.
- LV2: Fix param defaults (mirror other formats).
- Standalone: Disable window resizing on Linux (mirror other OSes).
- Examples: Fix
fourcccollision between two example plugins.
#0.45.3
cargo-truce: Fix plugin-name to path sanitization mismatch betweenbuild/installandpackage(display names with filesystem-reserved characters likeTruce Dry/Wetproduced a ~15 KB empty installer becauseproductbuild's distribution.xml referenced the raw name while the on-disk.pkgfiles used the sanitized name).
#0.45.2
- AAX: Fix ABI mismatch bug (broken since 0.43.0).
- AAX /
cargo-truce: Add Pro Toolspluginrunnertocargo truce validate(used if present). - LV2: Fix editor positioning quirks (some quirks remain but no showstoppers).
cargo-truce: Update help with--target-cpu.cargo-truce: Thread--target-cpuargs throughinstall,package, andruncommands (with sane defaults).cargo-truce:statusno longer runs auval (too slow for the purpose); usevalidatefor that.- Stylistic sweep.
#0.45.0
- New CI gate exercises every prior release's example crates against the current
truceHEAD, catching backwards-incompatible changes before they ship. - Initial SIMD block operations. New
truce-simdcrate shipswide-backedscale_block/mul_block/mix_block/mac_block/copy_block/zero_blockplustanh_block/db_to_linear_block/linear_to_db_blockmath helpers, with scalar fallbacks. Six new examples (gain-simd,saturate,drywet,gate,widen,surround-meter) demonstrate the shapes. cargo truce buildnow defaults x86_64 builds to-C target-cpu=x86-64-v3(AVX2 + FMA + BMI2) so the SIMD paths above activate without any per-developer config. New--target-cpu <value>flag acceptsbaseline(rustc default = SSE2-only),v2/v3/v4,nativefor the local-CPU dev-loop, or any literal rustc target-cpu name.- Plugin display names that contain filesystem-reserved characters (e.g.
Truce Dry/Wet) are now sanitized at the path-construction boundary, so the on-disk bundle lands atTruce Dry-Wet.aaxpluginwhile DAWs still display the raw name from the plist.
#0.44.0
- VST3 + CLAP on macOS now link as
MH_BUNDLEinstead ofMH_DYLIB. Fixes load under hosts that take the strictCFBundleLoadExecutablepath (DawDreamer's JUCE-based VST3 host is the one we validated against). Most desktop DAWs have more forgiving loaders and weren't affected, but the strict path is the correct Mach-O shape for a bundle. Built from a Ruststaticlibviaclang -bundle. Breaking change for pre-0.44.0 plugins: the plugin crate's[lib]block needscrate-type = ["cdylib", "staticlib", "rlib"](was["cdylib", "rlib"]).cargo truce install/packagefails loudly with the exact one-line edit if the staticlib is missing. - AU v3: Fix installs broken since 0.42.0.
- AU v2: Fix
PresentPresethandler broken since 0.40, auval passes again across all bundled examples. cargo truce package --formats <list>now works on Linux, matching the existing macOS / Windows behavior. Internally drives the underlyingcargo truce buildinvocation.- CI hardening: every
cargo trucesubcommand (install, validate, package, uninstall, doctor, status, reset-au) now runs on macOS, Linux, and Windows on every PR. New scaffold-and-round-trip workflow exercisescargo truce newagainst single-effect, single-instrument, and mixed-workspace shapes. - Doc sweep across the in-tree comments and rustdoc.
#0.43.0
- SysEx + UMP support (work in progress). Initial plumbing for System Exclusive messages and MIDI 2.0 UMPs
#0.42.1
- Params:
IntParamvalue displays no longer pick up theFloatParam{:.1}/{:.2}formatters, so transpose's semitone knob now reads0 stinstead of0.0 st. Internally,ParamInfogained akind: ParamValueKindfield set by#[derive(Params)]from the field type. - Example tidy: the
Mixknobs on both fundsp reverbs and theDepthknob on tremolo now declareunit = "%", so they render as25%/0%instead of0.25/0.00.
#0.42.0
- iOS support. AU v3 plug-ins now build, install, and run on both the iOS Simulator (
cargo truce install --ios) and tethered devices (cargo truce install --ios-device). Truce ships a Swift container app template with embedded editor, Play button, status label, info sheet, and a hamburger-menu landscape layout. New[[plugin]]knobs intruce.toml:ios_icon_set,ios_orientations,ios_scale_editor_to_fit(defaulttrue),ios_minimum_os_version,ios_app_group,ios_url. Touch input is pinned per-finger so multi-touch doesn't hijack an in-progress drag.mute_preview_outputworks on both standalone and the iOS container for analyzer-style plug-ins. Custom container apps and iced's iOS backend remain unsupported (latter blocked upstream). See the new iOS chapter. - iOS screenshot regression:
cargo truce screenshot --ioscaptures the simulator's actual rendered output (the only path that sees iOS-specific compositing);--checkgates baselines in CI.
#0.41.0
- AAX: Fix knobs sync bug on log-ranged parameters. The C++ shim defaulted to a linear taper for every param's normalize / denormalize, so AAX would round-trip a log-ranged knob through
RenderAudiointo a different plain value than the editor wrote. Wirerange_typethroughTruceAaxParamInfoso the shim picks the matchingAAX_ITaperDelegateper param. ABI bump:TRUCE_AAX_ABI_VERSIONto 2. - Standalone: Drop the "(standalone)" suffix from the window title.
- baseview: bump to the latest revision.
- Workspace: README status updated to stable;
repository/homepagemetadata added to every crate's Cargo.toml for crates.io publishing readiness.
#0.40.2
- Move example READMEs out to truce-website (no code impact).
- Wrap VST3 / VST2 / AU / AAX state-save and state-load callbacks in
catch_unwind. A panic from usersave_state/load_stateused to unwind across theextern "C"FFI boundary back into the host UB on most toolchains, abort on others. The save paths now pre-zero the host's out pointers so a panic mid-write leaves the host seeing an empty blob rather than a stale buffer.
#0.40.1
- AU v3: Wire
macos_iconthrough the bundle template. When set intruce.toml, the per-plugin.icnsis copied into the.app'sContents/Resources/andCFBundleIconFileis added to the outer Info.plist, matching the standalone-host behavior.
#0.40.0
- CLAP: Use the macOS bundle layout (
Contents/MacOS+Info.plist). Fixes load in Bitwig (#51). - CLAP: Wire stubs for
get_resize_hints,set_transient,suggest_title,set_size,adjust_sizeso the custom-editor button appears in Bitwig. - fundsp: New
truce-example-fundsp-reverb-workershowing a background-thread graph rebuild with a lock-free swap into the audio threadprocess()stays allocation-free. - fundsp: Rename the inline-rebuild example to
truce-example-fundsp-reverb-simple(pedagogical, rt-unsafe). - Follow stable Rust toolchain (unpin from 1.90).
- Dead-code removal, stylistic fixes.
#0.39.3
- New example integrating with fundsp; added small helpers.
- AU v2: Fix registration bug causing GUI init issues.
- LV2: Fix URI mismatch between manifest and runtime.
#0.39.2
- Consistent naming scheme for package installers across macOS, Windows, and Linux.
#0.39.1
- Standalone on macOS: Fix audio input after install, was missing the audio-input entitlement.
#0.39.0
- LV2: Add packaging support.
- Enable notarization for example plugins.
- Installer: Harden against permission issues from prior installs.
- Wire
macos_icon,windows_icon,welcome_bmp,welcome_htmlfor example plugins. - Installer / packaging bug fixes.
- Bump MSRV to 1.90.
#0.38.0
- LV2: Fix MIDI effect categorization.
- Improved precision ergonomics using fundsp-style preludes.
- Breaking: renamed
param.smoothed_next()toparam.read()to support consistent float precision use. Upgrade path is a mechanical. - Minor fixes.