💥 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
|
chronological order. If a new feature did not require any changes to existing
|
||||||
code then it will not be listed here.
|
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]
|
## [2022-09-04]
|
||||||
|
|
||||||
- `Smoother::next_block_mapped()` and `Smoother::next_block_exact_mapped()` have
|
- `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) {
|
for (channel_samples, rm_outputs) in block.iter_samples().zip(&mut rm_outputs) {
|
||||||
let output_gain = self.params.output_gain.smoothed.next();
|
let output_gain = self.params.output_gain.smoothed.next();
|
||||||
for (sample, rm_output) in channel_samples.into_iter().zip(rm_outputs) {
|
for (sample, rm_output) in channel_samples.into_iter().zip(rm_outputs) {
|
||||||
|
|
|
@ -291,7 +291,7 @@ impl Crossover {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.iir_crossover.process(
|
self.iir_crossover.process(
|
||||||
self.params.num_bands.value as usize,
|
self.params.num_bands.value() as usize,
|
||||||
&main_channel_samples,
|
&main_channel_samples,
|
||||||
bands,
|
bands,
|
||||||
);
|
);
|
||||||
|
@ -331,7 +331,7 @@ impl Crossover {
|
||||||
];
|
];
|
||||||
|
|
||||||
self.fir_crossover.process(
|
self.fir_crossover.process(
|
||||||
self.params.num_bands.value as usize,
|
self.params.num_bands.value() as usize,
|
||||||
main_io,
|
main_io,
|
||||||
band_outputs,
|
band_outputs,
|
||||||
channel_idx,
|
channel_idx,
|
||||||
|
@ -371,12 +371,12 @@ impl Crossover {
|
||||||
match self.params.crossover_type.value() {
|
match self.params.crossover_type.value() {
|
||||||
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
|
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
|
||||||
self.buffer_config.sample_rate,
|
self.buffer_config.sample_rate,
|
||||||
self.params.num_bands.value as usize,
|
self.params.num_bands.value() as usize,
|
||||||
crossover_frequencies,
|
crossover_frequencies,
|
||||||
),
|
),
|
||||||
CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update(
|
CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update(
|
||||||
self.buffer_config.sample_rate,
|
self.buffer_config.sample_rate,
|
||||||
self.params.num_bands.value as usize,
|
self.params.num_bands.value() as usize,
|
||||||
crossover_frequencies,
|
crossover_frequencies,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ struct Diopser {
|
||||||
should_update_filters: Arc<AtomicBool>,
|
should_update_filters: Arc<AtomicBool>,
|
||||||
/// If this is 1 and any of the filter parameters are still smoothing, thenn the filter
|
/// 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
|
/// 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
|
/// reduce the DSP load of automation parameters. It can also cause some fun sounding glitchy
|
||||||
/// effects when the precision is low.
|
/// effects when the precision is low.
|
||||||
next_filter_smoothing_in: i32,
|
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
|
// necessary, and allow smoothing only every n samples using the automation precision
|
||||||
// parameter
|
// parameter
|
||||||
let smoothing_interval =
|
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() {
|
for mut channel_samples in buffer.iter_samples() {
|
||||||
self.maybe_update_filters(smoothing_interval);
|
self.maybe_update_filters(smoothing_interval);
|
||||||
|
@ -303,7 +303,7 @@ impl Plugin for Diopser {
|
||||||
for filter in self
|
for filter in self
|
||||||
.filters
|
.filters
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.take(self.params.filter_stages.value as usize)
|
.take(self.params.filter_stages.value() as usize)
|
||||||
{
|
{
|
||||||
samples = filter.process(samples);
|
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
|
// TODO: This wrecks the DSP load at high smoothing accuracy, perhaps also use SIMD here
|
||||||
const MIN_FREQUENCY: f32 = 5.0;
|
const MIN_FREQUENCY: f32 = 5.0;
|
||||||
let max_frequency = self.sample_rate / 2.05;
|
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]
|
// The index of the filter normalized to range [-1, 1]
|
||||||
let filter_proportion =
|
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
|
// The spread parameter adds an offset to the frequency depending on the number of the
|
||||||
// filter
|
// filter
|
||||||
|
|
|
@ -120,7 +120,7 @@ impl Plugin for Gain {
|
||||||
setter.end_set_parameter(¶ms.gain);
|
setter.end_set_parameter(¶ms.gain);
|
||||||
new_value
|
new_value
|
||||||
}
|
}
|
||||||
None => params.gain.value as f64,
|
None => params.gain.value() as f64,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.suffix(" dB"),
|
.suffix(" dB"),
|
||||||
|
|
|
@ -212,8 +212,8 @@ impl Plugin for PolyModSynth {
|
||||||
} => {
|
} => {
|
||||||
let initial_phase: f32 = self.prng.gen();
|
let initial_phase: f32 = self.prng.gen();
|
||||||
// This starts with the attack portion of the amplitude envelope
|
// This starts with the attack portion of the amplitude envelope
|
||||||
let mut amp_envelope = Smoother::new(SmoothingStyle::Exponential(
|
let amp_envelope = Smoother::new(SmoothingStyle::Exponential(
|
||||||
self.params.amp_attack_ms.value,
|
self.params.amp_attack_ms.value(),
|
||||||
));
|
));
|
||||||
amp_envelope.reset(0.0);
|
amp_envelope.reset(0.0);
|
||||||
amp_envelope.set_target(sample_rate, 1.0);
|
amp_envelope.set_target(sample_rate, 1.0);
|
||||||
|
@ -522,7 +522,7 @@ impl PolyModSynth {
|
||||||
{
|
{
|
||||||
*releasing = true;
|
*releasing = true;
|
||||||
amp_envelope.style =
|
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);
|
amp_envelope.set_target(sample_rate, 0.0);
|
||||||
|
|
||||||
// If this targetted a single voice ID, we're done here. Otherwise there may be
|
// 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();
|
let gain = self.params.gain.smoothed.next();
|
||||||
|
|
||||||
// This plugin can be either triggered by MIDI or controleld by a parameter
|
// 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
|
// Act on the next MIDI event
|
||||||
while let Some(event) = next_event {
|
while let Some(event) = next_event {
|
||||||
if event.timing() > sample_id as u32 {
|
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
|
// These plans have already been made during initialization we can switch between versions
|
||||||
// without reallocating
|
// without reallocating
|
||||||
let fft_plan = &mut self.plan_for_order.as_mut().unwrap()
|
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;
|
let mut smoothed_pitch_value = 0.0;
|
||||||
self.stft
|
self.stft
|
||||||
|
@ -395,11 +395,11 @@ impl Plugin for PubertySimulator {
|
||||||
|
|
||||||
impl PubertySimulator {
|
impl PubertySimulator {
|
||||||
fn window_size(&self) -> usize {
|
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 {
|
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.
|
/// `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;
|
let mut is_peaking = false;
|
||||||
for sample in channel_samples.iter_mut() {
|
for sample in channel_samples.iter_mut() {
|
||||||
if sample.is_finite() {
|
if sample.is_finite() {
|
||||||
is_peaking |= sample.abs() > self.params.threshold_gain.value;
|
is_peaking |= sample.abs() > self.params.threshold_gain.value();
|
||||||
} else {
|
} else {
|
||||||
// Infinity or NaN values need to be completely filtered out, because otherwise
|
// Infinity or NaN values need to be completely filtered out, because otherwise
|
||||||
// we'll try to mix them back into the signal later
|
// 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
|
// 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
|
// directly. And the sine wave is scaled down to the threshold minus 24 dB
|
||||||
let sine_sample =
|
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;
|
self.osc_phase_tau += self.osc_phase_tau_dt;
|
||||||
if self.osc_phase_tau >= std::f32::consts::TAU {
|
if self.osc_phase_tau >= std::f32::consts::TAU {
|
||||||
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.
|
// for every 512 samples.
|
||||||
let effective_sample_rate =
|
let effective_sample_rate =
|
||||||
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
|
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
|
0.0
|
||||||
} else {
|
} 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()
|
.exp()
|
||||||
};
|
};
|
||||||
let attack_new_t = 1.0 - attack_old_t;
|
let attack_new_t = 1.0 - attack_old_t;
|
||||||
// The same as `attack_old_t`, but for the release phase of the envelope follower
|
// 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
|
0.0
|
||||||
} else {
|
} 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()
|
.exp()
|
||||||
};
|
};
|
||||||
let release_new_t = 1.0 - release_old_t;
|
let release_new_t = 1.0 - release_old_t;
|
||||||
|
@ -611,24 +611,24 @@ impl CompressorBank {
|
||||||
// See `update_envelopes()`
|
// See `update_envelopes()`
|
||||||
let effective_sample_rate =
|
let effective_sample_rate =
|
||||||
self.sample_rate / (self.window_size as f32 / overlap_times as f32);
|
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
|
0.0
|
||||||
} else {
|
} 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()
|
.exp()
|
||||||
};
|
};
|
||||||
let attack_new_t = 1.0 - attack_old_t;
|
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
|
0.0
|
||||||
} else {
|
} 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()
|
.exp()
|
||||||
};
|
};
|
||||||
let release_new_t = 1.0 - release_old_t;
|
let release_new_t = 1.0 - release_old_t;
|
||||||
|
|
||||||
// For the channel linking
|
// For the channel linking
|
||||||
let num_channels = self.sidechain_spectrum_magnitudes.len() as f32;
|
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));
|
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() {
|
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
|
// 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.
|
// than with lower knee values. These scaling factors are used as exponents.
|
||||||
let downwards_knee_scaling_factor =
|
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
|
// Note the square root here, since the curve needs to go the other way for the upwards
|
||||||
// version
|
// version
|
||||||
let upwards_knee_scaling_factor =
|
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_thresholds.len() == buffer.len());
|
||||||
assert!(self.downwards_ratio_recips.len() == buffer.len());
|
assert!(self.downwards_ratio_recips.len() == buffer.len());
|
||||||
|
@ -768,13 +768,13 @@ impl CompressorBank {
|
||||||
) {
|
) {
|
||||||
// See `compress` for more details
|
// See `compress` for more details
|
||||||
let downwards_knee_scaling_factor =
|
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 =
|
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
|
// For the channel linking
|
||||||
let num_channels = self.sidechain_spectrum_magnitudes.len() as f32;
|
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));
|
let this_channel_t = 1.0 - (other_channels_t * (num_channels - 1.0));
|
||||||
|
|
||||||
assert!(self.sidechain_spectrum_magnitudes[channel_idx].len() == buffer.len());
|
assert!(self.sidechain_spectrum_magnitudes[channel_idx].len() == buffer.len());
|
||||||
|
@ -859,23 +859,23 @@ impl CompressorBank {
|
||||||
fn update_if_needed(&mut self, params: &SpectralCompressorParams) {
|
fn update_if_needed(&mut self, params: &SpectralCompressorParams) {
|
||||||
// The threshold curve is a polynomial in log-log (decibels-octaves) space. The reuslt from
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// be a flat offset to the sidechain input at the default settings.
|
||||||
let slope = match params.threshold.mode.value() {
|
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 => {
|
ThresholdMode::SidechainMatch | ThresholdMode::SidechainCompress => {
|
||||||
params.threshold.curve_slope.value
|
params.threshold.curve_slope.value()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let curve = params.threshold.curve_curve.value;
|
let curve = params.threshold.curve_curve.value();
|
||||||
let log2_center_freq = params.threshold.center_frequency.value.log2();
|
let log2_center_freq = params.threshold.center_frequency.value().log2();
|
||||||
|
|
||||||
let downwards_high_freq_ratio_rolloff =
|
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 =
|
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
|
let log2_nyquist_freq = self
|
||||||
.log2_freqs
|
.log2_freqs
|
||||||
.last()
|
.last()
|
||||||
|
@ -886,7 +886,7 @@ impl CompressorBank {
|
||||||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||||
.is_ok()
|
.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
|
for ((log2_freq, threshold), (knee_start, knee_end)) in self
|
||||||
.log2_freqs
|
.log2_freqs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -900,9 +900,9 @@ impl CompressorBank {
|
||||||
let offset = log2_freq - log2_center_freq;
|
let offset = log2_freq - log2_center_freq;
|
||||||
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
||||||
let knee_start_db =
|
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 =
|
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
|
// This threshold must never reach zero as it's used in divisions to get a gain ratio
|
||||||
// above the threshold
|
// above the threshold
|
||||||
|
@ -917,7 +917,7 @@ impl CompressorBank {
|
||||||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||||
.is_ok()
|
.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
|
for ((log2_freq, threshold), (knee_start, knee_end)) in self
|
||||||
.log2_freqs
|
.log2_freqs
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -931,9 +931,9 @@ impl CompressorBank {
|
||||||
let offset = log2_freq - log2_center_freq;
|
let offset = log2_freq - log2_center_freq;
|
||||||
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
let threshold_db = intercept + (slope * offset) + (curve * offset * offset);
|
||||||
let knee_start_db =
|
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 =
|
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);
|
*threshold = util::db_to_gain(threshold_db).max(f32::EPSILON);
|
||||||
*knee_start = util::db_to_gain(knee_start_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
|
// If the high-frequency rolloff is enabled then higher frequency bins will have their
|
||||||
// ratios reduced to reduce harshness. This follows the octave scale.
|
// 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 {
|
if downwards_high_freq_ratio_rolloff == 0.0 {
|
||||||
self.downwards_ratio_recips.fill(target_ratio_recip);
|
self.downwards_ratio_recips.fill(target_ratio_recip);
|
||||||
} else {
|
} else {
|
||||||
|
@ -971,7 +971,7 @@ impl CompressorBank {
|
||||||
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
|
||||||
.is_ok()
|
.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 {
|
if upwards_high_freq_ratio_rolloff == 0.0 {
|
||||||
self.upwards_ratio_recips.fill(target_ratio_recip);
|
self.upwards_ratio_recips.fill(target_ratio_recip);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -357,7 +357,7 @@ impl Plugin for SpectralCompressor {
|
||||||
// These plans have already been made during initialization we can switch between versions
|
// These plans have already been made during initialization we can switch between versions
|
||||||
// without reallocating
|
// without reallocating
|
||||||
let fft_plan = &mut self.plan_for_order.as_mut().unwrap()
|
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();
|
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
|
// 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
|
// 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
|
// threshold option. When sidechaining is enabled this is used to gain up the sidechain
|
||||||
// signal instead.
|
// signal instead.
|
||||||
let input_gain = gain_compensation.sqrt();
|
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
|
// TODO: Auto makeup gain
|
||||||
|
|
||||||
// This is mixed in later with latency compensation applied
|
// This is mixed in later with latency compensation applied
|
||||||
|
@ -459,11 +459,11 @@ impl Plugin for SpectralCompressor {
|
||||||
|
|
||||||
impl SpectralCompressor {
|
impl SpectralCompressor {
|
||||||
fn window_size(&self) -> usize {
|
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 {
|
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.
|
/// `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
|
// 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
|
// 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.
|
// 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());
|
complex_fft_buffer[..first_non_dc_bin_idx].fill(Complex32::default());
|
||||||
} else {
|
} else {
|
||||||
// The `output_gain` parameter also contains gain compensation for the windowingq, we don't
|
// The `output_gain` parameter also contains gain compensation for the windowingq, we don't
|
||||||
// want to compensate for that
|
// 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() {
|
for bin in complex_fft_buffer[..first_non_dc_bin_idx].iter_mut() {
|
||||||
*bin *= output_gain_recip;
|
*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.
|
/// value then this offset is taken into account to form the effective value.
|
||||||
///
|
///
|
||||||
/// This does **not** update the smoother.
|
/// 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
|
/// 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
|
/// 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.
|
/// value then this offset is taken into account to form the effective value.
|
||||||
///
|
///
|
||||||
/// This does **not** update the smoother.
|
/// 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
|
/// 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
|
/// 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`]).
|
/// parameters (i.e. [`FloatParam`]).
|
||||||
///
|
///
|
||||||
/// This does **not** update the smoother.
|
/// 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
|
/// 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
|
/// restoring a plugin so everything is in sync. In that case the smoother should completely
|
||||||
/// reset to the current value.
|
/// 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.
|
//! Simple boolean parameters.
|
||||||
|
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::internals::ParamPtr;
|
use super::internals::ParamPtr;
|
||||||
use super::{Param, ParamFlags, ParamMut};
|
use super::{Param, ParamFlags, ParamMut};
|
||||||
|
|
||||||
/// A simple boolean parameter.
|
/// A simple boolean parameter.
|
||||||
#[repr(C, align(4))]
|
|
||||||
pub struct BoolParam {
|
pub struct BoolParam {
|
||||||
/// The field's current value, after monophonic modulation has been applied.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||||
/// clamped, and this value persists after new automation events.
|
/// clamped, and this value persists after new automation events.
|
||||||
modulation_offset: f32,
|
modulation_offset: AtomicF32,
|
||||||
/// The field's default value.
|
/// The field's default value.
|
||||||
default: bool,
|
default: bool,
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ pub struct BoolParam {
|
||||||
|
|
||||||
impl Display for BoolParam {
|
impl Display for BoolParam {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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)),
|
(v, Some(func)) => write!(f, "{}", func(v)),
|
||||||
(true, None) => write!(f, "On"),
|
(true, None) => write!(f, "On"),
|
||||||
(false, None) => write!(f, "Off"),
|
(false, None) => write!(f, "Off"),
|
||||||
|
@ -77,22 +78,22 @@ impl Param for BoolParam {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn plain_value(&self) -> Self::Plain {
|
fn plain_value(&self) -> Self::Plain {
|
||||||
self.value
|
self.value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn normalized_value(&self) -> f32 {
|
fn normalized_value(&self) -> f32 {
|
||||||
self.normalized_value
|
self.normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||||
self.unmodulated_value
|
self.unmodulated_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_normalized_value(&self) -> f32 {
|
fn unmodulated_normalized_value(&self) -> f32 {
|
||||||
self.unmodulated_normalized_value
|
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -153,48 +154,50 @@ impl Param for BoolParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParamMut for BoolParam {
|
impl ParamMut for BoolParam {
|
||||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
fn set_plain_value(&self, plain: Self::Plain) {
|
||||||
self.unmodulated_value = plain;
|
let unmodulated_value = plain;
|
||||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||||
if self.modulation_offset == 0.0 {
|
|
||||||
self.value = self.unmodulated_value;
|
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||||
self.normalized_value = self.unmodulated_normalized_value;
|
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||||
|
(unmodulated_value, unmodulated_normalized_value)
|
||||||
} else {
|
} else {
|
||||||
self.normalized_value =
|
let normalized_value =
|
||||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||||
self.value = self.preview_plain(self.normalized_value);
|
|
||||||
}
|
(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 {
|
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
|
// 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
|
// 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
|
// different from `normalized`. This is not necesasry for the modulation as these
|
||||||
// values are never shown to the host.
|
// values are never shown to the host.
|
||||||
self.unmodulated_value = self.preview_plain(normalized);
|
self.set_plain_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
fn modulate_value(&self, modulation_offset: f32) {
|
||||||
self.modulation_offset = modulation_offset;
|
self.modulation_offset
|
||||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
.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
|
// Can't really smooth a binary parameter now can you
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,11 +207,11 @@ impl BoolParam {
|
||||||
/// parameter.
|
/// parameter.
|
||||||
pub fn new(name: impl Into<String>, default: bool) -> Self {
|
pub fn new(name: impl Into<String>, default: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: default,
|
value: AtomicBool::new(default),
|
||||||
normalized_value: if default { 1.0 } else { 0.0 },
|
normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
|
||||||
unmodulated_value: default,
|
unmodulated_value: AtomicBool::new(default),
|
||||||
unmodulated_normalized_value: if default { 1.0 } else { 0.0 },
|
unmodulated_normalized_value: AtomicF32::new(if default { 1.0 } else { 0.0 }),
|
||||||
modulation_offset: 0.0,
|
modulation_offset: AtomicF32::new(0.0),
|
||||||
default,
|
default,
|
||||||
|
|
||||||
flags: ParamFlags::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
|
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
/// 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> {
|
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)
|
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)
|
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)
|
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)
|
self.inner.update_smoother(sample_rate, reset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParamMut for EnumParamInner {
|
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)
|
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)
|
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)
|
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)
|
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
|
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
/// 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.inner.inner = self.inner.inner.hide_in_generic_ui();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the active enum variant.
|
|
||||||
pub fn value(&self) -> T {
|
|
||||||
self.plain_value()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnumParamInner {
|
impl EnumParamInner {
|
||||||
|
@ -402,7 +403,7 @@ impl EnumParamInner {
|
||||||
|
|
||||||
/// Set the parameter based on a serialized stable string identifier. Return whether the ID was
|
/// Set the parameter based on a serialized stable string identifier. Return whether the ID was
|
||||||
/// known and the parameter was set.
|
/// 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
|
match self
|
||||||
.ids
|
.ids
|
||||||
.and_then(|ids| ids.iter().position(|candidate| *candidate == id))
|
.and_then(|ids| ids.iter().position(|candidate| *candidate == id))
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
//! Continuous (or discrete, with a step size) floating point parameters.
|
//! Continuous (or discrete, with a step size) floating point parameters.
|
||||||
|
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::internals::ParamPtr;
|
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
|
/// A floating point parameter that's stored unnormalized. The range is used for the normalization
|
||||||
/// process.
|
/// 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 {
|
pub struct FloatParam {
|
||||||
/// The field's current plain value, after monophonic modulation has been applied.
|
/// 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.
|
/// 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
|
/// 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.
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||||
/// clamped, and this value persists after new automation events.
|
/// clamped, and this value persists after new automation events.
|
||||||
modulation_offset: f32,
|
modulation_offset: AtomicF32,
|
||||||
/// The field's default plain, unnormalized value.
|
/// The field's default plain, unnormalized value.
|
||||||
default: f32,
|
default: f32,
|
||||||
/// An optional smoother that will automatically interpolate between the new automation values
|
/// An optional smoother that will automatically interpolate between the new automation values
|
||||||
|
@ -84,12 +76,12 @@ pub struct FloatParam {
|
||||||
impl Display for FloatParam {
|
impl Display for FloatParam {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match (&self.value_to_string, &self.step_size) {
|
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)) => {
|
(None, Some(step_size)) => {
|
||||||
let num_digits = decimals_from_step_size(*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]
|
#[inline]
|
||||||
fn plain_value(&self) -> Self::Plain {
|
fn plain_value(&self) -> Self::Plain {
|
||||||
self.value
|
self.value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn normalized_value(&self) -> Self::Plain {
|
fn normalized_value(&self) -> f32 {
|
||||||
self.normalized_value
|
self.normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||||
self.unmodulated_value
|
self.unmodulated_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_normalized_value(&self) -> f32 {
|
fn unmodulated_normalized_value(&self) -> f32 {
|
||||||
self.unmodulated_normalized_value
|
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -196,52 +188,54 @@ impl Param for FloatParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParamMut for FloatParam {
|
impl ParamMut for FloatParam {
|
||||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
fn set_plain_value(&self, plain: Self::Plain) {
|
||||||
self.unmodulated_value = plain;
|
let unmodulated_value = plain;
|
||||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||||
if self.modulation_offset == 0.0 {
|
|
||||||
self.value = self.unmodulated_value;
|
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||||
self.normalized_value = self.unmodulated_normalized_value;
|
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||||
|
(unmodulated_value, unmodulated_normalized_value)
|
||||||
} else {
|
} else {
|
||||||
self.normalized_value =
|
let normalized_value =
|
||||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||||
self.value = self.preview_plain(self.normalized_value);
|
|
||||||
}
|
(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 {
|
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
|
// 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
|
// 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
|
// different from `normalized`. This is not necesasry for the modulation as these
|
||||||
// values are never shown to the host.
|
// values are never shown to the host.
|
||||||
self.unmodulated_value = self.preview_plain(normalized);
|
self.set_plain_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
fn modulate_value(&self, modulation_offset: f32) {
|
||||||
self.modulation_offset = modulation_offset;
|
self.modulation_offset
|
||||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
.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 {
|
if reset {
|
||||||
self.smoothed.reset(self.value);
|
self.smoothed.reset(self.plain_value());
|
||||||
} else {
|
} 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.
|
/// parameter.
|
||||||
pub fn new(name: impl Into<String>, default: f32, range: FloatRange) -> Self {
|
pub fn new(name: impl Into<String>, default: f32, range: FloatRange) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: default,
|
value: AtomicF32::new(default),
|
||||||
normalized_value: range.normalize(default),
|
normalized_value: AtomicF32::new(range.normalize(default)),
|
||||||
unmodulated_value: default,
|
unmodulated_value: AtomicF32::new(default),
|
||||||
unmodulated_normalized_value: range.normalize(default),
|
unmodulated_normalized_value: AtomicF32::new(range.normalize(default)),
|
||||||
modulation_offset: 0.0,
|
modulation_offset: AtomicF32::new(0.0),
|
||||||
default,
|
default,
|
||||||
smoothed: Smoother::none(),
|
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
|
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
//! Stepped integer parameters.
|
//! Stepped integer parameters.
|
||||||
|
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::internals::ParamPtr;
|
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
|
/// A discrete integer parameter that's stored unnormalized. The range is used for the normalization
|
||||||
/// process.
|
/// 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 {
|
pub struct IntParam {
|
||||||
/// The field's current plain value, after monophonic modulation has been applied.
|
/// 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.
|
/// 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
|
/// 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.
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// `unmodulated_normalized_`. This needs to be stored separately since the normalied values are
|
||||||
/// clamped, and this value persists after new automation events.
|
/// clamped, and this value persists after new automation events.
|
||||||
modulation_offset: f32,
|
modulation_offset: AtomicF32,
|
||||||
/// The field's default plain, unnormalized value.
|
/// The field's default plain, unnormalized value.
|
||||||
default: i32,
|
default: i32,
|
||||||
/// An optional smoother that will automatically interpolate between the new automation values
|
/// An optional smoother that will automatically interpolate between the new automation values
|
||||||
|
@ -80,8 +72,8 @@ pub struct IntParam {
|
||||||
impl Display for IntParam {
|
impl Display for IntParam {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self.value_to_string {
|
match &self.value_to_string {
|
||||||
Some(func) => write!(f, "{}{}", func(self.value), self.unit),
|
Some(func) => write!(f, "{}{}", func(self.value()), self.unit),
|
||||||
_ => write!(f, "{}{}", self.value, self.unit),
|
_ => write!(f, "{}{}", self.value(), self.unit),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,22 +95,22 @@ impl Param for IntParam {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn plain_value(&self) -> Self::Plain {
|
fn plain_value(&self) -> Self::Plain {
|
||||||
self.value
|
self.value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn normalized_value(&self) -> f32 {
|
fn normalized_value(&self) -> f32 {
|
||||||
self.normalized_value
|
self.normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_plain_value(&self) -> Self::Plain {
|
fn unmodulated_plain_value(&self) -> Self::Plain {
|
||||||
self.unmodulated_value
|
self.unmodulated_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unmodulated_normalized_value(&self) -> f32 {
|
fn unmodulated_normalized_value(&self) -> f32 {
|
||||||
self.unmodulated_normalized_value
|
self.unmodulated_normalized_value.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -176,52 +168,54 @@ impl Param for IntParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParamMut for IntParam {
|
impl ParamMut for IntParam {
|
||||||
fn set_plain_value(&mut self, plain: Self::Plain) {
|
fn set_plain_value(&self, plain: Self::Plain) {
|
||||||
self.unmodulated_value = plain;
|
let unmodulated_value = plain;
|
||||||
self.unmodulated_normalized_value = self.preview_normalized(plain);
|
let unmodulated_normalized_value = self.preview_normalized(plain);
|
||||||
if self.modulation_offset == 0.0 {
|
|
||||||
self.value = self.unmodulated_value;
|
let modulation_offset = self.modulation_offset.load(Ordering::Relaxed);
|
||||||
self.normalized_value = self.unmodulated_normalized_value;
|
let (value, normalized_value) = if modulation_offset == 0.0 {
|
||||||
|
(unmodulated_value, unmodulated_normalized_value)
|
||||||
} else {
|
} else {
|
||||||
self.normalized_value =
|
let normalized_value =
|
||||||
(self.unmodulated_normalized_value + self.modulation_offset).clamp(0.0, 1.0);
|
(unmodulated_normalized_value + modulation_offset).clamp(0.0, 1.0);
|
||||||
self.value = self.preview_plain(self.normalized_value);
|
|
||||||
}
|
(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 {
|
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
|
// 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
|
// 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
|
// different from `normalized`. This is not necesasry for the modulation as these
|
||||||
// values are never shown to the host.
|
// values are never shown to the host.
|
||||||
self.unmodulated_value = self.preview_plain(normalized);
|
self.set_plain_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn modulate_value(&mut self, modulation_offset: f32) {
|
fn modulate_value(&self, modulation_offset: f32) {
|
||||||
self.modulation_offset = modulation_offset;
|
self.modulation_offset
|
||||||
self.set_normalized_value(self.unmodulated_normalized_value);
|
.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 {
|
if reset {
|
||||||
self.smoothed.reset(self.value);
|
self.smoothed.reset(self.plain_value());
|
||||||
} else {
|
} 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.
|
/// parameter.
|
||||||
pub fn new(name: impl Into<String>, default: i32, range: IntRange) -> Self {
|
pub fn new(name: impl Into<String>, default: i32, range: IntRange) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: default,
|
value: AtomicI32::new(default),
|
||||||
normalized_value: range.normalize(default),
|
normalized_value: AtomicF32::new(range.normalize(default)),
|
||||||
unmodulated_value: default,
|
unmodulated_value: AtomicI32::new(default),
|
||||||
unmodulated_normalized_value: range.normalize(default),
|
unmodulated_normalized_value: AtomicF32::new(range.normalize(default)),
|
||||||
modulation_offset: 0.0,
|
modulation_offset: AtomicF32::new(0.0),
|
||||||
default,
|
default,
|
||||||
smoothed: Smoother::none(),
|
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
|
/// Enable polyphonic modulation for this parameter. The ID is used to uniquely identify this
|
||||||
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
/// parameter in [`NoteEvent::PolyModulation][crate::prelude::NoteEvent::PolyModulation`]
|
||||||
/// events, and must thus be unique between _all_ polyphonically modulatable parameters. See the
|
/// 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.
|
/// erasure.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub enum ParamPtr {
|
pub enum ParamPtr {
|
||||||
FloatParam(*mut super::FloatParam),
|
FloatParam(*const super::FloatParam),
|
||||||
IntParam(*mut super::IntParam),
|
IntParam(*const super::IntParam),
|
||||||
BoolParam(*mut super::BoolParam),
|
BoolParam(*const super::BoolParam),
|
||||||
/// Since we can't encode the actual enum here, this inner parameter struct contains all of the
|
/// 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.
|
/// 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
|
// These pointers only point to fields on structs kept in an `Arc<dyn Params>`, and the caller
|
||||||
|
|
Loading…
Reference in a new issue