Skip to main content

truce_core/
editor.rs

1use std::ops::Deref;
2use std::sync::Arc;
3
4use truce_params::Params;
5use truce_params::sample::Float;
6
7use crate::events::TransportInfo;
8
9/// A raw pointer wrapper that is `Send + Sync`.
10///
11/// Used to capture `*const Params` / host-handle pointers in
12/// `PluginContext` closures without the `ptr as usize` hack. The
13/// `Send`/`Sync` impls are unconditional in `T` - they have to be,
14/// because the wrapped types are typically `#[repr(C)]` host structs
15/// that are themselves `!Send + !Sync` by default. Construction is
16/// therefore `unsafe`: each call site must justify why cross-thread
17/// access to the pointed-to data is sound.
18///
19/// Justifications used in-tree:
20/// - **`P: Params`** - fields are atomic; concurrent reads from the
21///   GUI thread while the audio thread writes are safe by design.
22/// - **Format-host handles** (`clap_host`, `AEffect`, etc.) - used
23///   only from a single thread (UI), and the wrapping is purely for
24///   capturing in `Send + Sync` closures stored in `PluginContext`.
25///
26/// The pointed-to data must outlive the `SendPtr`. In the plugin
27/// context, the plugin instance (which owns the params) always
28/// outlives the editor.
29pub struct SendPtr<T>(*const T);
30
31impl<T> SendPtr<T> {
32    /// Wrap a raw pointer.
33    ///
34    /// # Safety
35    /// The caller must ensure that:
36    /// 1. The pointed-to data outlives every clone of this `SendPtr`.
37    /// 2. Cross-thread access to `*ptr` is sound - either because `T`
38    ///    is `Sync`, because access is synchronized externally
39    ///    (atomic fields, Mutex, single-thread-only access pattern),
40    ///    or because the wrapper is only ever read on a thread where
41    ///    `T: Sync` would hold.
42    pub unsafe fn new(ptr: *const T) -> Self {
43        Self(ptr)
44    }
45
46    /// Dereference the pointer.
47    ///
48    /// # Safety
49    /// The pointed-to data must still be alive.
50    #[must_use]
51    pub unsafe fn get(&self) -> &T {
52        unsafe { &*self.0 }
53    }
54
55    /// Get the raw pointer.
56    #[must_use]
57    pub fn as_ptr(&self) -> *const T {
58        self.0
59    }
60}
61
62impl<T> Clone for SendPtr<T> {
63    fn clone(&self) -> Self {
64        *self
65    }
66}
67
68impl<T> Copy for SendPtr<T> {}
69
70// SAFETY: justified at each `unsafe SendPtr::new(...)` call site.
71unsafe impl<T> Send for SendPtr<T> {}
72unsafe impl<T> Sync for SendPtr<T> {}
73
74/// Raw platform window handle for GUI parenting.
75#[derive(Clone, Copy, Debug)]
76pub enum RawWindowHandle {
77    AppKit(*mut std::ffi::c_void), // macOS NSView*
78    UiKit(*mut std::ffi::c_void),  // iOS / iPadOS UIView*
79    Win32(*mut std::ffi::c_void),  // HWND
80    X11(u64),                      // X11 Window ID
81}
82
83/// Plugin GUI editor.
84pub trait Editor: Send {
85    /// Initial window size in logical points.
86    ///
87    /// On a 2x Retina display, `(400, 300)` produces an 800x600 pixel window.
88    /// On a 1x display, it produces a 400x300 pixel window.
89    fn size(&self) -> (u32, u32);
90
91    /// Create the GUI as a child of the host-provided parent window.
92    fn open(&mut self, parent: RawWindowHandle, context: PluginContext);
93
94    /// Destroy the GUI.
95    fn close(&mut self);
96
97    /// Called ~60fps on the host's UI thread for repaint/animation.
98    fn idle(&mut self) {}
99
100    /// Host requests a resize. Return true to accept.
101    fn set_size(&mut self, _width: u32, _height: u32) -> bool {
102        false
103    }
104
105    /// Whether the plugin supports resizing.
106    fn can_resize(&self) -> bool {
107        false
108    }
109
110    /// Whether the editor permits the standalone window to be
111    /// maximized (the WM maximize button / double-click-titlebar
112    /// maximize / macOS zoom-and-fullscreen / Windows maximize box).
113    ///
114    /// Standalone-only: in CLAP / VST3 / AU the host owns the window
115    /// frame, so this is ignored there (same as `size_increment`'s
116    /// WM-snap note). Subordinate to [`Self::can_resize`] - a
117    /// non-resizable editor can never be maximized regardless of this
118    /// value, since the standalone pins min == max, which already
119    /// blocks it.
120    ///
121    /// Defaults to `false`: the standalone host removes the maximize
122    /// affordance from resizable editors, so the window stays within
123    /// the edge-drag bounds the WM already enforces and can't jump past
124    /// the editor's [`Self::max_size`] into an unpainted margin around
125    /// the clamped surface. Override to `true` for editors that render
126    /// correctly at arbitrary size (typically an unbounded `max_size`)
127    /// and want the maximize affordance.
128    fn can_maximize(&self) -> bool {
129        false
130    }
131
132    /// Minimum size the editor can render at, in logical points.
133    /// Defaults to `(1, 1)`. Wrappers consult this for CLAP's
134    /// `gui_get_resize_hints` and VST3's `checkSizeConstraint`.
135    /// Ignored when `can_resize()` returns `false`.
136    fn min_size(&self) -> (u32, u32) {
137        (1, 1)
138    }
139
140    /// Maximum size the editor can render at, in logical points.
141    /// Defaults to `(u32::MAX, u32::MAX)`. Same wrapper consumers
142    /// as `min_size`.
143    fn max_size(&self) -> (u32, u32) {
144        (u32::MAX, u32::MAX)
145    }
146
147    /// Logical-point granularity for interactive resize, or `None`
148    /// for free (pixel-precise) resizing. The standalone X11 host
149    /// maps this onto WM resize increments (`PResizeInc`) so the
150    /// window manager snaps edge-drags to whole cells - the same
151    /// mechanism terminal emulators use to snap to character cells.
152    /// The snap counts from [`Self::min_size`], which is already
153    /// cell-aligned, so every allowed size lands on a boundary.
154    /// Ignored when `can_resize()` returns `false`.
155    fn size_increment(&self) -> Option<(u32, u32)> {
156        None
157    }
158
159    /// Aspect-ratio constraint as `(numerator, denominator)`, or
160    /// `None` for free resizing. CLAP, VST3, AU v3, and standalone
161    /// honour this; VST2 / LV2 / AAX silently ignore. Integer pair
162    /// (not `f64`) avoids the Cubase-9 aspect-rounding quirk JUCE
163    /// special-cases.
164    fn aspect_ratio(&self) -> Option<(u32, u32)> {
165        None
166    }
167
168    /// Hint that the renderer prefers power-of-two surface sizes
169    /// (some GPU-backed editors). Maps onto CLAP's
170    /// `clap_gui_resize_hints.preserve_aspect_ratio` /
171    /// `aspect_ratio_width` siblings; ignored on formats without
172    /// an equivalent.
173    fn prefers_pow2(&self) -> bool {
174        false
175    }
176
177    /// Host notifies the editor of a new content scale factor.
178    ///
179    /// DPI/scale is a host→plugin concept: on VST3 Windows the host
180    /// delivers it via `IPlugViewContentScaleSupport`; on CLAP via
181    /// `clap_plugin_gui::set_scale`; on macOS/Cocoa `AppKit` handles
182    /// Retina backing automatically and hosts typically never call
183    /// this at all. Editors that need to size off-screen buffers in
184    /// physical pixels should react here, not by exposing a pull-style
185    /// `scale_factor()` method that format wrappers were tempted to
186    /// multiply `size()` by (which caused double-scaling on macOS VST3).
187    fn set_scale_factor(&mut self, _factor: f64) {}
188
189    /// Plugin state was restored (preset recall, undo, session load).
190    ///
191    /// Called after `load_state()` while the editor is open. Re-read any
192    /// cached state from the plugin. Parameter values are already updated
193    /// and will be picked up on the next render - this is only needed for
194    /// custom state stored outside the parameter system.
195    fn state_changed(&mut self) {}
196
197    /// Render a headless screenshot of the editor at its natural size.
198    ///
199    /// `params` is a type-erased default-state instance the caller
200    /// constructs from the plugin's `Params` type. Backends use it to
201    /// build a synthetic `PluginContext` / render context so the
202    /// screenshot reflects parameter defaults without needing a live
203    /// host.
204    ///
205    /// Returns `(rgba_pixels, physical_width, physical_height)` - RGBA8
206    /// row-major, ready to feed into `truce_test::assert_screenshot_pixels`.
207    /// Default impl returns `None`; backends that support headless
208    /// capture (built-in widgets, egui, iced, slint) override.
209    ///
210    /// Used by `truce_test::assert_screenshot::<Plugin>(...)` for one-line
211    /// snapshot regression tests. Editors backed by frameworks that
212    /// don't expose a headless render path (e.g. raw-window-handle
213    /// users wiring their own Metal/OpenGL) keep the default `None`.
214    fn screenshot(&mut self, params: Arc<dyn truce_params::Params>) -> Option<(Vec<u8>, u32, u32)> {
215        let _ = params;
216        None
217    }
218}
219
220/// Fluent terminal for `editor()` impls: box any concrete editor into
221/// the `Box<dyn Editor>` the trait returns, dropping the `Box::new(…)`
222/// wrapper.
223///
224/// ```ignore
225/// fn editor(&self) -> Box<dyn Editor> {
226///     EguiEditor::new(self.params.clone(), (W, H), ui)
227///         .with_visuals(theme)
228///         .into_editor()
229/// }
230/// ```
231///
232/// Implemented for every [`Editor`] via a blanket impl and re-exported
233/// from every `truce::prelude*`, so it's in scope without an extra
234/// import - egui / iced / slint / hand-rolled editors all use it.
235/// Layout-only plugins use `truce_gui::IntoLayoutEditor` instead (its
236/// `into_editor` takes `&Arc<Params>` and picks the built-in renderer).
237pub trait IntoEditor {
238    /// Box this editor into a `Box<dyn Editor>`.
239    fn into_editor(self) -> Box<dyn Editor>;
240}
241
242impl<E: Editor + 'static> IntoEditor for E {
243    fn into_editor(self) -> Box<dyn Editor> {
244        Box::new(self)
245    }
246}
247
248/// Bridge between the editor and the host / plugin. Format wrappers
249/// (CLAP / VST3 / VST2 / AU / AAX / LV2) implement this trait - or
250/// build a [`ClosureBridge`] from per-method closures - and pass an
251/// `Arc<dyn EditorBridge>` to the editor through [`PluginContext`].
252///
253/// Editors call into the bridge for everything they can't do
254/// directly: starting / ending an automation gesture, reading or
255/// writing parameters in normalized or plain form, requesting a
256/// window resize, exchanging custom state, sampling the host's
257/// transport. Implementations carry whatever per-format pointers
258/// the work needs (`clap_host*`, `AEffect*`, an `Arc<P>` for the
259/// param store, etc.).
260///
261/// `Send + Sync` is required so editors can clone the
262/// `Arc<dyn EditorBridge>` and hand it to UI worker threads or
263/// background animation timers without forcing every implementor to
264/// rederive thread-safety bounds.
265pub trait EditorBridge: Send + Sync {
266    /// Start an automation gesture for `id`. Hosts that show "touched"
267    /// state in the automation lane use this to render the
268    /// in-progress edit.
269    fn begin_edit(&self, id: u32);
270    /// Set parameter `id` to `normalized` (clamped to `0.0..=1.0`).
271    /// Format wrappers usually plumb this through both the plugin's
272    /// own param store and the host's automation channel.
273    fn set_param(&self, id: u32, normalized: f64);
274    /// End the automation gesture started by [`Self::begin_edit`].
275    fn end_edit(&self, id: u32);
276    /// Ask the host to resize the editor window to `(w, h)` logical
277    /// points. Returns `true` if the host accepted the request.
278    fn request_resize(&self, w: u32, h: u32) -> bool;
279    /// Read the parameter's current normalized value from the plugin
280    /// (host→GUI sync path).
281    fn get_param(&self, id: u32) -> f64;
282    /// Read the parameter's current plain (denormalized) value.
283    fn get_param_plain(&self, id: u32) -> f64;
284    /// Format the parameter's current value as a display string,
285    /// applying the plugin's `format_value` impl + unit suffix.
286    fn format_param(&self, id: u32) -> String;
287    /// Format into a caller-provided buffer instead of returning a
288    /// fresh `String`. The default impl calls
289    /// [`Self::format_param`] and copies, so the *bridge-internal*
290    /// allocation still happens; the win for the caller is that the
291    /// `out` buffer's capacity is reused across calls (e.g.
292    /// `ParamCache::sync` polls one label per changed param per
293    /// frame and would otherwise drop+reallocate the cached
294    /// `String` slot every time). Bridges that produce the formatted
295    /// string from raw value bytes can override to drop the
296    /// internal allocation too.
297    fn format_param_into(&self, id: u32, out: &mut String) {
298        out.clear();
299        out.push_str(&self.format_param(id));
300    }
301    /// Read a meter value (0.0–1.0) by meter ID. Returns 0.0 if the
302    /// meter ID isn't registered.
303    fn get_meter(&self, id: u32) -> f32;
304    /// Read the plugin's custom state (everything outside the
305    /// parameter system). Returns an empty `Vec` when the plugin has
306    /// no custom state.
307    fn get_state(&self) -> Vec<u8>;
308    /// Write custom state back to the plugin (calls `load_state()`).
309    fn set_state(&self, data: Vec<u8>);
310    /// Most-recently-reported host transport state, or `None` if the
311    /// host does not expose transport to plugin editors or the plugin
312    /// has not yet received a process block.
313    ///
314    /// Format wrappers populate a shared [`TransportSlot`](crate::TransportSlot)
315    /// from their process callback; this method reads from it.
316    fn transport(&self) -> Option<TransportInfo>;
317}
318
319/// Adapter that implements [`EditorBridge`] over per-method closures.
320///
321/// Format wrappers that prefer to compose state inline via closures
322/// construct one of these and wrap it in an `Arc<dyn EditorBridge>`.
323/// Wrappers that already have a typed host-pointer struct should
324/// `impl EditorBridge` for that struct directly and skip this
325/// adapter; one less layer of indirection per call.
326pub struct ClosureBridge {
327    pub begin_edit: Box<dyn Fn(u32) + Send + Sync>,
328    pub set_param: Box<dyn Fn(u32, f64) + Send + Sync>,
329    pub end_edit: Box<dyn Fn(u32) + Send + Sync>,
330    pub request_resize: Box<dyn Fn(u32, u32) -> bool + Send + Sync>,
331    pub get_param: Box<dyn Fn(u32) -> f64 + Send + Sync>,
332    pub get_param_plain: Box<dyn Fn(u32) -> f64 + Send + Sync>,
333    pub format_param: Box<dyn Fn(u32) -> String + Send + Sync>,
334    pub get_meter: Box<dyn Fn(u32) -> f32 + Send + Sync>,
335    pub get_state: Box<dyn Fn() -> Vec<u8> + Send + Sync>,
336    pub set_state: Box<dyn Fn(Vec<u8>) + Send + Sync>,
337    pub transport: Box<dyn Fn() -> Option<TransportInfo> + Send + Sync>,
338}
339
340impl EditorBridge for ClosureBridge {
341    fn begin_edit(&self, id: u32) {
342        (self.begin_edit)(id);
343    }
344    fn set_param(&self, id: u32, normalized: f64) {
345        (self.set_param)(id, normalized);
346    }
347    fn end_edit(&self, id: u32) {
348        (self.end_edit)(id);
349    }
350    fn request_resize(&self, w: u32, h: u32) -> bool {
351        (self.request_resize)(w, h)
352    }
353    fn get_param(&self, id: u32) -> f64 {
354        (self.get_param)(id)
355    }
356    fn get_param_plain(&self, id: u32) -> f64 {
357        (self.get_param_plain)(id)
358    }
359    fn format_param(&self, id: u32) -> String {
360        (self.format_param)(id)
361    }
362    fn get_meter(&self, id: u32) -> f32 {
363        (self.get_meter)(id)
364    }
365    fn get_state(&self) -> Vec<u8> {
366        (self.get_state)()
367    }
368    fn set_state(&self, data: Vec<u8>) {
369        (self.set_state)(data);
370    }
371    fn transport(&self) -> Option<TransportInfo> {
372        (self.transport)()
373    }
374}
375
376/// Context passed to [`Editor::open`]. Carries:
377///
378/// - An `Arc<dyn EditorBridge>` - the host-plugin protocol surface
379///   (begin/set/end edit, `request_resize`, `get_state`, transport, …).
380/// - An `Arc<P>` typed parameter store - plugin authors `Deref` to
381///   `&P` and read fields directly: `state.gain.read()`.
382///
383/// The default `P = dyn Params` keeps the trait-object boundary
384/// (`Editor::open(ctx: PluginContext)`) one-typed; editor crates
385/// that want typed access (truce-egui, truce-slint, truce-iced) carry
386/// their own `<P>` and reconstitute `PluginContext<P>` internally
387/// via [`PluginContext::with_params`] using the `Arc<P>` they stored
388/// at editor construction.
389///
390/// `Clone` is two refcount bumps (bridge + params). Editors that need
391/// to hand the context to UI worker threads or animation timers clone
392/// freely.
393pub struct PluginContext<P: ?Sized = dyn Params> {
394    bridge: Arc<dyn EditorBridge>,
395    params: Arc<P>,
396}
397
398impl<P: ?Sized> Clone for PluginContext<P> {
399    fn clone(&self) -> Self {
400        Self {
401            bridge: Arc::clone(&self.bridge),
402            params: Arc::clone(&self.params),
403        }
404    }
405}
406
407impl<P: ?Sized> PluginContext<P> {
408    /// Build a typed context from any [`EditorBridge`] implementor and
409    /// the plugin's typed param store.
410    pub fn new(bridge: Arc<dyn EditorBridge>, params: Arc<P>) -> Self {
411        Self { bridge, params }
412    }
413
414    /// Access the underlying bridge handle. Editors that want to clone
415    /// the bridge into a worker thread without cloning the surrounding
416    /// `PluginContext` use this.
417    #[must_use]
418    pub fn bridge(&self) -> &Arc<dyn EditorBridge> {
419        &self.bridge
420    }
421
422    /// Access the typed param store as an `Arc`. Use this when you
423    /// need to capture the params in a `'static` closure (e.g. an iced
424    /// `Subscription` or a worker thread).
425    #[must_use]
426    pub fn params(&self) -> &Arc<P> {
427        &self.params
428    }
429
430    /// Replace the param-store generic parameter while reusing the
431    /// same bridge. Used by editor crates that receive the dyn-erased
432    /// `PluginContext` from [`Editor::open`] and want the typed
433    /// `PluginContext<P>` for their UI closure.
434    pub fn with_params<Q: ?Sized>(&self, params: Arc<Q>) -> PluginContext<Q> {
435        PluginContext {
436            bridge: Arc::clone(&self.bridge),
437            params,
438        }
439    }
440
441    pub fn begin_edit(&self, id: impl Into<u32>) {
442        self.bridge.begin_edit(id.into());
443    }
444    pub fn set_param(&self, id: impl Into<u32>, normalized: f64) {
445        self.bridge.set_param(id.into(), normalized);
446    }
447    pub fn end_edit(&self, id: impl Into<u32>) {
448        self.bridge.end_edit(id.into());
449    }
450    /// Begin + set + end in one call. Use for click-to-toggle widgets
451    /// and similar single-shot edits where the gesture and the value
452    /// arrive together.
453    pub fn automate(&self, id: impl Into<u32>, normalized: f64) {
454        let id = id.into();
455        self.bridge.begin_edit(id);
456        self.bridge.set_param(id, normalized);
457        self.bridge.end_edit(id);
458    }
459    #[must_use]
460    pub fn request_resize(&self, w: u32, h: u32) -> bool {
461        self.bridge.request_resize(w, h)
462    }
463    pub fn format_param(&self, id: impl Into<u32>) -> String {
464        self.bridge.format_param(id.into())
465    }
466    /// Format into a caller-owned buffer. See
467    /// [`EditorBridge::format_param_into`] for the allocation
468    /// trade-off - the caller's buffer is reused, but bridges that
469    /// don't override the default impl still allocate internally.
470    pub fn format_param_into(&self, id: impl Into<u32>, out: &mut String) {
471        self.bridge.format_param_into(id.into(), out);
472    }
473    pub fn get_meter(&self, id: impl Into<u32>) -> f32 {
474        self.bridge.get_meter(id.into())
475    }
476    #[must_use]
477    pub fn get_state(&self) -> Vec<u8> {
478        self.bridge.get_state()
479    }
480    pub fn set_state(&self, data: Vec<u8>) {
481        self.bridge.set_state(data);
482    }
483    #[must_use]
484    pub fn transport(&self) -> Option<TransportInfo> {
485        self.bridge.transport()
486    }
487}
488
489impl PluginContext<dyn Params> {
490    /// Build a dyn-erased context from a [`ClosureBridge`]. Convenience
491    /// for format wrappers that compose state inline via closures.
492    pub fn from_closures(bridge: ClosureBridge, params: Arc<dyn Params>) -> Self {
493        Self {
494            bridge: Arc::new(bridge),
495            params,
496        }
497    }
498}
499
500impl<P: Params + 'static> PluginContext<P> {
501    /// Drop the typed `<P>` and return the dyn-erased context that
502    /// crosses the `Editor::open` trait-object boundary.
503    #[must_use]
504    pub fn dyn_erase(self) -> PluginContext<dyn Params> {
505        PluginContext {
506            bridge: self.bridge,
507            params: self.params as Arc<dyn Params>,
508        }
509    }
510}
511
512/// Plugin authors read parameter fields directly via `Deref`:
513/// `state.gain.read()`, `state.bypass.value()`. The `state`
514/// here is `&PluginContext<MyParams>` and `Deref::Target = MyParams`.
515impl<P: ?Sized> Deref for PluginContext<P> {
516    type Target = P;
517    fn deref(&self) -> &P {
518        &self.params
519    }
520}
521
522/// Build a [`PluginContext`] backed only by `params`. All write
523/// closures are no-ops; reads delegate to the params `Arc`; the
524/// transport reports the deterministic
525/// [`crate::events::TransportInfo::for_screenshot`] state so
526/// screenshot tests stay reproducible across CI runs.
527///
528/// Used by editor backends inside their `Editor::screenshot()` impl,
529/// and re-exported from `truce-test` for plugin authors that want to
530/// drive snapshot tests directly.
531pub fn for_test_params(params: Arc<dyn Params>) -> PluginContext<dyn Params> {
532    let p_get = Arc::clone(&params);
533    let p_plain = Arc::clone(&params);
534    let p_fmt = Arc::clone(&params);
535    let transport = TransportInfo::for_screenshot();
536    PluginContext::from_closures(
537        ClosureBridge {
538            begin_edit: Box::new(|_| {}),
539            set_param: Box::new(|_, _| {}),
540            end_edit: Box::new(|_| {}),
541            request_resize: Box::new(|_, _| false),
542            get_param: Box::new(move |id| p_get.get_normalized(id).unwrap_or(0.5)),
543            get_param_plain: Box::new(move |id| p_plain.get_plain(id).unwrap_or(0.0)),
544            format_param: Box::new(move |id| {
545                let plain = p_fmt.get_plain(id).unwrap_or(0.0);
546                p_fmt
547                    .format_value(id, plain)
548                    .unwrap_or_else(|| format!("{plain:.2}"))
549            }),
550            get_meter: Box::new(|_| 0.0),
551            get_state: Box::new(Vec::new),
552            set_state: Box::new(|_| {}),
553            transport: Box::new(move || Some(transport)),
554        },
555        params,
556    )
557}
558
559// ---------------------------------------------------------------------------
560// Precision-routed parameter reads
561//
562// The editor-bridge surface is sample-agnostic (`f64` on the wire, the
563// lossless lowest-common-denominator that round-trips any host
564// automation precision). These two extension traits route the call
565// site to the user's chosen precision - same pattern as
566// `FloatParamReadF32` / `FloatParamReadF64` for the audio-thread
567// param reads. Brought into scope via `pub use ... as _;` in each
568// prelude:
569//   - `prelude` / `prelude32`        → `PluginContextReadF32`
570//   - `prelude64` / `prelude64m`     → `PluginContextReadF64`
571//
572// Single-prelude code dispatches unambiguously. Importing both
573// preludes in the same file collides on `get_param` - the right
574// error if the file hasn't committed to a precision.
575// ---------------------------------------------------------------------------
576
577/// `f32`-precision parameter reads on `PluginContext`. Brought into
578/// scope by `truce::prelude` / `truce::prelude32` / `truce::prelude64m`
579/// (the `f32`-buffer preludes). GUI binding crates (slint, egui,
580/// iced) take `f32` natively, so this is the common case.
581pub trait PluginContextReadF32 {
582    /// Normalized `[0, 1]` value of the parameter, narrowed to `f32`.
583    fn get_param(&self, id: impl Into<u32>) -> f32;
584    /// Plain (denormalized) value of the parameter, narrowed to `f32`.
585    fn get_param_plain(&self, id: impl Into<u32>) -> f32;
586}
587
588/// `f64`-precision parameter reads on `PluginContext`. Brought into
589/// scope by `truce::prelude64`. Same surface as
590/// [`PluginContextReadF32`] but returns the bridge's `f64` value
591/// directly without narrowing.
592pub trait PluginContextReadF64 {
593    /// Normalized `[0, 1]` value of the parameter.
594    fn get_param(&self, id: impl Into<u32>) -> f64;
595    /// Plain (denormalized) value of the parameter.
596    fn get_param_plain(&self, id: impl Into<u32>) -> f64;
597}
598
599impl<P: ?Sized> PluginContextReadF32 for PluginContext<P> {
600    fn get_param(&self, id: impl Into<u32>) -> f32 {
601        self.bridge.get_param(id.into()).to_f32()
602    }
603    fn get_param_plain(&self, id: impl Into<u32>) -> f32 {
604        self.bridge.get_param_plain(id.into()).to_f32()
605    }
606}
607
608impl<P: ?Sized> PluginContextReadF64 for PluginContext<P> {
609    fn get_param(&self, id: impl Into<u32>) -> f64 {
610        self.bridge.get_param(id.into())
611    }
612    fn get_param_plain(&self, id: impl Into<u32>) -> f64 {
613        self.bridge.get_param_plain(id.into())
614    }
615}