char-mid-side/src/lib.rs

159 lines
4.0 KiB
Rust

use nih_plug::prelude::*;
use nih_plug_vizia::ViziaState;
use std::sync::Arc;
mod editor;
fn decode_mid_side(channel_0: &mut [f32], channel_1: &mut [f32]) {
assert_eq!(channel_0.len(), channel_1.len());
for sample_idx in 0..channel_0.len() {
let mid = channel_0[sample_idx];
let side = channel_1[sample_idx];
let left = mid - side;
let right = mid + side;
channel_0[sample_idx] = left;
channel_1[sample_idx] = right;
}
}
fn decode_signal_balance(channel_0: &mut [f32], channel_1: &mut [f32]) {
// positive balance -> pan right
// negative balance -> pan left
assert_eq!(channel_0.len(), channel_1.len());
for sample_idx in 0..channel_0.len() {
let sig = channel_0[sample_idx];
let balance = channel_1[sample_idx];
let left = sig * (0.5 * -balance + 1.0);
let right = sig * (0.5 * balance + 1.0);
channel_0[sample_idx] = left;
channel_1[sample_idx] = right;
}
}
// time to wire up nih-plug. all of this is boilerplate
#[derive(Enum, PartialEq, Eq)]
pub enum MidSideMode {
#[id = "conventional"]
Conventional,
#[id = "balance"]
PanAmount,
}
#[derive(Params)]
pub struct MidSideParams {
#[id = "mode"]
mode: EnumParam<MidSideMode>,
#[persist = "editor-state"]
editor_state: Arc<ViziaState>,
}
impl Default for MidSideParams {
fn default() -> Self {
Self {
mode: EnumParam::new("Mode", MidSideMode::Conventional),
editor_state: editor::default_state(),
}
}
}
struct MidSide {
params: Arc<MidSideParams>,
}
impl Default for MidSide {
fn default() -> Self {
Self {
params: Arc::new(MidSideParams::default()),
}
}
}
impl Plugin for MidSide {
const NAME: &'static str = "charlotte's mid side";
const VENDOR: &'static str = "charlotte athena som";
const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
const EMAIL: &'static str = "charlotte@som.codes";
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),
aux_input_ports: &[],
aux_output_ports: &[],
names: PortNames::const_default(),
}];
const MIDI_INPUT: MidiConfig = MidiConfig::None;
const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
type SysExMessage = ();
type BackgroundTask = ();
fn initialize(
&mut self,
_audio_io_layout: &AudioIOLayout,
_buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>,
) -> bool {
true
}
fn reset(&mut self) {}
fn process(
&mut self,
buffer: &mut Buffer,
_aux: &mut AuxiliaryBuffers,
_context: &mut impl ProcessContext<Self>,
) -> ProcessStatus {
assert_eq!(buffer.channels(), 2);
let mode = self.params.mode.value();
for (_block_idx, block) in buffer.iter_blocks(128) {
let mut block_channels = block.into_iter();
let channel_0 = block_channels.next().unwrap();
let channel_1 = block_channels.next().unwrap();
match mode {
MidSideMode::Conventional => decode_mid_side(channel_0, channel_1),
MidSideMode::PanAmount => decode_signal_balance(channel_0, channel_1),
}
}
ProcessStatus::Normal
}
fn params(&self) -> Arc<dyn Params> {
self.params.clone()
}
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(self.params.editor_state.clone(), self.params.clone())
}
}
impl Vst3Plugin for MidSide {
const VST3_CLASS_ID: [u8; 16] = *b"charmid-side\0\0\0\0";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[
Vst3SubCategory::Fx,
Vst3SubCategory::Tools,
Vst3SubCategory::Stereo, // this plugin only supports stereo configuration
];
}
nih_export_vst3!(MidSide);