diff --git a/.gitignore b/.gitignore index 9e636844..c2a7f34b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ public/midi_renderer.wasm public/oscilloscope.wasm public/spectrum_viz_full.wasm public/sampler.wasm +public/safety_limiter.wasm diff --git a/Justfile b/Justfile index d0d90c23..fc0c574f 100644 --- a/Justfile +++ b/Justfile @@ -87,6 +87,7 @@ build-all: cp ./engine/target/wasm32-unknown-unknown/release/oscilloscope.wasm ./public cp ./engine/target/wasm32-unknown-unknown/release/spectrum_viz_full.wasm ./public cp ./engine/target/wasm32-unknown-unknown/release/sampler.wasm ./public + cp ./engine/target/wasm32-unknown-unknown/release/safety_limiter.wasm ./public cp ./engine/build/* ./src just build-sinsy @@ -319,3 +320,7 @@ build-sampler: debug-sampler: cd ./engine/sampler && cargo build --target wasm32-unknown-unknown && \ cp ../target/wasm32-unknown-unknown/debug/sampler.wasm ../../public + +debug-safety-limiter: + cd ./engine/safety_limiter && cargo build --target wasm32-unknown-unknown && \ + cp ../target/wasm32-unknown-unknown/debug/safety_limiter.wasm ../../public diff --git a/engine/Cargo.lock b/engine/Cargo.lock index ac8b82c8..9ac7508a 100644 --- a/engine/Cargo.lock +++ b/engine/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adsr" @@ -873,6 +873,14 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safety_limiter" +version = "0.1.0" +dependencies = [ + "compressor", + "dsp", +] + [[package]] name = "sample_editor" version = "0.1.0" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 2bd2739d..7d5ba443 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -31,7 +31,8 @@ members = [ "midi_renderer", "oscilloscope", "canvas_utils", - "sampler" + "sampler", + "safety_limiter" ] [profile.release] diff --git a/engine/compressor/Cargo.toml b/engine/compressor/Cargo.toml index e1575b27..6cd69f31 100644 --- a/engine/compressor/Cargo.toml +++ b/engine/compressor/Cargo.toml @@ -9,3 +9,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] dsp = { path = "../dsp" } + +[features] +exports = [] +default = ["exports"] diff --git a/engine/compressor/src/lib.rs b/engine/compressor/src/lib.rs index 9920a770..c500a3f0 100644 --- a/engine/compressor/src/lib.rs +++ b/engine/compressor/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(const_float_methods)] + use dsp::{ circular_buffer::CircularBuffer, db_to_gain, @@ -224,7 +226,7 @@ fn detect_level_peak( /// Given the attack time in milliseconds, compute the coefficient for a one-pole lowpass filter to /// be used in the envelope follower. -fn compute_attack_coefficient(attack_time_ms: f32) -> f32 { +pub const fn compute_attack_coefficient(attack_time_ms: f32) -> f32 { let attack_time_s = (attack_time_ms * 0.001).max(0.0001); let attack_time_samples = attack_time_s * SAMPLE_RATE; let attack_coefficient = 1. - 1. / attack_time_samples; @@ -233,7 +235,7 @@ fn compute_attack_coefficient(attack_time_ms: f32) -> f32 { /// Given the release time in milliseconds, compute the coefficient for a one-pole highpass filter /// to be used in the envelope follower. -fn compute_release_coefficient(release_time_ms: f32) -> f32 { +pub const fn compute_release_coefficient(release_time_ms: f32) -> f32 { let release_time_s = (release_time_ms * 0.001).max(0.0001); let release_time_samples = release_time_s * SAMPLE_RATE; let release_coefficient = 1. / release_time_samples; @@ -547,6 +549,7 @@ impl MultibandCompressor { } } +#[cfg(feature = "exports")] #[no_mangle] pub extern "C" fn init_compressor() -> *mut MultibandCompressor { use std::fmt::Write; @@ -561,24 +564,28 @@ pub extern "C" fn init_compressor() -> *mut MultibandCompressor { Box::into_raw(Box::new(compressor)) } +#[cfg(feature = "exports")] #[no_mangle] pub extern "C" fn get_compressor_input_buf_ptr(compressor: *mut MultibandCompressor) -> *mut f32 { let compressor = unsafe { &mut *compressor }; compressor.input_buffer.as_mut_ptr() } +#[cfg(feature = "exports")] #[no_mangle] pub extern "C" fn get_compressor_output_buf_ptr(compressor: *mut MultibandCompressor) -> *mut f32 { let compressor = unsafe { &mut *compressor }; compressor.output_buffer.as_mut_ptr() } +#[cfg(feature = "exports")] #[no_mangle] pub extern "C" fn get_sab_ptr(compressor: *mut MultibandCompressor) -> *mut f32 { let compressor = unsafe { &mut *compressor }; compressor.sab.as_mut_ptr() } +#[cfg(feature = "exports")] #[no_mangle] pub extern "C" fn process_compressor( compressor: *mut MultibandCompressor, diff --git a/engine/waveform_renderer/src/lib.rs b/engine/waveform_renderer/src/lib.rs index 69f1193c..0beeb605 100644 --- a/engine/waveform_renderer/src/lib.rs +++ b/engine/waveform_renderer/src/lib.rs @@ -76,7 +76,7 @@ pub fn get_waveform_buf_ptr(ctx: *mut WaveformRendererCtx) -> *mut f32 { } #[inline(always)] -fn ms_to_samples(sample_rate: f32, ms: f32) -> u32 { ((ms * sample_rate) / 1000.) as u32 } +const fn ms_to_samples(sample_rate: f32, ms: f32) -> u32 { ((ms * sample_rate) / 1000.) as u32 } #[inline(always)] fn sample_to_y_val(sample: f32, half_height: f32, max_distance_from_0: f32) -> u32 { diff --git a/engine/wavetable/src/fm/mod.rs b/engine/wavetable/src/fm/mod.rs index e36d83ed..3e164061 100644 --- a/engine/wavetable/src/fm/mod.rs +++ b/engine/wavetable/src/fm/mod.rs @@ -2699,7 +2699,8 @@ pub extern "C" fn fm_synth_get_wavetable_data_ptr( ctx.wavetables[wavetable_ix] = new_wavetable; } else if ctx.wavetables.len() != wavetable_ix { panic!( - "Tried to set wavetable index {} but only {} wavetables exist", + "Tried to set wavetable index {} but only {} wavetables exist. Wavetable bank indices are \ + managed by the JS code in `FmSynth.tsx`; it's not our fault!", wavetable_ix, ctx.wavetables.len() ); diff --git a/engine/wavetable/src/lib.rs b/engine/wavetable/src/lib.rs index 854bc879..c7fddd67 100644 --- a/engine/wavetable/src/lib.rs +++ b/engine/wavetable/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(const_maybe_uninit_assume_init, get_mut_unchecked)] +#![feature(get_mut_unchecked)] pub mod fm; diff --git a/faust-compiler/FaustWorkletModuleTemplate.template.js b/faust-compiler/FaustWorkletModuleTemplate.template.js index 1f3352e5..04f0b9c5 100644 --- a/faust-compiler/FaustWorkletModuleTemplate.template.js +++ b/faust-compiler/FaustWorkletModuleTemplate.template.js @@ -274,11 +274,7 @@ class FaustAudioWorkletProcessor extends AudioWorkletProcessor { for (let i = 0; i < Math.min(outputs.length, this.dspOutChannels.length); i++) { const dspOutput = this.dspOutChannels[i]; for (let channelIx = 0; channelIx < outputs[i].length; channelIx++) { - // If we send NaNs or out-of-range values, computer gets very upset and destroys - // my entire system audio on Linux until I shut down this AWP. - for (let sampleIx = 0; sampleIx < outputs[i][channelIx].length; sampleIx++) { - outputs[i][channelIx][sampleIx] = clamp(-1.0, 1.0, dspOutput[sampleIx]); - } + outputs[i][channelIx].set(dspOutput); } } diff --git a/src/graphEditor/nodes/CustomAudio/FMSynth/FMSynth.tsx b/src/graphEditor/nodes/CustomAudio/FMSynth/FMSynth.tsx index 1ba322dd..d4520ead 100644 --- a/src/graphEditor/nodes/CustomAudio/FMSynth/FMSynth.tsx +++ b/src/graphEditor/nodes/CustomAudio/FMSynth/FMSynth.tsx @@ -466,6 +466,7 @@ export default class FMSynth implements ForeignNode { samples: bank.samples, }; if ( + this.lastSetWavetableData.wavetableIx === setWavetableData.wavetableIx && this.lastSetWavetableData.baseFrequency === setWavetableData.baseFrequency && this.lastSetWavetableData.waveformsPerDimension === setWavetableData.waveformsPerDimension && this.lastSetWavetableData.waveformLength === setWavetableData.waveformLength &&