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