very simple graph with busy loop
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+2381
File diff suppressed because it is too large
Load Diff
+15
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "audio_plugin_proto"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `lib` artifact is needed for the standalone target
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossbeam = "0.8.4"
|
||||||
|
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug", features = ["assert_process_allocs", "standalone"] }
|
||||||
|
nih_plug_egui = { git = "https://github.com/robbert-vdh/nih-plug" }
|
||||||
|
nih_plug_xtask = { git = "https://github.com/robbert-vdh/nih-plug.git" }
|
||||||
|
once_cell = "1.19.0"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() -> nih_plug_xtask::Result<()> {
|
||||||
|
nih_plug_xtask::main()
|
||||||
|
}
|
||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
use crossbeam::queue::ArrayQueue;
|
||||||
|
use egui::{Color32, ColorImage, TextureHandle};
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread::spawn;
|
||||||
|
|
||||||
|
const WIDTH: usize = 300;
|
||||||
|
const HEIGHT: usize = 180;
|
||||||
|
|
||||||
|
pub fn processor_loop(
|
||||||
|
texture: &OnceCell<TextureHandle>,
|
||||||
|
samples: &ArrayQueue<f32>,
|
||||||
|
sample_rate: f32,
|
||||||
|
) {
|
||||||
|
let mut time_left_in_column = 0.1;
|
||||||
|
let mut image = vec![0; WIDTH * HEIGHT * 3];
|
||||||
|
let mut colum_max = f32::NEG_INFINITY;
|
||||||
|
loop {
|
||||||
|
let Some(sample) = samples.pop() else {
|
||||||
|
// TODO: busy loop = very very bad
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
colum_max = colum_max.max(sample);
|
||||||
|
time_left_in_column -= sample_rate.recip();
|
||||||
|
if time_left_in_column < 0.0 {
|
||||||
|
image.rotate_left(3);
|
||||||
|
time_left_in_column += 0.1;
|
||||||
|
|
||||||
|
let value = colum_max;
|
||||||
|
colum_max = f32::NEG_INFINITY;
|
||||||
|
|
||||||
|
let x = WIDTH - 1;
|
||||||
|
for y in 0..HEIGHT {
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 0] = 0x00;
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 1] = 0x00;
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 2] = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = (HEIGHT as f32 / 2.0) - util::gain_to_db(value);
|
||||||
|
let y = y.round().clamp(0.0, (HEIGHT - 1) as f32) as usize;
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 0] = 0xff;
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 1] = 0xff;
|
||||||
|
image[y * WIDTH * 3 + x * 3 + 2] = 0xff;
|
||||||
|
|
||||||
|
if let Some(tex) = texture.get() {
|
||||||
|
tex.clone().set(
|
||||||
|
ColorImage::from_rgb([WIDTH, HEIGHT], &image),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is mostly identical to the gain example, minus some fluff, and with a GUI.
|
||||||
|
pub struct GainGraph {
|
||||||
|
params: Arc<GainParams>,
|
||||||
|
samples: Option<Arc<ArrayQueue<f32>>>,
|
||||||
|
texture: Arc<OnceCell<TextureHandle>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct GainParams {
|
||||||
|
/// The editor state, saved together with the parameter state so the custom scaling can be
|
||||||
|
/// restored.
|
||||||
|
#[persist = "editor-state"]
|
||||||
|
editor_state: Arc<EguiState>,
|
||||||
|
|
||||||
|
#[id = "gain"]
|
||||||
|
pub gain: FloatParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GainGraph {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
params: Arc::new(GainParams::default()),
|
||||||
|
texture: Arc::new(OnceCell::new()),
|
||||||
|
samples: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GainParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_state: EguiState::from_size(300, 300),
|
||||||
|
|
||||||
|
// See the main gain example for more details
|
||||||
|
gain: FloatParam::new(
|
||||||
|
"Gain",
|
||||||
|
util::db_to_gain(0.0),
|
||||||
|
FloatRange::Skewed {
|
||||||
|
min: util::db_to_gain(-30.0),
|
||||||
|
max: util::db_to_gain(30.0),
|
||||||
|
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_smoother(SmoothingStyle::Logarithmic(50.0))
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for GainGraph {
|
||||||
|
const NAME: &'static str = "Gain Graph (egui)";
|
||||||
|
const VENDOR: &'static str = "Sam Sartor";
|
||||||
|
const URL: &'static str = "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 editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
|
let params = self.params.clone();
|
||||||
|
let texture = self.texture.clone();
|
||||||
|
create_egui_editor(
|
||||||
|
self.params.editor_state.clone(),
|
||||||
|
(),
|
||||||
|
|_, _| {},
|
||||||
|
move |egui_ctx, setter, _state| {
|
||||||
|
egui::CentralPanel::default().show(egui_ctx, |ui| {
|
||||||
|
// NOTE: See `plugins/diopser/src/editor.rs` for an example using the generic UI widget
|
||||||
|
|
||||||
|
ui.label("Gain");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.gain, setter));
|
||||||
|
|
||||||
|
let tex = texture.get_or_init(|| {
|
||||||
|
ui.ctx().load_texture(
|
||||||
|
"graph-texture",
|
||||||
|
ColorImage::new([WIDTH, HEIGHT], Color32::from_rgb(0, 0, 0)),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
ui.image((tex.id(), tex.size_vec2()));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
_audio_io_layout: &AudioIOLayout,
|
||||||
|
buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
let samples = Arc::new(ArrayQueue::new(buffer_config.max_buffer_size as usize * 2));
|
||||||
|
self.samples = Some(samples.clone());
|
||||||
|
let texture = self.texture.clone();
|
||||||
|
let sample_rate = buffer_config.sample_rate;
|
||||||
|
spawn(move || processor_loop(&texture, &samples, sample_rate));
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
_aux: &mut AuxiliaryBuffers,
|
||||||
|
_context: &mut impl ProcessContext<Self>,
|
||||||
|
) -> ProcessStatus {
|
||||||
|
let gain = self.params.gain.value();
|
||||||
|
for channel_samples in buffer.iter_samples() {
|
||||||
|
let mut value = 0.0;
|
||||||
|
for sample in channel_samples {
|
||||||
|
*sample *= gain;
|
||||||
|
value += *sample;
|
||||||
|
}
|
||||||
|
if let Some(samples) = &self.samples {
|
||||||
|
let _ = samples.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStatus::Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate(&mut self) {
|
||||||
|
self.samples = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vst3Plugin for GainGraph {
|
||||||
|
const VST3_CLASS_ID: [u8; 16] = *b"GainGraph_______";
|
||||||
|
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||||
|
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools];
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_export_vst3!(GainGraph);
|
||||||
Reference in New Issue
Block a user