feat: implement clip launcher plugin with MIDI-triggered recording and looping
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
131
src/lib.rs
131
src/lib.rs
@@ -0,0 +1,131 @@
|
||||
use nih_plug::prelude::*;
|
||||
|
||||
mod engine;
|
||||
use engine::{Engine, ClipState};
|
||||
|
||||
/// 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()).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 } => {
|
||||
let output_velocity = self.engine.process_midi_note(note, velocity, true);
|
||||
// Queue output MIDI event
|
||||
self.pending_midi.push(NoteEvent::NoteOn {
|
||||
timing,
|
||||
note,
|
||||
velocity: output_velocity,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
NoteEvent::NoteOff { timing, note, velocity, channel } => {
|
||||
let output_velocity = self.engine.process_midi_note(note, velocity, false);
|
||||
self.pending_midi.push(NoteEvent::NoteOff {
|
||||
timing,
|
||||
note,
|
||||
velocity: output_velocity,
|
||||
channel,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Process audio
|
||||
for channel_samples in buffer.iter_samples() {
|
||||
let (input, mut output) = channel_samples;
|
||||
let num_samples = input.len();
|
||||
|
||||
// Process this block
|
||||
self.engine.process_audio(input, &mut output, num_samples);
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty params struct (no parameters needed for this plugin)
|
||||
#[derive(Params)]
|
||||
struct ClipLauncherParams;
|
||||
|
||||
impl Default for ClipLauncherParams {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
// Export the plugin
|
||||
nih_export_vst3!(ClipLauncher);
|
||||
nih_export_clap!(ClipLauncher);
|
||||
|
||||
Reference in New Issue
Block a user