Skip to content

Commit

Permalink
Add bezier curve ADSR curve type
Browse files Browse the repository at this point in the history
 * Lots of fancy stuff to make it so that the handle for adjusting it is on the curve itself and the curve stays bounded
 * Update backend to de/serialize the new rampfn type
 * Clean up all warnings in the wasm engine
 * Fix some random bugs and other issues
  • Loading branch information
Ameobea committed Dec 26, 2024
1 parent 37133c2 commit 894f1f6
Show file tree
Hide file tree
Showing 35 changed files with 548 additions and 211 deletions.
9 changes: 9 additions & 0 deletions backend/src/models/synth_preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,17 @@ pub struct AudioThreadData {
debug_name: Option<String>,
}

#[derive(Serialize, Deserialize)]
pub struct Point2D {
x: f32,
y: f32,
}

// export type RampFn =
// | { type: 'linear' }
// | { type: 'instant' }
// | { type: 'exponential'; exponent: number };
// | { type: 'bezier'; controlPoints: { x: number; y: number }[]; }
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum RampFn {
Expand All @@ -159,6 +166,8 @@ pub enum RampFn {
Instant,
#[serde(rename = "exponential")]
Exponential { exponent: f32 },
#[serde(rename = "bezier")]
Bezier { control_points: Vec<Point2D> },
}

// export interface AdsrStep {
Expand Down
3 changes: 3 additions & 0 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions engine/adsr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ version = "0.1.0"
crate-type = ["cdylib", "rlib"]

[dependencies]
dsp = {path = "../dsp" }
dsp = { path = "../dsp", default-features = false }
common = { path = "../common", default-features = false }

[features]
exports = []
default = ["exports"] # TODO REVERT
default = ["exports"]
58 changes: 32 additions & 26 deletions engine/adsr/src/exports.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::rc::Rc;

use crate::{managed_adsr::ManagedAdsr, Adsr, AdsrStep, RampFn, RENDERED_BUFFER_SIZE};
use common::ref_static_mut;

use crate::{
managed_adsr::ManagedAdsr, Adsr, AdsrLengthMode, AdsrStep, RampFn, RENDERED_BUFFER_SIZE,
};

extern "C" {
fn log_err(msg: *const u8, len: usize);
Expand Down Expand Up @@ -28,54 +32,56 @@ fn round_tiny_to_zero(val: f32) -> f32 {
}
}

/// Number of `f32`s needed to encode one step in the encoded ADSR step format
const STEP_F32_COUNT: usize = 7;

fn decode_steps(encoded_steps: &[f32]) -> Vec<AdsrStep> {
assert_eq!(
encoded_steps.len() % 4,
encoded_steps.len() % STEP_F32_COUNT,
0,
"`encoded_steps` length must be divisible by 4"
"`encoded_steps` length must be divisible by {STEP_F32_COUNT}"
);
encoded_steps
.chunks_exact(4)
.map(|vals| match vals {
&[x, y, ramp_fn_type, ramp_fn_param] => {
.array_chunks::<STEP_F32_COUNT>()
.map(
|&[x, y, ramp_fn_type, ramp_fn_param_0, ramp_fn_param_1, ramp_fn_param_2, ramp_fn_param_3]| {
let ramper = match ramp_fn_type {
x if x == 0. => RampFn::Instant,
x if x == 1. => RampFn::Linear,
x if x == 2. => RampFn::Exponential {
exponent: ramp_fn_param,
exponent: ramp_fn_param_0,
},
x if x == 3. => RampFn::Bezier {
x1: ramp_fn_param_0,
y1: ramp_fn_param_1,
x2: ramp_fn_param_2,
y2: ramp_fn_param_3,
},
_ => unreachable!("Invalid ramp fn type val"),
other => unreachable!("Invalid ramp fn type val: {other}"),
};
AdsrStep {
x: round_tiny_to_zero(x),
y: round_tiny_to_zero(y),
ramper,
}
},
_ => unreachable!(),
})
)
.collect()
}

static mut ENCODED_ADSR_STEP_BUF: Vec<f32> = Vec::new();

/// Resizes the step buffer to hold at least `step_count` steps (`step_count * 4` f32s)
/// Resizes the step buffer to hold at least `step_count` steps (`step_count * STEP_F32_COUNT` f32s)
#[no_mangle]
pub unsafe extern "C" fn get_encoded_adsr_step_buf_ptr(step_count: usize) -> *mut f32 {
let needed_capacity = step_count * 4;
if ENCODED_ADSR_STEP_BUF.capacity() < needed_capacity {
let additional = needed_capacity - ENCODED_ADSR_STEP_BUF.capacity();
ENCODED_ADSR_STEP_BUF.reserve(additional);
let needed_capacity = step_count * STEP_F32_COUNT;
let encoded_step_buf = ref_static_mut!(ENCODED_ADSR_STEP_BUF);
if encoded_step_buf.capacity() < needed_capacity {
let additional = needed_capacity - encoded_step_buf.capacity();
encoded_step_buf.reserve(additional);
}
ENCODED_ADSR_STEP_BUF.set_len(needed_capacity);
ENCODED_ADSR_STEP_BUF.as_mut_ptr()
}

#[derive(Clone, Copy)]
#[repr(u32)]
pub enum AdsrLengthMode {
Ms = 0,
Beats = 1,
encoded_step_buf.set_len(needed_capacity);
encoded_step_buf.as_mut_ptr()
}

impl AdsrLengthMode {
Expand Down Expand Up @@ -126,7 +132,7 @@ pub unsafe extern "C" fn create_adsr_ctx(
let length_mode = AdsrLengthMode::from_u32(length_mode);

let rendered: Rc<[f32; RENDERED_BUFFER_SIZE]> = Rc::new([0.0f32; RENDERED_BUFFER_SIZE]);
let decoded_steps = decode_steps(ENCODED_ADSR_STEP_BUF.as_slice());
let decoded_steps = decode_steps(ref_static_mut!(ENCODED_ADSR_STEP_BUF).as_slice());
assert!(adsr_count > 0);

let mut adsrs = Vec::with_capacity(adsr_count);
Expand Down Expand Up @@ -164,7 +170,7 @@ pub unsafe extern "C" fn create_adsr_ctx(

#[no_mangle]
pub unsafe extern "C" fn update_adsr_steps(ctx: *mut AdsrContext) {
let decoded_steps = decode_steps(ENCODED_ADSR_STEP_BUF.as_slice());
let decoded_steps = decode_steps(ref_static_mut!(ENCODED_ADSR_STEP_BUF).as_slice());
for adsr in &mut (*ctx).adsrs {
adsr.adsr.set_steps(decoded_steps.clone());
}
Expand Down
33 changes: 29 additions & 4 deletions engine/adsr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![feature(get_mut_unchecked, array_windows)]
#![feature(get_mut_unchecked, array_windows, array_chunks)]

use std::rc::Rc;

Expand All @@ -19,24 +19,43 @@ const SAMPLE_RATE: usize = 44_100;
pub const RENDERED_BUFFER_SIZE: usize = SAMPLE_RATE;
const FRAME_SIZE: usize = 128;

#[derive(Clone, Copy)]
#[repr(u32)]
pub enum AdsrLengthMode {
Ms = 0,
Beats = 1,
}

#[derive(Clone, Copy)]
pub enum RampFn {
Instant,
Linear,
Exponential { exponent: f32 },
Bezier { x1: f32, y1: f32, x2: f32, y2: f32 },
}

impl RampFn {
pub fn from_u32(type_val: u32, param: f32) -> Self {
pub fn from_u32(type_val: u32, param0: f32, param1: f32, param2: f32, param3: f32) -> Self {
match type_val {
0 => Self::Instant,
1 => Self::Linear,
2 => Self::Exponential { exponent: param },
2 => Self::Exponential { exponent: param0 },
3 => Self::Bezier {
x1: param0,
y1: param1,
x2: param2,
y2: param3,
},
_ => panic!("Invlaid ramper fn type: {}", type_val),
}
}
}

fn eval_cubic_bezier(y0: f32, y1: f32, y2: f32, y3: f32, t: f32) -> f32 {
let mt = 1. - t;
y0 * mt.powi(3) + 3. * y1 * mt.powi(2) * t + 3. * y2 * mt * t.powi(2) + y3 * t.powi(3)
}

fn compute_pos(prev_step: &AdsrStep, next_step: &AdsrStep, phase: f32) -> f32 {
let distance = next_step.x - prev_step.x;
debug_assert!(distance > 0.);
Expand All @@ -53,7 +72,12 @@ fn compute_pos(prev_step: &AdsrStep, next_step: &AdsrStep, phase: f32) -> f32 {
let y_diff = next_step.y - prev_step.y;
let x = (phase - prev_step.x) / distance;
prev_step.y + x.powf(exponent) * y_diff
// prev_step.y + even_faster_pow(x, exponent) * y_diff
},
RampFn::Bezier { y1, y2, .. } => {
let y0 = prev_step.y;
let y3 = next_step.y;
let t = (phase - prev_step.x) / (next_step.x - prev_step.x);
eval_cubic_bezier(y0, y1, y2, y3, t)
},
}
}
Expand Down Expand Up @@ -120,6 +144,7 @@ pub struct EarlyReleaseConfig {
}

impl EarlyReleaseConfig {
#[cfg(feature = "exports")]
pub(crate) fn from_parts(
early_release_mode_type: usize,
early_release_mode_param: usize,
Expand Down
2 changes: 1 addition & 1 deletion engine/adsr/src/managed_adsr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{exports::AdsrLengthMode, Adsr, SAMPLE_RATE};
use crate::{Adsr, AdsrLengthMode, SAMPLE_RATE};

fn ms_to_samples(ms: f32) -> f32 { (ms / 1000.) * SAMPLE_RATE as f32 }

Expand Down
4 changes: 2 additions & 2 deletions engine/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ pub fn set_raw_panic_hook(log_err: unsafe extern "C" fn(ptr: *const u8, len: usi
std::panic::set_hook(Box::new(hook))
}

/// Implements `&mut *std::ptr::addr_of_mut!(x)` to work around the annoying new Rust rules on
/// Implements `&mut *&raw mut x` to work around the annoying new Rust rules on
/// referencing static muts
#[macro_export]
macro_rules! ref_static_mut {
($x:expr) => {
unsafe { &mut *std::ptr::addr_of_mut!($x) }
unsafe { &mut *&raw mut $x }
};
}
2 changes: 0 additions & 2 deletions engine/compressor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![feature(const_float_methods)]

use dsp::{
circular_buffer::CircularBuffer,
db_to_gain,
Expand Down
22 changes: 12 additions & 10 deletions engine/dsp/src/lookup_tables.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
const LOOKUP_TABLE_SIZE: usize = 1024 * 16;

static mut SINE_LOOKUP_TABLE: *mut [f32; LOOKUP_TABLE_SIZE] = std::ptr::null_mut();
static mut SINE_LOOKUP_TABLE: [f32; LOOKUP_TABLE_SIZE] = [0.; LOOKUP_TABLE_SIZE];

fn get_sine_lookup_table_ptr() -> *const [f32; LOOKUP_TABLE_SIZE] { &raw const SINE_LOOKUP_TABLE }

fn get_sine_lookup_table_ptr_mut() -> *mut [f32; LOOKUP_TABLE_SIZE] { &raw mut SINE_LOOKUP_TABLE }

pub fn get_sine_lookup_table() -> &'static [f32; LOOKUP_TABLE_SIZE] {
unsafe { &*SINE_LOOKUP_TABLE }
unsafe { &*get_sine_lookup_table_ptr() }
}

#[inline(always)]
fn uninit<T>() -> T { unsafe { std::mem::MaybeUninit::uninit().assume_init() } }

#[cold]
pub fn maybe_init_lookup_tables() {
unsafe {
if SINE_LOOKUP_TABLE.is_null() {
SINE_LOOKUP_TABLE = Box::into_raw(Box::new(uninit()));

if SINE_LOOKUP_TABLE[1] == 0. {
let base_ptr = get_sine_lookup_table_ptr_mut() as *mut f32;
for i in 0..LOOKUP_TABLE_SIZE {
*(*SINE_LOOKUP_TABLE).get_unchecked_mut(i) =
(std::f32::consts::PI * 2. * (i as f32 / LOOKUP_TABLE_SIZE as f32)).sin();
let val = (std::f32::consts::PI * 2. * (i as f32 / LOOKUP_TABLE_SIZE as f32)).sin();
let ptr = base_ptr.add(i);
std::ptr::write(ptr, val);
}
}
}
Expand Down
26 changes: 14 additions & 12 deletions engine/engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,25 +228,27 @@ pub fn set_foreign_connectables(foreign_connectables_json: &str) {
#[wasm_bindgen]
pub fn render_small_view(vc_id: &str, target_dom_id: &str) {
let uuid = Uuid::from_str(&vc_id).expect("Invalid UUID string passed to `render_small_view`!");
let vc_entry = get_vcm().get_vc_by_id_mut(uuid).unwrap_or_else(|| {
panic!(
"Attempted to get audio connectables of VC with ID {} but it wasn't found",
vc_id
)
});
let Some(vc_entry) = get_vcm().get_vc_by_id_mut(uuid) else {
error!(
"Attempted to get audio connectables of VC with ID {vc_id} while trying to render small \
view, but it wasn't found",
);
return;
};

vc_entry.context.render_small_view(target_dom_id);
}

#[wasm_bindgen]
pub fn cleanup_small_view(vc_id: &str, target_dom_id: &str) {
let uuid = Uuid::from_str(&vc_id).expect("Invalid UUID string passed to `cleanup_small_view`!");
let vc_entry = get_vcm().get_vc_by_id_mut(uuid).unwrap_or_else(|| {
panic!(
"Attempted to get audio connectables of VC with ID {} but it wasn't found",
vc_id
)
});
let Some(vc_entry) = get_vcm().get_vc_by_id_mut(uuid) else {
error!(
"Attempted to get audio connectables of VC with ID {vc_id} while cleaning up small view, \
but it wasn't found",
);
return;
};

vc_entry.context.cleanup_small_view(target_dom_id);
}
Expand Down
Loading

0 comments on commit 894f1f6

Please sign in to comment.