💥 Use interior mutability for parameters
Instead of the previous technically-unsound approach. While it wouldn't cause any issues in practice, it did break Rust's guarantees. That was a design choice after adding support for editors in NIH-plug, but this is probably the better long term solution. The downside is that all uses of `param.value` now need to be changed to `param.value()`.
This commit is contained in:
parent
5966e353da
commit
c566888fa3
|
@ -6,6 +6,17 @@ new and what's changed, this document lists all breaking changes in reverse
|
|||
chronological order. If a new feature did not require any changes to existing
|
||||
code then it will not be listed here.
|
||||
|
||||
## [2022-09-06]
|
||||
|
||||
- Parameter values are now accessed using `param.value()` instead of
|
||||
`param.value`, with `param.value()` being an alias for the existing
|
||||
`param.plain_value()` function. The old approach, while perfectly safe in
|
||||
practice, was technically unsound because it used mutable pointers to
|
||||
parameters that may also be simultaneously read from in an editor GUI. With
|
||||
this change the parameters now use actual relaxed atomic stores and loads to
|
||||
avoid mutable aliasing, which means the value fields are now no longer
|
||||
directly accessible.
|
||||
|
||||
## [2022-09-04]
|
||||
|
||||
- `Smoother::next_block_mapped()` and `Smoother::next_block_exact_mapped()` have
|
||||
|
|
|
@ -392,7 +392,7 @@ impl Plugin for Crisp {
|
|||
}
|
||||
}
|
||||
|
||||
if self.params.wet_only.value {
|
||||
if self.params.wet_only.value() {
|
||||
for (channel_samples, rm_outputs) in block.iter_samples().zip(&mut rm_outputs) {
|
||||
let output_gain = self.params.output_gain.smoothed.next();
|
||||
for (sample, rm_output) in channel_samples.into_iter().zip(rm_outputs) {
|
||||
|
|
|
@ -291,7 +291,7 @@ impl Crossover {
|
|||
}
|
||||
|
||||
self.iir_crossover.process(
|
||||
self.params.num_bands.value as usize,
|
||||
self.params.num_bands.value() as usize,
|
||||
&main_channel_samples,
|
||||
bands,
|
||||
);
|
||||
|
@ -331,7 +331,7 @@ impl Crossover {
|
|||
];
|
||||
|
||||
self.fir_crossover.process(
|
||||
self.params.num_bands.value as usize,
|
||||
self.params.num_bands.value() as usize,
|
||||
main_io,
|
||||
band_outputs,
|
||||
channel_idx,
|
||||
|
@ -371,12 +371,12 @@ impl Crossover {
|
|||
match self.params.crossover_type.value() {
|
||||
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
|
||||
self.buffer_config.sample_rate,
|
||||
self.params.num_bands.value as usize,
|
||||
self.params.num_bands.value() as usize,
|
||||
crossover_frequencies,
|
||||
),
|
||||
CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update(
|
||||
self.buffer_config.sample_rate,
|
||||
self.params.num_bands.value as usize,
|
||||
self.params.num_bands.value() as usize,
|
||||
crossover_frequencies,
|
||||
),
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ struct Diopser {
|
|||
should_update_filters: Arc<AtomicBool>,
|
||||
/// If this is 1 and any of the filter parameters are still smoothing, thenn the filter
|
||||
/// coefficients should be recalculated on the next sample. After that, this gets reset to
|
||||
/// `unnormalize_automation_precision(self.params.automation_precision.value)`. This is to
|
||||
/// `unnormalize_automation_precision(self.params.automation_precision.value())`. This is to
|
||||
/// reduce the DSP load of automation parameters. It can also cause some fun sounding glitchy
|
||||
/// effects when the precision is low.
|
||||
next_filter_smoothing_in: i32,
|
||||
|
@ -291,7 +291,7 @@ impl Plugin for Diopser {
|
|||
// necessary, and allow smoothing only every n samples using the automation precision
|
||||
// parameter
|
||||
let smoothing_interval =
|
||||
unnormalize_automation_precision(self.params.automation_precision.value);
|
||||
unnormalize_automation_precision(self.params.automation_precision.value());
|
||||
|
||||
for mut channel_samples in buffer.iter_samples() {
|
||||
self.maybe_update_filters(smoothing_interval);
|
||||
|
@ -303,7 +303,7 @@ impl Plugin for Diopser {
|
|||
for filter in self
|
||||
.filters
|
||||
.iter_mut()
|
||||
.take(self.params.filter_stages.value as usize)
|
||||
.take(self.params.filter_stages.value() as usize)
|
||||
{
|
||||
samples = filter.process(samples);
|
||||
}
|
||||
|
@ -379,10 +379,10 @@ impl Diopser {
|
|||
// TODO: This wrecks the DSP load at high smoothing accuracy, perhaps also use SIMD here
|
||||
const MIN_FREQUENCY: f32 = 5.0;
|
||||
let max_frequency = self.sample_rate / 2.05;
|
||||
for filter_idx in 0..self.params.filter_stages.value as usize {
|
||||
for filter_idx in 0..self.params.filter_stages.value() as usize {
|
||||
// The index of the filter normalized to range [-1, 1]
|
||||
let filter_proportion =
|
||||
(filter_idx as f32 / self.params.filter_stages.value as f32) * 2.0 - 1.0;
|
||||
(filter_idx as f32 / self.params.filter_stages.value() as f32) * 2.0 - 1.0;
|
||||
|
||||
// The spread parameter adds an offset to the frequency depending on the number of the
|
||||
// filter
|
||||
|
|
|
@ -120,7 +120,7 @@ impl Plugin for Gain {
|
|||
setter.end_set_parameter(¶ms.gain);
|
||||
new_value
|
||||
}
|
||||
None => params.gain.value as f64,
|
||||
None => params.gain.value() as f64,
|
||||
}
|
||||
})
|
||||
.suffix(" dB"),
|
||||
|
|
|
@ -212,8 +212,8 @@ impl Plugin for PolyModSynth {
|
|||
} => {
|
||||
let initial_phase: f32 = self.prng.gen();
|
||||
// This starts with the attack portion of the amplitude envelope
|
||||
let mut amp_envelope = Smoother::new(SmoothingStyle::Exponential(
|
||||
self.params.amp_attack_ms.value,
|
||||
let amp_envelope = Smoother::new(SmoothingStyle::Exponential(
|
||||
self.params.amp_attack_ms.value(),
|
||||
));
|
||||
amp_envelope.reset(0.0);
|
||||
amp_envelope.set_target(sample_rate, 1.0);
|
||||
|
@ -522,7 +522,7 @@ impl PolyModSynth {
|
|||
{
|
||||
*releasing = true;
|
||||
amp_envelope.style =
|
||||
SmoothingStyle::Exponential(self.params.amp_release_ms.value);
|
||||
SmoothingStyle::Exponential(self.params.amp_release_ms.value());
|
||||
amp_envelope.set_target(sample_rate, 0.0);
|
||||
|
||||
// If this targetted a single voice ID, we're done here. Otherwise there may be
|
||||
|
|
|
@ -150,7 +150,7 @@ impl Plugin for Sine {
|
|||
let gain = self.params.gain.smoothed.next();
|
||||
|
||||
// This plugin can be either triggered by MIDI or controleld by a parameter
|
||||
let sine = if self.params.use_midi.value {
|
||||
let sine = if self.params.use_midi.value() {
|
||||
// Act on the next MIDI event
|
||||
while let Some(event) = next_event {
|
||||
if event.timing() > sample_id as u32 {
|
||||
|
|
|
@ -245,7 +245,7 @@ impl Plugin for PubertySimulator {
|
|||
// These plans have already been made during initialization we can switch between versions
|
||||
// without reallocating
|
||||
let fft_plan = &mut self.plan_for_order.as_mut().unwrap()
|
||||
[self.params.window_size_order.value as usize - MIN_WINDOW_ORDER];
|
||||
[self.params.window_size_order.value() as usize - MIN_WINDOW_ORDER];
|
||||
|
||||
let mut smoothed_pitch_value = 0.0;
|
||||
self.stft
|
||||
|
@ -395,11 +395,11 @@ impl Plugin for PubertySimulator {
|
|||
|
||||
impl PubertySimulator {
|
||||
fn window_size(&self) -> usize {
|
||||
1 << self.params.window_size_order.value as usize
|
||||
1 << self.params.window_size_order.value() as usize
|
||||
}
|
||||
|
||||
fn overlap_times(&self) -> usize {
|
||||
1 << self.params.overlap_times_order.value as usize
|
||||
1 << self.params.overlap_times_order.value() as usize
|
||||
}
|
||||
|
||||
/// `window_size` should not exceed `MAX_WINDOW_SIZE` or this will allocate.
|
||||
|
|
|
@ -209,7 +209,7 @@ impl Plugin for SafetyLimiter {
|
|||
let mut is_peaking = false;
|
||||
for sample in channel_samples.iter_mut() {
|
||||
if sample.is_finite() {
|
||||
is_peaking |= sample.abs() > self.params.threshold_gain.value;
|
||||
is_peaking |= sample.abs() > self.params.threshold_gain.value();
|
||||
} else {
|
||||
// Infinity or NaN values need to be completely filtered out, because otherwise
|
||||
// we'll try to mix them back into the signal later
|
||||
|
@ -253,7 +253,7 @@ impl Plugin for SafetyLimiter {
|
|||
// This phase runs from 0 to `2 * pi` as an optimization, so we can use it
|
||||
// directly. And the sine wave is scaled down to the threshold minus 24 dB
|
||||
let sine_sample =
|
||||
self.osc_phase_tau.sin() * (self.params.threshold_gain.value * 0.125);
|
||||
self.osc_phase_tau.sin() * (self.params.threshold_gain.value() * 0.125);
|
||||
self.osc_phase_tau += self.osc_phase_tau_dt;
|
||||
if self.osc_phase_tau >= std::f32::consts::TAU {
|
||||
self.osc_phase_tau -= std::f32::consts::TAU;
|
||||
|
|
|
@ -570,18 +570,18 @@ impl CompressorBank {
|
|||
// for every 512 samples.
|
||||
let effective_sample_rate =
|
||||
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
|
||||
let attack_old_t = if params.global.compressor_attack_ms.value == 0.0 {
|
||||
let attack_old_t = if params.global.compressor_attack_ms.value() == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(-1.0 / (params.global.compressor_attack_ms.value / 1000.0 * effective_sample_rate))
|
||||
(-1.0 / (params.global.compressor_attack_ms.value() / 1000.0 * effective_sample_rate))
|
||||
.exp()
|
||||
};
|
||||
let attack_new_t = 1.0 - attack_old_t;
|
||||
// The same as `attack_old_t`, but for the release phase of the envelope follower
|
||||
let release_old_t = if params.global.compressor_release_ms.value == 0.0 {
|
||||
let release_old_t = if params.global.compressor_release_ms.value() == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(-1.0 / (params.global.compressor_release_ms.value / 1000.0 * effective_sample_rate))
|
||||
(-1.0 / (params.global.compressor_release_ms.value() / 1000.0 * effective_sample_rate))
|
||||
.exp()
|
||||
};
|
||||
let release_new_t = 1.0 - release_old_t;
|
||||
|
@ -611,24 +611,24 @@ impl CompressorBank {
|
|||
// See `update_envelopes()`
|
||||
let effective_sample_rate =
|
||||
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
|
||||
let attack_old_t = if params.global.compressor_attack_ms.value == 0.0 {
|
||||
let attack_old_t = if params.global.compressor_attack_ms.value() == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(-1.0 / (params.global.compressor_attack_ms.value / 1000.0 * effective_sample_rate))
|
||||
(-1.0 / (params.global.compressor_attack_ms.value() / 1000.0 * effective_sample_rate))
|
||||
.exp()
|
||||
};
|
||||
let attack_new_t = 1.0 - attack_old_t;
|
||||
let release_old_t = if params.global.compressor_release_ms.value == 0.0 {
|
||||
let release_old_t = if params.global.compressor_release_ms.value() == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(-1.0 / (params.global.compressor_release_ms.value / 1000.0 * effective_sample_rate))
|
||||
(-1.0 / (params.global.compressor_release_ms.value() / 1000.0 * effective_sample_rate))
|
||||
.exp()
|
||||
};
|
||||
let release_new_t = 1.0 - release_old_t;
|
||||
|
||||
// For the channel linking
|
||||
let num_channels = self.sidechain_spectrum_magnitudes.len() as f32;
|
||||
let other_channels_t = params.threshold.sc_channel_link.value / num_channels;
|
||||
let other_channels_t = params.threshold.sc_channel_link.value() / num_channels;
|
||||
let this_channel_t = 1.0 - (other_channels_t * (num_channels - 1.0));
|
||||
|
||||
for (bin_idx, envelope) in self.envelopes[channel_idx].iter_mut().enumerate() {
|
||||
|
@ -689,11 +689,11 @@ impl CompressorBank {
|
|||
// bandwidths, the middle values needs to be pushed more towards the post-knee threshold
|
||||
// than with lower knee values. These scaling factors are used as exponents.
|
||||
let downwards_knee_scaling_factor =
|
||||
compute_knee_scaling_factor(params.compressors.downwards.knee_width_db.value);
|
||||
compute_knee_scaling_factor(params.compressors.downwards.knee_width_db.value());
|
||||
// Note the square root here, since the curve needs to go the other way for the upwards
|
||||
// version
|
||||
let upwards_knee_scaling_factor =
|
||||
compute_knee_scaling_factor(params.compressors.upwards.knee_width_db.value).sqrt();
|
||||
compute_knee_scaling_factor(params.compressors.upwards.knee_width_db.value()).sqrt();
|
||||
|
||||
assert!(self.downwards_thresholds.len() == buffer.len());
|
||||
assert!(self.downwards_ratio_recips.len() == buffer.len());
|
||||
|
@ -768,13 +768,13 @@ impl CompressorBank {
|
|||
) {
|
||||
// See `compress` for more details
|
||||
let downwards_knee_scaling_factor =
|
||||
compute_knee_scaling_factor(params.compressors.downwards.knee_width_db.value);
|
||||
compute_knee_scaling_factor(params.compressors.downwards.knee_width_db.value());
|
||||
let upwards_knee_scaling_factor =
|
||||
compute_knee_scaling_factor(params.compressors.upwards.knee_width_db.value).sqrt();
|
||||
compute_knee_scaling_factor(params.compressors.upwards.knee_width_db.value()).sqrt();
|
||||
|
||||
// For the channel linking
|
||||
let num_channels = self.sidechain_spectrum_magnitudes.len() as f32;
|
||||
let other_channels_t = params.threshold.sc_channel_link.value / num_channels;
|
||||
let other_channels_t = params.threshold.sc_channel_link.value() / num_channels;
|
||||
let this_channel_t = 1.0 - (other_channels_t * (num_channels - 1.0));
|
||||
|
||||
assert!(self.sidechain_spectrum_magnitudes[channel_idx].len() == buffer.len());
|
||||
|
@ -859,23 +859,23 @@ impl CompressorBank {
|
|||
fn update_if_needed(&mut self, params: &SpectralCompressorParams) {
|
||||
// The threshold curve is a polynomial in log-log (decibels-octaves) space. The reuslt from
|
||||
// evaluating this needs to be converted to linear gain for the compressors.
|
||||
let intercept = params.threshold.threshold_db.value;
|
||||
let intercept = params.threshold.threshold_db.value();
|
||||
// The cheeky 3 additional dB/octave attenuation is to match pink noise with the default
|
||||
// settings. When using sidechaining we explicitly don't want this because the curve should
|
||||
// be a flat offset to the sidechain input at the default settings.
|
||||
let slope = match params.threshold.mode.value() {
|
||||
ThresholdMode::Internal => params.threshold.curve_slope.value - 3.0,
|
||||
ThresholdMode::Internal => params.threshold.curve_slope.value() - 3.0,
|
||||
ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => {
|
||||
params.threshold.curve_slope.value
|
||||
params.threshold.curve_slope.value()
|
||||
}
|
||||
};
|
||||
let curve = params.threshold.curve_curve.value;
|
||||
let log2_center_freq = params.threshold.center_frequency.value.log2();
|
||||
let curve = params.threshold.curve_curve.value();
|
||||
let log2_center_freq = params.threshold.center_frequency.value().log2();
|
||||
|
||||
let downwards_high_freq_ratio_rolloff =
|
||||
params.compressors.downwards.high_freq_ratio_rolloff.value;
|
||||
params.compressors.downwards.high_freq_ratio_rolloff.value();
|
||||
let upwards_high_freq_ratio_rolloff =
|
||||
params.compressors.upwards.high_freq_ratio_rolloff.value;
|
||||
params.compressors.upwards.high_freq_ratio_rolloff.value();
|
||||
let log2_nyquist_freq = self
|
||||
.log2_freqs
|
||||
.last()
|
||||
|
@ -886,7 +886,7 @@ impl CompressorBank {
|
|||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_ok()
|
||||
{
|
||||
let intercept = intercept + params.compressors.downwards.threshold_offset_db.value;
|
||||
let intercept = intercept + params.compressors.downwards.threshold_offset_db.value();
|
||||
for ((log2_freq, threshold), (knee_start, knee_end)) in self
|
||||
.log2_freqs
|
||||
.iter()
|
||||
|
@ -900,9 +900,9 @@ impl CompressorBank {
|
|||
let offset = log2_freq - log2_center_freq;
|
||||
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
||||
let knee_start_db =
|
||||
threshold_db - (params.compressors.downwards.knee_width_db.value / 2.0);
|
||||
threshold_db - (params.compressors.downwards.knee_width_db.value() / 2.0);
|
||||
let knee_end_db =
|
||||
threshold_db + (params.compressors.downwards.knee_width_db.value / 2.0);
|
||||
threshold_db + (params.compressors.downwards.knee_width_db.value() / 2.0);
|
||||
|
||||
// This threshold must never reach zero as it's used in divisions to get a gain ratio
|
||||
// above the threshold
|
||||
|
@ -917,7 +917,7 @@ impl CompressorBank {
|
|||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_ok()
|
||||
{
|
||||
let intercept = intercept + params.compressors.upwards.threshold_offset_db.value;
|
||||
let intercept = intercept + params.compressors.upwards.threshold_offset_db.value();
|
||||
for ((log2_freq, threshold), (knee_start, knee_end)) in self
|
||||
.log2_freqs
|
||||
.iter()
|
||||
|
@ -931,9 +931,9 @@ impl CompressorBank {
|
|||
let offset = log2_freq - log2_center_freq;
|
||||
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
||||
let knee_start_db =
|
||||
threshold_db - (params.compressors.upwards.knee_width_db.value / 2.0);
|
||||
threshold_db - (params.compressors.upwards.knee_width_db.value() / 2.0);
|
||||
let knee_end_db =
|
||||
threshold_db + (params.compressors.upwards.knee_width_db.value / 2.0);
|
||||
threshold_db + (params.compressors.upwards.knee_width_db.value() / 2.0);
|
||||
|
||||
*threshold = util::db_to_gain(threshold_db).max(f32::EPSILON);
|
||||
*knee_start = util::db_to_gain(knee_start_db).max(f32::EPSILON);
|
||||
|
@ -948,7 +948,7 @@ impl CompressorBank {
|
|||
{
|
||||
// If the high-frequency rolloff is enabled then higher frequency bins will have their
|
||||
// ratios reduced to reduce harshness. This follows the octave scale.
|
||||
let target_ratio_recip = params.compressors.downwards.ratio.value.recip();
|
||||
let target_ratio_recip = params.compressors.downwards.ratio.value().recip();
|
||||
if downwards_high_freq_ratio_rolloff == 0.0 {
|
||||
self.downwards_ratio_recips.fill(target_ratio_recip);
|
||||
} else {
|
||||
|
@ -971,7 +971,7 @@ impl CompressorBank {
|
|||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_ok()
|
||||
{
|
||||
let target_ratio_recip = params.compressors.upwards.ratio.value.recip();
|
||||
let target_ratio_recip = params.compressors.upwards.ratio.value().recip();
|
||||
if upwards_high_freq_ratio_rolloff == 0.0 {
|
||||
self.upwards_ratio_recips.fill(target_ratio_recip);
|
||||
} else {
|
||||
|
|
|
@ -357,7 +357,7 @@ impl Plugin for SpectralCompressor {
|
|||
// These plans have already been made during initialization we can switch between versions
|
||||
// without reallocating
|
||||
let fft_plan = &mut self.plan_for_order.as_mut().unwrap()
|
||||
[self.params.global.window_size_order.value as usize - MIN_WINDOW_ORDER];
|
||||
[self.params.global.window_size_order.value() as usize - MIN_WINDOW_ORDER];
|
||||
let num_bins = self.complex_fft_buffer.len();
|
||||
// The Hann window function spreads the DC signal out slightly, so we'll clear all 0-20 Hz
|
||||
// bins for this. With small window sizes you probably don't want this as it would result in
|
||||
|
@ -378,7 +378,7 @@ impl Plugin for SpectralCompressor {
|
|||
// threshold option. When sidechaining is enabled this is used to gain up the sidechain
|
||||
// signal instead.
|
||||
let input_gain = gain_compensation.sqrt();
|
||||
let output_gain = self.params.global.output_gain.value * gain_compensation.sqrt();
|
||||
let output_gain = self.params.global.output_gain.value() * gain_compensation.sqrt();
|
||||
// TODO: Auto makeup gain
|
||||
|
||||
// This is mixed in later with latency compensation applied
|
||||
|
@ -459,11 +459,11 @@ impl Plugin for SpectralCompressor {
|
|||
|
||||
impl SpectralCompressor {
|
||||
fn window_size(&self) -> usize {
|
||||
1 << self.params.global.window_size_order.value as usize
|
||||
1 << self.params.global.window_size_order.value() as usize
|
||||
}
|
||||
|
||||
fn overlap_times(&self) -> usize {
|
||||
1 << self.params.global.overlap_times_order.value as usize
|
||||
1 << self.params.global.overlap_times_order.value() as usize
|
||||
}
|
||||
|
||||
/// `window_size` should not exceed `MAX_WINDOW_SIZE` or this will allocate.
|
||||
|
@ -531,12 +531,12 @@ fn process_stft_main(
|
|||
// The DC and other low frequency bins doesn't contain much semantic meaning anymore after all
|
||||
// of this, so it only ends up consuming headroom. Otherwise they're gained down by the output
|
||||
// gain to prevent makeup gain from making these bins too loud.
|
||||
if params.global.dc_filter.value {
|
||||
if params.global.dc_filter.value() {
|
||||
complex_fft_buffer[..first_non_dc_bin_idx].fill(Complex32::default());
|
||||
} else {
|
||||
// The `output_gain` parameter also contains gain compensation for the windowingq, we don't
|
||||
// want to compensate for that
|
||||
let output_gain_recip = params.global.output_gain.value.recip();
|
||||
let output_gain_recip = params.global.output_gain.value().recip();
|
||||
for bin in complex_fft_buffer[..first_non_dc_bin_idx].iter_mut() {
|
||||
*bin *= output_gain_recip;
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ pub(crate) trait ParamMut: Param {
|
|||
/// value then this offset is taken into account to form the effective value.
|
||||
///
|
||||
/// This does **not** update the smoother.
|
||||
fn set_plain_value(&mut self, plain: Self::Plain);
|
||||
fn set_plain_value(&self, plain: Self::Plain);
|
||||
|
||||
/// Set this parameter based on a normalized value. The normalized value will be snapped to the
|
||||
/// step size for continuous parameters (i.e. [`FloatParam`]). If
|
||||
|
@ -179,7 +179,7 @@ pub(crate) trait ParamMut: Param {
|
|||
/// value then this offset is taken into account to form the effective value.
|
||||
///
|
||||
/// This does **not** update the smoother.
|
||||
fn set_normalized_value(&mut self, normalized: f32);
|
||||
fn set_normalized_value(&self, normalized: f32);
|
||||
|
||||
/// Add a modulation offset to the value's unmodulated value. This value sticks until this
|
||||
/// function is called again with a 0.0 value. Out of bound values will be clamped to the
|
||||
|
@ -187,10 +187,10 @@ pub(crate) trait ParamMut: Param {
|
|||
/// parameters (i.e. [`FloatParam`]).
|
||||
///
|
||||
/// This does **not** update the smoother.
|
||||
fn modulate_value(&mut self, modulation_offset: f32);
|
||||
fn modulate_value(&self, modulation_offset: f32);
|
||||
|
||||
/// Update the smoother state to point to the current value. Also used when initializing and
|
||||
/// restoring a plugin so everything is in sync. In that case the smoother should completely
|
||||
/// reset to the current value.
|
||||
fn update_smoother(&mut self, sample_rate: f32, reset: bool);
|
||||
fn update_smoother(&self, sample_rate: f32, reset: bool);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
//! Simple boolean parameters.
|
||||
|
||||
use atomic_float::AtomicF32;
|
||||
use std::fmt::Display;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::internals::ParamPtr;
|
||||
use super::{Param, ParamFlags, ParamMut};
|
||||
|
||||
/// A simple boolean parameter.
|
||||
#[repr(C, align(4))]
|
||||
pub struct BoolParam {
|
||||
/// The field's current value, after monophonic modulation has been applied.
|
||||
pub value: bool,
|
||||
value: AtomicBool,
|
||||
/// The field's current value normalized to the `[0, 1]` range.
|
||||
normalized_value: f32,
|
||||
normalized_value: AtomicF32,
|
||||
/// The field's value before any monophonic automation coming from the host has been applied.
|
||||
/// This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_value: bool,
|
||||
unmodulated_value: AtomicBool,
|
||||
/// The field's value normalized to the `[0, 1]` range before any monophonic automation coming
|
||||
/// from the host has been applied. This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_normalized_value: f32,
|
||||
unmodulated_normalized_value: AtomicF32,
|
||||
/// A value in `[-1, 1]` indicating the amount of modulation applied to
|
||||
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||
/// clamped, and this value persists after new automation events.
|
||||
modulation_offset: f32,
|
||||
modulation_offset: AtomicF32,
|
||||
/// The field's default value.
|
||||
default: bool,
|
||||
|
||||
|
@ -52,7 +53,7 @@ pub struct BoolParam {
|
|||
|
||||
impl Display for BoolParam {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match (self.value, &self.value_to_string) {
|
||||
match (self.value(), &self.value_to_string) {
|
||||
(v, Some(func)) => write!(f, "{}", func(v)),
|
||||
(true, None) => write!(f, "On"),
|
||||
(false, None) => write!(f, "Off"),
|
||||
|
@ -77,22 +78,22 @@ impl Param for BoolParam {
|
|||
|
||||
#[inline]
|
||||
fn plain_value(&self) -> Self::Plain {
|
||||
self.value
|
||||
self.value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn normalized_value(&self) -> f32 {
|
||||
self.normalized_value
|
||||
self.normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||
self.unmodulated_value
|
||||
self.unmodulated_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_normalized_value(&self) -> f32 {
|
||||
self.unmodulated_normalized_value
|
||||
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -153,48 +154,50 @@ impl Param for BoolParam {
|
|||
}
|
||||
|
||||
impl ParamMut for BoolParam {
|
||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
||||
self.unmodulated_value = plain;
|
||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
fn set_plain_value(&self, plain: Self::Plain) {
|
||||
let unmodulated_value = plain;
|
||||
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
|
||||
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||
(unmodulated_value, unmodulated_normalized_value)
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
let normalized_value =
|
||||
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||
|
||||
(self.preview_plain(normalized_value), normalized_value)
|
||||
};
|
||||
|
||||
self.value.store(value, Ordering::Relaxed);
|
||||
self.normalized_value
|
||||
.store(normalized_value, Ordering::Relaxed);
|
||||
self.unmodulated_value
|
||||
.store(unmodulated_value, Ordering::Relaxed);
|
||||
self.unmodulated_normalized_value
|
||||
.store(unmodulated_normalized_value, Ordering::Relaxed);
|
||||
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_normalized_value(&mut self, normalized: f32) {
|
||||
fn set_normalized_value(&self, normalized: f32) {
|
||||
// NOTE: The double conversion here is to make sure the state is reproducible. State is
|
||||
// saved and restored using plain values, and the new normalized value will be
|
||||
// different from `normalized`. This is not necesasry for the modulation as these
|
||||
// values are never shown to the host.
|
||||
self.unmodulated_value = self.preview_plain(normalized);
|
||||
self.unmodulated_normalized_value = self.preview_normalized(self.unmodulated_value);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
}
|
||||
self.set_plain_value(self.preview_plain(normalized))
|
||||
}
|
||||
|
||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
||||
self.modulation_offset = modulation_offset;
|
||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
||||
fn modulate_value(&self, modulation_offset: f32) {
|
||||
self.modulation_offset
|
||||
.store(modulation_offset, Ordering::Relaxed);
|
||||
|
||||
// TODO: This renormalizes this value, which is not necessary
|
||||
self.set_plain_value(self.plain_value());
|
||||
}
|
||||
|
||||
fn update_smoother(&mut self, _sample_rate: f32, _init: bool) {
|
||||
fn update_smoother(&self, _sample_rate: f32, _init: bool) {
|
||||
// Can't really smooth a binary parameter now can you
|
||||
}
|
||||
}
|
||||
|
@ -204,11 +207,11 @@ impl BoolParam {
|
|||
/// parameter.
|
||||
pub fn new(name: impl Into<String>, default: bool) -> Self {
|
||||
Self {
|
||||
value: default,
|
||||
normalized_value: if default { 1.0 } else { 0.0 },
|
||||
unmodulated_value: default,
|
||||
unmodulated_normalized_value: if default { 1.0 } else { 0.0 },
|
||||
modulation_offset: 0.0,
|
||||
value: AtomicBool::new(default),
|
||||
normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
|
||||
unmodulated_value: AtomicBool::new(default),
|
||||
unmodulated_normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
|
||||
modulation_offset: AtomicF32::new(0.0),
|
||||
default,
|
||||
|
||||
flags: ParamFlags::default(),
|
||||
|
@ -221,6 +224,13 @@ impl BoolParam {
|
|||
}
|
||||
}
|
||||
|
||||
/// The field's current plain value, after monophonic modulation has been applied. Equivalent to
|
||||
/// calling `param.plain_value()`.
|
||||
#[inline]
|
||||
pub fn value(&self) -> bool {
|
||||
self.plain_value()
|
||||
}
|
||||
|
||||
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
||||
|
|
|
@ -267,37 +267,37 @@ impl Param for EnumParamInner {
|
|||
}
|
||||
|
||||
impl<T: Enum + PartialEq> ParamMut for EnumParam<T> {
|
||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
||||
fn set_plain_value(&self, plain: Self::Plain) {
|
||||
self.inner.set_plain_value(T::to_index(plain) as i32)
|
||||
}
|
||||
|
||||
fn set_normalized_value(&mut self, normalized: f32) {
|
||||
fn set_normalized_value(&self, normalized: f32) {
|
||||
self.inner.set_normalized_value(normalized)
|
||||
}
|
||||
|
||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
||||
fn modulate_value(&self, modulation_offset: f32) {
|
||||
self.inner.modulate_value(modulation_offset)
|
||||
}
|
||||
|
||||
fn update_smoother(&mut self, sample_rate: f32, reset: bool) {
|
||||
fn update_smoother(&self, sample_rate: f32, reset: bool) {
|
||||
self.inner.update_smoother(sample_rate, reset)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParamMut for EnumParamInner {
|
||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
||||
fn set_plain_value(&self, plain: Self::Plain) {
|
||||
self.inner.set_plain_value(plain)
|
||||
}
|
||||
|
||||
fn set_normalized_value(&mut self, normalized: f32) {
|
||||
fn set_normalized_value(&self, normalized: f32) {
|
||||
self.inner.set_normalized_value(normalized)
|
||||
}
|
||||
|
||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
||||
fn modulate_value(&self, modulation_offset: f32) {
|
||||
self.inner.modulate_value(modulation_offset)
|
||||
}
|
||||
|
||||
fn update_smoother(&mut self, sample_rate: f32, reset: bool) {
|
||||
fn update_smoother(&self, sample_rate: f32, reset: bool) {
|
||||
self.inner.update_smoother(sample_rate, reset)
|
||||
}
|
||||
}
|
||||
|
@ -326,6 +326,12 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the active enum variant.
|
||||
#[inline]
|
||||
pub fn value(&self) -> T {
|
||||
self.plain_value()
|
||||
}
|
||||
|
||||
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
||||
|
@ -376,11 +382,6 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
|||
self.inner.inner = self.inner.inner.hide_in_generic_ui();
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the active enum variant.
|
||||
pub fn value(&self) -> T {
|
||||
self.plain_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl EnumParamInner {
|
||||
|
@ -402,7 +403,7 @@ impl EnumParamInner {
|
|||
|
||||
/// Set the parameter based on a serialized stable string identifier. Return whether the ID was
|
||||
/// known and the parameter was set.
|
||||
pub fn set_from_id(&mut self, id: &str) -> bool {
|
||||
pub fn set_from_id(&self, id: &str) -> bool {
|
||||
match self
|
||||
.ids
|
||||
.and_then(|ids| ids.iter().position(|candidate| *candidate == id))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Continuous (or discrete, with a step size) floating point parameters.
|
||||
|
||||
use atomic_float::AtomicF32;
|
||||
use std::fmt::Display;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::internals::ParamPtr;
|
||||
|
@ -10,31 +12,21 @@ use super::{Param, ParamFlags, ParamMut};
|
|||
|
||||
/// A floating point parameter that's stored unnormalized. The range is used for the normalization
|
||||
/// process.
|
||||
//
|
||||
// XXX: To keep the API simple and to allow the optimizer to do its thing, the values are stored as
|
||||
// plain primitive values that are modified through the `*mut` pointers from the plugin's
|
||||
// `Params` object. Technically modifying these while the GUI is open is unsound. We could
|
||||
// remedy this by changing `value` to be an atomic type and adding a function also called
|
||||
// `value()` to load that value, but in practice that should not be necessary if we don't do
|
||||
// anything crazy other than modifying this value. On both AArch64 and x86(_64) reads and
|
||||
// writes to naturally aligned values up to word size are atomic, so there's no risk of reading
|
||||
// a partially written to value here. We should probably reconsider this at some point though.
|
||||
#[repr(C, align(4))]
|
||||
pub struct FloatParam {
|
||||
/// The field's current plain value, after monophonic modulation has been applied.
|
||||
pub value: f32,
|
||||
value: AtomicF32,
|
||||
/// The field's current value normalized to the `[0, 1]` range.
|
||||
normalized_value: f32,
|
||||
normalized_value: AtomicF32,
|
||||
/// The field's plain, unnormalized value before any monophonic automation coming from the host
|
||||
/// has been applied. This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_value: f32,
|
||||
unmodulated_value: AtomicF32,
|
||||
/// The field's value normalized to the `[0, 1]` range before any monophonic automation coming
|
||||
/// from the host has been applied. This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_normalized_value: f32,
|
||||
unmodulated_normalized_value: AtomicF32,
|
||||
/// A value in `[-1, 1]` indicating the amount of modulation applied to
|
||||
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||
/// clamped, and this value persists after new automation events.
|
||||
modulation_offset: f32,
|
||||
modulation_offset: AtomicF32,
|
||||
/// The field's default plain, unnormalized value.
|
||||
default: f32,
|
||||
/// An optional smoother that will automatically interpolate between the new automation values
|
||||
|
@ -84,12 +76,12 @@ pub struct FloatParam {
|
|||
impl Display for FloatParam {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match (&self.value_to_string, &self.step_size) {
|
||||
(Some(func), _) => write!(f, "{}{}", func(self.value), self.unit),
|
||||
(Some(func), _) => write!(f, "{}{}", func(self.value()), self.unit),
|
||||
(None, Some(step_size)) => {
|
||||
let num_digits = decimals_from_step_size(*step_size);
|
||||
write!(f, "{:.num_digits$}{}", self.value, self.unit)
|
||||
write!(f, "{:.num_digits$}{}", self.value(), self.unit)
|
||||
}
|
||||
_ => write!(f, "{}{}", self.value, self.unit),
|
||||
_ => write!(f, "{}{}", self.value(), self.unit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,22 +103,22 @@ impl Param for FloatParam {
|
|||
|
||||
#[inline]
|
||||
fn plain_value(&self) -> Self::Plain {
|
||||
self.value
|
||||
self.value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn normalized_value(&self) -> Self::Plain {
|
||||
self.normalized_value
|
||||
fn normalized_value(&self) -> f32 {
|
||||
self.normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||
self.unmodulated_value
|
||||
self.unmodulated_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_normalized_value(&self) -> f32 {
|
||||
self.unmodulated_normalized_value
|
||||
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -196,52 +188,54 @@ impl Param for FloatParam {
|
|||
}
|
||||
|
||||
impl ParamMut for FloatParam {
|
||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
||||
self.unmodulated_value = plain;
|
||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
fn set_plain_value(&self, plain: Self::Plain) {
|
||||
let unmodulated_value = plain;
|
||||
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
|
||||
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||
(unmodulated_value, unmodulated_normalized_value)
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
let normalized_value =
|
||||
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||
|
||||
(self.preview_plain(normalized_value), normalized_value)
|
||||
};
|
||||
|
||||
self.value.store(value, Ordering::Relaxed);
|
||||
self.normalized_value
|
||||
.store(normalized_value, Ordering::Relaxed);
|
||||
self.unmodulated_value
|
||||
.store(unmodulated_value, Ordering::Relaxed);
|
||||
self.unmodulated_normalized_value
|
||||
.store(unmodulated_normalized_value, Ordering::Relaxed);
|
||||
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_normalized_value(&mut self, normalized: f32) {
|
||||
fn set_normalized_value(&self, normalized: f32) {
|
||||
// NOTE: The double conversion here is to make sure the state is reproducible. State is
|
||||
// saved and restored using plain values, and the new normalized value will be
|
||||
// different from `normalized`. This is not necesasry for the modulation as these
|
||||
// values are never shown to the host.
|
||||
self.unmodulated_value = self.preview_plain(normalized);
|
||||
self.unmodulated_normalized_value = self.preview_normalized(self.unmodulated_value);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
}
|
||||
self.set_plain_value(self.preview_plain(normalized))
|
||||
}
|
||||
|
||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
||||
self.modulation_offset = modulation_offset;
|
||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
||||
fn modulate_value(&self, modulation_offset: f32) {
|
||||
self.modulation_offset
|
||||
.store(modulation_offset, Ordering::Relaxed);
|
||||
|
||||
// TODO: This renormalizes this value, which is not necessary
|
||||
self.set_plain_value(self.plain_value());
|
||||
}
|
||||
|
||||
fn update_smoother(&mut self, sample_rate: f32, reset: bool) {
|
||||
fn update_smoother(&self, sample_rate: f32, reset: bool) {
|
||||
if reset {
|
||||
self.smoothed.reset(self.value);
|
||||
self.smoothed.reset(self.plain_value());
|
||||
} else {
|
||||
self.smoothed.set_target(sample_rate, self.value);
|
||||
self.smoothed.set_target(sample_rate, self.plain_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,11 +245,11 @@ impl FloatParam {
|
|||
/// parameter.
|
||||
pub fn new(name: impl Into<String>, default: f32, range: FloatRange) -> Self {
|
||||
Self {
|
||||
value: default,
|
||||
normalized_value: range.normalize(default),
|
||||
unmodulated_value: default,
|
||||
unmodulated_normalized_value: range.normalize(default),
|
||||
modulation_offset: 0.0,
|
||||
value: AtomicF32::new(default),
|
||||
normalized_value: AtomicF32::new(range.normalize(default)),
|
||||
unmodulated_value: AtomicF32::new(default),
|
||||
unmodulated_normalized_value: AtomicF32::new(range.normalize(default)),
|
||||
modulation_offset: AtomicF32::new(0.0),
|
||||
default,
|
||||
smoothed: Smoother::none(),
|
||||
|
||||
|
@ -272,6 +266,13 @@ impl FloatParam {
|
|||
}
|
||||
}
|
||||
|
||||
/// The field's current plain value, after monophonic modulation has been applied. Equivalent to
|
||||
/// calling `param.plain_value()`.
|
||||
#[inline]
|
||||
pub fn value(&self) -> f32 {
|
||||
self.plain_value()
|
||||
}
|
||||
|
||||
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Stepped integer parameters.
|
||||
|
||||
use atomic_float::AtomicF32;
|
||||
use std::fmt::Display;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::internals::ParamPtr;
|
||||
|
@ -10,31 +12,21 @@ use super::{Param, ParamFlags, ParamMut};
|
|||
|
||||
/// A discrete integer parameter that's stored unnormalized. The range is used for the normalization
|
||||
/// process.
|
||||
//
|
||||
// XXX: To keep the API simple and to allow the optimizer to do its thing, the values are stored as
|
||||
// plain primitive values that are modified through the `*mut` pointers from the plugin's
|
||||
// `Params` object. Technically modifying these while the GUI is open is unsound. We could
|
||||
// remedy this by changing `value` to be an atomic type and adding a function also called
|
||||
// `value()` to load that value, but in practice that should not be necessary if we don't do
|
||||
// anything crazy other than modifying this value. On both AArch64 and x86(_64) reads and
|
||||
// writes to naturally aligned values up to word size are atomic, so there's no risk of reading
|
||||
// a partially written to value here. We should probably reconsider this at some point though.
|
||||
#[repr(C, align(4))]
|
||||
pub struct IntParam {
|
||||
/// The field's current plain value, after monophonic modulation has been applied.
|
||||
pub value: i32,
|
||||
value: AtomicI32,
|
||||
/// The field's current value normalized to the `[0, 1]` range.
|
||||
normalized_value: f32,
|
||||
normalized_value: AtomicF32,
|
||||
/// The field's plain, unnormalized value before any monophonic automation coming from the host
|
||||
/// has been applied. This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_value: i32,
|
||||
unmodulated_value: AtomicI32,
|
||||
/// The field's value normalized to the `[0, 1]` range before any monophonic automation coming
|
||||
/// from the host has been applied. This will always be the same as `value` for VST3 plugins.
|
||||
unmodulated_normalized_value: f32,
|
||||
unmodulated_normalized_value: AtomicF32,
|
||||
/// A value in `[-1, 1]` indicating the amount of modulation applied to
|
||||
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||
/// clamped, and this value persists after new automation events.
|
||||
modulation_offset: f32,
|
||||
modulation_offset: AtomicF32,
|
||||
/// The field's default plain, unnormalized value.
|
||||
default: i32,
|
||||
/// An optional smoother that will automatically interpolate between the new automation values
|
||||
|
@ -80,8 +72,8 @@ pub struct IntParam {
|
|||
impl Display for IntParam {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.value_to_string {
|
||||
Some(func) => write!(f, "{}{}", func(self.value), self.unit),
|
||||
_ => write!(f, "{}{}", self.value, self.unit),
|
||||
Some(func) => write!(f, "{}{}", func(self.value()), self.unit),
|
||||
_ => write!(f, "{}{}", self.value(), self.unit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,22 +95,22 @@ impl Param for IntParam {
|
|||
|
||||
#[inline]
|
||||
fn plain_value(&self) -> Self::Plain {
|
||||
self.value
|
||||
self.value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn normalized_value(&self) -> f32 {
|
||||
self.normalized_value
|
||||
self.normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||
self.unmodulated_value
|
||||
self.unmodulated_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unmodulated_normalized_value(&self) -> f32 {
|
||||
self.unmodulated_normalized_value
|
||||
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -176,52 +168,54 @@ impl Param for IntParam {
|
|||
}
|
||||
|
||||
impl ParamMut for IntParam {
|
||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
||||
self.unmodulated_value = plain;
|
||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
fn set_plain_value(&self, plain: Self::Plain) {
|
||||
let unmodulated_value = plain;
|
||||
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||
|
||||
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||
(unmodulated_value, unmodulated_normalized_value)
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
let normalized_value =
|
||||
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||
|
||||
(self.preview_plain(normalized_value), normalized_value)
|
||||
};
|
||||
|
||||
self.value.store(value, Ordering::Relaxed);
|
||||
self.normalized_value
|
||||
.store(normalized_value, Ordering::Relaxed);
|
||||
self.unmodulated_value
|
||||
.store(unmodulated_value, Ordering::Relaxed);
|
||||
self.unmodulated_normalized_value
|
||||
.store(unmodulated_normalized_value, Ordering::Relaxed);
|
||||
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_normalized_value(&mut self, normalized: f32) {
|
||||
fn set_normalized_value(&self, normalized: f32) {
|
||||
// NOTE: The double conversion here is to make sure the state is reproducible. State is
|
||||
// saved and restored using plain values, and the new normalized value will be
|
||||
// different from `normalized`. This is not necesasry for the modulation as these
|
||||
// values are never shown to the host.
|
||||
self.unmodulated_value = self.preview_plain(normalized);
|
||||
self.unmodulated_normalized_value = self.preview_normalized(self.unmodulated_value);
|
||||
if self.modulation_offset == 0.0 {
|
||||
self.value = self.unmodulated_value;
|
||||
self.normalized_value = self.unmodulated_normalized_value;
|
||||
} else {
|
||||
self.normalized_value =
|
||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
||||
self.value = self.preview_plain(self.normalized_value);
|
||||
}
|
||||
if let Some(f) = &self.value_changed {
|
||||
f(self.value);
|
||||
}
|
||||
self.set_plain_value(self.preview_plain(normalized))
|
||||
}
|
||||
|
||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
||||
self.modulation_offset = modulation_offset;
|
||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
||||
fn modulate_value(&self, modulation_offset: f32) {
|
||||
self.modulation_offset
|
||||
.store(modulation_offset, Ordering::Relaxed);
|
||||
|
||||
// TODO: This renormalizes this value, which is not necessary
|
||||
self.set_plain_value(self.plain_value());
|
||||
}
|
||||
|
||||
fn update_smoother(&mut self, sample_rate: f32, reset: bool) {
|
||||
fn update_smoother(&self, sample_rate: f32, reset: bool) {
|
||||
if reset {
|
||||
self.smoothed.reset(self.value);
|
||||
self.smoothed.reset(self.plain_value());
|
||||
} else {
|
||||
self.smoothed.set_target(sample_rate, self.value);
|
||||
self.smoothed.set_target(sample_rate, self.plain_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,11 +225,11 @@ impl IntParam {
|
|||
/// parameter.
|
||||
pub fn new(name: impl Into<String>, default: i32, range: IntRange) -> Self {
|
||||
Self {
|
||||
value: default,
|
||||
normalized_value: range.normalize(default),
|
||||
unmodulated_value: default,
|
||||
unmodulated_normalized_value: range.normalize(default),
|
||||
modulation_offset: 0.0,
|
||||
value: AtomicI32::new(default),
|
||||
normalized_value: AtomicF32::new(range.normalize(default)),
|
||||
unmodulated_value: AtomicI32::new(default),
|
||||
unmodulated_normalized_value: AtomicF32::new(range.normalize(default)),
|
||||
modulation_offset: AtomicF32::new(0.0),
|
||||
default,
|
||||
smoothed: Smoother::none(),
|
||||
|
||||
|
@ -251,6 +245,13 @@ impl IntParam {
|
|||
}
|
||||
}
|
||||
|
||||
/// The field's current plain value, after monophonic modulation has been applied. Equivalent to
|
||||
/// calling `param.plain_value()`.
|
||||
#[inline]
|
||||
pub fn value(&self) -> i32 {
|
||||
self.plain_value()
|
||||
}
|
||||
|
||||
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
||||
|
|
|
@ -115,12 +115,12 @@ unsafe impl<P: Params> Params for Arc<P> {
|
|||
/// erasure.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum ParamPtr {
|
||||
FloatParam(*mut super::FloatParam),
|
||||
IntParam(*mut super::IntParam),
|
||||
BoolParam(*mut super::BoolParam),
|
||||
FloatParam(*const super::FloatParam),
|
||||
IntParam(*const super::IntParam),
|
||||
BoolParam(*const super::BoolParam),
|
||||
/// Since we can't encode the actual enum here, this inner parameter struct contains all of the
|
||||
/// relevant information from the enum so it can be type erased.
|
||||
EnumParam(*mut super::enums::EnumParamInner),
|
||||
EnumParam(*const super::enums::EnumParamInner),
|
||||
}
|
||||
|
||||
// These pointers only point to fields on structs kept in an `Arc<dyn Params>`, and the caller
|
||||
|
|
Loading…
Reference in a new issue