use std::{
ops::Range,
sync::{mpsc::SyncSender, Arc},
};
use crate::{
modulation::{matrix::ModulationMatrix, processor::MODULATION_PROCESSOR_BLOCK_SIZE},
source::{
amplified::AmplifiedSource, file::preloaded::PreloadedFileSource,
mapped::ChannelMappedSource, panned::PannedSource, Source, SourceTime,
},
utils::{
ahdsr::{AhdsrEnvelope, AhdsrParameters, AhdsrStage},
buffer::{scale_buffer, InterleavedBufferMut},
speed_from_note,
},
FileSource, NotePlaybackId, PlaybackStatusContext, PlaybackStatusEvent,
};
use super::{
granular::{GrainPool, GranularParameters},
modulation::SamplerVoiceModulationState,
};
type SamplerVoiceAmplifiedSource = AmplifiedSource<ChannelMappedSource<PreloadedFileSource>>;
type SamplerVoicePannedSource = PannedSource<SamplerVoiceAmplifiedSource>;
type SamplerVoiceSource = SamplerVoicePannedSource;
const GRAIN_POOL_SIZE: usize = 100;
pub(crate) struct SamplerVoice {
note_id: Option<NotePlaybackId>,
note: u8,
note_volume: f32,
note_panning: f32,
source: SamplerVoiceSource,
envelope: AhdsrEnvelope,
release_start_frame: Option<u64>,
grain_pool_started: bool,
grain_pool: Option<Box<GrainPool<GRAIN_POOL_SIZE>>>,
modulation_state: Option<Box<SamplerVoiceModulationState>>,
}
impl SamplerVoice {
pub fn new(file_source: PreloadedFileSource, channel_count: usize, _sample_rate: u32) -> Self {
let note_id = None;
let note = 60; let note_volume = 1.0;
let note_panning = 0.0;
let source = {
let channel_mapped = ChannelMappedSource::new(file_source, channel_count);
let amplified = AmplifiedSource::new(channel_mapped, 1.0);
PannedSource::new(amplified, 0.0)
};
let envelope = AhdsrEnvelope::new();
let release_start_frame = None;
let grain_pool_started = false;
let grain_pool = None;
let modulation_state = None;
Self {
note_id,
note,
note_volume,
note_panning,
source,
envelope,
release_start_frame,
grain_pool_started,
grain_pool,
modulation_state,
}
}
#[inline]
pub fn note_id(&self) -> Option<NotePlaybackId> {
self.note_id
}
#[inline]
pub fn is_active(&self) -> bool {
self.note_id.is_some()
}
#[inline]
pub fn in_release_stage(&self) -> bool {
self.envelope.stage() == AhdsrStage::Release
}
#[inline]
pub fn release_start_frame(&self) -> Option<u64> {
self.release_start_frame
}
pub fn set_playback_status_sender(&mut self, sender: Option<SyncSender<PlaybackStatusEvent>>) {
self.file_source_mut().set_playback_status_sender(sender);
}
#[allow(clippy::too_many_arguments)]
pub fn start(
&mut self,
note_id: NotePlaybackId,
note: u8,
volume: f32,
panning: f32,
base_transpose: i32,
base_finetune: i32,
base_volume: f32,
base_panning: f32,
envelope_parameters: &Option<AhdsrParameters>,
granular_parameters: &Option<GranularParameters>,
context: Option<PlaybackStatusContext>,
) {
self.reset();
self.note = note;
self.note_volume = volume;
self.note_panning = panning;
let note_speed = speed_from_note(note);
let pitch_factor =
2.0_f64.powf((base_transpose as f64) / 12.0 + (base_finetune as f64) / 1200.0);
let effective_speed = note_speed * pitch_factor;
let effective_volume = base_volume * volume;
let effective_panning = (base_panning + panning).clamp(-1.0, 1.0);
self.file_source_mut().set_speed(effective_speed, None);
self.amplified_source_mut().set_volume(effective_volume);
self.panned_source_mut().set_panning(effective_panning);
debug_assert!(
self.grain_pool.is_some() == granular_parameters.is_some(),
"Expecting valid grain parameters when granular playback is enabled",
);
if let Some((grain_pool, granular_parameters)) = self
.grain_pool
.as_deref_mut()
.zip(granular_parameters.as_ref())
{
self.grain_pool_started = true;
grain_pool.start(
granular_parameters,
effective_speed,
effective_volume,
effective_panning,
);
}
self.file_source_mut().set_playback_status_context(context);
if let Some(envelope_parameters) = envelope_parameters {
self.envelope.note_on(envelope_parameters, 1.0); }
if let Some(state) = &mut self.modulation_state {
state.start(note, volume);
}
self.note_id = Some(note_id);
}
pub fn stop(
&mut self,
envelope_parameters: &Option<AhdsrParameters>,
current_sample_frame: u64,
) {
if self.is_active() {
self.release_start_frame = Some(current_sample_frame);
if let Some(envelope_parameters) = envelope_parameters {
self.envelope.note_off(envelope_parameters);
} else {
self.file_source_mut().stop();
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.stop();
}
}
if let Some(state) = &mut self.modulation_state {
state.stop();
}
}
}
pub fn reset(&mut self) {
if self.is_active() {
self.file_source_mut().reset();
self.file_source_mut().set_playback_status_context(None);
self.note_id = None;
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.reset();
}
}
self.release_start_frame = None;
}
pub fn set_speed(
&mut self,
speed: f64,
glide: Option<f32>,
base_transpose: i32,
base_finetune: i32,
) {
let pitch_factor =
2.0_f64.powf((base_transpose as f64) / 12.0 + (base_finetune as f64) / 1200.0);
let effective_speed = speed * pitch_factor;
self.file_source_mut().set_speed(effective_speed, glide);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_speed(effective_speed);
}
}
pub fn set_base_pitch(&mut self, base_transpose: i32, base_finetune: i32) {
let note_speed = speed_from_note(self.note);
let pitch_factor =
2.0_f64.powf((base_transpose as f64) / 12.0 + (base_finetune as f64) / 1200.0);
let effective_speed = note_speed * pitch_factor;
self.file_source_mut().set_speed(effective_speed, None);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_speed(effective_speed);
}
}
pub fn set_volume(&mut self, volume: f32, base_volume: f32) {
self.note_volume = volume;
let effective_volume = base_volume * volume;
self.amplified_source_mut().set_volume(effective_volume);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_volume(effective_volume);
}
}
pub fn set_base_volume(&mut self, base_volume: f32) {
let effective_volume = base_volume * self.note_volume;
self.amplified_source_mut().set_volume(effective_volume);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_volume(effective_volume);
}
}
pub fn set_panning(&mut self, panning: f32, base_panning: f32) {
self.note_panning = panning;
let effective_panning = (base_panning + panning).clamp(-1.0, 1.0);
self.panned_source_mut().set_panning(effective_panning);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_panning(effective_panning);
}
}
pub fn set_base_panning(&mut self, base_panning: f32) {
let effective_panning = (base_panning + self.note_panning).clamp(-1.0, 1.0);
self.panned_source_mut().set_panning(effective_panning);
if let Some(grain_pool) = &mut self.grain_pool {
grain_pool.set_panning(effective_panning);
}
}
pub fn set_loop_range(&mut self, range: Option<Range<u64>>) {
let frame_count = self.file_source().file_buffer().frame_count() as u64;
assert!(
range.is_none()
|| range
.as_ref()
.is_some_and(|r| r.start < frame_count && r.end <= frame_count),
"Invalid loop range: {:?} not in range {:?}",
range,
0..frame_count
);
let repeat_count = if range.is_some() { usize::MAX } else { 0 };
self.file_source_mut().set_loop_range(range.clone());
self.file_source_mut().set_repeat(repeat_count);
if let Some(grain_pool) = &mut self.grain_pool {
let normalized = range.map(|r| {
(
r.start as f32 / frame_count as f32,
r.end as f32 / frame_count as f32,
)
});
grain_pool.set_loop_range(normalized);
}
}
pub fn enable_granular_playback(
&mut self,
modulation_matrix: ModulationMatrix,
sample_rate: u32,
sample_buffer: Arc<Box<[f32]>>,
) {
assert!(
!sample_buffer.is_empty(),
"Expecting a non empty mono sample buffer here - resampled!"
);
let file_buffer = self.file_source().file_buffer();
let sample_loop_range = file_buffer.loop_range().map(|range| {
let total = file_buffer.frame_count() as f32;
let start = range.start as f32 / total;
let end = range.end as f32 / total;
(start, end)
});
self.grain_pool = Some(Box::new(GrainPool::new(
sample_rate,
sample_buffer,
sample_loop_range,
)));
self.modulation_state = Some(Box::new(SamplerVoiceModulationState::new(
modulation_matrix,
)));
}
#[inline]
#[allow(unused)]
pub fn modulation_matrix(&self) -> Option<&ModulationMatrix> {
self.modulation_state.as_ref().map(|s| s.matrix())
}
#[inline]
pub fn modulation_matrix_mut(&mut self) -> Option<&mut ModulationMatrix> {
self.modulation_state.as_mut().map(|s| s.matrix_mut())
}
pub fn process(
&mut self,
output: &mut [f32],
channel_count: usize,
envelope_parameters: &Option<AhdsrParameters>,
granular_parameters: &Option<GranularParameters>,
time: &SourceTime,
) -> usize {
debug_assert!(self.is_active(), "Only active voices need to process");
debug_assert!(
self.grain_pool.is_some() == granular_parameters.is_some()
&& self.grain_pool.is_some() == self.modulation_state.is_some(),
"Expecting grain pool, parameters and modulation to be enabled or disabled together"
);
let written = match (
self.grain_pool.as_deref_mut(),
self.modulation_state.as_deref_mut(),
granular_parameters.as_ref(),
) {
(Some(grain_pool), Some(modulation_state), Some(granular_parameters)) => {
for chunk in output.chunks_mut(MODULATION_PROCESSOR_BLOCK_SIZE * channel_count) {
let chunk_frame_count = chunk.len() / channel_count;
modulation_state.process(chunk_frame_count);
grain_pool.process(
chunk,
channel_count,
granular_parameters,
&modulation_state.output(chunk_frame_count),
);
}
output.len()
}
_ => {
self.source.write(output, time)
}
};
let pos_mod = if let Some(state) = &self.modulation_state {
if state.matrix().output_size() > 0 {
state.matrix().output_at(
super::Sampler::GRAIN_POSITION.id(),
state.matrix().output_size() - 1,
)
} else {
0.0
}
} else {
0.0
};
if let Some(grain_pool_playhead) = self
.grain_pool
.as_ref()
.zip(granular_parameters.as_ref())
.map(|(pool, parameters)| pool.playback_position(parameters, pos_mod))
{
let sample_buffer = self.file_source().file_buffer();
let is_start_event = self.grain_pool_started;
self.grain_pool_started = false;
self.file_source_mut()
.file_source_impl_mut()
.send_playback_position_status(
time,
is_start_event,
(grain_pool_playhead * sample_buffer.buffer().len() as f32) as u64,
sample_buffer.channel_count(),
sample_buffer.sample_rate(),
);
}
if let Some(envelope_parameters) = envelope_parameters {
let mut output = &mut output[..written];
if matches!(
self.envelope.stage(),
AhdsrStage::Sustain | AhdsrStage::Idle
) {
scale_buffer(output, self.envelope.output());
} else {
for frame in output.frames_mut(channel_count) {
let envelope_value = self.envelope.run(envelope_parameters);
for sample in frame {
*sample *= envelope_value;
}
}
}
}
if self.source.is_exhausted()
|| self.grain_pool.as_ref().is_some_and(|s| s.is_exhausted())
|| (envelope_parameters.is_some() && self.envelope.stage() == AhdsrStage::Idle)
{
self.reset();
if let Some(grain_pool_exhausted) = self.grain_pool.as_ref().map(|p| p.is_exhausted()) {
self.file_source_mut()
.file_source_impl_mut()
.send_playback_stopped_status(grain_pool_exhausted);
}
}
written
}
#[inline]
pub(crate) fn panned_source_mut(&mut self) -> &mut SamplerVoicePannedSource {
&mut self.source
}
#[inline]
pub(crate) fn amplified_source_mut(&mut self) -> &mut SamplerVoiceAmplifiedSource {
self.source.input_source_mut()
}
#[inline]
pub(crate) fn file_source(&self) -> &PreloadedFileSource {
self.source.input_source().input_source().input_source()
}
#[inline]
pub(crate) fn file_source_mut(&mut self) -> &mut PreloadedFileSource {
self.source
.input_source_mut()
.input_source_mut()
.input_source_mut()
}
}