dfpworm start
This commit is contained in:
parent
ac98cbe6c7
commit
30c6bf5f6f
7 changed files with 280 additions and 21 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -152,15 +152,6 @@ version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
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]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -1823,7 +1814,6 @@ source = "git+https://github.com/robbert-vdh/nih-plug.git#28b149ec4d62757d0b4488
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"anymap3",
|
"anymap3",
|
||||||
"assert_no_alloc",
|
|
||||||
"atomic_float 0.1.0",
|
"atomic_float 0.1.0",
|
||||||
"atomic_refcell",
|
"atomic_refcell",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
|
|
@ -5,3 +5,12 @@ members = [
|
||||||
"plugins/dfpworm",
|
"plugins/dfpworm",
|
||||||
"plugins/gain",
|
"plugins/gain",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
strip = "symbols"
|
||||||
|
|
||||||
|
[profile.profiling]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
strip = "none"
|
||||||
|
|
|
@ -8,5 +8,5 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atomic_float = "1.1"
|
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" }
|
nih_plug_vizia = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" }
|
||||||
|
|
36
plugins/dfpworm/dfpwm-spec.md
Normal file
36
plugins/dfpworm/dfpwm-spec.md
Normal file
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
77
plugins/dfpworm/src/dfpwm.rs
Normal file
77
plugins/dfpworm/src/dfpwm.rs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,161 @@
|
||||||
pub fn add(left: u64, right: u64) -> u64 {
|
use std::sync::Arc;
|
||||||
left + right
|
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
|
||||||
|
mod dfpwm;
|
||||||
|
|
||||||
|
pub struct Worm {
|
||||||
|
params: Arc<WormParams>,
|
||||||
|
|
||||||
|
compress_contexts: Vec<dfpwm::Context>,
|
||||||
|
decompress_contexts: Vec<dfpwm::Context>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[derive(Params)]
|
||||||
mod tests {
|
struct WormParams {
|
||||||
use super::*;
|
#[id = "response-increase"]
|
||||||
|
response_increase: FloatParam,
|
||||||
|
#[id = "response-decrease"]
|
||||||
|
response_decrease: FloatParam,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
impl Default for Worm {
|
||||||
fn it_works() {
|
fn default() -> Self {
|
||||||
let result = add(2, 2);
|
Self {
|
||||||
assert_eq!(result, 4);
|
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<dyn Params> {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
audio_io_layout: &AudioIOLayout,
|
||||||
|
_buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> 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<Self>,
|
||||||
|
) -> 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);
|
||||||
|
|
|
@ -8,5 +8,5 @@ crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atomic_float = "1.1"
|
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" }
|
nih_plug_vizia = { git = "https://github.com/robbert-vdh/nih-plug.git", version = "0.0.0" }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue