Files
jack-looper/src/lib.rs
Loic Coenen 33f84ca5eb fix: use deref to access output slice in audio copy
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
2026-05-01 00:31:32 +00:00

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);