169 lines
5.3 KiB
Rust
169 lines
5.3 KiB
Rust
use std::sync::Arc;
|
|
use nih_plug::prelude::*;
|
|
use nih_plug::plugin::vst3::Vst3Plugin;
|
|
use nih_plug::plugin::clap::ClapPlugin;
|
|
use nih_plug::wrapper::clap::features::ClapFeature;
|
|
use nih_plug::wrapper::vst3::subcategories::Vst3SubCategory;
|
|
|
|
mod engine;
|
|
use engine::Engine;
|
|
|
|
/// The main plugin struct
|
|
pub struct ClipLauncher {
|
|
/// The engine managing clips
|
|
engine: Engine,
|
|
/// Pending MIDI output events
|
|
pending_midi: Vec<NoteEvent<()>>,
|
|
}
|
|
|
|
impl Default for ClipLauncher {
|
|
fn default() -> Self {
|
|
Self {
|
|
engine: Engine::new(2, 44100.0),
|
|
pending_midi: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Plugin for ClipLauncher {
|
|
const NAME: &'static str = "Clip Launcher";
|
|
const VENDOR: &'static str = "Your Company";
|
|
const URL: &'static str = "";
|
|
const EMAIL: &'static str = "";
|
|
const VERSION: &'static str = "0.1.0";
|
|
|
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
|
AudioIOLayout {
|
|
main_input_channels: NonZeroU32::new(2),
|
|
main_output_channels: NonZeroU32::new(2),
|
|
..AudioIOLayout::const_default()
|
|
},
|
|
];
|
|
|
|
const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
|
|
const MIDI_OUTPUT: MidiConfig = MidiConfig::Basic;
|
|
|
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
|
|
|
type SysExMessage = ();
|
|
type BackgroundTask = ();
|
|
|
|
fn initialize(
|
|
&mut self,
|
|
_audio_io_layout: &AudioIOLayout,
|
|
buffer_config: &BufferConfig,
|
|
_context: &mut impl InitContext<Self>,
|
|
) -> bool {
|
|
self.engine = Engine::new(
|
|
_audio_io_layout.main_input_channels.map(|c| c.get() as usize).unwrap_or(2),
|
|
buffer_config.sample_rate,
|
|
);
|
|
true
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
// Clear all clips on reset
|
|
self.engine.clips.clear();
|
|
self.pending_midi.clear();
|
|
}
|
|
|
|
fn process(
|
|
&mut self,
|
|
buffer: &mut Buffer,
|
|
_aux: &mut AuxiliaryBuffers,
|
|
context: &mut impl ProcessContext<Self>,
|
|
) -> ProcessStatus {
|
|
// Process MIDI input events
|
|
while let Some(event) = context.next_event() {
|
|
match event {
|
|
NoteEvent::NoteOn { timing, note, velocity, channel, voice_id } => {
|
|
let output_velocity = self.engine.process_midi_note(note, velocity as u8, true);
|
|
// Queue output MIDI event
|
|
self.pending_midi.push(NoteEvent::NoteOn {
|
|
timing,
|
|
note,
|
|
velocity: output_velocity as f32,
|
|
channel,
|
|
voice_id,
|
|
});
|
|
}
|
|
NoteEvent::NoteOff { timing, note, velocity, channel, voice_id } => {
|
|
let output_velocity = self.engine.process_midi_note(note, velocity as u8, false);
|
|
self.pending_midi.push(NoteEvent::NoteOff {
|
|
timing,
|
|
note,
|
|
velocity: output_velocity as f32,
|
|
channel,
|
|
voice_id,
|
|
});
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Process audio
|
|
let num_channels = buffer.channels() as usize;
|
|
let num_frames = buffer.samples() as usize;
|
|
let input_slices = buffer.as_slice(); // &[&[f32]]
|
|
|
|
// Build interleaved input buffer
|
|
let mut interleaved_input = vec![0.0; num_frames * num_channels];
|
|
for ch in 0..num_channels {
|
|
for frame in 0..num_frames {
|
|
interleaved_input[frame * num_channels + ch] = input_slices[ch][frame];
|
|
}
|
|
}
|
|
|
|
// Build interleaved output buffer (will be filled by engine)
|
|
let mut interleaved_output = vec![0.0; num_frames * num_channels];
|
|
|
|
self.engine.process_audio(&interleaved_input, &mut interleaved_output, num_frames);
|
|
|
|
// Copy back to buffer's output channels
|
|
for (ch, channel_samples) in buffer.iter_samples().enumerate() {
|
|
let (_, output) = *channel_samples;
|
|
for frame in 0..num_frames {
|
|
output[frame] = interleaved_output[frame * num_channels + ch];
|
|
}
|
|
}
|
|
|
|
// Send pending MIDI output events
|
|
for event in self.pending_midi.drain(..) {
|
|
context.send_event(event);
|
|
}
|
|
|
|
ProcessStatus::Normal
|
|
}
|
|
|
|
fn params(&self) -> Arc<dyn Params> {
|
|
Arc::new(ClipLauncherParams {
|
|
dummy: BoolParam::new("Dummy", false),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Vst3Plugin for ClipLauncher {
|
|
const VST3_CLASS_ID: [u8; 16] = *b"ClipLauncher1234";
|
|
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[Vst3SubCategory::Fx];
|
|
}
|
|
|
|
impl ClapPlugin for ClipLauncher {
|
|
const CLAP_ID: &'static str = "com.yourcompany.clip-launcher";
|
|
const CLAP_DESCRIPTION: Option<&'static str> = Some("A clip launcher plugin");
|
|
const CLAP_MANUAL_URL: Option<&'static str> = None;
|
|
const CLAP_SUPPORT_URL: Option<&'static str> = None;
|
|
const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::AudioEffect, ClapFeature::Stereo];
|
|
}
|
|
|
|
/// Empty params struct (no parameters needed for this plugin)
|
|
#[derive(Params)]
|
|
struct ClipLauncherParams {
|
|
/// Placeholder parameter to satisfy the derive macro
|
|
#[id = "dummy"]
|
|
dummy: BoolParam,
|
|
}
|
|
|
|
// Export the plugin
|
|
nih_export_vst3!(ClipLauncher);
|
|
nih_export_clap!(ClipLauncher);
|