clap_clap/ext/
params.rs

1use std::{
2    ffi::CStr,
3    fmt::{Display, Formatter},
4};
5
6use crate::{
7    events::{InputEvents, OutputEvents},
8    ext,
9    ffi::{
10        CLAP_PARAM_CLEAR_ALL, CLAP_PARAM_CLEAR_AUTOMATIONS, CLAP_PARAM_CLEAR_MODULATIONS,
11        CLAP_PARAM_IS_AUTOMATABLE, CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL,
12        CLAP_PARAM_IS_AUTOMATABLE_PER_KEY, CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID,
13        CLAP_PARAM_IS_AUTOMATABLE_PER_PORT, CLAP_PARAM_IS_BYPASS, CLAP_PARAM_IS_ENUM,
14        CLAP_PARAM_IS_HIDDEN, CLAP_PARAM_IS_MODULATABLE, CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL,
15        CLAP_PARAM_IS_MODULATABLE_PER_KEY, CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID,
16        CLAP_PARAM_IS_MODULATABLE_PER_PORT, CLAP_PARAM_IS_PERIODIC, CLAP_PARAM_IS_READONLY,
17        CLAP_PARAM_IS_STEPPED, CLAP_PARAM_REQUIRES_PROCESS, CLAP_PARAM_RESCAN_ALL,
18        CLAP_PARAM_RESCAN_INFO, CLAP_PARAM_RESCAN_TEXT, CLAP_PARAM_RESCAN_VALUES, clap_host_params,
19    },
20    host::Host,
21    id,
22    id::ClapId,
23    impl_flags_u32,
24    plugin::Plugin,
25};
26
27#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28#[repr(u32)]
29pub enum InfoFlags {
30    /// Is this param stepped? (integer values only)
31    /// if so the double value is converted to integer using a cast (equivalent
32    /// to trunc).
33    Stepped = CLAP_PARAM_IS_STEPPED,
34    /// Useful for periodic parameters like a phase
35    Periodic = CLAP_PARAM_IS_PERIODIC,
36    /// The parameter should not be shown to the user, because it is currently
37    /// not used. It is not necessary to process automation for this
38    /// parameter.
39    Hidden = CLAP_PARAM_IS_HIDDEN,
40    /// The parameter can't be changed by the host.
41    Readonly = CLAP_PARAM_IS_READONLY,
42    /// This parameter is used to merge the plugin and host bypass button.
43    /// It implies that the parameter is stepped.
44    /// min: 0 -> bypass off
45    /// max: 1 -> bypass on
46    Bypass = CLAP_PARAM_IS_BYPASS,
47    /// When set:
48    /// - automation can be recorded
49    /// - automation can be played back
50    ///
51    /// The host can send live user changes for this parameter regardless of
52    /// this flag.
53    ///
54    /// If this parameter affects the internal processing structure of the
55    /// plugin, ie: max delay, fft size, ... and the plugins needs to
56    /// re-allocate its working buffers, then it should call
57    /// host->request_restart(), and perform the change once the plugin is
58    /// re-activated.
59    Automatable = CLAP_PARAM_IS_AUTOMATABLE,
60    /// Does this parameter support per note automations?
61    AutomatablePerNoteId = CLAP_PARAM_IS_AUTOMATABLE_PER_NOTE_ID,
62    /// Does this parameter support per key automations?
63    AutomatablePerKey = CLAP_PARAM_IS_AUTOMATABLE_PER_KEY,
64    /// Does this parameter support per channel automations?
65    AutomatablePerChannel = CLAP_PARAM_IS_AUTOMATABLE_PER_CHANNEL,
66    /// Does this parameter support per port automations?
67    AutomatablePerPort = CLAP_PARAM_IS_AUTOMATABLE_PER_PORT,
68    /// Does this parameter support the modulation signal?
69    Modulatable = CLAP_PARAM_IS_MODULATABLE,
70    /// Does this parameter support per note modulations?
71    ModulatablePerNoteId = CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID,
72    /// Does this parameter support per key modulations?
73    ModulatablePerKey = CLAP_PARAM_IS_MODULATABLE_PER_KEY,
74    /// Does this parameter support per channel modulations?
75    ModulatablePerChannel = CLAP_PARAM_IS_MODULATABLE_PER_CHANNEL,
76    /// Does this parameter support per port modulations?
77    ModulatablePerPort = CLAP_PARAM_IS_MODULATABLE_PER_PORT,
78    /// Any change to this parameter will affect the plugin output and requires
79    /// to be done via process() if the plugin is active.
80    ///
81    /// A simple example would be a DC Offset, changing it will change the
82    /// output signal and must be processed.
83    RequiresProcess = CLAP_PARAM_REQUIRES_PROCESS,
84    /// This parameter represents an enumerated value.
85    /// If you set this flag, then you must set IsStepped too.
86    /// All values from min to max must not have a blank value_to_text().
87    Enum = CLAP_PARAM_IS_ENUM,
88}
89
90impl_flags_u32!(InfoFlags);
91
92/// Describes a parameter.
93#[derive(Debug, Clone, PartialEq)]
94pub struct ParamInfo {
95    /// Stable parameter identifier, it must never change.
96    pub id: ClapId,
97
98    pub flags: u32,
99
100    /// The display name. eg: "Volume". This does not need to be unique. Do not
101    /// include the module text in this. The host should concatenate/format
102    /// the module + name in the case where showing the name alone would be
103    /// too vague.
104    pub name: String,
105
106    // The module path containing the param, eg: "Oscillators/Wavetable 1".
107    // '/' will be used as a separator to show a tree-like structure.
108    pub module: String,
109
110    /// Minimum plain value. Must be finite.
111    pub min_value: f64,
112    /// Maximum plain value. Must be finite.
113    pub max_value: f64,
114    /// Default plain value. Must be in [min, max] range.
115    pub default_value: f64,
116}
117
118impl ParamInfo {
119    /// # Safety
120    ///
121    /// The `values` fields: 'name' and 'module' must be valid, null-terminated
122    /// C strings.
123    pub unsafe fn try_from_unchecked(value: clap_param_info) -> Result<Self, Error> {
124        Ok(Self {
125            id: value.id.try_into().unwrap_or(ClapId::invalid_id()),
126            flags: value.flags,
127            // SAFETY: The safety condition is upheld by the caller.
128            name: unsafe { CStr::from_ptr(value.name.as_ptr()) }
129                .to_str()?
130                .to_owned(),
131            // SAFETY: The safety condition is upheld by the caller.
132            module: unsafe { CStr::from_ptr(value.module.as_ptr()) }
133                .to_str()?
134                .to_owned(),
135            min_value: value.min_value,
136            max_value: value.max_value,
137            default_value: value.default_value,
138        })
139    }
140}
141
142pub trait Params<P: Plugin> {
143    fn count(plugin: &P) -> u32;
144
145    fn get_info(plugin: &P, param_index: u32) -> Option<ParamInfo>;
146
147    fn get_value(plugin: &P, param_id: ClapId) -> Option<f64>;
148
149    /// Fills out_buffer with a null-terminated UTF-8 string that represents the
150    /// parameter at the given 'value' argument. eg: "2.3 kHz". The host
151    /// should always use this to format parameter values before displaying
152    /// it to the user.
153    fn value_to_text(
154        plugin: &P,
155        param_id: ClapId,
156        value: f64,
157        out_buf: &mut [u8],
158    ) -> Result<(), crate::Error>;
159
160    /// Converts the null-terminated UTF-8 param_value_text into a double and
161    /// writes it to out_value. The host can use this to convert user input
162    /// into a parameter value.
163    fn text_to_value(
164        plugin: &P,
165        param_id: ClapId,
166        param_value_text: &str,
167    ) -> Result<f64, crate::Error>;
168
169    /// Flushes a set of parameter changes.
170    /// This method must not be called concurrently to clap_plugin->process().
171    ///
172    /// Note: if the plugin is processing, then the process() call will already
173    /// achieve the parameter update (bidirectional), so a call to flush
174    /// isn't required, also be aware that the plugin may use the sample
175    /// offset in process(), while this information would be lost within
176    /// flush().
177    fn flush_inactive(plugin: &P, in_events: &InputEvents, out_events: &OutputEvents);
178
179    fn flush(audio_thread: &P::AudioThread, in_events: &InputEvents, out_events: &OutputEvents);
180}
181
182impl<P: Plugin> Params<P> for () {
183    fn count(_: &P) -> u32 {
184        0
185    }
186
187    fn get_info(_: &P, _: u32) -> Option<ParamInfo> {
188        None
189    }
190
191    fn get_value(_: &P, _: ClapId) -> Option<f64> {
192        None
193    }
194
195    fn value_to_text(_: &P, _: ClapId, _: f64, _: &mut [u8]) -> Result<(), crate::Error> {
196        Ok(())
197    }
198
199    fn text_to_value(_: &P, _: ClapId, _: &str) -> Result<f64, crate::Error> {
200        Ok(0.0)
201    }
202
203    fn flush_inactive(_: &P, _: &InputEvents, _: &OutputEvents) {}
204
205    fn flush(_: &P::AudioThread, _: &InputEvents, _: &OutputEvents) {}
206}
207
208pub(crate) use ffi::PluginParams;
209
210use crate::ffi::clap_param_info;
211
212mod ffi {
213    use std::{
214        ffi::{CStr, c_char},
215        marker::PhantomData,
216        ptr::{copy_nonoverlapping, slice_from_raw_parts_mut},
217    };
218
219    use crate::{
220        events::{InputEvents, OutputEvents},
221        ext::params::{Error, Params},
222        ffi::{
223            clap_id, clap_input_events, clap_output_events, clap_param_info, clap_plugin,
224            clap_plugin_params,
225        },
226        plugin::{ClapPlugin, Plugin},
227    };
228
229    /// # SAFETY:
230    ///
231    /// `N` must be larger than 0. `src` and `dst` buffers must be
232    /// non-overlapping.
233    ///
234    /// A trailing null byte will be added.  At most `N-1` bytes will be copied.
235    unsafe fn copy_utf8_to_cstr<const N: usize>(src: &str, dst: &mut [c_char; N]) {
236        // N > 0, so subtracting 1 won't underflow.
237        let n = src.len().min(N - 1);
238        // SAFETY: The caller upholds the safety requirements.
239        unsafe {
240            copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr() as *mut _, n);
241        }
242        // n is within bounds.
243        dst[n] = b'\0' as _;
244    }
245
246    extern "C-unwind" fn count<E, P>(plugin: *const clap_plugin) -> u32
247    where
248        P: Plugin,
249        E: Params<P>,
250    {
251        if plugin.is_null() {
252            return 0;
253        }
254        // SAFETY: We just checked that the pointer is non-null and the plugin
255        // has been obtained from host and is tied to type P.
256        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
257
258        // SAFETY: This function is called on the main thread.
259        // It is guaranteed that we are the only function accessing the plugin now.
260        // So the mutable reference to plugin for the duration of this call is
261        // safe.
262        let plugin = unsafe { clap_plugin.plugin() };
263
264        E::count(plugin)
265    }
266
267    extern "C-unwind" fn get_info<E, P>(
268        plugin: *const clap_plugin,
269        param_index: u32,
270        param_info: *mut clap_param_info,
271    ) -> bool
272    where
273        P: Plugin,
274        E: Params<P>,
275    {
276        if plugin.is_null() {
277            return false;
278        }
279        // SAFETY: We just checked that the pointer is non-null and the plugin
280        // has been obtained from host and is tied to type P.
281        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
282
283        // SAFETY: This function is called on the main thread.
284        // It is guaranteed that we are the only function accessing the plugin now.
285        // So the mutable reference to plugin for the duration of this call is
286        // safe.
287        let plugin = unsafe { clap_plugin.plugin() };
288
289        // SAFETY: We just checked if parm_info is non-null.
290        let Some(param_info) = (unsafe { param_info.as_mut() }) else {
291            return false;
292        };
293        let Some(info) = E::get_info(plugin, param_index) else {
294            return false;
295        };
296
297        param_info.id = info.id.into();
298        param_info.flags = info.flags;
299
300        // SAFETY: `param_info.name.len() > 0`, and the buffers aren't overlapping.
301        unsafe { copy_utf8_to_cstr(&info.name, &mut param_info.name) };
302        // SAFETY: `param_info.module.len() > 0`, and the buffers aren't overlapping.
303        unsafe { copy_utf8_to_cstr(&info.module, &mut param_info.module) };
304
305        param_info.min_value = info.min_value;
306        param_info.max_value = info.max_value;
307        param_info.default_value = info.default_value;
308        true
309    }
310
311    extern "C-unwind" fn get_value<E, P>(
312        plugin: *const clap_plugin,
313        param_id: clap_id,
314        out_value: *mut f64,
315    ) -> bool
316    where
317        P: Plugin,
318        E: Params<P>,
319    {
320        if plugin.is_null() {
321            return false;
322        }
323        // SAFETY: We just checked that the pointer is non-null and the plugin
324        // has been obtained from host and is tied to type P.
325        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
326
327        // SAFETY: This function is called on the main thread.
328        // It is guaranteed that we are the only function accessing the plugin now.
329        // So the mutable reference to plugin for the duration of this call is
330        // safe.
331        let plugin = unsafe { clap_plugin.plugin() };
332
333        let Ok(param_id) = param_id.try_into() else {
334            return false;
335        };
336        let Some(value) = E::get_value(plugin, param_id) else {
337            return false;
338        };
339
340        unsafe { out_value.as_mut() }.map(|v| *v = value).is_some()
341    }
342
343    extern "C-unwind" fn value_to_text<E, P>(
344        plugin: *const clap_plugin,
345        param_id: clap_id,
346        value: f64,
347        out_buffer: *mut c_char,
348        out_buffer_capacity: u32,
349    ) -> bool
350    where
351        P: Plugin,
352        E: Params<P>,
353    {
354        if plugin.is_null() {
355            return false;
356        }
357        // SAFETY: We just checked that the pointer is non-null and the plugin
358        // has been obtained from host and is tied to type P.
359        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
360
361        // SAFETY: This function is called on the main thread.
362        // It is guaranteed that we are the only function accessing the plugin now.
363        // So the mutable reference to plugin for the duration of this call is
364        // safe.
365        let plugin = unsafe { clap_plugin.plugin() };
366
367        let out_buffer_capacity = if out_buffer_capacity > 0 {
368            debug_assert!(usize::try_from(out_buffer_capacity).is_ok());
369            out_buffer_capacity as usize
370        } else {
371            return true;
372        };
373        let buf = if !out_buffer.is_null() {
374            unsafe { &mut *slice_from_raw_parts_mut(out_buffer as *mut u8, out_buffer_capacity) }
375        } else {
376            return false;
377        };
378        // We fill `buf` with zeroes, so that the user supplied string will be
379        // null-terminated no matter what length.
380        buf.fill(b'\0');
381
382        let Ok(param_id) = param_id.try_into() else {
383            return false;
384        };
385        E::value_to_text(
386            plugin,
387            param_id,
388            value,
389            &mut buf[0..out_buffer_capacity - 1],
390        )
391        .is_ok()
392    }
393
394    extern "C-unwind" fn text_to_value<E, P>(
395        plugin: *const clap_plugin,
396        param_id: clap_id,
397        param_value_text: *const c_char,
398        out_value: *mut f64,
399    ) -> bool
400    where
401        P: Plugin,
402        E: Params<P>,
403    {
404        if plugin.is_null() {
405            return false;
406        }
407        // SAFETY: We just checked that the pointer is non-null and the plugin
408        // has been obtained from host and is tied to type P.
409        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
410
411        // SAFETY: This function is called on the main thread.
412        // It is guaranteed that we are the only function accessing the plugin now.
413        // So the mutable reference to plugin for the duration of this call is
414        // safe.
415        let plugin = unsafe { clap_plugin.plugin() };
416
417        let write_value = || -> Result<(), Error> {
418            let text = unsafe { param_value_text.as_ref() }
419                .map(|p| unsafe { CStr::from_ptr(p) }.to_str())
420                .ok_or(Error::Nullptr)??;
421            let value = E::text_to_value(plugin, param_id.try_into()?, text)
422                .map_err(|_| Error::ParseFloat(None))?;
423            unsafe { out_value.as_mut() }
424                .map(|v| *v = value)
425                .ok_or(Error::Nullptr)
426        };
427
428        write_value().is_ok()
429    }
430
431    extern "C-unwind" fn flush<E, P>(
432        plugin: *const clap_plugin,
433        r#in: *const clap_input_events,
434        out: *const clap_output_events,
435    ) where
436        P: Plugin,
437        E: Params<P>,
438    {
439        let Some(r#in) = (unsafe { r#in.as_ref() }) else {
440            return;
441        };
442        let in_events = if r#in.size.is_some() && r#in.get.is_some() {
443            unsafe { InputEvents::new_unchecked(r#in) }
444        } else {
445            return;
446        };
447
448        let Some(r#out) = (unsafe { out.as_ref() }) else {
449            return;
450        };
451        let out_events = if out.try_push.is_some() {
452            unsafe { OutputEvents::new_unchecked(out) }
453        } else {
454            return;
455        };
456
457        if plugin.is_null() {
458            return;
459        }
460        // SAFETY: We just checked that the pointer is non-null and the plugin
461        // has been obtained from host and is tied to type P.
462        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
463
464        if clap_plugin.is_active() {
465            // SAFETY: This function is called on the audio thread.  It is guaranteed that
466            // we are the only function accessing audio_thread now. So a mutable reference
467            // to audio_thread for the duration of this call is safe.
468            let audio_thread = unsafe { clap_plugin.audio_thread() }.unwrap();
469            E::flush(audio_thread, &in_events, &out_events)
470        } else {
471            // SAFETY: This function is called on the main thread.
472            // It is guaranteed that we are the only function accessing the plugin now.
473            // So the mutable reference to plugin for the duration of this call is
474            // safe.
475            let plugin = unsafe { clap_plugin.plugin() };
476            E::flush_inactive(plugin, &in_events, &out_events);
477        }
478    }
479
480    pub struct PluginParams<P> {
481        #[allow(unused)]
482        clap_plugin_params: clap_plugin_params,
483        _marker: PhantomData<P>,
484    }
485
486    impl<P: Plugin> PluginParams<P> {
487        pub fn new<E: Params<P>>(_: E) -> Self {
488            Self {
489                clap_plugin_params: clap_plugin_params {
490                    count: Some(count::<E, P>),
491                    get_info: Some(get_info::<E, P>),
492                    get_value: Some(get_value::<E, P>),
493                    value_to_text: Some(value_to_text::<E, P>),
494                    text_to_value: Some(text_to_value::<E, P>),
495                    flush: Some(flush::<E, P>),
496                },
497                _marker: PhantomData,
498            }
499        }
500    }
501}
502
503#[derive(Debug, Copy, Clone, PartialEq, Eq)]
504#[repr(u32)]
505pub enum RescanFlags {
506    /// The parameter values did change, e.g. after loading a preset.
507    /// The host will scan all the parameters value.
508    /// The host will not record those changes as automation points.
509    /// New values takes effect immediately.
510    Values = CLAP_PARAM_RESCAN_VALUES,
511    /// The value to text conversion changed, and the text needs to be rendered
512    /// again.
513    Text = CLAP_PARAM_RESCAN_TEXT,
514    /// The parameter info did change, use this flag for:
515    /// - name change
516    /// - module change
517    /// - is_periodic (flag)
518    /// - is_hidden (flag)
519    ///
520    /// New info takes effect immediately.
521    Info = CLAP_PARAM_RESCAN_INFO,
522    /// Invalidates everything the host knows about parameters.
523    /// It can only be used while the plugin is deactivated.
524    /// If the plugin is activated use clap_host->restart() and delay any change
525    /// until the host calls clap_plugin->deactivate().
526    ///
527    /// You must use this flag if:
528    /// - some parameters were added or removed.
529    /// - some parameters had critical changes:
530    ///   - is_per_note (flag)
531    ///   - is_per_key (flag)
532    ///   - is_per_channel (flag)
533    ///   - is_per_port (flag)
534    ///   - is_readonly (flag)
535    ///   - is_bypass (flag)
536    ///   - is_stepped (flag)
537    ///   - is_modulatable (flag)
538    ///   - min_value
539    ///   - max_value
540    ///   - cookie
541    All = CLAP_PARAM_RESCAN_ALL,
542}
543
544impl_flags_u32!(RescanFlags);
545
546#[derive(Debug, Copy, Clone, PartialEq, Eq)]
547#[repr(u32)]
548pub enum ClearFlags {
549    /// Clears all possible references to a parameter
550    All = CLAP_PARAM_CLEAR_ALL,
551    /// Clears all automations to a parameter
552    Automations = CLAP_PARAM_CLEAR_AUTOMATIONS,
553    /// Clears all modulations to a parameter
554    Modulations = CLAP_PARAM_CLEAR_MODULATIONS,
555}
556
557impl_flags_u32!(ClearFlags);
558
559#[derive(Debug)]
560pub struct HostParams<'a> {
561    host: &'a Host,
562    clap_host_params: &'a clap_host_params,
563}
564
565impl<'a> HostParams<'a> {
566    /// # Safety
567    ///
568    /// All extension interface function pointers must be non-null (Some), and
569    /// the functions must be thread-safe.
570    pub(crate) const unsafe fn new_unchecked(
571        host: &'a Host,
572        clap_host_params: &'a clap_host_params,
573    ) -> Self {
574        Self {
575            host,
576            clap_host_params,
577        }
578    }
579
580    /// Rescan the full list of parameters according to the flags.
581    pub fn rescan(&self, flags: u32) {
582        // SAFETY: By construction the function pointer: `clap_host_param.rescan` is
583        // non-null (Some).
584        unsafe { self.clap_host_params.rescan.unwrap()(self.host.clap_host(), flags) }
585    }
586
587    /// Clears references to a parameter.
588    pub fn clear(&self, param_id: ClapId, flags: u32) {
589        // SAFETY: By construction the function pointer: `clap_host_param.clear` is
590        // non-null (Some).
591        unsafe {
592            self.clap_host_params.clear.unwrap()(self.host.clap_host(), param_id.into(), flags)
593        }
594    }
595
596    /// Request a parameter flush.
597    ///
598    /// The host will then schedule a call to either:
599    /// - clap_plugin.process()
600    /// - clap_plugin_params.flush()
601    ///
602    /// This function is always safe to use and should not be called from an
603    /// audio-thread as the plugin would already be within process() or
604    /// flush().
605    pub fn request_flush(&self) {
606        // SAFETY: By construction the function pointer: `clap_host_param.request_flush`
607        // is non-null (Some).
608        unsafe { self.clap_host_params.request_flush.unwrap()(self.host.clap_host()) }
609    }
610}
611
612#[derive(Debug, PartialEq)]
613pub enum Error {
614    ConvertToText(f64),
615    ParseFloat(Option<std::num::ParseFloatError>),
616    IdError(id::Error),
617    Nullptr,
618    Utf8Error(std::str::Utf8Error),
619}
620
621impl Display for Error {
622    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
623        match self {
624            Error::ConvertToText(val) => write!(f, "conversion from value to text: {val}"),
625            Error::ParseFloat(e) => write!(f, "float conversion: {:?}", e),
626            Error::IdError(e) => write!(f, "ClapId error: {e}"),
627            Error::Nullptr => write!(f, "null pointer"),
628            Error::Utf8Error(e) => write!(f, "UTF-8 encoding error: {e}"),
629        }
630    }
631}
632
633impl std::error::Error for Error {}
634
635impl From<std::str::Utf8Error> for Error {
636    fn from(value: std::str::Utf8Error) -> Self {
637        Self::Utf8Error(value)
638    }
639}
640
641impl From<id::Error> for Error {
642    fn from(value: id::Error) -> Self {
643        Self::IdError(value)
644    }
645}
646
647impl From<std::num::ParseFloatError> for Error {
648    fn from(value: std::num::ParseFloatError) -> Self {
649        Self::ParseFloat(Some(value))
650    }
651}
652
653impl From<Error> for crate::Error {
654    fn from(value: Error) -> Self {
655        ext::Error::Params(value).into()
656    }
657}