Add a second just as bad mode to Puberty Simulator
This commit is contained in:
parent
c6acdfa020
commit
743998e388
|
@ -87,7 +87,11 @@ enum PitchShiftingMode {
|
||||||
/// Directly linearly interpolate sine and cosine waves from different bins. This obviously
|
/// Directly linearly interpolate sine and cosine waves from different bins. This obviously
|
||||||
/// sounds very bad, but it also sounds kind of hilarious.
|
/// sounds very bad, but it also sounds kind of hilarious.
|
||||||
#[name = "Very broken"]
|
#[name = "Very broken"]
|
||||||
VeryBroken,
|
InterpolateRectangular,
|
||||||
|
/// The same as `InterpolateRectangular`, but interpolating the polar forms instead. This sounds
|
||||||
|
/// slightly better, which actually ends up making it sound a lot worse.
|
||||||
|
#[name = "Also very broken"]
|
||||||
|
InterpolatePolar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PubertySimulator {
|
impl Default for PubertySimulator {
|
||||||
|
@ -146,7 +150,7 @@ impl Default for PubertySimulatorParams {
|
||||||
)
|
)
|
||||||
.with_value_to_string(power_of_two_val2str)
|
.with_value_to_string(power_of_two_val2str)
|
||||||
.with_string_to_value(power_of_two_str2val),
|
.with_string_to_value(power_of_two_str2val),
|
||||||
mode: EnumParam::new("Mode", PitchShiftingMode::VeryBroken),
|
mode: EnumParam::new("Mode", PitchShiftingMode::InterpolateRectangular),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,37 +267,42 @@ impl Plugin for PubertySimulator {
|
||||||
.process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut [])
|
.process_with_scratch(real_fft_buffer, &mut self.complex_fft_buffer, &mut [])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// This simply interpolates between the complex sinusoids from the frequency bins
|
// TODO: Move this to helper functions. These functions capture a lot of variables
|
||||||
// for this bin's frequency scaled by the octave pitch multiplies. The iteration
|
// here so that might require some work. And branch preductors are probably
|
||||||
// order dependson the pitch shifting direction since we're doing it in place.
|
// good enough to be able to put the match inside of the `process_bin`
|
||||||
|
// function, but it seems preferable to have it outside of the loop.
|
||||||
let num_bins = self.complex_fft_buffer.len();
|
let num_bins = self.complex_fft_buffer.len();
|
||||||
let mut process_bin = match self.params.mode.value() {
|
match self.params.mode.value() {
|
||||||
PitchShiftingMode::VeryBroken => |bin_idx| {
|
PitchShiftingMode::InterpolateRectangular => {
|
||||||
|
// This simply interpolates the sine and cosine waves composing the complex
|
||||||
|
// sinusoids from the frequency bins to neighbouring frequency bins scaled
|
||||||
|
// by the octave pitch multiplies. The iteration order dependson the pitch
|
||||||
|
// shifting direction since we're doing it in place.
|
||||||
|
let mut process_bin = |bin_idx| {
|
||||||
let frequency = bin_idx as f32 / window_size as f32 * sample_rate;
|
let frequency = bin_idx as f32 / window_size as f32 * sample_rate;
|
||||||
let target_frequency = frequency * frequency_multiplier;
|
let target_frequency = frequency * frequency_multiplier;
|
||||||
|
|
||||||
// Simple linear interpolation
|
// Simple linear interpolation
|
||||||
let target_bin = target_frequency / sample_rate * window_size as f32;
|
let target_bin = target_frequency / sample_rate * window_size as f32;
|
||||||
let target_bin_low = target_bin.floor() as usize;
|
let target_bin_floor = target_bin.floor() as usize;
|
||||||
let target_bin_high = target_bin.ceil() as usize;
|
let target_bin_ceil = target_bin.ceil() as usize;
|
||||||
let target_low_t = target_bin % 1.0;
|
let target_floor_t = target_bin % 1.0;
|
||||||
let target_high_t = 1.0 - target_low_t;
|
let target_ceil_t = 1.0 - target_floor_t;
|
||||||
let target_low = self
|
let target_floor = self
|
||||||
.complex_fft_buffer
|
.complex_fft_buffer
|
||||||
.get(target_bin_low)
|
.get(target_bin_floor)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let target_high = self
|
let target_ceil = self
|
||||||
.complex_fft_buffer
|
.complex_fft_buffer
|
||||||
.get(target_bin_high)
|
.get(target_bin_ceil)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.complex_fft_buffer[bin_idx] = (target_low * target_low_t
|
self.complex_fft_buffer[bin_idx] = (target_floor * target_floor_t
|
||||||
+ target_high * target_high_t)
|
+ target_ceil * target_ceil_t)
|
||||||
* 3.0 // Random extra gain, not sure
|
* 3.0 // Random extra gain, not sure
|
||||||
* gain_compensation;
|
* gain_compensation;
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if frequency_multiplier >= 1.0 {
|
if frequency_multiplier >= 1.0 {
|
||||||
|
@ -305,6 +314,57 @@ impl Plugin for PubertySimulator {
|
||||||
process_bin(bin_idx);
|
process_bin(bin_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
PitchShiftingMode::InterpolatePolar => {
|
||||||
|
// Same as the above, but interpolating in the polar form instead. While
|
||||||
|
// this does sound more correct it doesn't sound nearly as hilarious, and it
|
||||||
|
// just sounds bad at this point. But maybe there's some use for this.
|
||||||
|
let mut process_bin = |bin_idx| {
|
||||||
|
let frequency = bin_idx as f32 / window_size as f32 * sample_rate;
|
||||||
|
let target_frequency = frequency * frequency_multiplier;
|
||||||
|
|
||||||
|
// Simple linear interpolation
|
||||||
|
let target_bin = target_frequency / sample_rate * window_size as f32;
|
||||||
|
let target_bin_floor = target_bin.floor() as usize;
|
||||||
|
let target_bin_ceil = target_bin.ceil() as usize;
|
||||||
|
let target_floor_t = target_bin % 1.0;
|
||||||
|
let target_ceil_t = 1.0 - target_floor_t;
|
||||||
|
let target_floor = self
|
||||||
|
.complex_fft_buffer
|
||||||
|
.get(target_bin_floor)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let target_ceil = self
|
||||||
|
.complex_fft_buffer
|
||||||
|
.get(target_bin_ceil)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let target_floor_magnitude = target_floor.norm();
|
||||||
|
let target_floor_phase = target_floor.arg();
|
||||||
|
let target_ceil_magnitude = target_ceil.norm();
|
||||||
|
let target_ceil_phase = target_ceil.arg();
|
||||||
|
|
||||||
|
self.complex_fft_buffer[bin_idx] = Complex32::from_polar(
|
||||||
|
(target_floor_magnitude * target_floor_t)
|
||||||
|
+ (target_ceil_magnitude * target_ceil_t),
|
||||||
|
(target_floor_phase * target_floor_t)
|
||||||
|
+ (target_ceil_phase * target_ceil_t),
|
||||||
|
) * 3.0 // Random extra gain, not sure
|
||||||
|
* gain_compensation;
|
||||||
|
};
|
||||||
|
|
||||||
|
if frequency_multiplier >= 1.0 {
|
||||||
|
for bin_idx in 0..num_bins {
|
||||||
|
process_bin(bin_idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for bin_idx in (0..num_bins).rev() {
|
||||||
|
process_bin(bin_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the imaginary components on the first and last bin are zero
|
// Make sure the imaginary components on the first and last bin are zero
|
||||||
self.complex_fft_buffer[0].im = 0.0;
|
self.complex_fft_buffer[0].im = 0.0;
|
||||||
|
|
Loading…
Reference in a new issue