very simple graph with busy loop

This commit is contained in:
2024-06-18 01:42:42 -04:00
commit 6175df75bd
5 changed files with 2612 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+2381
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -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"
+3
View File
@@ -0,0 +1,3 @@
fn main() -> nih_plug_xtask::Result<()> {
nih_plug_xtask::main()
}
+212
View File
@@ -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(&params.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);