voicemeeter\interface\parameters/
strip.rs

1//! Strip parameters
2use super::*;
3
4/// Parameters for a strip.
5///
6/// A strip is a physical or virtual input.
7///
8/// Returned by [`VoicemeeterRemote::parameters().strip(i)`](VoicemeeterRemote::parameters)
9///
10///
11/// # Example
12///
13/// ```rust,no_run
14/// use voicemeeter::VoicemeeterRemote;
15///
16/// // Get the client.
17/// let remote = VoicemeeterRemote::new()?;
18///
19/// // Get the label of strip 1 (index 0)
20/// println!("{}", remote.parameters().strip(0)?.label().get()?);
21/// // Set strip 3 (index 2) to output to A1
22/// remote.parameters().strip(2)?.a1().set(true)?;
23///
24/// // Ensure the change is registered.
25/// remote.is_parameters_dirty()?;
26///
27/// // We need to sleep here because otherwise the change won't be registered,
28/// // in a long running program this is not needed.
29/// std::thread::sleep(std::time::Duration::from_millis(50));
30///
31/// # Ok::<(), Box<dyn std::error::Error>>(())
32/// ```
33pub struct Strip<'a> {
34    remote: &'a VoicemeeterRemote,
35    strip_index: ZIndex,
36}
37
38impl<'a> Strip<'a> {
39    #[doc(hidden)]
40    pub fn new(remote: &'a VoicemeeterRemote, strip_index: ZIndex) -> Self {
41        Strip {
42            remote,
43            strip_index,
44        }
45    }
46    /// Get the identifier for a parameter on this strip: `Strip[i].{dot}`
47    pub fn param(&self, dot: impl Display) -> Cow<'static, ParameterNameRef> {
48        // TODO: Should this maybe allow custom names?
49        Cow::Owned(format!("{STRIP}[{}].{}", self.strip_index, dot).into())
50    }
51    /// Strip is physical
52    #[rustfmt::skip]
53    pub fn is_physical(&self) -> bool {
54        matches!((self.remote.program, self.strip_index.0),
55            | (VoicemeeterApplication::Voicemeeter, 0..=1)
56            | (VoicemeeterApplication::VoicemeeterBanana, 0..=2)
57            | (VoicemeeterApplication::VoicemeeterPotato, 0..=4)
58            | (VoicemeeterApplication::PotatoX64Bits, 0..=4)
59        )
60    }
61
62    /// Strip is virtual
63    pub fn is_virtual(&self) -> bool {
64        !(self.is_physical() || matches!(self.remote.program, VoicemeeterApplication::Other))
65    }
66
67    /// Mono Button
68    pub fn mono(&self) -> BoolParameter<'_> {
69        BoolParameter::new(self.param("Mono"), self.remote)
70    }
71
72    /// Mute Button
73    pub fn mute(&self) -> BoolParameter<'_> {
74        BoolParameter::new(self.param("Mute"), self.remote)
75    }
76
77    /// Solo Button
78    pub fn solo(&self) -> BoolParameter<'_> {
79        BoolParameter::new(self.param("Solo"), self.remote)
80    }
81
82    // FIXME: Only available in virtual input and input8
83    /// Mute Center Button
84    pub fn mute_center(&self) -> BoolParameter<'_> {
85        BoolParameter::new(self.param("MC"), self.remote)
86    }
87
88    /// Gain slider
89    pub fn gain(&self) -> FloatParameter<'_> {
90        FloatParameter::new(self.param("Gain"), self.remote, -60.0..=12.0)
91    }
92
93    // TODO: zindex for bus
94    /// Gain slider for a bus
95    pub fn gain_layer(&self, layer: impl Into<ZIndex>) -> FloatParameter<'_> {
96        let layer = layer.into();
97        let name = self.param(format!("GainLayer[{layer}]"));
98        FloatParameter::new(name, self.remote, -60.0..=12.0)
99    }
100
101    // TODO: zindex for bus
102    /// Pan in x direction
103    pub fn pan_x(&self) -> FloatParameter<'_> {
104        FloatParameter::new(self.param("Pan_x"), self.remote, -0.5..=0.5)
105    }
106
107    /// Pan in y direction
108    pub fn pan_y(&self) -> FloatParameter<'_> {
109        // FIXME: docs says for range: 0 to 1.0 (-0.5 to 0.5 for 5.1 pan pot)
110        FloatParameter::new_unranged(self.param("Pan_y"), self.remote)
111    }
112
113    /// Color of physical strip in x direction
114    pub fn color_x(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
115        if self.is_virtual() {
116            Err(InvalidTypeError::ExpectedPhysical {
117                name: STRIP,
118                strip_index: self.strip_index,
119                parameter: "Color_x".to_string(),
120            })
121        } else {
122            Ok(FloatParameter::new(
123                self.param("Color_x"),
124                self.remote,
125                -0.5..=0.5,
126            ))
127        }
128    }
129
130    /// Color of physical strip in y direction
131    pub fn color_y(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
132        if self.is_virtual() {
133            Err(InvalidTypeError::ExpectedPhysical {
134                name: STRIP,
135                strip_index: self.strip_index,
136                parameter: "Color_y".to_string(),
137            })
138        } else {
139            Ok(FloatParameter::new(
140                self.param("Color_y"),
141                self.remote,
142                0.0..=1.0,
143            ))
144        }
145    }
146
147    /// FX of physical strip in x direction
148    pub fn fx_x(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
149        if self.is_virtual() {
150            Err(InvalidTypeError::ExpectedPhysical {
151                name: STRIP,
152                strip_index: self.strip_index,
153                parameter: "fx_x".to_string(),
154            })
155        } else {
156            Ok(FloatParameter::new(
157                self.param("fx_x"),
158                self.remote,
159                -0.5..=0.5,
160            ))
161        }
162    }
163
164    /// FX of physical strip in y direction
165    pub fn fx_y(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
166        if self.is_virtual() {
167            Err(InvalidTypeError::ExpectedPhysical {
168                name: STRIP,
169                strip_index: self.strip_index,
170                parameter: "fx_y".to_string(),
171            })
172        } else {
173            Ok(FloatParameter::new(
174                self.param("fx_y"),
175                self.remote,
176                0.0..=1.0,
177            ))
178        }
179    }
180
181    /// Audability
182    pub fn audability(&self) -> FloatParameter<'_> {
183        FloatParameter::new(self.param("Audability"), self.remote, 0.0..=10.0)
184    }
185    // FIXME: Only available in virtual input aux
186    /// Compression
187    ///
188    /// See also [Strip::comp_detailed] for detailed compressor settings
189    pub fn comp(&self) -> FloatParameter<'_> {
190        FloatParameter::new(self.param("Comp"), self.remote, 0.0..=10.0)
191    }
192
193    /// Compressor detailed parameters/settings
194    ///
195    /// Only works on Voicemeeter Potato
196    pub fn comp_detailed(&self) -> Result<StripCompressor<'_>, ParameterError> {
197        const VALID: &[VoicemeeterApplication] = &[
198            VoicemeeterApplication::VoicemeeterPotato,
199            VoicemeeterApplication::PotatoX64Bits,
200        ];
201        if VALID.contains(&self.remote.program) {
202            if self.is_physical() {
203                Ok(StripCompressor::new(self.remote, self.strip_index))
204            } else {
205                Err(InvalidTypeError::ExpectedPhysical {
206                    name: STRIP,
207                    strip_index: self.strip_index,
208                    parameter: "Comp".to_string(),
209                }
210                .into())
211            }
212        } else {
213            Err(InvalidVoicemeeterVersion {
214                expected: VALID,
215                found: self.remote.program,
216                parameter: self.param("Comp").to_string(),
217            }
218            .into())
219        }
220    }
221
222    /// Gate
223    ///
224    /// See also [Strip::gate_detailed] for detailed gate settings
225    pub fn gate(&self) -> FloatParameter<'_> {
226        FloatParameter::new(self.param("Gate"), self.remote, 0.0..=10.0)
227    }
228
229    /// Gate detailed parameters/settings
230    ///
231    /// Only works on Voicemeeter Potato
232    pub fn gate_detailed(&self) -> Result<StripGate<'_>, ParameterError> {
233        const VALID: &[VoicemeeterApplication] = &[
234            VoicemeeterApplication::VoicemeeterPotato,
235            VoicemeeterApplication::PotatoX64Bits,
236        ];
237
238        if VALID.contains(&self.remote.program) {
239            if self.is_physical() {
240                Ok(StripGate::new(self.remote, self.strip_index))
241            } else {
242                Err(InvalidTypeError::ExpectedPhysical {
243                    name: STRIP,
244                    strip_index: self.strip_index,
245                    parameter: "Gate".to_string(),
246                }
247                .into())
248            }
249        } else {
250            Err(InvalidVoicemeeterVersion {
251                expected: VALID,
252                found: self.remote.program,
253                parameter: self.param("Gate").to_string(),
254            }
255            .into())
256        }
257    }
258
259    /// Denoiser Knob
260    pub fn denoiser(&self) -> Result<FloatParameter<'_>, ParameterError> {
261        const VALID: &[VoicemeeterApplication] = &[
262            VoicemeeterApplication::VoicemeeterPotato,
263            VoicemeeterApplication::PotatoX64Bits,
264        ];
265
266        if VALID.contains(&self.remote.program) {
267            if self.is_physical() {
268                Ok(FloatParameter::new(
269                    self.param("Denoiser"),
270                    self.remote,
271                    0.0..10.0,
272                ))
273            } else {
274                Err(InvalidTypeError::ExpectedPhysical {
275                    name: STRIP,
276                    strip_index: self.strip_index,
277                    parameter: "Gate".to_string(),
278                }
279                .into())
280            }
281        } else {
282            Err(InvalidVoicemeeterVersion {
283                expected: VALID,
284                found: self.remote.program,
285                parameter: self.param("Gate").to_string(),
286            }
287            .into())
288        }
289    }
290
291    /// Karaoke
292    pub fn karaoke(&self) -> IntParameter<'_> {
293        IntParameter::new(self.param("Karaoke"), self.remote, 0..=4)
294    }
295
296    /// Limit
297    pub fn limit(&self) -> IntParameter<'_> {
298        IntParameter::new(self.param("Limit"), self.remote, -40..=12)
299    }
300
301    /// EQGain1 of virtual strip
302    pub fn eq_gain1(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
303        if self.is_physical() {
304            Err(InvalidTypeError::ExpectedPhysical {
305                name: STRIP,
306                strip_index: self.strip_index,
307                parameter: "EQGain1".to_string(),
308            })
309        } else {
310            Ok(FloatParameter::new(
311                self.param("EQGain1"),
312                self.remote,
313                -12.0..=12.0,
314            ))
315        }
316    }
317
318    /// EQGain2 of virtual strip
319    pub fn eq_gain2(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
320        if self.is_physical() {
321            Err(InvalidTypeError::ExpectedPhysical {
322                name: STRIP,
323                strip_index: self.strip_index,
324                parameter: "EQGain2".to_string(),
325            })
326        } else {
327            Ok(FloatParameter::new(
328                self.param("EQGain2"),
329                self.remote,
330                -12.0..=12.0,
331            ))
332        }
333    }
334
335    /// EQGain3 of virtual strip
336    pub fn eq_gain3(&self) -> Result<FloatParameter<'_>, InvalidTypeError> {
337        if self.is_physical() {
338            Err(InvalidTypeError::ExpectedPhysical {
339                name: STRIP,
340                strip_index: self.strip_index,
341                parameter: "EQGain3".to_string(),
342            })
343        } else {
344            Ok(FloatParameter::new(
345                self.param("EQGain3"),
346                self.remote,
347                -12.0..=12.0,
348            ))
349        }
350    }
351
352    /// Label
353    pub fn label(&self) -> StringParameter<'_> {
354        StringParameter::new(self.param("Label"), self.remote)
355    }
356
357    /// Out BUS Assignation for A1
358    pub fn a1(&self) -> BoolParameter<'_> {
359        BoolParameter::new(self.param("A1"), self.remote)
360    }
361    /// Out BUS Assignation for A2
362    pub fn a2(&self) -> BoolParameter<'_> {
363        BoolParameter::new(self.param("A2"), self.remote)
364    }
365    /// Out BUS Assignation for A3
366    pub fn a3(&self) -> BoolParameter<'_> {
367        BoolParameter::new(self.param("A3"), self.remote)
368    }
369    /// Out BUS Assignation for A4
370    pub fn a4(&self) -> BoolParameter<'_> {
371        BoolParameter::new(self.param("A4"), self.remote)
372    }
373    /// Out BUS Assignation for A5
374    pub fn a5(&self) -> BoolParameter<'_> {
375        BoolParameter::new(self.param("A5"), self.remote)
376    }
377    /// Out BUS Assignation for B1
378    pub fn b1(&self) -> BoolParameter<'_> {
379        BoolParameter::new(self.param("B1"), self.remote)
380    }
381    /// Out BUS Assignation for B2
382    pub fn b2(&self) -> BoolParameter<'_> {
383        BoolParameter::new(self.param("B2"), self.remote)
384    }
385    /// Out BUS Assignation for B3
386    pub fn b3(&self) -> BoolParameter<'_> {
387        BoolParameter::new(self.param("B3"), self.remote)
388    }
389    /// EQ Button
390    pub fn eq_on(&self) -> BoolParameter<'_> {
391        BoolParameter::new(self.param("EQ.on"), self.remote)
392    }
393    /// EQ Memory Slot
394    pub fn eq_ab(&self) -> BoolParameter<'_> {
395        BoolParameter::new(self.param("EQ.AB"), self.remote)
396    }
397    /// EQ on channel
398    pub fn eq(&self, channel: usize) -> Result<EqChannelParameter<'_>, ParameterError> {
399        const VALID: &[VoicemeeterApplication] = &[
400            VoicemeeterApplication::VoicemeeterPotato,
401            VoicemeeterApplication::PotatoX64Bits,
402        ];
403        let eq = EqChannelParameter::new_strip(self.remote, self.strip_index, channel);
404        if VALID.contains(&self.remote.program) {
405            if self.is_physical() {
406                Ok(eq)
407            } else {
408                Err(InvalidTypeError::ExpectedPhysical {
409                    name: STRIP,
410                    strip_index: self.strip_index,
411                    parameter: eq.name().to_string(),
412                }
413                .into())
414            }
415        } else {
416            Err(InvalidVoicemeeterVersion {
417                expected: VALID,
418                found: self.remote.program,
419                parameter: eq.name().to_string(),
420            }
421            .into())
422        }
423    }
424    /// Fade to
425    pub fn fade_to(&self) -> TupleParameter<'_, i32, usize> {
426        TupleParameter::new(self.param("FadeTo"), self.remote)
427    }
428    /// Fade by
429    pub fn fade_by(&self) -> TupleParameter<'_, i32, usize> {
430        TupleParameter::new(self.param("FadeBy"), self.remote)
431    }
432    /// Send Level To Reverb
433    pub fn reverb(&self) -> FloatParameter<'_> {
434        FloatParameter::new(self.param("Reverb"), self.remote, 0.0..=10.0)
435    }
436    /// Send Level To Delay
437    pub fn delay(&self) -> FloatParameter<'_> {
438        FloatParameter::new(self.param("Delay"), self.remote, 0.0..=10.0)
439    }
440    /// Send Level To External Fx1
441    pub fn fx1(&self) -> FloatParameter<'_> {
442        FloatParameter::new(self.param("Fx1"), self.remote, 0.0..=10.0)
443    }
444    /// Send Level To External Fx2
445    pub fn fx2(&self) -> FloatParameter<'_> {
446        FloatParameter::new(self.param("Fx2"), self.remote, 0.0..=10.0)
447    }
448    /// Post Reverb button
449    pub fn post_reverb(&self) -> BoolParameter<'_> {
450        BoolParameter::new(self.param("PostReverb"), self.remote)
451    }
452    /// Post Delay button
453    pub fn post_delay(&self) -> BoolParameter<'_> {
454        BoolParameter::new(self.param("PostDelay"), self.remote)
455    }
456    /// Post Fx1 button
457    pub fn post_fx1(&self) -> BoolParameter<'_> {
458        BoolParameter::new(self.param("PostFx1"), self.remote)
459    }
460    /// Post Fx2 button
461    pub fn post_fx2(&self) -> BoolParameter<'_> {
462        BoolParameter::new(self.param("PostFx2"), self.remote)
463    }
464
465    /// Application gain
466    pub fn app_gain_indexed(&self, application_index: ZIndex) -> FloatParameter<'_, true, false> {
467        FloatParameter::new(
468            self.param(format!("App[{application_index}].Gain")),
469            self.remote,
470            0.0..=1.0,
471        )
472    }
473
474    /// Application Mute
475    pub fn app_mute_indexed(&self, application_index: ZIndex) -> BoolParameter<'_, true, false> {
476        BoolParameter::new(
477            self.param(format!("App[{application_index}].Mute")),
478            self.remote,
479        )
480    }
481
482    /// Application gain
483    pub fn app_gain(&self) -> TupleParameter<'_, String, f32, true, false> {
484        TupleParameter::new(self.param("AppGain"), self.remote)
485    }
486
487    /// Application Mute
488    pub fn app_mute(&self) -> TupleParameter<'_, String, bool, true, false> {
489        TupleParameter::new(self.param("AppMute"), self.remote)
490    }
491
492    /// Audio Device information
493    pub fn device(&self) -> Result<StripDevice<'a>, InvalidTypeError> {
494        if self.is_virtual() {
495            Err(InvalidTypeError::ExpectedPhysical {
496                name: STRIP,
497                strip_index: self.strip_index,
498                parameter: "device".to_string(),
499            })
500        } else {
501            Ok(StripDevice::new(self.remote, self.strip_index))
502        }
503    }
504
505    /// VAIO input enable/disable.
506    ///
507    /// # Notes requires VAIO extension
508    pub fn vaio(&self) -> BoolParameter<'_> {
509        BoolParameter::new(self.param("VAIO"), self.remote)
510    }
511}
512
513/// Bus device parameters
514pub struct StripDevice<'a> {
515    remote: &'a VoicemeeterRemote,
516    strip_index: ZIndex,
517}
518
519impl<'a> StripDevice<'a> {
520    fn new(remote: &'a VoicemeeterRemote, strip_index: ZIndex) -> Self {
521        Self {
522            remote,
523            strip_index,
524        }
525    }
526
527    /// Get the identifier for a device parameter on this strip: `Strip[i].device.{dot}`
528    pub fn param(&self, dot: impl ToString) -> Cow<'static, ParameterNameRef> {
529        Cow::Owned(format!("{STRIP}[{}].device.{}", self.strip_index, dot.to_string()).into())
530    }
531
532    /// Name of the device.
533    pub fn name(&self) -> StringParameter<'a, false, true> {
534        StringParameter::new(self.param("name"), self.remote)
535    }
536
537    /// Samplerate of the device.
538    pub fn sr(&self) -> IntParameter<'a, false, true> {
539        IntParameter::new_unranged(self.param("sr"), self.remote)
540    }
541    /// WDM device
542    pub fn wdm(&self) -> StringParameter<'a, true, false> {
543        StringParameter::new(self.param("wdm"), self.remote)
544    }
545    /// KS device
546    pub fn ks(&self) -> StringParameter<'a, true, false> {
547        StringParameter::new(self.param("ks"), self.remote)
548    }
549    /// MME device
550    pub fn mme(&self) -> StringParameter<'a, true, false> {
551        StringParameter::new(self.param("mme"), self.remote)
552    }
553    /// ASIO device
554    pub fn asio(&self) -> StringParameter<'a, true, false> {
555        StringParameter::new(self.param("asio"), self.remote)
556    }
557}
558
559/// Compressor detailed parameters/settings
560///
561/// Only works on Voicemeeter Potato
562pub struct StripCompressor<'a> {
563    remote: &'a VoicemeeterRemote,
564    strip_index: ZIndex,
565}
566
567impl<'a> StripCompressor<'a> {
568    fn new(remote: &'a VoicemeeterRemote, strip_index: ZIndex) -> Self {
569        Self {
570            remote,
571            strip_index,
572        }
573    }
574
575    /// Get the identifier for a compressor parameter on this strip: `Strip[i].compressor.{dot}`
576    pub fn param(&self, dot: impl ToString) -> Cow<'static, ParameterNameRef> {
577        Cow::Owned(format!("{STRIP}[{}].comp.{}", self.strip_index, dot.to_string()).into())
578    }
579
580    /// Input Gain
581    ///
582    /// To control the gain before compression.
583    pub fn gain_in(&self) -> FloatParameter<'a, true, true> {
584        FloatParameter::new(self.param("GainIn"), self.remote, -24.0..24.0)
585    }
586    /// Ratio
587    ///
588    /// Gives the compression rate.
589    pub fn ratio(&self) -> FloatParameter<'a, true, true> {
590        FloatParameter::new(self.param("Ratio"), self.remote, 1.0..8.0)
591    }
592    /// Threshold
593    ///
594    /// Define a level to start the compression when
595    /// the input signal goes over this threshold.
596    pub fn threshold(&self) -> FloatParameter<'a, true, true> {
597        FloatParameter::new(self.param("Threshold"), self.remote, -40.0..-3.0)
598    }
599    /// Attack Time (ms)
600    ///
601    /// to control the compression behavior on
602    /// sound attack (when the input signal starts to go over the threshold)
603    pub fn attack(&self) -> FloatParameter<'a, true, true> {
604        FloatParameter::new(self.param("Attack"), self.remote, 0.0..200.0)
605    }
606    /// Release Time (ms)
607    ///  to control the compression behavior when
608    /// the signal goes down
609    pub fn release(&self) -> FloatParameter<'a, true, true> {
610        FloatParameter::new(self.param("Release"), self.remote, 0.0..5000.0)
611    }
612    /// Knee.
613    ///
614    /// To control the compression transition softness on
615    /// threshold point
616    pub fn knee(&self) -> FloatParameter<'a, true, true> {
617        FloatParameter::new(self.param("Knee"), self.remote, 0.0..1.0)
618    }
619    /// Output Gain
620    ///
621    /// To control the gain after compression
622    pub fn gain_out(&self) -> FloatParameter<'a, true, true> {
623        FloatParameter::new(self.param("GainOut"), self.remote, -24.0..24.0)
624    }
625    /// Auto Make Up Option
626    ///
627    /// apply an output gain automatically
628    /// computed to compensate the compression
629    pub fn make_up(&self) -> BoolParameter<'a, true, true> {
630        BoolParameter::new(self.param("MakeUp"), self.remote)
631    }
632}
633
634/// Gate detailed parameters/settings
635///
636/// Only works on Voicemeeter Potato
637pub struct StripGate<'a> {
638    remote: &'a VoicemeeterRemote,
639    strip_index: ZIndex,
640}
641
642impl<'a> StripGate<'a> {
643    fn new(remote: &'a VoicemeeterRemote, strip_index: ZIndex) -> Self {
644        Self {
645            remote,
646            strip_index,
647        }
648    }
649
650    /// Get the identifier for a gate parameter on this strip: `Strip[i].gate.{dot}`
651    pub fn param(&self, dot: impl ToString) -> Cow<'static, ParameterNameRef> {
652        Cow::Owned(format!("{STRIP}[{}].Gate.{}", self.strip_index, dot.to_string()).into())
653    }
654
655    /// Threshold
656    ///
657    /// If input gain is below this level the gate is
658    /// closing, above this level the gate is opening.
659    pub fn threshold(&self) -> FloatParameter<'a, true, true> {
660        FloatParameter::new(self.param("Threshold"), self.remote, -60.0..-10.0)
661    }
662    /// Damping Max
663    ///
664    /// Allows limiting the gain reduction when
665    /// the gate is closing. Per default OFF = -inf, the gate
666    /// completely remove the signal when closing.
667    pub fn damping(&self) -> FloatParameter<'a, true, true> {
668        FloatParameter::new(self.param("Damping"), self.remote, -60.0..-10.0)
669    }
670    /// Band Pass Sidechain (hz)
671    ///
672    /// This parameters allows to define a Band Pass frequency (1,5 octave)
673    /// in the sidechain (input signal
674    /// controlling the gate). Then the gate will react on specific
675    /// frequency range only.
676    pub fn bp_sidechain(&self) -> FloatParameter<'a, true, true> {
677        FloatParameter::new(self.param("BPSidechain"), self.remote, 100.0..=4000.0)
678    }
679    /// Attack Time (ms)
680    ///
681    /// Define how long it takes to open the gate (gain increasing time).
682    pub fn attack(&self) -> FloatParameter<'a, true, true> {
683        FloatParameter::new(self.param("Attack"), self.remote, 0.0..=1000.0)
684    }
685    /// Hold Time (ms)
686    ///
687    /// Define the minimal time the gate stays
688    /// opened anyway (whatever the input gain).
689    pub fn hold(&self) -> FloatParameter<'a, true, true> {
690        FloatParameter::new(self.param("Hold"), self.remote, 0.0..=5000.0)
691    }
692    /// Release Time (ms)
693    ///
694    /// Define how long it takes to close the gate
695    /// (gain decreasing time).
696    pub fn release(&self) -> FloatParameter<'a, true, true> {
697        FloatParameter::new(self.param("Release"), self.remote, 0.0..=5000.0)
698    }
699}