use simdeez::prelude::*;
use crate::soundfont::{EnvelopeCurveType, EnvelopeOptions};
use crate::voice::{EnvelopeControlData, ReleaseType, VoiceControlData};
use self::lerpers::{SIMDLerper, SIMDLerperConcave, SIMDLerperConvex, StageTime};
use super::{SIMDSampleMono, SIMDVoiceGenerator, VoiceGeneratorBase};
mod lerpers;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum EnvelopeStage {
Delay = 0,
Attack = 1,
Hold = 2,
Decay = 3,
Sustain = 4,
Release = 5, Finished = 6,
}
impl EnvelopeStage {
pub fn as_usize(&self) -> usize {
*self as usize
}
pub fn next_stage(&self) -> EnvelopeStage {
match self {
EnvelopeStage::Delay => EnvelopeStage::Attack,
EnvelopeStage::Attack => EnvelopeStage::Hold,
EnvelopeStage::Hold => EnvelopeStage::Decay,
EnvelopeStage::Decay => EnvelopeStage::Sustain,
EnvelopeStage::Sustain => EnvelopeStage::Release,
EnvelopeStage::Release => EnvelopeStage::Finished,
EnvelopeStage::Finished => EnvelopeStage::Finished,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum EnvelopePart {
Lerp {
target: f32, duration: u32, },
LerpConcave {
target: f32,
duration: u32,
},
LerpConvex {
target: f32,
duration: u32,
},
Hold(f32),
}
impl EnvelopePart {
pub fn lerp(target: f32, duration: u32) -> EnvelopePart {
EnvelopePart::Lerp { target, duration }
}
pub fn lerp_concave(target: f32, duration: u32) -> EnvelopePart {
EnvelopePart::LerpConcave { target, duration }
}
pub fn lerp_convex(target: f32, duration: u32) -> EnvelopePart {
EnvelopePart::LerpConvex { target, duration }
}
pub fn hold(value: f32) -> EnvelopePart {
EnvelopePart::Hold(value)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct EnvelopeDescriptor {
pub start_percent: f32, pub delay: f32, pub attack: f32, pub hold: f32, pub decay: f32, pub sustain_percent: f32, pub release: f32, }
impl EnvelopeDescriptor {
#[allow(clippy::wrong_self_convention)]
pub fn to_envelope_params(
&self,
samplerate: u32,
options: EnvelopeOptions,
) -> EnvelopeParameters {
let samplerate = samplerate as f32;
let attack = match options.attack_curve {
EnvelopeCurveType::Linear => {
EnvelopePart::lerp_convex(1.0, (self.attack * samplerate) as u32)
}
EnvelopeCurveType::Exponential => {
EnvelopePart::lerp(1.0, (self.attack * samplerate) as u32)
}
};
let decay = match options.decay_curve {
EnvelopeCurveType::Exponential => {
EnvelopePart::lerp(self.sustain_percent, (self.decay * samplerate) as u32)
}
EnvelopeCurveType::Linear => {
EnvelopePart::lerp_concave(self.sustain_percent, (self.decay * samplerate) as u32)
}
};
let release = match options.release_curve {
EnvelopeCurveType::Exponential => {
EnvelopePart::lerp(0.0, (self.release * samplerate) as u32)
}
EnvelopeCurveType::Linear => {
EnvelopePart::lerp_concave(0.0, (self.release * samplerate) as u32)
}
};
EnvelopeParameters {
start: self.start_percent,
parts: [
EnvelopePart::lerp(self.start_percent, (self.delay * samplerate) as u32),
attack,
EnvelopePart::lerp(1.0, (self.hold * samplerate) as u32),
decay,
EnvelopePart::hold(self.sustain_percent),
release,
EnvelopePart::hold(0.0),
],
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EnvelopeParameters {
start: f32,
pub parts: [EnvelopePart; 7],
}
impl EnvelopeParameters {
fn get_stage_data<T: Simd>(
&self,
stage: EnvelopeStage,
start_amp: f32,
) -> VoiceEnvelopeState<T> {
simd_invoke!(T, {
let stage_info = &self.parts[stage.as_usize()];
match stage_info {
EnvelopePart::Lerp { target, duration } => {
let duration = *duration;
let target = *target;
if duration == 0 {
self.get_stage_data(stage.next_stage(), target)
} else {
let data = StageData::Lerp(
SIMDLerper::new(start_amp, target),
StageTime::new(0, duration),
);
VoiceEnvelopeState {
current_stage: stage,
stage_data: data,
}
}
}
EnvelopePart::LerpConcave { target, duration } => {
let duration = *duration;
let target = *target;
if duration == 0 {
self.get_stage_data(stage.next_stage(), target)
} else {
let data = StageData::LerpConcave(
SIMDLerperConcave::new(start_amp, target),
StageTime::new(0, duration),
);
VoiceEnvelopeState {
current_stage: stage,
stage_data: data,
}
}
}
EnvelopePart::LerpConvex { target, duration } => {
let duration = *duration;
let target = *target;
if duration == 0 {
self.get_stage_data(stage.next_stage(), target)
} else {
let data = StageData::LerpConvex(
SIMDLerperConvex::new(start_amp, target),
StageTime::new(0, duration),
);
VoiceEnvelopeState {
current_stage: stage,
stage_data: data,
}
}
}
EnvelopePart::Hold(value) => {
let data = StageData::Constant(T::Vf32::set1(*value));
VoiceEnvelopeState {
current_stage: stage,
stage_data: data,
}
}
}
})
}
pub fn get_stage_duration(&self, stage: EnvelopeStage) -> u32 {
let stage_info = &self.parts[stage.as_usize()];
match stage_info {
EnvelopePart::Lerp {
target: _,
duration,
} => *duration,
EnvelopePart::LerpConcave {
target: _,
duration,
} => *duration,
EnvelopePart::LerpConvex {
target: _,
duration,
} => *duration,
EnvelopePart::Hold(_) => 0,
}
}
pub fn modify_stage_data(&mut self, part: usize, data: EnvelopePart) {
self.parts[part] = data;
}
}
enum StageData<T: Simd> {
Lerp(SIMDLerper<T>, StageTime<T>),
LerpConcave(SIMDLerperConcave<T>, StageTime<T>),
LerpConvex(SIMDLerperConvex<T>, StageTime<T>),
Constant(T::Vf32),
}
struct VoiceEnvelopeState<T: Simd> {
current_stage: EnvelopeStage,
stage_data: StageData<T>,
}
pub struct SIMDVoiceEnvelope<T: Simd> {
original_params: EnvelopeParameters,
params: EnvelopeParameters,
allow_release: bool,
state: VoiceEnvelopeState<T>,
sample_rate: f32,
killed: bool,
}
impl<T: Simd> SIMDVoiceEnvelope<T> {
pub fn new(
original_params: EnvelopeParameters,
params: EnvelopeParameters,
allow_release: bool,
sample_rate: f32,
) -> Self {
let state = params.get_stage_data(EnvelopeStage::Delay, params.start);
SIMDVoiceEnvelope {
original_params,
params,
allow_release,
state,
sample_rate,
killed: false,
}
}
pub fn get_value_at_current_time(&self) -> f32 {
match &self.state.stage_data {
StageData::Lerp(lerper, stage_time) => {
lerper.lerp(stage_time.simd_array_start_f32() / stage_time.stage_end_time_f32())
}
StageData::LerpConcave(lerper, stage_time) => {
lerper.lerp(stage_time.simd_array_start_f32() / stage_time.stage_end_time_f32())
}
StageData::LerpConvex(lerper, stage_time) => {
lerper.lerp(stage_time.simd_array_start_f32() / stage_time.stage_end_time_f32())
}
StageData::Constant(constant) => constant[0],
}
}
pub fn current_stage(&self) -> &EnvelopeStage {
&self.state.current_stage
}
fn switch_to_next_stage(&mut self) {
let amp = self.get_value_at_current_time();
self.state = self
.params
.get_stage_data(self.current_stage().next_stage(), amp);
}
fn update_stage(&mut self) {
let amp = self.get_value_at_current_time();
self.state = self.params.get_stage_data(*self.current_stage(), amp);
}
fn increment_time_by(&mut self, increment: u32) {
match &mut self.state.stage_data {
StageData::Lerp(_, stage_time) => {
stage_time.increment_by(increment);
}
StageData::LerpConcave(_, stage_time) => {
stage_time.increment_by(increment);
}
StageData::LerpConvex(_, stage_time) => {
stage_time.increment_by(increment);
}
StageData::Constant(_) => {}
}
}
fn manually_build_simd_sample(&mut self) -> SIMDSampleMono<T> {
simd_invoke!(T, {
let mut values = T::Vf32::set1(0.0);
for i in 0..T::Vf32::WIDTH {
let sample = self.get_value_at_current_time();
values[i] = sample;
self.increment_time_by(1);
let should_progress = match &mut self.state.stage_data {
StageData::Lerp(_, stage_time)
| StageData::LerpConcave(_, stage_time)
| StageData::LerpConvex(_, stage_time) => {
stage_time.is_ending() && !stage_time.is_intersecting_end()
}
StageData::Constant(_) => false,
};
if should_progress {
self.switch_to_next_stage();
}
}
SIMDSampleMono(values)
})
}
pub fn get_modified_envelope(
mut params: EnvelopeParameters,
envelope: EnvelopeControlData,
sample_rate: f32,
) -> EnvelopeParameters {
fn calculate_curve(value: u8, duration: f32) -> f32 {
match value {
0..=64 => (value as f32 / 64.0).powi(5) * duration,
65..=128 => duration + ((value as f32 - 64.0) / 64.0).powi(3) * 15.0,
_ => duration,
}
}
if let Some(attack) = envelope.attack {
let old_duration =
params.get_stage_duration(EnvelopeStage::Attack) as f32 / sample_rate;
let duration = (calculate_curve(attack, old_duration) * sample_rate) as u32;
let part = EnvelopeStage::Attack.as_usize();
match params.parts[part] {
EnvelopePart::Lerp {
target,
duration: _,
} => params.modify_stage_data(part, EnvelopePart::lerp(target, duration)),
EnvelopePart::LerpConvex {
target,
duration: _,
} => params.modify_stage_data(part, EnvelopePart::lerp_convex(target, duration)),
_ => {}
}
}
if let Some(release) = envelope.release {
let old_duration =
params.get_stage_duration(EnvelopeStage::Release) as f32 / sample_rate;
let duration = (calculate_curve(release, old_duration).max(0.02) * sample_rate) as u32;
let part = EnvelopeStage::Release.as_usize();
match params.parts[part] {
EnvelopePart::Lerp {
target,
duration: _,
} => params.modify_stage_data(part, EnvelopePart::lerp(target, duration)),
EnvelopePart::LerpConcave {
target,
duration: _,
} => params.modify_stage_data(part, EnvelopePart::lerp_concave(target, duration)),
_ => {}
}
}
params
}
pub fn modify_envelope(&mut self, envelope: EnvelopeControlData) {
if !self.killed {
self.params =
Self::get_modified_envelope(self.original_params, envelope, self.sample_rate);
self.update_stage();
}
}
}
impl<T: Simd> VoiceGeneratorBase for SIMDVoiceEnvelope<T> {
#[inline(always)]
fn ended(&self) -> bool {
self.state.current_stage == EnvelopeStage::Finished
}
#[inline(always)]
fn signal_release(&mut self, rel_type: ReleaseType) {
if rel_type == ReleaseType::Kill {
self.params.modify_stage_data(
5,
EnvelopePart::lerp(0.0, (0.001 * self.sample_rate) as u32),
);
self.update_stage();
self.killed = true;
}
if self.allow_release || self.killed {
let amp = self.get_value_at_current_time();
self.state = self.params.get_stage_data(EnvelopeStage::Release, amp);
}
}
#[inline(always)]
fn process_controls(&mut self, control: &VoiceControlData) {
self.modify_envelope(control.envelope);
}
}
impl<T: Simd> SIMDVoiceGenerator<T, SIMDSampleMono<T>> for SIMDVoiceEnvelope<T> {
#[inline(always)]
fn next_sample(&mut self) -> SIMDSampleMono<T> {
simd_invoke!(T, {
match &mut self.state.stage_data {
StageData::Lerp(lerper, stage_time) => {
if stage_time.is_ending() {
if stage_time.is_intersecting_end() {
self.manually_build_simd_sample()
} else {
self.switch_to_next_stage();
self.next_sample()
}
} else {
let values = lerper.lerp_simd(stage_time.progress_simd_array());
stage_time.increment();
SIMDSampleMono(values)
}
}
StageData::LerpConcave(lerper, stage_time) => {
if stage_time.is_ending() {
if stage_time.is_intersecting_end() {
self.manually_build_simd_sample()
} else {
self.switch_to_next_stage();
self.next_sample()
}
} else {
let values = lerper.lerp_simd(stage_time.progress_simd_array());
stage_time.increment();
SIMDSampleMono(values)
}
}
StageData::LerpConvex(lerper, stage_time) => {
if stage_time.is_ending() {
if stage_time.is_intersecting_end() {
self.manually_build_simd_sample()
} else {
self.switch_to_next_stage();
self.next_sample()
}
} else {
let values = lerper.lerp_simd(stage_time.progress_simd_array());
stage_time.increment();
SIMDSampleMono(values)
}
}
StageData::Constant(constant) => SIMDSampleMono(*constant),
}
})
}
}
#[cfg(test)]
mod tests {
use simdeez::simd_runtime_generate;
use to_vec::ToVec;
use super::*;
fn assert_vf32_equal<S: Simd>(a: S::Vf32, b: S::Vf32) {
for i in 0..S::Vf32::WIDTH {
assert_eq!(a[i], b[i]);
}
}
fn simd_from_vec<S: Simd>(vec: Vec<f32>) -> S::Vf32 {
let mut initial = S::Vf32::set1(0.0);
let mut iter = vec.into_iter();
for i in 0..S::Vf32::WIDTH {
initial[i] = iter.next().unwrap();
}
initial
}
#[test]
fn test_simd_lerp() {
simd_runtime_generate!(
fn run() {
let lerper = SIMDLerper::<S>::new(0.0, 1.0);
assert_eq!(lerper.lerp(0.0), 0.0);
assert_eq!(lerper.lerp(0.5), 0.5);
assert_eq!(lerper.lerp(1.0), 1.0);
assert_vf32_equal::<S>(lerper.lerp_simd(S::Vf32::set1(0.0)), S::Vf32::set1(0.0));
assert_vf32_equal::<S>(lerper.lerp_simd(S::Vf32::set1(0.5)), S::Vf32::set1(0.5));
assert_vf32_equal::<S>(lerper.lerp_simd(S::Vf32::set1(1.0)), S::Vf32::set1(1.0));
}
);
run();
}
#[test]
fn test_stage_time() {
fn simd_from_range<S: Simd>(range: std::ops::Range<usize>) -> S::Vf32 {
simd_from_vec::<S>(range.map(|v| v as f32).to_vec())
}
simd_runtime_generate!(
fn run() {
let mut time = StageTime::<S>::new(5, 20);
let mut time2 = StageTime::<S>::new(4, 20);
assert_eq!(time.simd_array_start(), 5);
assert!(!time.is_ending());
let end_simd = S::Vf32::set1(20.0);
assert_vf32_equal::<S>(
*time.raw_simd_array(),
simd_from_range::<S>(5..(5 + S::Vf32::WIDTH)),
);
assert_vf32_equal::<S>(
time.progress_simd_array(),
simd_from_range::<S>(5..(5 + S::Vf32::WIDTH)) / end_simd,
);
let mut i = 5;
while time.simd_array_start() + S::Vf32::WIDTH as u32 <= 20 {
assert_vf32_equal::<S>(
*time.raw_simd_array(),
simd_from_range::<S>(i..(i + S::Vf32::WIDTH)),
);
assert_vf32_equal::<S>(
time.progress_simd_array(),
simd_from_range::<S>(5..(5 + S::Vf32::WIDTH)) / end_simd,
);
assert_eq!(time.simd_array_start(), i as u32);
assert!(!time.is_ending());
assert!(!time.is_intersecting_end());
time.increment();
time2.increment();
i += S::Vf32::WIDTH;
}
assert_eq!(time.simd_array_start(), i as u32);
assert!(time.is_ending());
assert!(time.is_intersecting_end());
assert!(!time2.is_ending());
time2.increment();
assert!(time2.is_ending());
assert!(!time2.is_intersecting_end());
}
);
run();
}
#[test]
fn test_envelope() {
#![allow(clippy::same_item_push)]
fn push_simd_to_vec<S: Simd>(vec: &mut Vec<f32>, simd: S::Vf32) {
for i in 0..S::Vf32::WIDTH {
vec.push(simd[i]);
}
}
fn lerp(from: f32, to: f32, fac: f32) -> f32 {
from + (to - from) * fac
}
fn lerp_to_zero_curve(from: f32, fac: f32) -> f32 {
let mult = (1. - fac).powi(8);
mult * from
}
fn lerp_concave(from: f32, to: f32, fac: f32) -> f32 {
let mult = (1. - fac).powi(8);
(from - to) * mult + to
}
simd_runtime_generate!(
fn run() {
let mut vec = Vec::new();
let descriptor = EnvelopeDescriptor {
start_percent: 0.5,
delay: 0.0,
attack: 15.0,
hold: 0.0,
decay: 17.0,
sustain_percent: 0.4,
release: 16.0,
};
let params = descriptor.to_envelope_params(1, Default::default());
assert!(matches!(
params.parts[EnvelopeStage::Decay.as_usize()],
EnvelopePart::LerpConcave {
target,
duration
} if target == 0.4 && duration == 17
));
let mut env = SIMDVoiceEnvelope::<S>::new(params, params, true, 1.0);
let mut i = 0;
while i < 48 {
push_simd_to_vec::<S>(&mut vec, env.next_sample().0);
i += S::Vf32::WIDTH;
}
env.signal_release(ReleaseType::Standard);
assert_eq!(env.current_stage(), &EnvelopeStage::Release);
while i < 48 + 32 {
push_simd_to_vec::<S>(&mut vec, env.next_sample().0);
i += S::Vf32::WIDTH;
}
let mut expected_vec = Vec::new();
for i in 0..15 {
expected_vec.push(lerp(0.5, 1.0, i as f32 / 15.0));
}
for i in 0..17 {
expected_vec.push(lerp_concave(1.0, 0.4, i as f32 / 17.0));
}
for _ in 0..16 {
expected_vec.push(0.4);
}
for i in 0..16 {
expected_vec.push(lerp_to_zero_curve(0.4, i as f32 / 16.0));
}
for _ in 0..16 {
expected_vec.push(0.0);
}
for v in vec.iter_mut().chain(expected_vec.iter_mut()) {
*v = (*v * 10000.0).round() / 10000.0;
}
assert_eq!(vec, expected_vec);
}
);
run();
}
}