mod history;
use crate::error::{check_count, check_range};
use crate::filter::{Biquad, Design};
use crate::formant::{Formant, FormantConfig, ANALYSIS_FRAMES};
use crate::harmony::{HarmonyConfig, HarmonyState, HARMONY_VOICES};
use crate::midi::MidiEvent;
use crate::numeric::fixed::{Q12, Q14, Q6, Q8};
use crate::numeric::{ring, NumericContext, HISTORY};
use crate::pitch::tracker::PitchTracker;
use crate::pitch::{PitchConfig, PitchEstimate};
use crate::scale::{CorrectionTable, NoteMask};
use crate::{Error, SampleRate};
use history::prepare_history;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase")
)]
pub enum MultiVoiceMode {
#[default]
Choir,
Harmony,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase")
)]
pub struct CorrectionConfig {
pub enabled: bool,
pub notes: NoteMask,
pub retune_ms: f32,
}
impl Default for CorrectionConfig {
fn default() -> Self {
Self {
enabled: true,
notes: NoteMask::default(),
retune_ms: 0.0,
}
}
}
impl CorrectionConfig {
pub fn validate(self) -> Result<(), Error> {
check_range(
"resynthesis.correction.retune_ms",
self.retune_ms,
0.0,
400.0,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase"),
tsify(large_number_types_as_bigints)
)]
pub struct ResynthesisConfig {
pub pitch: PitchConfig,
pub correction: CorrectionConfig,
pub pitch_ratio: f32,
pub formant: FormantConfig,
pub mode: MultiVoiceMode,
pub choir_voices: u8,
pub choir_detune: f32,
pub choir_delay: f32,
pub choir_spread_percent: f32,
pub stereo_output: bool,
pub harmony: HarmonyConfig,
pub harmony_gain: f32,
pub harmonic_modulation: f32,
pub dry_mix: f32,
pub random_seed: u64,
}
impl Default for ResynthesisConfig {
fn default() -> Self {
Self {
pitch: PitchConfig::default(),
correction: CorrectionConfig::default(),
pitch_ratio: 1.0,
formant: FormantConfig::default(),
mode: MultiVoiceMode::Choir,
choir_voices: 1,
choir_detune: 50.0,
choir_delay: 60.0,
choir_spread_percent: 100.0,
stereo_output: false,
harmony: HarmonyConfig::default(),
harmony_gain: 0.5,
harmonic_modulation: 0.0,
dry_mix: 0.0,
random_seed: 0x1234_abcd,
}
}
}
impl ResynthesisConfig {
pub fn validate(self) -> Result<(), Error> {
self.pitch.validate()?;
self.correction.validate()?;
self.formant.validate()?;
self.harmony.validate()?;
check_range("resynthesis.pitch_ratio", self.pitch_ratio, 0.25, 4.0)?;
check_range("resynthesis.choir_detune", self.choir_detune, 10.0, 100.0)?;
check_range("resynthesis.choir_delay", self.choir_delay, 20.0, 200.0)?;
let field = "resynthesis.choir_spread_percent";
check_range(field, self.choir_spread_percent, 0.0, 100.0)?;
check_range("resynthesis.harmony_gain", self.harmony_gain, 0.0, 1.0)?;
let field = "resynthesis.harmonic_modulation";
check_range(field, self.harmonic_modulation, 0.0, 24.0)?;
check_range("resynthesis.dry_mix", self.dry_mix, 0.0, 1.0)?;
check_count(
"resynthesis.choir_voices",
i64::from(self.choir_voices),
1,
16,
)
}
}
const SEEDS: [i32; 16] = [
0, 0x14d1, 0x07f3, 0x115c, 0x1213, 0x1904, 0x0800, 0x2116, 0x1a53, 0x221c, 0x028d, 0x1dee,
0x0dda, 0x01da, 0x167f, 0x0896,
];
const OVERLAP_FRAMES: usize = 1543;
const FORMANT_OFFSET_FRAMES: i32 = (OVERLAP_FRAMES - ANALYSIS_FRAMES) as i32;
const READER_LAG_FRAMES: i32 = 50;
pub(crate) const LATENCY_FRAMES: usize = OVERLAP_FRAMES + READER_LAG_FRAMES as usize;
const CONTROL_DUE: i32 = 100;
const DRIFT_LIMIT: Q14 = Q14::from_bits(3276);
const FADE_STEP: Q14 = Q14::from_bits(0x333);
#[inline]
fn position_of(frame: i32) -> Q14 {
Q14::from_bits(frame.wrapping_shl(14))
}
pub(crate) struct Resynthesis {
rate: SampleRate,
config: ResynthesisConfig,
pitch: PitchTracker,
raw: Box<[Q14; HISTORY]>,
write: usize,
cosine: [Q14; 128],
formant: Formant,
harmony: HarmonyState,
scale: CorrectionTable,
filtered: Box<[Q14; HISTORY]>,
lead: Box<[Q14; HISTORY]>,
periods: Box<[Q6; HISTORY]>,
previous_periods: Box<[Q6; HISTORY]>,
ratios: Box<[Q14; HISTORY]>,
overlap: [Q14; OVERLAP_FRAMES],
overlap_index: usize,
delays: [i32; 16],
positions: [Q14; 16],
drift: [Q14; 16],
drift_ratio: [Q14; 16],
pan: [[Q14; 2]; 16],
harmony_pan: [[Q14; 2]; HARMONY_VOICES],
harmony_ratios: [Q14; HARMONY_VOICES],
harmony_gains: [Q12; HARMONY_VOICES],
voice_ratios: [Q14; HARMONY_VOICES + 1],
formant_position: i32,
post: [Biquad; 2],
correction: Q14,
correction_decay: Q14,
scale_initialized: bool,
scale_index: usize,
octave: i32,
shift: Q14,
modulation: i32,
modulation_phase: Q14,
dry_mix: Q8,
old_position: Q14,
fade: Q14,
current_output_period: Q6,
control_counter: i32,
drift_decay: Q14,
drift_amount: Q14,
previous_output: [Q14; 2],
}
impl Resynthesis {
pub(crate) fn new(rate: SampleRate, config: ResynthesisConfig) -> Self {
let mut result = Self {
rate,
config,
pitch: PitchTracker::new(rate, config.pitch),
raw: prepare_history(Q14::ZERO),
write: 0,
cosine: {
let mut phase = 0.0_f64;
std::array::from_fn(|_| {
let value = Q14::from_f64(phase.cos());
phase += std::f64::consts::PI / 64.0;
value
})
},
formant: Formant::new(rate, config.formant),
harmony: HarmonyState::new(rate, config.harmony),
scale: {
let mut scale = CorrectionTable::new(SampleRate::Hz44100, NoteMask::default());
scale.set_sample_rate(rate);
if config.correction.notes != NoteMask::default() {
scale.set_notes(config.correction.notes);
}
scale
},
filtered: prepare_history(Q14::ZERO),
lead: prepare_history(Q14::ZERO),
periods: prepare_history(Q6::ZERO),
previous_periods: prepare_history(Q6::ZERO),
ratios: prepare_history(Q14::ONE),
overlap: [Q14::ZERO; OVERLAP_FRAMES],
overlap_index: 0,
delays: [0; 16],
positions: [Q14::ZERO; 16],
drift: [Q14::ZERO; 16],
drift_ratio: [Q14::ONE; 16],
pan: [[Q14::ONE; 2]; 16],
harmony_pan: [[Q14::ZERO; 2]; HARMONY_VOICES],
harmony_ratios: [Q14::ONE; HARMONY_VOICES],
harmony_gains: [Q12::ZERO; HARMONY_VOICES],
voice_ratios: [Q14::ONE; HARMONY_VOICES + 1],
formant_position: 0,
post: [Biquad::new(), Biquad::new()],
correction: Q14::ONE,
correction_decay: Q14::ZERO,
scale_initialized: false,
scale_index: 0,
octave: 0,
shift: Q14::ONE,
modulation: 0,
modulation_phase: Q14::ZERO,
dry_mix: Q8::ZERO,
old_position: position_of(-READER_LAG_FRAMES),
fade: Q14::ONE,
current_output_period: Q6::ZERO,
control_counter: CONTROL_DUE,
drift_decay: Q14::ZERO,
drift_amount: Q14::ZERO,
previous_output: [Q14::ZERO; 2],
};
result.configure_coefficients();
result.configure_shift_filters();
result.reset();
result
}
fn configure_coefficients(&mut self) {
self.shift = Q14::from_f64(self.config.pitch_ratio as f64);
self.modulation = (self.config.harmonic_modulation as f64 / 8.0 * 16.0) as i32;
self.dry_mix = Q8::from_f64((self.config.dry_mix * 100.0) as f64 / 100.0);
self.correction_decay = if self.config.correction.retune_ms == 0.0 {
Q14::ZERO
} else {
let t = self.config.correction.retune_ms / 1000.0;
Q14::from_f64(0.5_f64.powf(1.0 / ((self.rate.hz() / 5.0) * t) as f64))
};
let base = (self.rate.hz() / 100.0) as f64 * 0.2;
let decay = (-std::f64::consts::LN_2 / base).exp();
self.drift_decay = Q14::from_f64(decay);
self.drift_amount = Q14::from_f64((1.0 - decay) * 0.008 * self.config.choir_detune as f64);
for voice in 0..self.config.choir_voices as usize {
if self.config.choir_voices > 1 {
let normalized =
(voice as f64 + voice as f64) / (self.config.choir_voices as f64 - 1.0) - 1.0;
let angle = 0.785398163565
- (self.config.choir_spread_percent as f64 * 0.785398163565 * 0.01)
* normalized;
self.pan[voice] = [Q14::from_f64(angle.cos()), Q14::from_f64(angle.sin())];
} else {
self.pan[voice] = [Q14::ONE; 2];
}
}
for (voice, fraction) in [0.3333333333333333, 0.6666666666666666, 0.0, 1.0]
.into_iter()
.enumerate()
{
let angle: f64 = 1.57079632713 * fraction;
let gain = self.config.harmony_gain as f64;
self.harmony_pan[voice] = [
Q14::from_f64(gain * angle.cos()),
Q14::from_f64(gain * angle.sin()),
];
}
}
fn configure_shift_filters(&mut self) {
for filter in &mut self.post {
if self.config.pitch_ratio > 1.0 {
let numerator = (self.rate.hz() as f64 * 0.49) as i32;
let cutoff = numerator as f32 / self.config.pitch_ratio;
filter.set_design(Design::low_pass(self.rate.hz(), cutoff, 0.707));
} else {
filter.bypass();
}
}
}
fn create_delays(&mut self) {
let parameter = if self.config.mode == MultiVoiceMode::Choir {
self.config.choir_delay
} else {
0.0
};
for (voice, seed) in SEEDS.iter().enumerate() {
self.delays[voice] = (*seed as f64 * (parameter as f64 * 0.005)) as i32;
self.positions[voice] =
position_of(self.write as i32 - READER_LAG_FRAMES - self.delays[voice]);
}
}
pub(crate) fn apply_config(&mut self, config: ResynthesisConfig) {
let old = self.config;
self.config = config;
self.pitch.set_config(config.pitch);
self.formant.set_config(config.formant);
self.harmony.apply_config(config.harmony);
if old.correction.notes != config.correction.notes
|| old.correction.enabled != config.correction.enabled
{
self.scale.set_notes(config.correction.notes);
self.scale_initialized = false;
}
if old.pitch_ratio != config.pitch_ratio {
self.configure_shift_filters();
}
if old.mode != config.mode || old.choir_delay != config.choir_delay {
self.create_delays();
}
if old.choir_voices < config.choir_voices {
for voice in old.choir_voices as usize..config.choir_voices as usize {
self.positions[voice] = self.positions[0];
}
}
self.configure_coefficients();
if old.stereo_output != config.stereo_output {
self.reset_audio();
}
}
pub(crate) fn reset(&mut self) {
self.scale.reset();
self.reset_audio();
}
fn reset_audio(&mut self) {
self.pitch.reset();
self.raw.fill(Q14::ZERO);
self.write = 0;
self.formant.reset();
self.harmony.reset();
self.filtered.fill(Q14::ZERO);
self.lead.fill(Q14::ZERO);
self.periods.fill(Q6::ZERO);
self.previous_periods.fill(Q6::ZERO);
self.ratios.fill(Q14::ONE);
self.overlap.fill(Q14::ZERO);
self.overlap_index = 0;
self.previous_output = [Q14::ZERO; 2];
for filter in &mut self.post {
filter.reset();
}
self.create_delays();
self.drift_ratio.fill(Q14::ONE);
self.old_position = self.positions[0];
self.fade = Q14::ONE;
self.harmony_ratios.fill(Q14::ONE);
self.voice_ratios.fill(Q14::ONE);
self.formant_position = 0;
self.current_output_period = Q6::ZERO;
self.correction = Q14::ONE;
self.scale_initialized = false;
}
pub(crate) fn apply_midi(&mut self, event: MidiEvent) {
if self.harmony.apply_validated_midi(event) {
self.control_counter = CONTROL_DUE;
}
}
pub(crate) fn apply_correction_midi(&mut self, event: MidiEvent) {
self.scale.apply_validated_midi(event);
self.scale_initialized = false;
}
pub(crate) fn tail_frames(&self) -> usize {
2 * HISTORY
+ if self.config.formant.enabled {
self.rate.as_u32() as usize * 2
} else {
0
}
+ if self.config.pitch_ratio > 1.0 {
self.rate.as_u32() as usize
} else {
0
}
}
pub(crate) fn estimate(&self) -> PitchEstimate {
let period = (self.periods[self.write].to_bits() as f64 / 64.0) as f32;
self.config.pitch.report(PitchEstimate {
period_samples: period,
frequency_hz: if period > 0.0 {
self.rate.hz() / period
} else {
0.0
},
voiced: period > 0.0,
})
}
pub(crate) fn resample_ratio(&self) -> f32 {
(self.correction.to_bits() as f64 / 16384.0) as f32
}
pub(crate) fn target_note(&self) -> Option<f32> {
let midi = self.estimate().midi_note()?;
if self.config.correction.enabled && self.scale_initialized && self.scale.count != 0 {
let class = self.scale.notes[self.scale_index] as f32;
Some(class + ((midi - class) / 12.0).round() * 12.0)
} else {
Some(midi)
}
}
pub(crate) fn clarity(&self) -> f32 {
self.pitch.clarity()
}
pub(crate) fn is_tracking(&self) -> bool {
!self.pitch.is_detecting()
}
pub(crate) fn voices(&self, ratios: &mut [f32; 16], gains: &mut [f32; 16]) -> u8 {
let lead = (self.voice_ratios[0].to_bits() as f64 / 16384.0) as f32;
ratios[0] = lead;
gains[0] = 1.0;
if self.config.mode == MultiVoiceMode::Choir {
let count = self.config.choir_voices as usize;
for voice in 1..count {
ratios[voice] =
(lead as f64 * self.drift_ratio[voice].to_bits() as f64 / 16384.0) as f32;
gains[voice] = 1.0;
}
count as u8
} else {
for voice in 0..HARMONY_VOICES {
ratios[voice + 1] =
(self.voice_ratios[voice + 1].to_bits() as f64 / 16384.0) as f32;
gains[voice + 1] = (self.harmony_gains[voice].to_bits() as f64 / 4096.0) as f32;
}
(HARMONY_VOICES + 1) as u8
}
}
pub(crate) fn formant_generation(&self) -> u32 {
self.formant.generation()
}
pub(crate) fn formant_envelope(
&self,
max_frequency_hz: f32,
input: &mut [f32],
output: &mut [f32],
) -> bool {
if !self.config.formant.enabled {
input.fill(0.0);
output.fill(0.0);
return false;
}
self.formant
.envelope(self.formant_position, max_frequency_hz, input, output)
}
fn update_correction(&mut self, period: Q6) {
let mut target_ratio = Q14::ONE;
if self.config.correction.enabled && self.scale.count != 0 {
let count = self.scale.count;
let table_period = |index: usize| Q6::from_bits(self.scale.midpoints[index]);
let mut target: Q6;
if !self.scale_initialized {
self.scale_initialized = true;
self.scale_index = 0;
self.octave = 0;
target = self.shift.mul(period);
if target <= Q6::ZERO {
self.correction = Q14::ONE;
return;
}
while table_period(1) > target {
self.octave += 1;
target = target << 1;
if self.octave >= 30 || target <= Q6::ZERO {
break;
}
}
}
target = self.shift.mul(if self.octave >= 0 {
period << self.octave as u32
} else {
period >> (-self.octave) as u32
});
while table_period(self.scale_index + 1).mul::<14, 6>(Q14::ONE) < target {
self.scale_index += 1;
if self.scale_index == count {
self.scale_index = 0;
target = target >> 1;
self.octave -= 1;
}
}
while target.mul::<14, 6>(Q14::ONE) < table_period(self.scale_index) {
if self.scale_index == 0 {
self.scale_index = count - 1;
target = target << 1;
self.octave += 1;
} else {
self.scale_index -= 1;
}
}
let denominator = Q6::from_bits(self.scale.periods[self.scale_index]);
if denominator != Q6::ZERO {
target_ratio = target.div(denominator);
}
}
self.correction = self.correction_decay.mul(self.correction - target_ratio) + target_ratio;
}
fn period_for(&self, delayed: usize, current: usize) -> (Q6, Q14) {
if self.periods[delayed] != Q6::ZERO {
(self.periods[delayed], self.ratios[delayed])
} else if self.periods[current] != Q6::ZERO {
(self.periods[current], self.ratios[current])
} else {
(self.previous_periods[delayed], self.ratios[delayed])
}
}
fn move_reader(
&mut self,
voice: usize,
ratio: Q14,
period: Q6,
offset: i32,
fade: bool,
) -> Q14 {
let mut position = self.positions[voice] + ratio;
let write = self.write as i32;
let upper = position_of(write - 40 - offset);
let span = (period.to_bits() >> 6).wrapping_mul(12).wrapping_div(10);
let lower = position_of(
write
.wrapping_sub(span)
.wrapping_sub(40)
.wrapping_sub(offset),
);
if position >= upper {
if fade {
self.old_position = position;
self.fade = Q14::ZERO;
}
position = position - period.rescale();
} else if position <= lower {
if fade {
self.old_position = position;
self.fade = Q14::ZERO;
}
position = position + period.rescale();
}
self.positions[voice] = position;
position
}
fn render_lead(&mut self, delayed: usize, numeric: &mut NumericContext) -> (Q14, Q14) {
let write = self.write;
let delay = ring(delayed as i32 - self.delays[0]);
let (period, mut ratio) = self.period_for(delay, write);
self.current_output_period = period;
ratio = ratio.mul::<14, 14>(self.drift_ratio[0]).mul(self.shift);
if period == Q6::ZERO {
ratio = Q14::ONE;
}
if ratio != Q14::ZERO {
self.periods[delay] = period.div(ratio);
}
self.old_position = self.old_position + ratio;
let position = self.move_reader(0, ratio, period, self.delays[0], true);
let (mut value, center) = history::interpolation(&self.filtered, position);
if self.fade < Q14::ONE {
let (old, _) = history::interpolation(&self.filtered, self.old_position);
value = value.mul(self.fade) + old.mul(Q14::ONE - self.fade);
self.fade = (self.fade + FADE_STEP).min(Q14::ONE);
}
if self.config.formant.enabled {
self.formant_position = center - FORMANT_OFFSET_FRAMES;
value = Q14::from_bits(self.formant.forward(
value.to_bits(),
self.formant_position,
numeric,
));
value = Q14::from_bits(
self.formant
.model_glottal(value.to_bits(), self.previous_output[0].to_bits()),
);
self.previous_output[0] = value;
}
self.lead[write] = value;
(value, ratio)
}
pub(crate) fn process_sample(&mut self, input: i16, numeric: &mut NumericContext) -> [Q14; 2] {
let sample = Q14::from_bits(i32::from(input) >> 1);
self.write += 1;
if self.write == HISTORY {
self.write = 0;
}
self.raw[self.write] = sample;
let was_tracking = !self.pitch.is_detecting();
let step = self.pitch.process_sample(sample.to_bits());
let write = self.write;
if write == 0 {
let voices = if self.config.mode == MultiVoiceMode::Choir {
self.config.choir_voices as usize
} else {
5
};
for voice in 0..voices {
self.positions[voice] = self.positions[voice] - position_of(HISTORY as i32);
}
self.old_position = self.old_position - position_of(HISTORY as i32);
}
if step.restarted {
self.correction = Q14::ONE;
self.scale_initialized = false;
}
if step.acquired {
self.formant.detection_mode_off();
}
if step.tracking_update {
self.update_correction(step.period);
}
if was_tracking && self.config.mode == MultiVoiceMode::Choir {
let counter = self.control_counter;
self.control_counter += 1;
if counter == CONTROL_DUE {
self.control_counter = 0;
for voice in 1..self.config.choir_voices as usize {
let random = Q14::from_bits((numeric.random.next_u31() >> 17) - 8192);
self.drift[voice] = (self.drift_decay.mul(self.drift[voice])
+ self.drift_amount.mul(random))
.clamp(-DRIFT_LIMIT, DRIFT_LIMIT);
self.drift_ratio[voice] = self.drift[voice] + Q14::ONE;
}
self.drift_ratio[0] = Q14::ONE;
}
}
self.periods[write] = step.period;
self.previous_periods[write] = step.previous_period;
self.ratios[write] = self.correction;
self.overlap_index += 1;
if self.overlap_index == OVERLAP_FRAMES {
self.overlap_index = 0;
}
self.overlap[self.overlap_index] = if self.config.formant.enabled {
let previous = self.raw[ring(write as i32 - 1)];
Q14::from_bits(self.formant.process_analysis(
sample.to_bits(),
previous.to_bits(),
self.periods.as_slice(),
write,
step.previous_period.to_bits(),
))
} else {
self.raw[ring(write as i32 - ANALYSIS_FRAMES as i32)]
};
let overlap_read = (self.overlap_index + ANALYSIS_FRAMES) % OVERLAP_FRAMES;
let mut filtered = self.overlap[overlap_read];
for filter in &mut self.post {
filtered = filter.process_sample(filtered);
}
self.filtered[write] = filtered;
let delayed = ring(write as i32 - OVERLAP_FRAMES as i32);
let dry = self.raw[delayed];
let mut mix = [Q14::ZERO; 2];
let mut final_ratio = Q14::ONE;
if self.config.mode == MultiVoiceMode::Choir {
let voice_count = self.config.choir_voices as usize;
{
let (value, ratio) = self.render_lead(delayed, numeric);
final_ratio = ratio;
self.voice_ratios[0] = ratio;
for (channel, sum) in mix.iter_mut().enumerate() {
*sum = *sum
+ if self.config.stereo_output {
value.mul(self.pan[0][channel])
} else {
value
};
}
}
{
let mut voice = 1;
while voice + 4 <= voice_count {
let mut positions = [Q14::ZERO; 4];
for (lane, position) in positions.iter_mut().enumerate() {
let current_voice = voice + lane;
let delay = ring(delayed as i32 - self.delays[current_voice]);
let (period, _) = self.period_for(delay, write);
self.current_output_period = period;
let ratio = self.drift_ratio[current_voice];
final_ratio = ratio;
*position = self.move_reader(
current_voice,
ratio,
period,
self.delays[current_voice],
false,
);
}
let values =
positions.map(|position| history::interpolation(&self.lead, position).0);
for (lane, value) in values.into_iter().enumerate() {
let current_voice = voice + lane;
for (channel, sum) in mix.iter_mut().enumerate() {
*sum = *sum
+ if self.config.stereo_output {
value.mul(self.pan[current_voice][channel])
} else {
value
};
}
}
voice += 4;
}
for voice in voice..voice_count {
let delay = ring(delayed as i32 - self.delays[voice]);
let (period, _) = self.period_for(delay, write);
self.current_output_period = period;
let ratio = self.drift_ratio[voice];
final_ratio = ratio;
let position =
self.move_reader(voice, ratio, period, self.delays[voice], false);
let (value, _) = history::interpolation(&self.lead, position);
for (channel, sum) in mix.iter_mut().enumerate() {
*sum = *sum
+ if self.config.stereo_output {
value.mul(self.pan[voice][channel])
} else {
value
};
}
}
}
for sum in &mut mix {
*sum = Q14::ONE.mul(*sum);
}
} else {
let (period, ratio) = self.period_for(delayed, write);
self.current_output_period = period;
let counter = self.control_counter;
self.control_counter += 1;
if counter == CONTROL_DUE {
self.control_counter = 0;
for voice in 0..HARMONY_VOICES {
let corrected_period = if ratio == Q14::ZERO {
Q6::ZERO
} else {
period.div(ratio)
};
let (ratio, gain) = self.harmony.advance_voice(corrected_period, voice);
self.harmony_ratios[voice] = ratio;
self.harmony_gains[voice] = gain;
}
}
for slot in 0..5 {
let mut current: Q14 = if slot == 0 {
ratio
} else {
self.harmony_ratios[slot - 1].mul(ratio)
};
current = self.shift.mul(current);
if period == Q6::ZERO {
current = Q14::ONE;
}
final_ratio = current;
self.voice_ratios[slot] = current;
let position = self.move_reader(slot, current, period, 0, false);
let (mut value, _) = history::interpolation(&self.filtered, position);
if slot > 0 {
value = value.mul(self.harmony_gains[slot - 1]);
}
for (channel, sum) in mix.iter_mut().enumerate() {
*sum = *sum
+ if self.config.stereo_output && slot > 0 {
value.mul(self.harmony_pan[slot - 1][channel])
} else {
value
};
}
}
if self.config.formant.enabled {
self.formant_position = (self.positions[0].to_bits() >> 14) - FORMANT_OFFSET_FRAMES;
mix = self
.formant
.forward_stereo(mix.map(Q14::to_bits), self.formant_position, numeric)
.map(Q14::from_bits);
for (channel, value) in mix.iter_mut().enumerate() {
*value =
Q14::from_bits(self.formant.model_glottal(
value.to_bits(),
self.previous_output[channel].to_bits(),
));
self.previous_output[channel] = *value;
}
}
}
if self.modulation != 0 {
if self.current_output_period != Q6::ZERO {
let step = self
.modulation
.wrapping_mul(4)
.wrapping_mul(final_ratio.to_bits())
.wrapping_div(self.current_output_period.to_bits());
self.modulation_phase = self.modulation_phase + Q14::from_bits(step);
if self.modulation_phase > Q14::ONE {
self.modulation_phase = self.modulation_phase - Q14::ONE;
}
} else {
self.modulation_phase =
Q14::from_bits((self.modulation_phase.to_bits() as f64 * 0.999) as i32);
}
let scaled = self.modulation_phase.to_bits().wrapping_mul(128);
let center = scaled.wrapping_add(8192) >> 14;
let frac = Q14::from_bits(scaled.wrapping_sub(center.wrapping_shl(14)));
let index = center.rem_euclid(128) as usize;
let left = self.cosine[(index + 127) % 128];
let middle = self.cosine[index];
let right = self.cosine[(index + 1) % 128];
let cosine = middle
+ ((((left + right) >> 1) - middle).mul::<14, 14>(frac) + ((right - left) >> 1))
.mul(frac);
for sum in &mut mix {
*sum = sum.mul(cosine);
}
}
let wet = Q8::ONE - self.dry_mix;
for sum in &mut mix {
*sum = (dry.mul::<8, 22>(self.dry_mix) + sum.mul::<8, 22>(wet)).rescale();
}
mix
}
}