From 30c6bf5f6fc097042dc7f09b275da44be70750cf Mon Sep 17 00:00:00 2001 From: Haze Weathers Date: Tue, 16 Sep 2025 14:47:30 -0600 Subject: [PATCH] dfpworm start --- Cargo.lock | 10 --- Cargo.toml | 9 ++ plugins/dfpworm/Cargo.toml | 2 +- plugins/dfpworm/dfpwm-spec.md | 36 ++++++++ plugins/dfpworm/src/dfpwm.rs | 77 ++++++++++++++++ plugins/dfpworm/src/lib.rs | 165 ++++++++++++++++++++++++++++++++-- plugins/gain/Cargo.toml | 2 +- 7 files changed, 280 insertions(+), 21 deletions(-) create mode 100644 plugins/dfpworm/dfpwm-spec.md create mode 100644 plugins/dfpworm/src/dfpwm.rs diff --git a/Cargo.lock b/Cargo.lock index 5bd0a92..d0b46c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,15 +152,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "assert_no_alloc" -version = "1.1.2" -source = "git+https://github.com/robbert-vdh/rust-assert-no-alloc.git?branch=feature%2Fnested-permit-forbid#a6fb4f62b9624715291e320ea5f0f70e73b035cf" -dependencies = [ - "backtrace", - "log", -] - [[package]] name = "async-broadcast" version = "0.5.1" @@ -1823,7 +1814,6 @@ source = "git+https://github.com/robbert-vdh/nih-plug.git#28b149ec4d62757d0b4488 dependencies = [ "anyhow", "anymap3", - "assert_no_alloc", "atomic_float 0.1.0", "atomic_refcell", "backtrace", diff --git a/Cargo.toml b/Cargo.toml index 0f91226..7146d8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,12 @@ members = [ "plugins/dfpworm", "plugins/gain", ] + +[profile.release] +lto = "thin" +strip = "symbols" + +[profile.profiling] +inherits = "release" +debug = true +strip = "none" diff --git a/plugins/dfpworm/Cargo.toml b/plugins/dfpworm/Cargo.toml index 1a0860d..c68e1ca 100644 --- a/plugins/dfpworm/Cargo.toml +++ b/plugins/dfpworm/Cargo.toml @@ -8,5 +8,5 @@ crate-type = ["cdylib"] [dependencies] atomic_float = "1.1" -nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0", features = ["assert_process_allocs"] } +nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" } nih_plug_vizia = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" } diff --git a/plugins/dfpworm/dfpwm-spec.md b/plugins/dfpworm/dfpwm-spec.md new file mode 100644 index 0000000..da9d4cb --- /dev/null +++ b/plugins/dfpworm/dfpwm-spec.md @@ -0,0 +1,36 @@ + +# Decoding +``` +sample = 1.0 if predictor(bit) else -1.0` +``` + +# Encoding +``` +if sample > last_predictor or (sample == last_predictor == 1.0): + bit = predictor(1) +else: + bit = predictor(0) +``` + +# Predictor + +## State +charge: f32 = 0.0 +strength: f32 = 1.0 +last_bit: bool = false + +## Parameters +- strength_increase: f32 = 7.0 / 127.0 +- strength_decrease: f32 = 20.0 / 128.0 + +## Procedure + +### Input comprehension +``` +target: f32 = 1.0 if bit == true else -1.0 +``` + +### Charge Adjustment +``` + +``` diff --git a/plugins/dfpworm/src/dfpwm.rs b/plugins/dfpworm/src/dfpwm.rs new file mode 100644 index 0000000..9965b9c --- /dev/null +++ b/plugins/dfpworm/src/dfpwm.rs @@ -0,0 +1,77 @@ +pub const DEFAULT_RESPONSE_INCREASE: f32 = 7.0 / 127.0; +pub const DEFAULT_RESPONSE_DECREASE: f32 = 20.0 / 128.0; + +const NUDGE: f32 = 1.0 / 128.0; + +pub struct Context { + pub response_increase: f32, + pub response_decrease: f32, + + level: f32, + response: f32, + last_bit: bool, +} + +impl Context { + pub fn new() -> Self { + Self { + response_increase: DEFAULT_RESPONSE_INCREASE, + response_decrease: DEFAULT_RESPONSE_DECREASE, + + level: 0.0, + response: NUDGE, + last_bit: false, + } + } + + pub fn reset(self: &mut Self) { + self.level = 0.0; + self.response = NUDGE; + self.last_bit = false; + } + + pub fn compress(self: &mut Self, sample: f32) -> bool { + let bit = sample > self.level || (sample == self.level && self.level == 1.0); + self.update(bit); + bit + } + + pub fn decompress(self: &mut Self, bit: bool) -> f32 { + // let last_bit = self.last_bit; + self.update(bit); + self.level + } + + fn update(self: &mut Self, current_bit: bool) { + // update level + let target: f32 = if current_bit { 1.0 } else { -1.0 }; + let new_level = (self.level + (self.response * (target - self.level))).clamp(-1.0, 1.0); + if new_level == self.level && self.level != target { + if target < self.level { + self.level -= NUDGE; + } else { + self.level += NUDGE; + } + } else { + self.level = new_level; + } + + // update response + let (response_target, response_delta) = if current_bit == self.last_bit { + (1.0f32, self.response_increase) + } else { + (0.0f32, self.response_decrease) + }; + let new_response = + (self.response + (response_delta * (response_target - self.response))).clamp(-1.0, 1.0); + if new_response == self.response && self.response != response_target { + if response_target < self.response { + self.response -= NUDGE; + } else { + self.response += NUDGE; + } + } else { + self.response = new_response; + } + } +} diff --git a/plugins/dfpworm/src/lib.rs b/plugins/dfpworm/src/lib.rs index b93cf3f..8a6a3ae 100644 --- a/plugins/dfpworm/src/lib.rs +++ b/plugins/dfpworm/src/lib.rs @@ -1,14 +1,161 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use std::sync::Arc; + +use nih_plug::prelude::*; + +mod dfpwm; + +pub struct Worm { + params: Arc, + + compress_contexts: Vec, + decompress_contexts: Vec, } -#[cfg(test)] -mod tests { - use super::*; +#[derive(Params)] +struct WormParams { + #[id = "response-increase"] + response_increase: FloatParam, + #[id = "response-decrease"] + response_decrease: FloatParam, +} - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +impl Default for Worm { + fn default() -> Self { + Self { + params: Arc::new(WormParams::default()), + + compress_contexts: Vec::new(), + decompress_contexts: Vec::new(), + } } } + +impl Default for WormParams { + fn default() -> Self { + Self { + response_increase: FloatParam::new( + "Response Increase", + dfpwm::DEFAULT_RESPONSE_INCREASE, + FloatRange::Linear { min: 0.0, max: 1.0 }, + ), + + response_decrease: FloatParam::new( + "Response Decrease", + dfpwm::DEFAULT_RESPONSE_DECREASE, + FloatRange::Linear { min: 0.0, max: 1.0 }, + ), + } + } +} + +impl Plugin for Worm { + const NAME: &'static str = "Dirty Frying Pan Worm"; + + const VENDOR: &'static str = "fogwaves"; + + const URL: &'static str = "https://example.com"; + + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + type SysExMessage = (); + + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn initialize( + &mut self, + audio_io_layout: &AudioIOLayout, + _buffer_config: &BufferConfig, + _context: &mut impl InitContext, + ) -> bool { + if let Some(n) = audio_io_layout.main_input_channels { + for _i in 0..n.into() { + self.compress_contexts.push(dfpwm::Context::new()); + self.decompress_contexts.push(dfpwm::Context::new()); + } + } + + true + } + + fn reset(&mut self) { + for ctx in self.compress_contexts.iter_mut() { + ctx.reset(); + } + for ctx in self.decompress_contexts.iter_mut() { + ctx.reset(); + } + } + + fn deactivate(&mut self) {} + + fn process( + &mut self, + buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + _context: &mut impl ProcessContext, + ) -> ProcessStatus { + for channel_samples in buffer.iter_samples() { + let response_increase = self.params.response_increase.smoothed.next(); + let response_decrease = self.params.response_decrease.smoothed.next(); + + for (i, sample) in channel_samples.into_iter().enumerate() { + self.compress_contexts[i].response_increase = response_increase; + self.decompress_contexts[i].response_decrease = response_decrease; + + let bit = self.compress_contexts[i].compress(*sample); + *sample = self.decompress_contexts[i].decompress(bit); + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for Worm { + const CLAP_ID: &'static str = "fogwaves.dfpworm"; + + const CLAP_DESCRIPTION: Option<&'static str> = + Some("Runs audio through the DFPWM 1-bit audio codec by Ben \"GreaseMonkey\" Russel."); + + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + + const CLAP_SUPPORT_URL: Option<&'static str> = None; + + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Mono, + ClapFeature::Distortion, + ]; +} + +impl Vst3Plugin for Worm { + const VST3_CLASS_ID: [u8; 16] = *b"FogwavesDFPWormC"; + + const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = + &[Vst3SubCategory::Fx, Vst3SubCategory::Distortion]; +} + +nih_export_clap!(Worm); +nih_export_vst3!(Worm); diff --git a/plugins/gain/Cargo.toml b/plugins/gain/Cargo.toml index d64823a..0c8d3cf 100644 --- a/plugins/gain/Cargo.toml +++ b/plugins/gain/Cargo.toml @@ -8,5 +8,5 @@ crate-type = ["cdylib"] [dependencies] atomic_float = "1.1" -nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0", features = ["assert_process_allocs"] } +nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" } nih_plug_vizia = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" }