Implement Diopser's safe mode for filter stages
These are now limited to 40 by default.
This commit is contained in:
parent
32f123b47d
commit
836a72dbc4
|
@ -179,7 +179,14 @@ fn other_params(cx: &mut Context) {
|
||||||
cx,
|
cx,
|
||||||
Data::params,
|
Data::params,
|
||||||
|params| ¶ms.filter_stages,
|
|params| ¶ms.filter_stages,
|
||||||
Data::safe_mode_clamper,
|
{
|
||||||
|
let safe_mode_clamper = Data::safe_mode_clamper.get(cx);
|
||||||
|
move |t| safe_mode_clamper.filter_stages_renormalize_display(t)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let safe_mode_clamper = Data::safe_mode_clamper.get(cx);
|
||||||
|
move |t| safe_mode_clamper.filter_stages_renormalize_event(t)
|
||||||
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.bottom(Pixels(10.0));
|
.bottom(Pixels(10.0));
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl<L: Lens<Target = SafeModeClamper>> View for SafeModeButton<L> {
|
||||||
safe_mode_clamper.enable(cx);
|
safe_mode_clamper.enable(cx);
|
||||||
self.scrolled_lines -= 1.0;
|
self.scrolled_lines -= 1.0;
|
||||||
} else {
|
} else {
|
||||||
safe_mode_clamper.disable(cx);
|
safe_mode_clamper.disable();
|
||||||
self.scrolled_lines += 1.0;
|
self.scrolled_lines += 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,42 @@
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use nih_plug::prelude::Param;
|
||||||
use nih_plug_vizia::vizia::prelude::EventContext;
|
use nih_plug_vizia::vizia::prelude::EventContext;
|
||||||
|
use nih_plug_vizia::widgets::ParamEvent;
|
||||||
|
|
||||||
use crate::params::DiopserParams;
|
use crate::params::{self, DiopserParams};
|
||||||
|
|
||||||
/// Restricts the ranges of several parameters when enabled. This makes it more difficult to
|
/// Restricts the ranges of several parameters when enabled. This makes it more difficult to
|
||||||
/// generate load resonances with Diopser's default settings.
|
/// generate load resonances with Diopser's default settings.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SafeModeClamper {
|
pub struct SafeModeClamper {
|
||||||
/// Whether the safe mode toggle has been enabled.
|
/// Whether the safe mode toggle has been enabled.
|
||||||
enabled: Arc<AtomicBool>,
|
enabled: Arc<AtomicBool>,
|
||||||
|
/// The rest of the parameters struct. Used to restrict the parameter ranges when safe mode gets
|
||||||
|
/// enabled.
|
||||||
|
params: Arc<DiopserParams>,
|
||||||
|
|
||||||
|
/// The minimum value for the filter stages parameter when safe mode is enabled, normalized as a
|
||||||
|
/// `[0, 1]` value of the original full range.
|
||||||
|
filter_stages_restricted_normalized_min: f32,
|
||||||
|
/// The maximum value for the filter stages parameter when safe mode is enabled, normalized as a
|
||||||
|
/// `[0, 1]` value of the original full range.
|
||||||
|
filter_stages_restricted_normalized_max: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SafeModeClamper {
|
impl SafeModeClamper {
|
||||||
pub fn new(params: Arc<DiopserParams>) -> Self {
|
pub fn new(params: Arc<DiopserParams>) -> Self {
|
||||||
|
let filter_stages_range = params::filter_stages_range();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
enabled: params.safe_mode.clone(),
|
enabled: params.safe_mode.clone(),
|
||||||
|
params,
|
||||||
|
|
||||||
|
filter_stages_restricted_normalized_min: filter_stages_range
|
||||||
|
.normalize(params::FILTER_STAGES_RESTRICTED_MIN),
|
||||||
|
filter_stages_restricted_normalized_max: filter_stages_range
|
||||||
|
.normalize(params::FILTER_STAGES_RESTRICTED_MAX),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,19 +50,71 @@ impl SafeModeClamper {
|
||||||
/// Enable or disable safe mode. Enabling safe mode immediately clamps the parameters to their
|
/// Enable or disable safe mode. Enabling safe mode immediately clamps the parameters to their
|
||||||
/// new restricted ranges.
|
/// new restricted ranges.
|
||||||
pub fn toggle(&self, cx: &mut EventContext) {
|
pub fn toggle(&self, cx: &mut EventContext) {
|
||||||
// TODO: Restrict the parameter ranges when the button is enabled
|
if !self.enabled.fetch_xor(true, Ordering::Relaxed) {
|
||||||
self.enabled.fetch_xor(true, Ordering::Relaxed);
|
self.restrict_range(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable safe mode. Enabling safe mode immediately clamps the parameters to their new
|
/// Enable safe mode. Enabling safe mode immediately clamps the parameters to their new
|
||||||
/// restricted ranges.
|
/// restricted ranges.
|
||||||
pub fn enable(&self, cx: &mut EventContext) {
|
pub fn enable(&self, cx: &mut EventContext) {
|
||||||
// TODO: Restrict the parameter ranges when the button is enabled
|
if !self.enabled.swap(true, Ordering::Relaxed) {
|
||||||
self.enabled.store(true, Ordering::Relaxed);
|
self.restrict_range(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable safe mode.
|
/// Disable safe mode.
|
||||||
pub fn disable(&self, cx: &mut EventContext) {
|
pub fn disable(&self) {
|
||||||
|
// Disablign safe mode never needs to modify any parameters
|
||||||
self.enabled.store(false, Ordering::Relaxed);
|
self.enabled.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Depending on whether the safe mode is enabled or not this either returns `t`
|
||||||
|
/// as is, or the range gets translated to the restricted range when safe mode is enabled. This
|
||||||
|
/// is used for displaying the value. When handling events the range should be expanded again to
|
||||||
|
/// the origianl values.
|
||||||
|
pub fn filter_stages_renormalize_display(&self, t: f32) -> f32 {
|
||||||
|
if self.status() {
|
||||||
|
let renormalized = (t - self.filter_stages_restricted_normalized_min)
|
||||||
|
/ (self.filter_stages_restricted_normalized_max
|
||||||
|
- self.filter_stages_restricted_normalized_min);
|
||||||
|
|
||||||
|
// This clamping may be necessary when safe mode is enabled but the effects from
|
||||||
|
// `restrict_range()` have not been processed yet
|
||||||
|
renormalized.clamp(0.0, 1.0)
|
||||||
|
} else {
|
||||||
|
t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Depending on whether the safe mode is enabled or not this either returns `t`
|
||||||
|
/// as is, or the restricted range gets translated back to the original range when safe mode is
|
||||||
|
/// enabled.
|
||||||
|
pub fn filter_stages_renormalize_event(&self, t: f32) -> f32 {
|
||||||
|
if self.status() {
|
||||||
|
// This is the opposite of `filter_stages_renormalize_display`
|
||||||
|
t * (self.filter_stages_restricted_normalized_max
|
||||||
|
- self.filter_stages_restricted_normalized_min)
|
||||||
|
+ self.filter_stages_restricted_normalized_min
|
||||||
|
} else {
|
||||||
|
t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CLamp the parameter values to the restricted range when enabling safe mode. This assumes
|
||||||
|
/// there's no active automation gesture for these parameters.
|
||||||
|
fn restrict_range(&self, cx: &mut EventContext) {
|
||||||
|
cx.emit(ParamEvent::BeginSetParameter(&self.params.filter_stages).upcast());
|
||||||
|
cx.emit(
|
||||||
|
ParamEvent::SetParameter(
|
||||||
|
&self.params.filter_stages,
|
||||||
|
self.params.filter_stages.unmodulated_plain_value().clamp(
|
||||||
|
params::FILTER_STAGES_RESTRICTED_MIN,
|
||||||
|
params::FILTER_STAGES_RESTRICTED_MAX,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.upcast(),
|
||||||
|
);
|
||||||
|
cx.emit(ParamEvent::EndSetParameter(&self.params.filter_stages).upcast());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,6 @@ use nih_plug_vizia::vizia::prelude::*;
|
||||||
use nih_plug_vizia::widgets::param_base::ParamWidgetBase;
|
use nih_plug_vizia::widgets::param_base::ParamWidgetBase;
|
||||||
use nih_plug_vizia::widgets::util::{self, ModifiersExt};
|
use nih_plug_vizia::widgets::util::{self, ModifiersExt};
|
||||||
|
|
||||||
use super::SafeModeClamper;
|
|
||||||
|
|
||||||
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
||||||
/// normalized parameter.
|
/// normalized parameter.
|
||||||
const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
||||||
|
@ -30,10 +28,18 @@ const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
||||||
/// A simplified version of `ParamSlider` that works with Diopser's safe mode. The slider's range is
|
/// A simplified version of `ParamSlider` that works with Diopser's safe mode. The slider's range is
|
||||||
/// restricted when safe mode is enabled.
|
/// restricted when safe mode is enabled.
|
||||||
#[derive(Lens)]
|
#[derive(Lens)]
|
||||||
pub struct RestrictedParamSlider<LSafeMode: Lens<Target = SafeModeClamper>> {
|
pub struct RestrictedParamSlider {
|
||||||
safe_mode: LSafeMode,
|
|
||||||
param_base: ParamWidgetBase,
|
param_base: ParamWidgetBase,
|
||||||
|
|
||||||
|
/// Renormalizes the parameter's normalized value to a `[0, 1]` value that is used to display
|
||||||
|
/// the parameter. This range may end up zooming in on a part of the parameter's original range
|
||||||
|
/// when safe mode is enabled.
|
||||||
|
renormalize_display: Box<dyn Fn(f32) -> f32>,
|
||||||
|
/// The inverse of `renormalize_display`. This is used to map a normalized `[0, 1]` screen
|
||||||
|
/// coordinate back to a `[0, 1]` normalized parameter value. These values may be different when
|
||||||
|
/// safe mode is enabled.
|
||||||
|
renormalize_event: Box<dyn Fn(f32) -> f32>,
|
||||||
|
|
||||||
/// Will be set to `true` when the field gets Alt+Click'ed which will replace the label with a
|
/// Will be set to `true` when the field gets Alt+Click'ed which will replace the label with a
|
||||||
/// text box.
|
/// text box.
|
||||||
text_input_active: bool,
|
text_input_active: bool,
|
||||||
|
@ -68,27 +74,28 @@ pub struct GranularDragStatus {
|
||||||
pub starting_value: f32,
|
pub starting_value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode> {
|
impl RestrictedParamSlider {
|
||||||
/// See the original `ParamSlider`.
|
/// See the original `ParamSlider`.
|
||||||
pub fn new<LParams, Params, P, FMap>(
|
pub fn new<L, Params, P, FMap>(
|
||||||
cx: &mut Context,
|
cx: &mut Context,
|
||||||
params: LParams,
|
params: L,
|
||||||
params_to_param: FMap,
|
params_to_param: FMap,
|
||||||
safe_mode: LSafeMode,
|
renormalize_display: impl Fn(f32) -> f32 + Clone + 'static,
|
||||||
|
renormalize_event: impl Fn(f32) -> f32 + 'static,
|
||||||
) -> Handle<Self>
|
) -> Handle<Self>
|
||||||
where
|
where
|
||||||
LParams: Lens<Target = Params> + Clone,
|
L: Lens<Target = Params> + Clone,
|
||||||
Params: 'static,
|
Params: 'static,
|
||||||
P: Param + 'static,
|
P: Param + 'static,
|
||||||
FMap: Fn(&Params) -> &P + Copy + 'static,
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
{
|
{
|
||||||
// We'll visualize the difference between the current value and the default value if the
|
// See the original `ParamSlider` implementation for more details.
|
||||||
// default value lies somewhere in the middle and the parameter is continuous. Otherwise
|
|
||||||
// this approach looks a bit jarring.
|
|
||||||
Self {
|
Self {
|
||||||
safe_mode,
|
|
||||||
param_base: ParamWidgetBase::new(cx, params.clone(), params_to_param),
|
param_base: ParamWidgetBase::new(cx, params.clone(), params_to_param),
|
||||||
|
|
||||||
|
renormalize_display: Box::new(renormalize_display.clone()),
|
||||||
|
renormalize_event: Box::new(renormalize_event),
|
||||||
|
|
||||||
text_input_active: false,
|
text_input_active: false,
|
||||||
drag_active: false,
|
drag_active: false,
|
||||||
granular_drag_status: None,
|
granular_drag_status: None,
|
||||||
|
@ -100,8 +107,6 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
cx,
|
cx,
|
||||||
ParamWidgetBase::build_view(params, params_to_param, move |cx, param_data| {
|
ParamWidgetBase::build_view(params, params_to_param, move |cx, param_data| {
|
||||||
// Can't use `.to_string()` here as that would include the modulation.
|
// Can't use `.to_string()` here as that would include the modulation.
|
||||||
let unmodulated_normalized_value_lens =
|
|
||||||
param_data.make_lens(|param| param.unmodulated_normalized_value());
|
|
||||||
let display_value_lens = param_data.make_lens(|param| {
|
let display_value_lens = param_data.make_lens(|param| {
|
||||||
param.normalized_value_to_string(param.unmodulated_normalized_value(), true)
|
param.normalized_value_to_string(param.unmodulated_normalized_value(), true)
|
||||||
});
|
});
|
||||||
|
@ -110,9 +115,14 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
// signed width of the bar. `start_t` is in `[0, 1]`, and `delta` is in
|
// signed width of the bar. `start_t` is in `[0, 1]`, and `delta` is in
|
||||||
// `[-1, 1]`.
|
// `[-1, 1]`.
|
||||||
let fill_start_delta_lens = {
|
let fill_start_delta_lens = {
|
||||||
let param_data = param_data.clone();
|
let renormalize_display = renormalize_display.clone();
|
||||||
unmodulated_normalized_value_lens.map(move |current_value| {
|
|
||||||
Self::compute_fill_start_delta(param_data.param(), *current_value)
|
param_data.make_lens(move |param| {
|
||||||
|
Self::compute_fill_start_delta(
|
||||||
|
renormalize_display(param.default_normalized_value()),
|
||||||
|
param.step_count(),
|
||||||
|
renormalize_display(param.unmodulated_normalized_value()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,8 +130,12 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
// plugins with hosts that support this), then this is the difference
|
// plugins with hosts that support this), then this is the difference
|
||||||
// between the 'true' value and the current value after modulation has been
|
// between the 'true' value and the current value after modulation has been
|
||||||
// applied. This follows the same format as `fill_start_delta_lens`.
|
// applied. This follows the same format as `fill_start_delta_lens`.
|
||||||
let modulation_start_delta_lens = param_data
|
let modulation_start_delta_lens = param_data.make_lens(move |param| {
|
||||||
.make_lens(move |param| Self::compute_modulation_fill_start_delta(param));
|
Self::compute_modulation_fill_start_delta(
|
||||||
|
renormalize_display(param.modulated_normalized_value()),
|
||||||
|
renormalize_display(param.unmodulated_normalized_value()),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
||||||
// label with the slider. Creating the textbox based on
|
// label with the slider. Creating the textbox based on
|
||||||
|
@ -129,7 +143,7 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
// created.
|
// created.
|
||||||
Binding::new(
|
Binding::new(
|
||||||
cx,
|
cx,
|
||||||
RestrictedParamSlider::<LSafeMode>::text_input_active,
|
RestrictedParamSlider::text_input_active,
|
||||||
move |cx, text_input_active| {
|
move |cx, text_input_active| {
|
||||||
if text_input_active.get(cx) {
|
if text_input_active.get(cx) {
|
||||||
Self::text_input_view(cx, display_value_lens.clone());
|
Self::text_input_view(cx, display_value_lens.clone());
|
||||||
|
@ -244,9 +258,11 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
/// style, the parameter's current value, and the parameter's step sizes. The resulting tuple
|
/// style, the parameter's current value, and the parameter's step sizes. The resulting tuple
|
||||||
/// `(start_t, delta)` corresponds to the start and the signed width of the bar. `start_t` is in
|
/// `(start_t, delta)` corresponds to the start and the signed width of the bar. `start_t` is in
|
||||||
/// `[0, 1]`, and `delta` is in `[-1, 1]`.
|
/// `[0, 1]`, and `delta` is in `[-1, 1]`.
|
||||||
fn compute_fill_start_delta<P: Param>(param: &P, current_value: f32) -> (f32, f32) {
|
fn compute_fill_start_delta(
|
||||||
let default_value = param.default_normalized_value();
|
default_value: f32,
|
||||||
let step_count = param.step_count();
|
step_count: Option<usize>,
|
||||||
|
current_value: f32,
|
||||||
|
) -> (f32, f32) {
|
||||||
let draw_fill_from_default = step_count.is_none() && (0.45..=0.55).contains(&default_value);
|
let draw_fill_from_default = step_count.is_none() && (0.45..=0.55).contains(&default_value);
|
||||||
|
|
||||||
if draw_fill_from_default {
|
if draw_fill_from_default {
|
||||||
|
@ -264,25 +280,23 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> RestrictedParamSlider<LSafeMode>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same as `compute_fill_start_delta`, but just showing the modulation offset.
|
/// The same as `compute_fill_start_delta`, but just showing the modulation offset.
|
||||||
fn compute_modulation_fill_start_delta<P: Param>(param: &P) -> (f32, f32) {
|
fn compute_modulation_fill_start_delta(
|
||||||
let modulation_start = param.unmodulated_normalized_value();
|
modulation_start: f32,
|
||||||
|
current_value: f32,
|
||||||
(
|
) -> (f32, f32) {
|
||||||
modulation_start,
|
(modulation_start, current_value - modulation_start)
|
||||||
param.modulated_normalized_value() - modulation_start,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `self.param_base.set_normalized_value()`, but resulting from a mouse drag. When using the
|
/// `self.param_base.set_normalized_value()`, but resulting from a mouse drag. This uses the
|
||||||
/// 'even' stepped slider styles from [`ParamSliderStyle`] this will remap the normalized range
|
/// restricted range if safe mode is enabled.
|
||||||
/// to match up with the fill value display. This still needs to be wrapped in a parameter
|
|
||||||
/// automation gesture.
|
|
||||||
fn set_normalized_value_drag(&self, cx: &mut EventContext, normalized_value: f32) {
|
fn set_normalized_value_drag(&self, cx: &mut EventContext, normalized_value: f32) {
|
||||||
self.param_base.set_normalized_value(cx, normalized_value);
|
let restricted_normalized_value = (self.renormalize_event)(normalized_value);
|
||||||
|
self.param_base
|
||||||
|
.set_normalized_value(cx, restricted_normalized_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<LSafeMode: Lens<Target = SafeModeClamper>> View for RestrictedParamSlider<LSafeMode> {
|
impl View for RestrictedParamSlider {
|
||||||
fn element(&self) -> Option<&'static str> {
|
fn element(&self) -> Option<&'static str> {
|
||||||
// We'll reuse the original ParamSlider's styling
|
// We'll reuse the original ParamSlider's styling
|
||||||
Some("param-slider")
|
Some("param-slider")
|
||||||
|
@ -394,13 +408,22 @@ impl<LSafeMode: Lens<Target = SafeModeClamper>> View for RestrictedParamSlider<L
|
||||||
// consistent
|
// consistent
|
||||||
let start_x =
|
let start_x =
|
||||||
util::remap_current_entity_x_t(cx, granular_drag_status.starting_value);
|
util::remap_current_entity_x_t(cx, granular_drag_status.starting_value);
|
||||||
let delta_x = ((*x - granular_drag_status.starting_x_coordinate)
|
|
||||||
* GRANULAR_DRAG_MULTIPLIER)
|
|
||||||
* cx.style.dpi_factor as f32;
|
|
||||||
|
|
||||||
self.set_normalized_value_drag(
|
// When the range is restricted the `delta_x` should also change
|
||||||
|
// accordingly
|
||||||
|
let min_x = (self.renormalize_event)(0.0);
|
||||||
|
let max_x = (self.renormalize_event)(1.0);
|
||||||
|
let delta_x = (*x - granular_drag_status.starting_x_coordinate)
|
||||||
|
* GRANULAR_DRAG_MULTIPLIER
|
||||||
|
* cx.style.dpi_factor as f32
|
||||||
|
* (max_x - min_x);
|
||||||
|
|
||||||
|
// We don't use `set_normalized_value_drag` because these values are already
|
||||||
|
// in the correct 'remapped' domain
|
||||||
|
self.param_base.set_normalized_value(
|
||||||
cx,
|
cx,
|
||||||
util::remap_current_entity_x_coordinate(cx, start_x + delta_x),
|
util::remap_current_entity_x_coordinate(cx, start_x + delta_x)
|
||||||
|
.clamp(min_x, max_x),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.granular_drag_status = None;
|
self.granular_drag_status = None;
|
||||||
|
|
|
@ -26,6 +26,11 @@ pub fn filter_stages_range() -> IntRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The filter stages parameters minimum value in safe mode.
|
||||||
|
pub const FILTER_STAGES_RESTRICTED_MIN: i32 = 0;
|
||||||
|
/// The filter stages parameters maximum value in safe mode.
|
||||||
|
pub const FILTER_STAGES_RESTRICTED_MAX: i32 = 40;
|
||||||
|
|
||||||
/// The filter frequency parameter's range. Also used in the `SpectrumAnalyzer` widget.
|
/// The filter frequency parameter's range. Also used in the `SpectrumAnalyzer` widget.
|
||||||
pub fn filter_frequency_range() -> FloatRange {
|
pub fn filter_frequency_range() -> FloatRange {
|
||||||
FloatRange::Skewed {
|
FloatRange::Skewed {
|
||||||
|
|
Loading…
Reference in a new issue