512 lines
14 KiB
Rust
512 lines
14 KiB
Rust
use crate::audio::node_graph::{AudioNode, NodeCategory, NodePort, Parameter, ParameterUnit, SignalType};
|
|
use crate::audio::midi::MidiEvent;
|
|
|
|
// Parameters
|
|
const PARAM_GAIN: u32 = 0;
|
|
const PARAM_ATTACK: u32 = 1;
|
|
const PARAM_RELEASE: u32 = 2;
|
|
const PARAM_TRANSPOSE: u32 = 3;
|
|
|
|
/// Metadata about a loaded sample layer (for preset serialization)
|
|
#[derive(Clone, Debug)]
|
|
pub struct LayerInfo {
|
|
pub file_path: String,
|
|
pub key_min: u8,
|
|
pub key_max: u8,
|
|
pub root_key: u8,
|
|
pub velocity_min: u8,
|
|
pub velocity_max: u8,
|
|
}
|
|
|
|
/// Single sample with velocity range and key range
|
|
#[derive(Clone)]
|
|
struct SampleLayer {
|
|
sample_data: Vec<f32>,
|
|
sample_rate: f32,
|
|
|
|
// Key range: C-1 = 0, C0 = 12, middle C (C4) = 60, C9 = 120
|
|
key_min: u8,
|
|
key_max: u8,
|
|
root_key: u8, // The original pitch of the sample
|
|
|
|
// Velocity range: 0-127
|
|
velocity_min: u8,
|
|
velocity_max: u8,
|
|
}
|
|
|
|
impl SampleLayer {
|
|
fn new(
|
|
sample_data: Vec<f32>,
|
|
sample_rate: f32,
|
|
key_min: u8,
|
|
key_max: u8,
|
|
root_key: u8,
|
|
velocity_min: u8,
|
|
velocity_max: u8,
|
|
) -> Self {
|
|
Self {
|
|
sample_data,
|
|
sample_rate,
|
|
key_min,
|
|
key_max,
|
|
root_key,
|
|
velocity_min,
|
|
velocity_max,
|
|
}
|
|
}
|
|
|
|
/// Check if this layer matches the given key and velocity
|
|
fn matches(&self, key: u8, velocity: u8) -> bool {
|
|
key >= self.key_min
|
|
&& key <= self.key_max
|
|
&& velocity >= self.velocity_min
|
|
&& velocity <= self.velocity_max
|
|
}
|
|
}
|
|
|
|
/// Active voice playing a sample
|
|
struct Voice {
|
|
layer_index: usize,
|
|
playhead: f32,
|
|
note: u8,
|
|
velocity: u8,
|
|
is_active: bool,
|
|
|
|
// Envelope
|
|
envelope_phase: EnvelopePhase,
|
|
envelope_value: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
enum EnvelopePhase {
|
|
Attack,
|
|
Sustain,
|
|
Release,
|
|
}
|
|
|
|
impl Voice {
|
|
fn new(layer_index: usize, note: u8, velocity: u8) -> Self {
|
|
Self {
|
|
layer_index,
|
|
playhead: 0.0,
|
|
note,
|
|
velocity,
|
|
is_active: true,
|
|
envelope_phase: EnvelopePhase::Attack,
|
|
envelope_value: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Multi-sample instrument with velocity layers and key zones
|
|
pub struct MultiSamplerNode {
|
|
name: String,
|
|
|
|
// Sample layers
|
|
layers: Vec<SampleLayer>,
|
|
layer_infos: Vec<LayerInfo>, // Metadata about loaded layers
|
|
|
|
// Voice management
|
|
voices: Vec<Voice>,
|
|
max_voices: usize,
|
|
|
|
// Parameters
|
|
gain: f32,
|
|
attack_time: f32, // seconds
|
|
release_time: f32, // seconds
|
|
transpose: i8, // semitones
|
|
|
|
inputs: Vec<NodePort>,
|
|
outputs: Vec<NodePort>,
|
|
parameters: Vec<Parameter>,
|
|
}
|
|
|
|
impl MultiSamplerNode {
|
|
pub fn new(name: impl Into<String>) -> Self {
|
|
let name = name.into();
|
|
|
|
let inputs = vec![
|
|
NodePort::new("MIDI In", SignalType::Midi, 0),
|
|
];
|
|
|
|
let outputs = vec![
|
|
NodePort::new("Audio Out", SignalType::Audio, 0),
|
|
];
|
|
|
|
let parameters = vec![
|
|
Parameter::new(PARAM_GAIN, "Gain", 0.0, 2.0, 1.0, ParameterUnit::Generic),
|
|
Parameter::new(PARAM_ATTACK, "Attack", 0.001, 1.0, 0.01, ParameterUnit::Time),
|
|
Parameter::new(PARAM_RELEASE, "Release", 0.01, 5.0, 0.1, ParameterUnit::Time),
|
|
Parameter::new(PARAM_TRANSPOSE, "Transpose", -24.0, 24.0, 0.0, ParameterUnit::Generic),
|
|
];
|
|
|
|
Self {
|
|
name,
|
|
layers: Vec::new(),
|
|
layer_infos: Vec::new(),
|
|
voices: Vec::new(),
|
|
max_voices: 16,
|
|
gain: 1.0,
|
|
attack_time: 0.01,
|
|
release_time: 0.1,
|
|
transpose: 0,
|
|
inputs,
|
|
outputs,
|
|
parameters,
|
|
}
|
|
}
|
|
|
|
/// Add a sample layer
|
|
pub fn add_layer(
|
|
&mut self,
|
|
sample_data: Vec<f32>,
|
|
sample_rate: f32,
|
|
key_min: u8,
|
|
key_max: u8,
|
|
root_key: u8,
|
|
velocity_min: u8,
|
|
velocity_max: u8,
|
|
) {
|
|
let layer = SampleLayer::new(
|
|
sample_data,
|
|
sample_rate,
|
|
key_min,
|
|
key_max,
|
|
root_key,
|
|
velocity_min,
|
|
velocity_max,
|
|
);
|
|
self.layers.push(layer);
|
|
}
|
|
|
|
/// Load a sample layer from a file path
|
|
pub fn load_layer_from_file(
|
|
&mut self,
|
|
path: &str,
|
|
key_min: u8,
|
|
key_max: u8,
|
|
root_key: u8,
|
|
velocity_min: u8,
|
|
velocity_max: u8,
|
|
) -> Result<(), String> {
|
|
use crate::audio::sample_loader::load_audio_file;
|
|
|
|
let sample_data = load_audio_file(path)?;
|
|
self.add_layer(
|
|
sample_data.samples,
|
|
sample_data.sample_rate as f32,
|
|
key_min,
|
|
key_max,
|
|
root_key,
|
|
velocity_min,
|
|
velocity_max,
|
|
);
|
|
|
|
// Store layer metadata for preset serialization
|
|
self.layer_infos.push(LayerInfo {
|
|
file_path: path.to_string(),
|
|
key_min,
|
|
key_max,
|
|
root_key,
|
|
velocity_min,
|
|
velocity_max,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get information about all loaded layers
|
|
pub fn get_layers_info(&self) -> &[LayerInfo] {
|
|
&self.layer_infos
|
|
}
|
|
|
|
/// Get sample data for a specific layer (for preset embedding)
|
|
pub fn get_layer_data(&self, layer_index: usize) -> Option<(Vec<f32>, f32)> {
|
|
self.layers.get(layer_index).map(|layer| {
|
|
(layer.sample_data.clone(), layer.sample_rate)
|
|
})
|
|
}
|
|
|
|
/// Update a layer's configuration
|
|
pub fn update_layer(
|
|
&mut self,
|
|
layer_index: usize,
|
|
key_min: u8,
|
|
key_max: u8,
|
|
root_key: u8,
|
|
velocity_min: u8,
|
|
velocity_max: u8,
|
|
) -> Result<(), String> {
|
|
if layer_index >= self.layers.len() {
|
|
return Err("Layer index out of bounds".to_string());
|
|
}
|
|
|
|
// Update the layer data
|
|
self.layers[layer_index].key_min = key_min;
|
|
self.layers[layer_index].key_max = key_max;
|
|
self.layers[layer_index].root_key = root_key;
|
|
self.layers[layer_index].velocity_min = velocity_min;
|
|
self.layers[layer_index].velocity_max = velocity_max;
|
|
|
|
// Update the layer info
|
|
if layer_index < self.layer_infos.len() {
|
|
self.layer_infos[layer_index].key_min = key_min;
|
|
self.layer_infos[layer_index].key_max = key_max;
|
|
self.layer_infos[layer_index].root_key = root_key;
|
|
self.layer_infos[layer_index].velocity_min = velocity_min;
|
|
self.layer_infos[layer_index].velocity_max = velocity_max;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove a layer
|
|
pub fn remove_layer(&mut self, layer_index: usize) -> Result<(), String> {
|
|
if layer_index >= self.layers.len() {
|
|
return Err("Layer index out of bounds".to_string());
|
|
}
|
|
|
|
self.layers.remove(layer_index);
|
|
if layer_index < self.layer_infos.len() {
|
|
self.layer_infos.remove(layer_index);
|
|
}
|
|
|
|
// Stop any voices playing this layer
|
|
for voice in &mut self.voices {
|
|
if voice.layer_index == layer_index {
|
|
voice.is_active = false;
|
|
} else if voice.layer_index > layer_index {
|
|
// Adjust indices for layers that were shifted down
|
|
voice.layer_index -= 1;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Find the best matching layer for a given note and velocity
|
|
fn find_layer(&self, note: u8, velocity: u8) -> Option<usize> {
|
|
self.layers
|
|
.iter()
|
|
.enumerate()
|
|
.find(|(_, layer)| layer.matches(note, velocity))
|
|
.map(|(index, _)| index)
|
|
}
|
|
|
|
/// Trigger a note
|
|
fn note_on(&mut self, note: u8, velocity: u8) {
|
|
let transposed_note = (note as i16 + self.transpose as i16).clamp(0, 127) as u8;
|
|
|
|
if let Some(layer_index) = self.find_layer(transposed_note, velocity) {
|
|
// Find an inactive voice or reuse the oldest one
|
|
let voice_index = self
|
|
.voices
|
|
.iter()
|
|
.position(|v| !v.is_active)
|
|
.unwrap_or_else(|| {
|
|
// All voices active, reuse the first one
|
|
if self.voices.len() < self.max_voices {
|
|
self.voices.len()
|
|
} else {
|
|
0
|
|
}
|
|
});
|
|
|
|
let voice = Voice::new(layer_index, note, velocity);
|
|
|
|
if voice_index < self.voices.len() {
|
|
self.voices[voice_index] = voice;
|
|
} else {
|
|
self.voices.push(voice);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Release a note
|
|
fn note_off(&mut self, note: u8) {
|
|
for voice in &mut self.voices {
|
|
if voice.note == note && voice.is_active {
|
|
voice.envelope_phase = EnvelopePhase::Release;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AudioNode for MultiSamplerNode {
|
|
fn category(&self) -> NodeCategory {
|
|
NodeCategory::Generator
|
|
}
|
|
|
|
fn inputs(&self) -> &[NodePort] {
|
|
&self.inputs
|
|
}
|
|
|
|
fn outputs(&self) -> &[NodePort] {
|
|
&self.outputs
|
|
}
|
|
|
|
fn parameters(&self) -> &[Parameter] {
|
|
&self.parameters
|
|
}
|
|
|
|
fn set_parameter(&mut self, id: u32, value: f32) {
|
|
match id {
|
|
PARAM_GAIN => {
|
|
self.gain = value.clamp(0.0, 2.0);
|
|
}
|
|
PARAM_ATTACK => {
|
|
self.attack_time = value.clamp(0.001, 1.0);
|
|
}
|
|
PARAM_RELEASE => {
|
|
self.release_time = value.clamp(0.01, 5.0);
|
|
}
|
|
PARAM_TRANSPOSE => {
|
|
self.transpose = value.clamp(-24.0, 24.0) as i8;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn get_parameter(&self, id: u32) -> f32 {
|
|
match id {
|
|
PARAM_GAIN => self.gain,
|
|
PARAM_ATTACK => self.attack_time,
|
|
PARAM_RELEASE => self.release_time,
|
|
PARAM_TRANSPOSE => self.transpose as f32,
|
|
_ => 0.0,
|
|
}
|
|
}
|
|
|
|
fn process(
|
|
&mut self,
|
|
_inputs: &[&[f32]],
|
|
outputs: &mut [&mut [f32]],
|
|
midi_inputs: &[&[MidiEvent]],
|
|
_midi_outputs: &mut [&mut Vec<MidiEvent>],
|
|
sample_rate: u32,
|
|
) {
|
|
if outputs.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let output = &mut outputs[0];
|
|
let frames = output.len() / 2;
|
|
|
|
// Clear output
|
|
output.fill(0.0);
|
|
|
|
// Process MIDI events
|
|
if !midi_inputs.is_empty() {
|
|
for event in midi_inputs[0].iter() {
|
|
if event.is_note_on() {
|
|
self.note_on(event.data1, event.data2);
|
|
} else if event.is_note_off() {
|
|
self.note_off(event.data1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract parameters needed for processing
|
|
let gain = self.gain;
|
|
let attack_time = self.attack_time;
|
|
let release_time = self.release_time;
|
|
|
|
// Process all active voices
|
|
for voice in &mut self.voices {
|
|
if !voice.is_active {
|
|
continue;
|
|
}
|
|
|
|
if voice.layer_index >= self.layers.len() {
|
|
continue;
|
|
}
|
|
|
|
let layer = &self.layers[voice.layer_index];
|
|
|
|
// Calculate playback speed
|
|
let semitone_diff = voice.note as i16 - layer.root_key as i16;
|
|
let speed = 2.0_f32.powf(semitone_diff as f32 / 12.0);
|
|
let speed_adjusted = speed * (layer.sample_rate / sample_rate as f32);
|
|
|
|
for frame in 0..frames {
|
|
// Read sample with linear interpolation
|
|
let playhead = voice.playhead;
|
|
let sample = if !layer.sample_data.is_empty() && playhead >= 0.0 {
|
|
let index = playhead.floor() as usize;
|
|
if index < layer.sample_data.len() {
|
|
let frac = playhead - playhead.floor();
|
|
let sample1 = layer.sample_data[index];
|
|
let sample2 = if index + 1 < layer.sample_data.len() {
|
|
layer.sample_data[index + 1]
|
|
} else {
|
|
0.0
|
|
};
|
|
sample1 + (sample2 - sample1) * frac
|
|
} else {
|
|
0.0
|
|
}
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
// Process envelope
|
|
match voice.envelope_phase {
|
|
EnvelopePhase::Attack => {
|
|
let attack_samples = attack_time * sample_rate as f32;
|
|
voice.envelope_value += 1.0 / attack_samples;
|
|
if voice.envelope_value >= 1.0 {
|
|
voice.envelope_value = 1.0;
|
|
voice.envelope_phase = EnvelopePhase::Sustain;
|
|
}
|
|
}
|
|
EnvelopePhase::Sustain => {
|
|
voice.envelope_value = 1.0;
|
|
}
|
|
EnvelopePhase::Release => {
|
|
let release_samples = release_time * sample_rate as f32;
|
|
voice.envelope_value -= 1.0 / release_samples;
|
|
if voice.envelope_value <= 0.0 {
|
|
voice.envelope_value = 0.0;
|
|
voice.is_active = false;
|
|
}
|
|
}
|
|
}
|
|
let envelope = voice.envelope_value.clamp(0.0, 1.0);
|
|
|
|
// Apply velocity scaling (0-127 -> 0-1)
|
|
let velocity_scale = voice.velocity as f32 / 127.0;
|
|
|
|
// Mix into output
|
|
let final_sample = sample * envelope * velocity_scale * gain;
|
|
output[frame * 2] += final_sample;
|
|
output[frame * 2 + 1] += final_sample;
|
|
|
|
// Advance playhead
|
|
voice.playhead += speed_adjusted;
|
|
|
|
// Stop if we've reached the end
|
|
if voice.playhead >= layer.sample_data.len() as f32 {
|
|
voice.is_active = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
self.voices.clear();
|
|
}
|
|
|
|
fn node_type(&self) -> &str {
|
|
"MultiSampler"
|
|
}
|
|
|
|
fn name(&self) -> &str {
|
|
&self.name
|
|
}
|
|
|
|
fn clone_node(&self) -> Box<dyn AudioNode> {
|
|
Box::new(Self::new(self.name.clone()))
|
|
}
|
|
}
|