Restructured project for separate plugins
This commit is contained in:
6
.cargo/config.toml
Normal file
6
.cargo/config.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[unstable]
|
||||||
|
unstable-options = true
|
||||||
|
bindeps = true
|
||||||
|
|
||||||
|
[build]
|
||||||
|
artifact-dir = "out"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
clap-host
|
||||||
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in library 'dsp'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--lib",
|
||||||
|
"--package=ebu-dsp"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "dsp",
|
||||||
|
"kind": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -17,6 +17,20 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "airfreshener"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"baseview",
|
||||||
|
"crossbeam",
|
||||||
|
"dsp",
|
||||||
|
"femtovg",
|
||||||
|
"image",
|
||||||
|
"nih_plug",
|
||||||
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.100"
|
||||||
@@ -321,7 +335,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ebu-dsp"
|
name = "dsp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"femtovg",
|
"femtovg",
|
||||||
@@ -330,17 +344,11 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ebu-plug-core"
|
name = "ebu-plugs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"baseview",
|
"airfreshener",
|
||||||
"crossbeam",
|
"helpers",
|
||||||
"ebu-dsp",
|
|
||||||
"femtovg",
|
|
||||||
"image",
|
|
||||||
"nih_plug",
|
|
||||||
"parking_lot",
|
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -469,6 +477,10 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ebu-plug-core"
|
name = "ebu-plugs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@@ -7,14 +7,10 @@ edition = "2024"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ebu-dsp = { version = "0.1.0", path = "ebu-dsp" }
|
airfreshener = { path = "plug-airfreshener", lib = true, artifact = "cdylib" }
|
||||||
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false }
|
|
||||||
parking_lot = "0.12.5"
|
[build-dependencies]
|
||||||
baseview = { git = "https://github.com/RustAudio/baseview", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", features = ["opengl"]}
|
helpers = { path = "helpers" }
|
||||||
crossbeam = "0.8.4"
|
|
||||||
femtovg = "0.19.3"
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
image = { version = "0.25.9", default-features = false, features = ["png"] }
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["ebu-dsp"]
|
members = ["dsp", "helpers", "plug-airfreshener"]
|
||||||
|
|||||||
3
build.rs
Normal file
3
build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
//helpers::copy_artifact_to_out_dir("airfreshener", "AirFreshener");
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ebu-dsp"
|
name = "dsp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
@@ -33,7 +33,6 @@ impl SpriteSheet {
|
|||||||
scale_factor: Arc<AtomicF32>,
|
scale_factor: Arc<AtomicF32>,
|
||||||
frame_width: usize,
|
frame_width: usize,
|
||||||
frame_height: usize,
|
frame_height: usize,
|
||||||
frames: usize,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let image = canvas
|
let image = canvas
|
||||||
.load_image_mem(data, ImageFlags::empty())
|
.load_image_mem(data, ImageFlags::empty())
|
||||||
6
helpers/Cargo.toml
Normal file
6
helpers/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
40
helpers/src/lib.rs
Normal file
40
helpers/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const CONFIG: &str = "debug";
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const CONFIG: &str = "release";
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const OS_LIB_EXT: &str = "dylib";
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const OS_LIB_EXT: &str = "dll";
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const OS_LIB_EXT: &str = "so";
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const OS_NAME: &str = "macOS";
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const OS_NAME: &str = "Windows";
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const OS_NAME: &str = "Linux";
|
||||||
|
|
||||||
|
pub fn built_lib_path(lib_name: &str) -> PathBuf {
|
||||||
|
Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap_or("".to_owned()))
|
||||||
|
.join(format!("target/{CONFIG}/lib{lib_name}.{OS_LIB_EXT}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_artifact_to_out_dir(lib_name: &str, pretty_name: &str) {
|
||||||
|
let src_path = built_lib_path(lib_name);
|
||||||
|
|
||||||
|
let dst_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap_or("".to_owned())).join(
|
||||||
|
format!("target/build/{CONFIG}/{pretty_name}_{OS_NAME}.clap"),
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::create_dir_all(dst_path.parent().unwrap())
|
||||||
|
.expect("Failed to create artifact output directory");
|
||||||
|
fs::copy(src_path, dst_path).expect("Failed to move artifact");
|
||||||
|
}
|
||||||
17
plug-airfreshener/Cargo.toml
Normal file
17
plug-airfreshener/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "airfreshener"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dsp = { version = "0.1.0", path = "../dsp" }
|
||||||
|
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", version = "0.0.0", default-features = false }
|
||||||
|
parking_lot = "0.12.5"
|
||||||
|
baseview = { git = "https://github.com/RustAudio/baseview", rev = "237d323c729f3aa99476ba3efa50129c5e86cad3", features = ["opengl"]}
|
||||||
|
crossbeam = "0.8.4"
|
||||||
|
femtovg = "0.19.3"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
image = { version = "0.25.9", default-features = false, features = ["png"] }
|
||||||
@@ -2,16 +2,16 @@ use crate::parameters::PluginParams;
|
|||||||
use baseview::{
|
use baseview::{
|
||||||
Event, EventStatus, MouseButton, MouseEvent, WindowEvent, WindowHandler, gl::GlContext,
|
Event, EventStatus, MouseButton, MouseEvent, WindowEvent, WindowHandler, gl::GlContext,
|
||||||
};
|
};
|
||||||
use ebu_dsp::{ScaledPoint, ScaledRect, Sprite, SpriteSheet};
|
use dsp::{ScaledPoint, ScaledRect, Sprite, SpriteSheet};
|
||||||
use femtovg::{Canvas, Color, FontId, Paint, Path, renderer::OpenGl};
|
use femtovg::{Canvas, Color, FontId, Paint, Path, renderer::OpenGl};
|
||||||
use nih_plug::prelude::*;
|
use nih_plug::prelude::*;
|
||||||
use std::sync::{Arc, atomic::Ordering};
|
use std::sync::{Arc, atomic::Ordering};
|
||||||
|
|
||||||
const DROID_SANS_FONT: &'static [u8] = include_bytes!("../assets/DroidSans.ttf");
|
const DROID_SANS_FONT: &'static [u8] = include_bytes!("../../assets/DroidSans.ttf");
|
||||||
|
|
||||||
const FRESHENER_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/sheet.png");
|
const FRESHENER_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/sheet.png");
|
||||||
const NOT_SO_FRESH_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg0.png");
|
const NOT_SO_FRESH_BG_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/bg0.png");
|
||||||
const FRESH_DUMBLEDORE_BG_IMAGE: &'static [u8] = include_bytes!("../assets/AirFreshener/bg1.png");
|
const FRESH_DUMBLEDORE_BG_IMAGE: &'static [u8] = include_bytes!("../../assets/AirFreshener/bg1.png");
|
||||||
const FRESHENER_FRAMES: usize = 256;
|
const FRESHENER_FRAMES: usize = 256;
|
||||||
const FRESHENER_FRAME_WIDTH: usize = 73;
|
const FRESHENER_FRAME_WIDTH: usize = 73;
|
||||||
const FRESHENER_FRAME_HEIGHT: usize = 144;
|
const FRESHENER_FRAME_HEIGHT: usize = 144;
|
||||||
@@ -88,7 +88,6 @@ impl PluginGui {
|
|||||||
scaling_factor.clone(),
|
scaling_factor.clone(),
|
||||||
FRESHENER_FRAME_WIDTH,
|
FRESHENER_FRAME_WIDTH,
|
||||||
FRESHENER_FRAME_HEIGHT,
|
FRESHENER_FRAME_HEIGHT,
|
||||||
FRESHENER_FRAMES,
|
|
||||||
);
|
);
|
||||||
this.freshener_image_bounds = this
|
this.freshener_image_bounds = this
|
||||||
.freshener_image
|
.freshener_image
|
||||||
@@ -100,6 +99,13 @@ impl PluginGui {
|
|||||||
);
|
);
|
||||||
this.not_so_fresh_image =
|
this.not_so_fresh_image =
|
||||||
Sprite::new(&mut canvas, NOT_SO_FRESH_BG_IMAGE, scaling_factor.clone());
|
Sprite::new(&mut canvas, NOT_SO_FRESH_BG_IMAGE, scaling_factor.clone());
|
||||||
|
|
||||||
|
let canvas_width = canvas.width();
|
||||||
|
let editor_width = params.editor_state.size.load().0;
|
||||||
|
if canvas_width != editor_width {
|
||||||
|
this.scaling_factor
|
||||||
|
.store(canvas_width as f32 / editor_width as f32, Ordering::Release);
|
||||||
|
}
|
||||||
this.canvas = Some(canvas);
|
this.canvas = Some(canvas);
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -164,8 +170,10 @@ impl WindowHandler for PluginGui {
|
|||||||
let freshness = self.params.freshness.unmodulated_normalized_value();
|
let freshness = self.params.freshness.unmodulated_normalized_value();
|
||||||
let frame_index = (freshness * (FRESHENER_FRAMES - 1) as f32).floor() as usize;
|
let frame_index = (freshness * (FRESHENER_FRAMES - 1) as f32).floor() as usize;
|
||||||
self.not_so_fresh_image.draw(canvas, 0.0, 0.0, 1.0);
|
self.not_so_fresh_image.draw(canvas, 0.0, 0.0, 1.0);
|
||||||
self.fresh_dumbledore_image.draw(canvas, 0.0, 0.0, freshness);
|
self.fresh_dumbledore_image
|
||||||
self.freshener_image.draw(canvas, FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y, frame_index);
|
.draw(canvas, 0.0, 0.0, freshness);
|
||||||
|
self.freshener_image
|
||||||
|
.draw(canvas, FRESHENER_SCREEN_X, FRESHENER_SCREEN_Y, frame_index);
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
218
plug-airfreshener/src/lib.rs
Normal file
218
plug-airfreshener/src/lib.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
mod editor;
|
||||||
|
mod gui;
|
||||||
|
mod parameters;
|
||||||
|
mod window;
|
||||||
|
|
||||||
|
use dsp::{
|
||||||
|
BiquadFilter, BiquadFilterState, Compressor, CompressorState, Decibel, Equalizer,
|
||||||
|
EqualizerState, FilterMode, FreqSplitter, FreqSplitterState, Lerp, Processor, Ratio,
|
||||||
|
SampleRate,
|
||||||
|
};
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use std::{num::NonZero, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{parameters::PluginParams, window::EditorWindow};
|
||||||
|
|
||||||
|
struct AirFreshener {
|
||||||
|
params: Arc<PluginParams>,
|
||||||
|
eqs: Vec<Equalizer>,
|
||||||
|
compressors: Vec<Compressor>,
|
||||||
|
splitters: Vec<FreqSplitter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AirFreshener {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
params: Arc::new(PluginParams::default()),
|
||||||
|
eqs: Vec::new(),
|
||||||
|
compressors: Vec::new(),
|
||||||
|
splitters: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for AirFreshener {
|
||||||
|
const NAME: &'static str = "Air Freshener";
|
||||||
|
const VENDOR: &'static str = "Simon Elberich";
|
||||||
|
// You can use `env!("CARGO_PKG_HOMEPAGE")` to reference the homepage field from the
|
||||||
|
// `Cargo.toml` file here
|
||||||
|
const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ";
|
||||||
|
const EMAIL: &'static str = "info@example.com";
|
||||||
|
|
||||||
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
// The first audio IO layout is used as the default. The other layouts may be selected either
|
||||||
|
// explicitly or automatically by the host or the user depending on the plugin API/backend.
|
||||||
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(2),
|
||||||
|
main_output_channels: NonZeroU32::new(2),
|
||||||
|
|
||||||
|
aux_input_ports: &[],
|
||||||
|
aux_output_ports: &[],
|
||||||
|
|
||||||
|
// Individual ports and the layout as a whole can be named here. By default these names
|
||||||
|
// are generated as needed. This layout will be called 'Stereo', while the other one is
|
||||||
|
// given the name 'Mono' based no the number of input and output channels.
|
||||||
|
names: PortNames::const_default(),
|
||||||
|
},
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(1),
|
||||||
|
main_output_channels: NonZeroU32::new(1),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const MIDI_INPUT: MidiConfig = MidiConfig::None;
|
||||||
|
// Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks
|
||||||
|
// whenever there are inter-buffer parameter changes. This way no changes to the plugin are
|
||||||
|
// required to support sample accurate automation and the wrapper handles all of the boring
|
||||||
|
// stuff like making sure transport and other timing information stays consistent between the
|
||||||
|
// splits.
|
||||||
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||||
|
|
||||||
|
// If the plugin can send or receive SysEx messages, it can define a type to wrap around those
|
||||||
|
// messages here. The type implements the `SysExMessage` trait, which allows conversion to and
|
||||||
|
// from plain byte buffers.
|
||||||
|
type SysExMessage = ();
|
||||||
|
// More advanced plugins can use this to run expensive background tasks. See the field's
|
||||||
|
// documentation for more information. `()` means that the plugin does not have any background
|
||||||
|
// tasks.
|
||||||
|
type BackgroundTask = ();
|
||||||
|
|
||||||
|
fn params(&self) -> Arc<dyn Params> {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
|
Some(Box::new(EditorWindow::new(self.params.clone())))
|
||||||
|
}
|
||||||
|
|
||||||
|
// This plugin doesn't need any special initialization, but if you need to do anything expensive
|
||||||
|
// then this would be the place. State is kept around when the host reconfigures the
|
||||||
|
// plugin. If we do need special initialization, we could implement the `initialize()` and/or
|
||||||
|
// `reset()` methods
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
audio_io_layout: &AudioIOLayout,
|
||||||
|
buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
//dsp::init();
|
||||||
|
|
||||||
|
let sample_rate = SampleRate(buffer_config.sample_rate as f64);
|
||||||
|
for _ in 0..audio_io_layout
|
||||||
|
.main_input_channels
|
||||||
|
.unwrap_or(NonZero::new(2).unwrap())
|
||||||
|
.get()
|
||||||
|
{
|
||||||
|
self.eqs.push(Equalizer::from_state(EqualizerState {
|
||||||
|
nodes: [
|
||||||
|
Some(BiquadFilter::from_state(
|
||||||
|
sample_rate,
|
||||||
|
BiquadFilterState {
|
||||||
|
gain_db: Decibel::from(8.0),
|
||||||
|
cutoff: 1900.0,
|
||||||
|
q: 0.5,
|
||||||
|
mode: FilterMode::HighShelf,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Some(BiquadFilter::from_state(
|
||||||
|
sample_rate,
|
||||||
|
BiquadFilterState {
|
||||||
|
gain_db: Decibel::from(-3.0),
|
||||||
|
cutoff: 2600.0,
|
||||||
|
mode: FilterMode::Peak,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.compressors.push(Compressor::from_state(
|
||||||
|
sample_rate,
|
||||||
|
CompressorState {
|
||||||
|
threshold: Decibel::from(-30.0),
|
||||||
|
ratio: Ratio(1.0, 0.9),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
self.splitters.push(FreqSplitter::from_state(
|
||||||
|
sample_rate,
|
||||||
|
FreqSplitterState {
|
||||||
|
split_frequency: 3050.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
_aux: &mut AuxiliaryBuffers,
|
||||||
|
_context: &mut impl ProcessContext<Self>,
|
||||||
|
) -> ProcessStatus {
|
||||||
|
_context.set_latency_samples(6);
|
||||||
|
for mut channel_samples in buffer.iter_samples() {
|
||||||
|
// Smoothing is optionally built into the parameters themselves
|
||||||
|
let freshness = self.params.freshness.smoothed.next() as f64;
|
||||||
|
|
||||||
|
/*let frequency = self.params.frequency.smoothed.next() as f64;
|
||||||
|
let ratio = self.params.ratio.smoothed.next() as f64;
|
||||||
|
let threshold = self.params.threshold.smoothed.next() as f64;
|
||||||
|
|
||||||
|
for compressor in &mut self.compressors {
|
||||||
|
compressor.set_ratio(Ratio(1.0, ratio));
|
||||||
|
compressor.set_threshold(Decibel::from(threshold));
|
||||||
|
}
|
||||||
|
for splitter in &mut self.splitters {
|
||||||
|
splitter.state.split_frequency = frequency;
|
||||||
|
}
|
||||||
|
for filter in &mut self.filters {
|
||||||
|
filter.state.cutoff = frequency;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
let fresh_lerp_of_bel_air = freshness as f32;
|
||||||
|
for (channel, sample) in channel_samples.iter_mut().enumerate() {
|
||||||
|
*sample = sample.lerp(self.eqs[channel].process(*sample), fresh_lerp_of_bel_air);
|
||||||
|
let (low, high) = self.splitters[channel].process(*sample);
|
||||||
|
*sample = low
|
||||||
|
+ high.lerp(
|
||||||
|
self.compressors[channel].process(high),
|
||||||
|
fresh_lerp_of_bel_air,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStatus::Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
// This can be used for cleaning up special resources like socket connections whenever the
|
||||||
|
// plugin is deactivated. Most plugins won't need to do anything here.
|
||||||
|
fn deactivate(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClapPlugin for AirFreshener {
|
||||||
|
const CLAP_ID: &'static str = "com.moist-plugins-gmbh.gain";
|
||||||
|
const CLAP_DESCRIPTION: Option<&'static str> = Some("A smoothed gain parameter example plugin");
|
||||||
|
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::Utility,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_export_clap!(AirFreshener);
|
||||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nightly
|
||||||
218
src/lib.rs
218
src/lib.rs
@@ -1,218 +0,0 @@
|
|||||||
mod editor;
|
|
||||||
mod gui;
|
|
||||||
mod parameters;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
use ebu_dsp::{
|
|
||||||
BiquadFilter, BiquadFilterState, Compressor, CompressorState, Decibel, Equalizer,
|
|
||||||
EqualizerState, FilterMode, FreqSplitter, FreqSplitterState, Lerp, Processor, Ratio,
|
|
||||||
SampleRate,
|
|
||||||
};
|
|
||||||
use nih_plug::prelude::*;
|
|
||||||
use std::{num::NonZero, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{parameters::PluginParams, window::EditorWindow};
|
|
||||||
|
|
||||||
struct AirFreshener {
|
|
||||||
params: Arc<PluginParams>,
|
|
||||||
eqs: Vec<Equalizer>,
|
|
||||||
compressors: Vec<Compressor>,
|
|
||||||
splitters: Vec<FreqSplitter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AirFreshener {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
params: Arc::new(PluginParams::default()),
|
|
||||||
eqs: Vec::new(),
|
|
||||||
compressors: Vec::new(),
|
|
||||||
splitters: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for AirFreshener {
|
|
||||||
const NAME: &'static str = "Air Freshener";
|
|
||||||
const VENDOR: &'static str = "Simon Elberich";
|
|
||||||
// You can use `env!("CARGO_PKG_HOMEPAGE")` to reference the homepage field from the
|
|
||||||
// `Cargo.toml` file here
|
|
||||||
const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ";
|
|
||||||
const EMAIL: &'static str = "info@example.com";
|
|
||||||
|
|
||||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
// The first audio IO layout is used as the default. The other layouts may be selected either
|
|
||||||
// explicitly or automatically by the host or the user depending on the plugin API/backend.
|
|
||||||
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
|
||||||
AudioIOLayout {
|
|
||||||
main_input_channels: NonZeroU32::new(2),
|
|
||||||
main_output_channels: NonZeroU32::new(2),
|
|
||||||
|
|
||||||
aux_input_ports: &[],
|
|
||||||
aux_output_ports: &[],
|
|
||||||
|
|
||||||
// Individual ports and the layout as a whole can be named here. By default these names
|
|
||||||
// are generated as needed. This layout will be called 'Stereo', while the other one is
|
|
||||||
// given the name 'Mono' based no the number of input and output channels.
|
|
||||||
names: PortNames::const_default(),
|
|
||||||
},
|
|
||||||
AudioIOLayout {
|
|
||||||
main_input_channels: NonZeroU32::new(1),
|
|
||||||
main_output_channels: NonZeroU32::new(1),
|
|
||||||
..AudioIOLayout::const_default()
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const MIDI_INPUT: MidiConfig = MidiConfig::None;
|
|
||||||
// Setting this to `true` will tell the wrapper to split the buffer up into smaller blocks
|
|
||||||
// whenever there are inter-buffer parameter changes. This way no changes to the plugin are
|
|
||||||
// required to support sample accurate automation and the wrapper handles all of the boring
|
|
||||||
// stuff like making sure transport and other timing information stays consistent between the
|
|
||||||
// splits.
|
|
||||||
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
|
||||||
|
|
||||||
// If the plugin can send or receive SysEx messages, it can define a type to wrap around those
|
|
||||||
// messages here. The type implements the `SysExMessage` trait, which allows conversion to and
|
|
||||||
// from plain byte buffers.
|
|
||||||
type SysExMessage = ();
|
|
||||||
// More advanced plugins can use this to run expensive background tasks. See the field's
|
|
||||||
// documentation for more information. `()` means that the plugin does not have any background
|
|
||||||
// tasks.
|
|
||||||
type BackgroundTask = ();
|
|
||||||
|
|
||||||
fn params(&self) -> Arc<dyn Params> {
|
|
||||||
self.params.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
|
||||||
Some(Box::new(EditorWindow::new(self.params.clone())))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This plugin doesn't need any special initialization, but if you need to do anything expensive
|
|
||||||
// then this would be the place. State is kept around when the host reconfigures the
|
|
||||||
// plugin. If we do need special initialization, we could implement the `initialize()` and/or
|
|
||||||
// `reset()` methods
|
|
||||||
|
|
||||||
fn initialize(
|
|
||||||
&mut self,
|
|
||||||
audio_io_layout: &AudioIOLayout,
|
|
||||||
buffer_config: &BufferConfig,
|
|
||||||
_context: &mut impl InitContext<Self>,
|
|
||||||
) -> bool {
|
|
||||||
//ebu_dsp::init();
|
|
||||||
|
|
||||||
let sample_rate = SampleRate(buffer_config.sample_rate as f64);
|
|
||||||
for _ in 0..audio_io_layout
|
|
||||||
.main_input_channels
|
|
||||||
.unwrap_or(NonZero::new(2).unwrap())
|
|
||||||
.get()
|
|
||||||
{
|
|
||||||
self.eqs.push(Equalizer::from_state(EqualizerState {
|
|
||||||
nodes: [
|
|
||||||
Some(BiquadFilter::from_state(
|
|
||||||
sample_rate,
|
|
||||||
BiquadFilterState {
|
|
||||||
gain_db: Decibel::from(8.0),
|
|
||||||
cutoff: 1900.0,
|
|
||||||
q: 0.5,
|
|
||||||
mode: FilterMode::HighShelf,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Some(BiquadFilter::from_state(
|
|
||||||
sample_rate,
|
|
||||||
BiquadFilterState {
|
|
||||||
gain_db: Decibel::from(-3.0),
|
|
||||||
cutoff: 2600.0,
|
|
||||||
mode: FilterMode::Peak,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.compressors.push(Compressor::from_state(
|
|
||||||
sample_rate,
|
|
||||||
CompressorState {
|
|
||||||
threshold: Decibel::from(-30.0),
|
|
||||||
ratio: Ratio(1.0, 0.9),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
self.splitters.push(FreqSplitter::from_state(
|
|
||||||
sample_rate,
|
|
||||||
FreqSplitterState {
|
|
||||||
split_frequency: 3050.0,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process(
|
|
||||||
&mut self,
|
|
||||||
buffer: &mut Buffer,
|
|
||||||
_aux: &mut AuxiliaryBuffers,
|
|
||||||
_context: &mut impl ProcessContext<Self>,
|
|
||||||
) -> ProcessStatus {
|
|
||||||
_context.set_latency_samples(6);
|
|
||||||
for mut channel_samples in buffer.iter_samples() {
|
|
||||||
// Smoothing is optionally built into the parameters themselves
|
|
||||||
let freshness = self.params.freshness.smoothed.next() as f64;
|
|
||||||
|
|
||||||
/*let frequency = self.params.frequency.smoothed.next() as f64;
|
|
||||||
let ratio = self.params.ratio.smoothed.next() as f64;
|
|
||||||
let threshold = self.params.threshold.smoothed.next() as f64;
|
|
||||||
|
|
||||||
for compressor in &mut self.compressors {
|
|
||||||
compressor.set_ratio(Ratio(1.0, ratio));
|
|
||||||
compressor.set_threshold(Decibel::from(threshold));
|
|
||||||
}
|
|
||||||
for splitter in &mut self.splitters {
|
|
||||||
splitter.state.split_frequency = frequency;
|
|
||||||
}
|
|
||||||
for filter in &mut self.filters {
|
|
||||||
filter.state.cutoff = frequency;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
let fresh_lerp_of_bel_air = freshness as f32;
|
|
||||||
for (channel, sample) in channel_samples.iter_mut().enumerate() {
|
|
||||||
*sample = sample.lerp(self.eqs[channel].process(*sample), fresh_lerp_of_bel_air);
|
|
||||||
let (low, high) = self.splitters[channel].process(*sample);
|
|
||||||
*sample = low
|
|
||||||
+ high.lerp(
|
|
||||||
self.compressors[channel].process(high),
|
|
||||||
fresh_lerp_of_bel_air,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessStatus::Normal
|
|
||||||
}
|
|
||||||
|
|
||||||
// This can be used for cleaning up special resources like socket connections whenever the
|
|
||||||
// plugin is deactivated. Most plugins won't need to do anything here.
|
|
||||||
fn deactivate(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClapPlugin for AirFreshener {
|
|
||||||
const CLAP_ID: &'static str = "com.moist-plugins-gmbh.gain";
|
|
||||||
const CLAP_DESCRIPTION: Option<&'static str> = Some("A smoothed gain parameter example plugin");
|
|
||||||
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::Utility,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
nih_export_clap!(AirFreshener);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user