Skip to main content

phonic/generator/
sampler.rs

1use std::{
2    ops::Range,
3    path::Path,
4    sync::{mpsc::SyncSender, Arc},
5    time::Duration,
6};
7
8use crossbeam_queue::ArrayQueue;
9use four_cc::FourCC;
10use strum::VariantNames;
11
12use crate::{
13    generator::{
14        Generator, GeneratorMessage, GeneratorMessagePayload, GeneratorPlaybackEvent,
15        GeneratorPlaybackMessage, GeneratorPlaybackOptions,
16    },
17    modulation::{ModulationConfig, ModulationSource, ModulationTarget},
18    parameter::{
19        formatters, EnumParameter, EnumParameterValue, FloatParameter, IntegerParameter, Parameter,
20        ParameterScaling, ParameterValueUpdate,
21    },
22    source::{
23        file::preloaded::PreloadedFileSource, mixed::MixedSource, unique_source_id, Source,
24        SourceTime,
25    },
26    utils::{
27        ahdsr::AhdsrParameters,
28        buffer::{add_buffers, clear_buffer},
29        dsp::lfo::LfoWaveform,
30    },
31    AudioFileBuffer, Error, FilePlaybackOptions, FileSource, NotePlaybackId, PlaybackId,
32    PlaybackStatusContext, PlaybackStatusEvent, ResamplingQuality,
33};
34
35// -------------------------------------------------------------------------------------------------
36
37mod granular;
38mod modulation;
39mod voice;
40
41use modulation::SamplerModulationState;
42use voice::SamplerVoice;
43
44pub use granular::{GrainOverlapMode, GrainPlaybackDirection, GrainWindowMode, GranularParameters};
45
46// -------------------------------------------------------------------------------------------------
47
48/// Generator-specific messages for [`Sampler`].
49///
50/// Send via [`GeneratorPlaybackHandle::send_message`](crate::GeneratorPlaybackHandle::send_message).
51pub enum SamplerMessage {
52    /// Set custom loop start and end in sample frames.
53    /// Pass `None` to disable looping entirely.
54    SetLoopRange(Option<Range<u64>>),
55}
56
57impl GeneratorMessage for SamplerMessage {
58    fn generator_name(&self) -> &'static str {
59        "Sampler"
60    }
61    fn payload(&self) -> &dyn std::any::Any {
62        self
63    }
64}
65
66// -------------------------------------------------------------------------------------------------
67
68/// Basic sampler which plays a single audio file with optional AHDSR envelope and/or
69/// granular playback on a predefined number of voices.
70///
71/// AHDSR and granular parameters can be automated.
72pub struct Sampler {
73    playback_id: PlaybackId,
74    playback_message_queue: Arc<ArrayQueue<GeneratorPlaybackMessage>>,
75    file_path: Arc<String>,
76    active_voices: usize,
77    voices: Vec<SamplerVoice>,
78    base_transpose: i32,
79    base_finetune: i32,
80    base_volume: f32,
81    base_panning: f32,
82    envelope_parameters: Option<AhdsrParameters>,
83    granular_parameters: Option<GranularParameters>,
84    modulation_state: Option<SamplerModulationState>,
85    active_parameters: Vec<Box<dyn Parameter>>,
86    playback_status_send: Option<SyncSender<PlaybackStatusEvent>>,
87    transient: bool, // True if the generator can exhaust
88    stopping: bool,  // True if stop has been called and we are waiting for voices to decay
89    stopped: bool,   // True if all voices have decayed after a stop call
90    options: GeneratorPlaybackOptions,
91    output_sample_rate: u32,
92    output_channel_count: usize,
93    temp_buffer: Vec<f32>,
94}
95
96// -------------------------------------------------------------------------------------------------
97
98impl Sampler {
99    // Base sampler parameters (always active)
100    pub const TRANSPOSE: IntegerParameter =
101        IntegerParameter::new(FourCC(*b"STRN"), "Transpose", -48..=48, 0).with_unit("st");
102
103    pub const FINETUNE: IntegerParameter =
104        IntegerParameter::new(FourCC(*b"SFTN"), "Finetune", -100..=100, 0).with_unit("ct");
105
106    pub const VOLUME: FloatParameter = FloatParameter::new(
107        FourCC(*b"SVOL"),
108        "Volume",
109        0.000001..=15.848932, // db_to_linear(-60.0)..=db_to_linear(24.0)
110        1.0,                  // 0dB
111    )
112    .with_scaling(ParameterScaling::Decibel(-60.0, 24.0))
113    .with_formatter(formatters::GAIN);
114
115    pub const PANNING: FloatParameter =
116        FloatParameter::new(FourCC(*b"SPAN"), "Panning", -1.0..=1.0, 0.0)
117            .with_formatter(formatters::PAN);
118
119    /// Base sampler parameter descriptors (transpose, finetune, volume, panning).
120    pub fn base_parameters() -> Vec<Box<dyn Parameter>> {
121        vec![
122            Self::TRANSPOSE.into_box(),
123            Self::FINETUNE.into_box(),
124            Self::VOLUME.into_box(),
125            Self::PANNING.into_box(),
126        ]
127    }
128
129    // Envelope parameters (only active when ahdsr playback is enabled)
130    const MIN_TIME_SEC: f32 = 0.0;
131    const MAX_TIME_SEC: f32 = 10.0;
132
133    pub const AMP_ATTACK: FloatParameter = FloatParameter::new(
134        FourCC(*b"AATK"),
135        "Attack",
136        Self::MIN_TIME_SEC..=Self::MAX_TIME_SEC,
137        0.001,
138    )
139    .with_scaling(ParameterScaling::Exponential(2.0))
140    .with_unit("s");
141    pub const AMP_HOLD: FloatParameter = FloatParameter::new(
142        FourCC(*b"AHLD"),
143        "Hold",
144        Self::MIN_TIME_SEC..=Self::MAX_TIME_SEC,
145        0.75,
146    )
147    .with_scaling(ParameterScaling::Exponential(2.0))
148    .with_unit("s");
149    pub const AMP_DECAY: FloatParameter = FloatParameter::new(
150        FourCC(*b"ADCY"),
151        "Decay",
152        Self::MIN_TIME_SEC..=Self::MAX_TIME_SEC,
153        0.5,
154    )
155    .with_scaling(ParameterScaling::Exponential(2.0))
156    .with_unit("s");
157    pub const AMP_SUSTAIN: FloatParameter = FloatParameter::new(
158        FourCC(*b"ASTN"), //
159        "Sustain",
160        0.0..=1.0,
161        0.75,
162    );
163    pub const AMP_RELEASE: FloatParameter = FloatParameter::new(
164        FourCC(*b"AREL"),
165        "Release",
166        Self::MIN_TIME_SEC..=Self::MAX_TIME_SEC,
167        1.0,
168    )
169    .with_scaling(ParameterScaling::Exponential(2.0))
170    .with_unit("s");
171
172    /// AHDSR envelope parameter descriptors.
173    pub fn envelope_parameters() -> Vec<Box<dyn Parameter>> {
174        vec![
175            Self::AMP_ATTACK.into_box(),
176            Self::AMP_HOLD.into_box(),
177            Self::AMP_DECAY.into_box(),
178            Self::AMP_SUSTAIN.into_box(),
179            Self::AMP_RELEASE.into_box(),
180        ]
181    }
182
183    /// Apply given [ParameterValueUpdate] to an [AhdsrParameters] object.
184    pub fn set_envelope_parameter(
185        params: &mut AhdsrParameters,
186        id: FourCC,
187        value: &ParameterValueUpdate,
188    ) -> Result<(), Error> {
189        match id {
190            _ if id == Self::AMP_ATTACK.id() => {
191                let seconds = Sampler::parameter_update_value(value, &Self::AMP_ATTACK)?;
192                params.set_attack_time(Duration::from_secs_f32(seconds.max(0.0)))?;
193            }
194            _ if id == Self::AMP_HOLD.id() => {
195                let seconds = Sampler::parameter_update_value(value, &Self::AMP_HOLD)?;
196                params.set_hold_time(Duration::from_secs_f32(seconds.max(0.0)))?;
197            }
198            _ if id == Self::AMP_DECAY.id() => {
199                let seconds = Sampler::parameter_update_value(value, &Self::AMP_DECAY)?;
200                params.set_decay_time(Duration::from_secs_f32(seconds.max(0.0)))?;
201            }
202            _ if id == Self::AMP_SUSTAIN.id() => {
203                let sustain = Sampler::parameter_update_value(value, &Self::AMP_SUSTAIN)?;
204                params.set_sustain_level(sustain)?;
205            }
206            _ if id == Self::AMP_RELEASE.id() => {
207                let seconds = Sampler::parameter_update_value(value, &Self::AMP_RELEASE)?;
208                params.set_release_time(Duration::from_secs_f32(seconds.max(0.0)))?;
209            }
210            _ => {
211                return Err(Error::ParameterError(format!(
212                    "Invalid/unknown envelope parameter '{id}'"
213                )))
214            }
215        }
216        Ok(())
217    }
218
219    // Granular playback parameters (only active when granular playback is enabled)
220    const MIN_GRAIN_SIZE_MS: f32 = 1.0;
221    const MAX_GRAIN_SIZE_MS: f32 = 1000.0;
222    const MIN_GRAIN_DENSITY_HZ: f32 = 1.0;
223    const MAX_GRAIN_DENSITY_HZ: f32 = 100.0;
224
225    pub const GRAIN_OVERLAP_MODE: EnumParameter = EnumParameter::new(
226        FourCC(*b"GOVM"),
227        "Overlap Mode",
228        GrainOverlapMode::VARIANTS,
229        GrainOverlapMode::Cloud as usize,
230    );
231
232    pub const GRAIN_WINDOW: EnumParameter = EnumParameter::new(
233        FourCC(*b"GWND"),
234        "Window",
235        GrainWindowMode::VARIANTS,
236        GrainWindowMode::Hann as usize,
237    );
238
239    pub const GRAIN_SIZE: FloatParameter = FloatParameter::new(
240        FourCC(*b"GSIZ"),
241        "Grain Size",
242        Self::MIN_GRAIN_SIZE_MS..=Self::MAX_GRAIN_SIZE_MS,
243        100.0,
244    )
245    .with_scaling(ParameterScaling::Exponential(2.0))
246    .with_unit("ms");
247
248    pub const GRAIN_DENSITY: FloatParameter = FloatParameter::new(
249        FourCC(*b"GDEN"),
250        "Density",
251        Self::MIN_GRAIN_DENSITY_HZ..=Self::MAX_GRAIN_DENSITY_HZ,
252        10.0,
253    )
254    .with_scaling(ParameterScaling::Exponential(2.0))
255    .with_unit("Hz");
256
257    pub const GRAIN_VARIATION: FloatParameter =
258        FloatParameter::new(FourCC(*b"GVAR"), "Variation", 0.0..=1.0, 0.0)
259            .with_formatter(formatters::PERCENT);
260
261    pub const GRAIN_SPRAY: FloatParameter =
262        FloatParameter::new(FourCC(*b"GSPY"), "Spray", 0.0..=1.0, 0.0)
263            .with_formatter(formatters::PERCENT);
264
265    pub const GRAIN_PAN_SPREAD: FloatParameter =
266        FloatParameter::new(FourCC(*b"GPAN"), "Pan Spread", 0.0..=1.0, 0.0)
267            .with_formatter(formatters::PERCENT);
268
269    pub const GRAIN_PLAYBACK_DIR: EnumParameter = EnumParameter::new(
270        FourCC(*b"GDIR"),
271        "Direction",
272        GrainPlaybackDirection::VARIANTS,
273        GrainPlaybackDirection::Forward as usize,
274    );
275    pub const GRAIN_POSITION: FloatParameter =
276        FloatParameter::new(FourCC(*b"GPOS"), "Position", 0.0..=1.0, 0.5)
277            .with_formatter(formatters::PERCENT);
278
279    pub const GRAIN_STEP: FloatParameter =
280        FloatParameter::new(FourCC(*b"GSTP"), "Step", -4.0..=4.0, 0.0).with_unit("x");
281
282    /// Granular playback parameter descriptors.
283    pub fn granular_parameters() -> Vec<Box<dyn Parameter>> {
284        vec![
285            Self::GRAIN_OVERLAP_MODE.into_box(),
286            Self::GRAIN_WINDOW.into_box(),
287            Self::GRAIN_SIZE.into_box(),
288            Self::GRAIN_DENSITY.into_box(),
289            Self::GRAIN_VARIATION.into_box(),
290            Self::GRAIN_SPRAY.into_box(),
291            Self::GRAIN_PAN_SPREAD.into_box(),
292            Self::GRAIN_PLAYBACK_DIR.into_box(),
293            Self::GRAIN_POSITION.into_box(),
294            Self::GRAIN_STEP.into_box(),
295        ]
296    }
297
298    /// Apply given [ParameterValueUpdate] to a [GranularParameters] object.
299    pub fn set_granular_parameter(
300        params: &mut GranularParameters,
301        id: FourCC,
302        value: &ParameterValueUpdate,
303    ) -> Result<(), Error> {
304        match id {
305            _ if id == Self::GRAIN_OVERLAP_MODE.id() => {
306                let mut enum_value = EnumParameterValue::<GrainOverlapMode>::from_description(
307                    Self::GRAIN_OVERLAP_MODE,
308                );
309                enum_value.apply_update(value);
310                params.overlap_mode = enum_value.value();
311            }
312            _ if id == Self::GRAIN_WINDOW.id() => {
313                let mut enum_value =
314                    EnumParameterValue::<GrainWindowMode>::from_description(Self::GRAIN_WINDOW);
315                enum_value.apply_update(value);
316                params.window = enum_value.value();
317            }
318            _ if id == Self::GRAIN_SIZE.id() => {
319                let ms = Sampler::parameter_update_value(value, &Self::GRAIN_SIZE)?;
320                params.size = ms;
321            }
322            _ if id == Self::GRAIN_DENSITY.id() => {
323                let hz = Sampler::parameter_update_value(value, &Self::GRAIN_DENSITY)?;
324                params.density = hz;
325            }
326            _ if id == Self::GRAIN_VARIATION.id() => {
327                let variation = Sampler::parameter_update_value(value, &Self::GRAIN_VARIATION)?;
328                params.variation = variation;
329            }
330            _ if id == Self::GRAIN_SPRAY.id() => {
331                let spray = Sampler::parameter_update_value(value, &Self::GRAIN_SPRAY)?;
332                params.spray = spray;
333            }
334            _ if id == Self::GRAIN_PAN_SPREAD.id() => {
335                let spread = Sampler::parameter_update_value(value, &Self::GRAIN_PAN_SPREAD)?;
336                params.pan_spread = spread;
337            }
338            _ if id == Self::GRAIN_PLAYBACK_DIR.id() => {
339                let mut enum_value = EnumParameterValue::<GrainPlaybackDirection>::from_description(
340                    Self::GRAIN_PLAYBACK_DIR,
341                );
342                enum_value.apply_update(value);
343                params.playback_direction = enum_value.value();
344            }
345            _ if id == Self::GRAIN_POSITION.id() => {
346                let position = Sampler::parameter_update_value(value, &Self::GRAIN_POSITION)?;
347                params.position = position;
348            }
349            _ if id == Self::GRAIN_STEP.id() => {
350                let step = Sampler::parameter_update_value(value, &Self::GRAIN_STEP)?;
351                params.step = step;
352            }
353            _ => {
354                return Err(Error::ParameterError(format!(
355                    "Invalid/unknown granular playback parameter '{id}'"
356                )))
357            }
358        }
359        Ok(())
360    }
361
362    // Modulation source descriptors
363    pub const MOD_SOURCE_LFO1: FourCC = FourCC(*b"LFO1");
364    pub const MOD_SOURCE_LFO2: FourCC = FourCC(*b"LFO2");
365    pub const MOD_SOURCE_VELOCITY: FourCC = FourCC(*b"VELM");
366    pub const MOD_SOURCE_KEYTRACK: FourCC = FourCC(*b"KEYM");
367
368    // Modulation parameters - LFO 1
369    pub const MOD_LFO1_RATE: FloatParameter =
370        FloatParameter::new(FourCC(*b"ML1R"), "LFO 1 Rate", 0.01..=20.0, 1.0)
371            .with_scaling(ParameterScaling::Exponential(2.0))
372            .with_unit("Hz");
373    pub const MOD_LFO1_WAVEFORM: EnumParameter = EnumParameter::new(
374        FourCC(*b"ML1W"),
375        "LFO 1 Waveform",
376        LfoWaveform::VARIANTS,
377        LfoWaveform::Sine as usize,
378    );
379
380    // Modulation parameters - LFO 2
381    pub const MOD_LFO2_RATE: FloatParameter =
382        FloatParameter::new(FourCC(*b"ML2R"), "LFO 2 Rate", 0.01..=20.0, 2.0)
383            .with_scaling(ParameterScaling::Exponential(2.0))
384            .with_unit("Hz");
385    pub const MOD_LFO2_WAVEFORM: EnumParameter = EnumParameter::new(
386        FourCC(*b"ML2W"),
387        "LFO 2 Waveform",
388        LfoWaveform::VARIANTS,
389        LfoWaveform::Triangle as usize,
390    );
391
392    /// Modulation configuration for the sampler (with granular playback enabled).
393    pub fn modulation_config() -> ModulationConfig {
394        ModulationConfig {
395            sources: vec![
396                ModulationSource::Lfo {
397                    id: Self::MOD_SOURCE_LFO1,
398                    name: "LFO 1",
399                    rate_param: Self::MOD_LFO1_RATE,
400                    waveform_param: Self::MOD_LFO1_WAVEFORM,
401                },
402                ModulationSource::Lfo {
403                    id: Self::MOD_SOURCE_LFO2,
404                    name: "LFO 2",
405                    rate_param: Self::MOD_LFO2_RATE,
406                    waveform_param: Self::MOD_LFO2_WAVEFORM,
407                },
408                ModulationSource::Velocity {
409                    id: Self::MOD_SOURCE_VELOCITY,
410                    name: "Velocity",
411                },
412                ModulationSource::Keytracking {
413                    id: Self::MOD_SOURCE_KEYTRACK,
414                    name: "Keytracking",
415                },
416            ],
417            targets: vec![
418                ModulationTarget::new(Self::GRAIN_SIZE.id(), Self::GRAIN_SIZE.name()),
419                ModulationTarget::new(Self::GRAIN_DENSITY.id(), Self::GRAIN_DENSITY.name()),
420                ModulationTarget::new(Self::GRAIN_VARIATION.id(), Self::GRAIN_VARIATION.name()),
421                ModulationTarget::new(Self::GRAIN_SPRAY.id(), Self::GRAIN_SPRAY.name()),
422                ModulationTarget::new(Self::GRAIN_PAN_SPREAD.id(), Self::GRAIN_PAN_SPREAD.name()),
423                ModulationTarget::new(Self::GRAIN_POSITION.id(), Self::GRAIN_POSITION.name()),
424                ModulationTarget::new(Self::GRAIN_STEP.id(), Self::GRAIN_STEP.name()),
425            ],
426        }
427    }
428
429    /// Create a new sampler with the given sample file
430    ///
431    /// # Arguments
432    /// * `file_path` - Full path to the sample file that should be played back.
433    /// * `envelope_parameters` - Optional parameters for the volume AHDSR envelope.
434    ///   When None, no envelope will be applied.
435    /// * `options` - Generic generator playback options.
436    /// * `output_sample_rate` - Output sample rate of the source -
437    ///   usually the player's audio backend's sample rate.
438    /// * `output_channel_count` - Output channel count -
439    ///   usually the player's audio backend's channel count.
440    pub fn from_file<P: AsRef<Path>>(
441        file_path: P,
442        options: GeneratorPlaybackOptions,
443        output_channel_count: usize,
444        output_sample_rate: u32,
445    ) -> Result<Self, Error> {
446        let file_source = PreloadedFileSource::from_file(
447            &file_path,
448            FilePlaybackOptions::default(),
449            output_sample_rate,
450        )?;
451
452        Self::from_file_source(
453            file_source,
454            options,
455            output_channel_count,
456            output_sample_rate,
457        )
458    }
459
460    /// Create a new sampler with the given raw encoded sample file buffer.
461    /// See [Self::from_file] for more info about the parameters.
462    pub fn from_file_buffer<P: AsRef<Path>>(
463        file_buffer: Vec<u8>,
464        file_path: P,
465        options: GeneratorPlaybackOptions,
466        output_channel_count: usize,
467        output_sample_rate: u32,
468    ) -> Result<Self, Error> {
469        let file_path = file_path.as_ref().to_string_lossy().to_string();
470        let file_source = PreloadedFileSource::from_file_buffer(
471            file_buffer,
472            &file_path,
473            FilePlaybackOptions::default(),
474            output_sample_rate,
475        )?;
476
477        Self::from_file_source(
478            file_source,
479            options,
480            output_channel_count,
481            output_sample_rate,
482        )
483    }
484
485    /// Create a new sampler with the given preloaded file source.
486    /// See [Self::from_file] for more info about the parameters.
487    pub fn from_file_source(
488        file_source: PreloadedFileSource,
489        options: GeneratorPlaybackOptions,
490        output_channel_count: usize,
491        output_sample_rate: u32,
492    ) -> Result<Self, Error> {
493        // Memorize file path
494        let file_path = Arc::new(file_source.file_name());
495
496        // Pre-allocate playback message queue so it fits all parameters and a bunch of trigger events
497        let playback_message_queue_size: usize = (Self::base_parameters().len()
498            + Self::envelope_parameters().len()
499            + Self::granular_parameters().len())
500            * 2
501            + 16;
502        let playback_message_queue = Arc::new(ArrayQueue::new(playback_message_queue_size));
503
504        // Create a new unique source id
505        let playback_id = unique_source_id();
506        let playback_status_send = None;
507
508        // Set voice playback options
509        let mut voice_playback_options = FilePlaybackOptions::default();
510        if let Some(duration) = options.playback_pos_emit_rate {
511            voice_playback_options = voice_playback_options.playback_pos_emit_rate(duration);
512        }
513        // de-click, in case there's no envelope
514        voice_playback_options.fade_out_duration = Some(Duration::from_millis(50));
515
516        // Allocate voices
517        let mut voices = Vec::with_capacity(options.voices);
518        for _ in 0..options.voices {
519            let file_source = file_source
520                .clone(voice_playback_options, output_sample_rate)
521                .map_err(|err| {
522                    Error::ParameterError(format!("Failed to create sampler voice: {err}"))
523                })?;
524            voices.push(SamplerVoice::new(
525                file_source,
526                output_channel_count,
527                output_sample_rate,
528            ));
529        }
530
531        // Base parameter values
532        let base_transpose = 0;
533        let base_finetune = 0;
534        let base_volume = 1.0;
535        let base_panning = 0.0;
536
537        // Optional parameters
538        let envelope_parameters = None;
539        let granular_parameters = None;
540
541        // Modulation state (with empty config - will be initialized in with_granular_playback)
542        let modulation_state = None;
543
544        let active_voices = 0;
545
546        // Base parameters are always active. Envelope and granular parameters are added when enabled.
547        let active_parameters = Self::base_parameters();
548
549        // Initial playback state
550        let transient = false;
551        let stopping = false;
552        let stopped = false;
553
554        // Pre-allocate temp buffer for mixing, using mixer's max sample buffer size
555        let temp_buffer = vec![0.0; MixedSource::MAX_MIX_BUFFER_SAMPLES];
556
557        Ok(Self {
558            playback_id,
559            playback_message_queue,
560            playback_status_send,
561            file_path,
562            active_voices,
563            voices,
564            base_transpose,
565            base_finetune,
566            base_volume,
567            base_panning,
568            envelope_parameters,
569            granular_parameters,
570            modulation_state,
571            active_parameters,
572            transient,
573            stopping,
574            stopped,
575            options,
576            output_sample_rate,
577            output_channel_count,
578            temp_buffer,
579        })
580    }
581
582    /// Builder method to enable AHDSR envelope on the sampler.
583    pub fn with_ahdsr(mut self, mut parameters: AhdsrParameters) -> Result<Self, Error> {
584        // Initialize the parameters with the output sample rate
585        parameters
586            .set_sample_rate(self.output_sample_rate)
587            .map_err(|err| {
588                Error::ParameterError(format!("Failed to initialize AHDSR parameters: {err}"))
589            })?;
590
591        // Add AHDSR parameters to the active parameters list
592        self.active_parameters.extend(Self::envelope_parameters());
593
594        self.envelope_parameters = Some(parameters);
595        Ok(self)
596    }
597
598    /// Builder method to enable granular playback on the sampler.
599    pub fn with_granular_playback(mut self, parameters: GranularParameters) -> Result<Self, Error> {
600        // Validate the parameters
601        parameters
602            .validate()
603            .map_err(|err| Error::ParameterError(format!("Invalid granular parameters: {err}")))?;
604
605        // Add granular parameters to the active parameters list
606        self.active_parameters.extend(Self::granular_parameters());
607
608        // Create modulation config
609        let modulation_config = Self::modulation_config();
610
611        // Add modulation parameters to the active parameters list
612        self.active_parameters
613            .extend(modulation_config.source_parameters());
614
615        // Resample file source, if needed and mix down to mono
616        let sample_buffer = Self::create_granular_sample_buffer(
617            self.voices.first().unwrap().file_source().file_buffer(),
618            self.output_sample_rate,
619        )?;
620
621        // Initialize modulation state
622        let modulation_state = SamplerModulationState::new(modulation_config);
623
624        // Initialize granular playback on all voices
625        for voice in &mut self.voices {
626            voice.enable_granular_playback(
627                modulation_state.create_matrix(self.output_sample_rate),
628                self.output_sample_rate,
629                sample_buffer.clone(),
630            );
631        }
632
633        self.modulation_state = Some(modulation_state);
634
635        self.granular_parameters = Some(parameters);
636        Ok(self)
637    }
638
639    /// Returns the file's currently applied loop point range in sample frames
640    /// or `None` if there is no loop range set.
641    pub fn loop_range(&self) -> Option<Range<u64>> {
642        self.voices
643            .first()
644            .and_then(|v| v.file_source().loop_range())
645    }
646
647    /// Set loop start and end in sample frames. Pass `None` to disable looping entirely.
648    /// Affects all voices (active and future) immediately.
649    pub fn set_loop_range(&mut self, range: Option<Range<u64>>) {
650        for voice in &mut self.voices {
651            voice.set_loop_range(range.clone());
652        }
653    }
654
655    /// Process pending playback messages from the queue.
656    fn process_playback_messages(&mut self, current_sample_frame: u64) {
657        while let Some(message) = self.playback_message_queue.pop() {
658            match message {
659                GeneratorPlaybackMessage::Stop => {
660                    self.stop(current_sample_frame);
661                }
662                GeneratorPlaybackMessage::Trigger { event } => {
663                    // Ignore all trigger messages while we're stopping
664                    if !self.stopping {
665                        match event {
666                            GeneratorPlaybackEvent::AllNotesOff => {
667                                self.trigger_all_notes_off(current_sample_frame);
668                            }
669                            GeneratorPlaybackEvent::NoteOn {
670                                note_id,
671                                note,
672                                volume,
673                                panning,
674                                context,
675                            } => {
676                                self.trigger_note_on(note_id, note, volume, panning, context);
677                            }
678                            GeneratorPlaybackEvent::NoteOff { note_id } => {
679                                self.trigger_note_off(note_id, current_sample_frame);
680                            }
681                            GeneratorPlaybackEvent::SetSpeed {
682                                note_id,
683                                speed,
684                                glide,
685                            } => {
686                                self.trigger_set_speed(note_id, speed, glide);
687                            }
688                            GeneratorPlaybackEvent::SetVolume { note_id, volume } => {
689                                self.trigger_set_volume(note_id, volume);
690                            }
691                            GeneratorPlaybackEvent::SetPanning { note_id, panning } => {
692                                self.trigger_set_panning(note_id, panning);
693                            }
694                            GeneratorPlaybackEvent::SetParameter { id, value } => {
695                                if let Err(err) = self.process_parameter_update(id, &value) {
696                                    log::warn!("Failed to process parameter '{id}' update: {err}");
697                                }
698                            }
699                            GeneratorPlaybackEvent::SetParameters { values } => {
700                                if let Err(err) = self.process_parameter_updates(&values) {
701                                    log::warn!("Failed to process parameter updates: {err}");
702                                }
703                            }
704                            GeneratorPlaybackEvent::SetModulation {
705                                source,
706                                target,
707                                amount,
708                                bipolar,
709                            } => {
710                                if let Err(err) =
711                                    self.set_modulation(source, target, amount, bipolar)
712                                {
713                                    log::warn!("Failed to set modulation: {err}");
714                                }
715                            }
716                            GeneratorPlaybackEvent::ClearModulation { source, target } => {
717                                if let Err(err) = self.clear_modulation(source, target) {
718                                    log::warn!("Failed to clear modulation: {err}");
719                                }
720                            }
721                            GeneratorPlaybackEvent::ProcessMessage { message } => {
722                                if let Err(err) = self.process_message(message.as_ref()) {
723                                    log::warn!("Failed to process sampler message: {err}");
724                                }
725                            }
726                        }
727                    }
728                }
729            }
730        }
731    }
732
733    fn stop(&mut self, current_sample_frame: u64) {
734        // Mark source as about to stop when this is a transient generator
735        self.stopping = self.transient;
736        // Stop all active voices, if any
737        self.trigger_all_notes_off(current_sample_frame);
738    }
739
740    /// Immediately trigger a note on (used by event processor)
741    fn trigger_note_on(
742        &mut self,
743        note_id: NotePlaybackId,
744        note: u8,
745        volume: Option<f32>,
746        panning: Option<f32>,
747        context: Option<PlaybackStatusContext>,
748    ) {
749        // Unwrap volume/pan
750        let volume_value = volume.unwrap_or(1.0);
751        let panning_value = panning.unwrap_or(0.0);
752
753        // Allocate a new voice
754        let voice_index = self.next_free_voice_index();
755        let voice = &mut self.voices[voice_index];
756
757        // Start the voice
758        voice.start(
759            note_id,
760            note,
761            volume_value,
762            panning_value,
763            self.base_transpose,
764            self.base_finetune,
765            self.base_volume,
766            self.base_panning,
767            &self.envelope_parameters,
768            &self.granular_parameters,
769            context,
770        );
771
772        // Ensure we're checking in the upcoming `write` if any voice needs processing.
773        self.active_voices += 1;
774    }
775
776    fn trigger_note_off(&mut self, note_id: NotePlaybackId, current_sample_frame: u64) {
777        if let Some(voice) = self
778            .voices
779            .iter_mut()
780            .find(|v| v.note_id() == Some(note_id))
781        {
782            voice.stop(&self.envelope_parameters, current_sample_frame);
783            // NB: do not modify `active_voices` here. it's updated in `write`
784        }
785    }
786
787    fn trigger_all_notes_off(&mut self, current_sample_frame: u64) {
788        for voice in &mut self.voices {
789            voice.stop(&self.envelope_parameters, current_sample_frame);
790            // NB: do not modify `active_voices` here. it's updated in `write`
791        }
792    }
793
794    fn trigger_set_speed(&mut self, note_id: NotePlaybackId, speed: f64, glide: Option<f32>) {
795        if let Some(voice) = self
796            .voices
797            .iter_mut()
798            .find(|v| v.note_id() == Some(note_id))
799        {
800            voice.set_speed(speed, glide, self.base_transpose, self.base_finetune);
801        }
802    }
803
804    fn trigger_set_volume(&mut self, note_id: NotePlaybackId, volume: f32) {
805        if let Some(voice) = self
806            .voices
807            .iter_mut()
808            .find(|v| v.note_id() == Some(note_id))
809        {
810            voice.set_volume(volume, self.base_volume);
811        }
812    }
813
814    fn trigger_set_panning(&mut self, note_id: NotePlaybackId, panning: f32) {
815        if let Some(voice) = self
816            .voices
817            .iter_mut()
818            .find(|v| v.note_id() == Some(note_id))
819        {
820            voice.set_panning(panning, self.base_panning);
821        }
822    }
823
824    /// Find a free voice or steal the oldest one.
825    /// Returns the index of the new voice, which is always valid.
826    fn next_free_voice_index(&self) -> usize {
827        // Try to find a completely free voice first
828        if let Some(index) = self.voices.iter().position(|v| !v.is_active()) {
829            return index;
830        }
831        // If all voices are active, find the best candidate to steal
832        // Prioritize:
833        //   a) Longest releasing voice (earliest release_start_sample_frame)
834        //   b) Oldest active voice (by playback_id)
835        let mut candidate_index = 0;
836        let mut earliest_release_time: Option<u64> = None;
837        let mut oldest_active_playback_id: Option<NotePlaybackId> = None;
838        for (index, voice) in self.voices.iter().enumerate() {
839            if self.envelope_parameters.is_some() && voice.in_release_stage() {
840                // This voice is in Release stage
841                if let Some(release_time) = voice.release_start_frame() {
842                    if earliest_release_time.is_none_or(|earliest| release_time < earliest) {
843                        earliest_release_time = Some(release_time);
844                        oldest_active_playback_id = None; // Reset active voices once we found a releasing voice
845                        candidate_index = index;
846                    }
847                }
848            } else if earliest_release_time.is_none() {
849                // This voice is active (not in Release stage)
850                // Only consider if we haven't found a releasing voice yet
851                if let Some(playback_id) = voice.note_id() {
852                    if oldest_active_playback_id.is_none_or(|oldest| playback_id < oldest) {
853                        oldest_active_playback_id = Some(playback_id);
854                        candidate_index = index;
855                    }
856                }
857            }
858        }
859        candidate_index
860    }
861
862    fn parameter_update_value(
863        value: &ParameterValueUpdate,
864        descriptor: &FloatParameter,
865    ) -> Result<f32, Error> {
866        match value {
867            ParameterValueUpdate::Normalized(norm) => {
868                Ok(descriptor.denormalize_value(norm.clamp(0.0, 1.0)))
869            }
870            ParameterValueUpdate::Raw(raw) => {
871                if let Some(v) = raw.downcast_ref::<f32>() {
872                    Ok(descriptor.clamp_value(*v))
873                } else if let Some(v) = raw.downcast_ref::<f64>() {
874                    Ok(descriptor.clamp_value(*v as f32))
875                } else {
876                    Err(Error::ParameterError(format!(
877                        "Unsupported payload type for sampler parameter '{}'",
878                        descriptor.name()
879                    )))
880                }
881            }
882        }
883    }
884
885    fn parameter_update_value_integer(
886        value: &ParameterValueUpdate,
887        descriptor: &IntegerParameter,
888    ) -> Result<i32, Error> {
889        match value {
890            ParameterValueUpdate::Normalized(norm) => {
891                Ok(descriptor.denormalize_value(norm.clamp(0.0, 1.0)))
892            }
893            ParameterValueUpdate::Raw(raw) => {
894                if let Some(v) = raw.downcast_ref::<i32>() {
895                    Ok(descriptor.clamp_value(*v))
896                } else if let Some(v) = raw.downcast_ref::<i64>() {
897                    Ok(descriptor.clamp_value(*v as i32))
898                } else {
899                    Err(Error::ParameterError(format!(
900                        "Unsupported payload type for sampler parameter '{}'",
901                        descriptor.name()
902                    )))
903                }
904            }
905        }
906    }
907
908    fn create_granular_sample_buffer(
909        file_buffer: Arc<AudioFileBuffer>,
910        output_sample_rate: u32,
911    ) -> Result<Arc<Box<[f32]>>, Error> {
912        if file_buffer.channel_count() == 1 && file_buffer.sample_rate() == output_sample_rate {
913            // No conversion necessary, just copy
914            Ok(Arc::new(file_buffer.buffer().to_vec().into_boxed_slice()))
915        } else {
916            // Create a temporary source to perform resampling with disabled looping
917            let mut source = PreloadedFileSource::from_shared_buffer(
918                file_buffer.clone(),
919                "granular temp sample",
920                FilePlaybackOptions::default()
921                    .playback_pos_emit_disabled()
922                    .resampling_quality(ResamplingQuality::Default)
923                    .repeat(0),
924                output_sample_rate,
925            )?;
926            let mut dest_mono_buffer = Vec::with_capacity(
927                (file_buffer.frame_count() as u64 * output_sample_rate as u64
928                    / file_buffer.sample_rate() as u64) as usize
929                    + 100,
930            );
931            let source_channel_count = source.channel_count();
932            let mut temp_buffer = vec![0.0; 1024 * source_channel_count];
933            let mut time = SourceTime::default();
934            loop {
935                // Read and resample, if needed
936                let read = source.write(&mut temp_buffer, &time);
937                if read == 0 {
938                    break;
939                }
940                // Downmix to mono
941                for frame in temp_buffer[..read].chunks(source_channel_count) {
942                    dest_mono_buffer.push(frame.iter().sum::<f32>() / source_channel_count as f32);
943                }
944                time.add_frames(read as u64 / source_channel_count as u64);
945            }
946            // Ensure sample buffer is not empty
947            if dest_mono_buffer.is_empty() {
948                dest_mono_buffer.push(0.0);
949            }
950            Ok(Arc::new(dest_mono_buffer.into_boxed_slice()))
951        }
952    }
953}
954
955// -------------------------------------------------------------------------------------------------
956
957impl Source for Sampler {
958    fn sample_rate(&self) -> u32 {
959        self.output_sample_rate
960    }
961
962    fn channel_count(&self) -> usize {
963        self.output_channel_count
964    }
965
966    fn is_exhausted(&self) -> bool {
967        self.stopped
968    }
969
970    fn weight(&self) -> usize {
971        self.active_voices.max(1)
972    }
973
974    fn write(&mut self, output: &mut [f32], time: &SourceTime) -> usize {
975        // Process playback messages
976        self.process_playback_messages(time.pos_in_frames);
977
978        // Return empty handed when exhausted or when there are no active voices
979        if self.stopped || (self.active_voices == 0 && !self.stopping) {
980            return 0;
981        }
982
983        // Clear output
984        clear_buffer(output);
985
986        // Mix active voices into the output
987        let mut active_voices = 0;
988        assert!(self.temp_buffer.len() >= output.len());
989        for voice in &mut self.voices {
990            if voice.is_active() {
991                let mix_buffer = &mut self.temp_buffer[..output.len()];
992                clear_buffer(mix_buffer);
993                let written = voice.process(
994                    mix_buffer,
995                    self.output_channel_count,
996                    &self.envelope_parameters,
997                    &self.granular_parameters,
998                    time,
999                );
1000                add_buffers(&mut output[..written], &mix_buffer[..written]);
1001                if voice.is_active() {
1002                    // count voices that are still active after processed
1003                    active_voices += 1;
1004                }
1005            }
1006        }
1007
1008        // Update `active_voices` based on the actual state
1009        self.active_voices = active_voices;
1010
1011        // Send a stop message when we got requested to stop and are now exhausted
1012        if self.stopping && active_voices == 0 {
1013            self.stopped = true;
1014            if let Some(sender) = &self.playback_status_send {
1015                if let Err(err) = sender.send(PlaybackStatusEvent::Stopped {
1016                    id: self.playback_id,
1017                    path: self.file_path.clone(),
1018                    context: None,
1019                    exhausted: true,
1020                }) {
1021                    log::warn!("Failed to send sampler playback status event: {err}");
1022                }
1023            }
1024        }
1025
1026        // We've cleared the entire buffer, so return the entire buffer
1027        output.len()
1028    }
1029}
1030
1031impl Generator for Sampler {
1032    fn generator_name(&self) -> String {
1033        self.file_path.to_string()
1034    }
1035
1036    fn playback_id(&self) -> PlaybackId {
1037        self.playback_id
1038    }
1039
1040    fn playback_options(&self) -> &GeneratorPlaybackOptions {
1041        &self.options
1042    }
1043
1044    fn playback_message_queue(&self) -> Arc<ArrayQueue<GeneratorPlaybackMessage>> {
1045        self.playback_message_queue.clone()
1046    }
1047
1048    fn playback_status_sender(&self) -> Option<SyncSender<PlaybackStatusEvent>> {
1049        self.playback_status_send.clone()
1050    }
1051    fn set_playback_status_sender(&mut self, sender: Option<SyncSender<PlaybackStatusEvent>>) {
1052        self.playback_status_send = sender.clone();
1053        for voice in &mut self.voices {
1054            voice.set_playback_status_sender(sender.clone());
1055        }
1056    }
1057
1058    fn is_transient(&self) -> bool {
1059        self.transient
1060    }
1061    fn set_is_transient(&mut self, is_transient: bool) {
1062        self.transient = is_transient
1063    }
1064
1065    fn parameters(&self) -> Vec<&dyn Parameter> {
1066        self.active_parameters.iter().map(|p| p.as_ref()).collect()
1067    }
1068
1069    fn process_parameter_update(
1070        &mut self,
1071        id: FourCC,
1072        value: &ParameterValueUpdate,
1073    ) -> Result<(), Error> {
1074        match id {
1075            // Base parameters
1076            _ if id == Sampler::TRANSPOSE.id() => {
1077                let semitones =
1078                    Sampler::parameter_update_value_integer(value, &Sampler::TRANSPOSE)?;
1079                self.base_transpose = semitones;
1080                // Recompute speed for all active voices
1081                for voice in &mut self.voices {
1082                    if voice.is_active() {
1083                        voice.set_base_pitch(self.base_transpose, self.base_finetune);
1084                    }
1085                }
1086                return Ok(());
1087            }
1088            _ if id == Sampler::FINETUNE.id() => {
1089                let cents = Sampler::parameter_update_value_integer(value, &Sampler::FINETUNE)?;
1090                self.base_finetune = cents;
1091                // Recompute speed for all active voices
1092                for voice in &mut self.voices {
1093                    if voice.is_active() {
1094                        voice.set_base_pitch(self.base_transpose, self.base_finetune);
1095                    }
1096                }
1097                return Ok(());
1098            }
1099            _ if id == Sampler::VOLUME.id() => {
1100                let volume = Sampler::parameter_update_value(value, &Sampler::VOLUME)?;
1101                self.base_volume = volume;
1102                // Recompute volume for all active voices
1103                for voice in &mut self.voices {
1104                    if voice.is_active() {
1105                        voice.set_base_volume(self.base_volume);
1106                    }
1107                }
1108                return Ok(());
1109            }
1110            _ if id == Sampler::PANNING.id() => {
1111                let panning = Sampler::parameter_update_value(value, &Sampler::PANNING)?;
1112                self.base_panning = panning;
1113                // Recompute panning for all active voices
1114                for voice in &mut self.voices {
1115                    if voice.is_active() {
1116                        voice.set_base_panning(self.base_panning);
1117                    }
1118                }
1119                return Ok(());
1120            }
1121            // Envelope parameters
1122            _ if id == Sampler::AMP_ATTACK.id()
1123                || id == Sampler::AMP_HOLD.id()
1124                || id == Sampler::AMP_DECAY.id()
1125                || id == Sampler::AMP_SUSTAIN.id()
1126                || id == Sampler::AMP_RELEASE.id() =>
1127            {
1128                if let Some(params) = &mut self.envelope_parameters {
1129                    return Self::set_envelope_parameter(params, id, value);
1130                }
1131            }
1132            // Granular parameters
1133            _ if id == Sampler::GRAIN_OVERLAP_MODE.id()
1134                || id == Sampler::GRAIN_WINDOW.id()
1135                || id == Sampler::GRAIN_SIZE.id()
1136                || id == Sampler::GRAIN_DENSITY.id()
1137                || id == Sampler::GRAIN_VARIATION.id()
1138                || id == Sampler::GRAIN_SPRAY.id()
1139                || id == Sampler::GRAIN_PAN_SPREAD.id()
1140                || id == Sampler::GRAIN_PLAYBACK_DIR.id()
1141                || id == Sampler::GRAIN_POSITION.id()
1142                || id == Sampler::GRAIN_STEP.id() =>
1143            {
1144                if let Some(params) = &mut self.granular_parameters {
1145                    return Self::set_granular_parameter(params, id, value);
1146                }
1147            }
1148            // Modulation Parameters
1149            _ if self
1150                .modulation_state
1151                .as_ref()
1152                .is_some_and(|state| state.is_source_parameter(id)) =>
1153            {
1154                let modulation_state = self.modulation_state.as_mut().unwrap();
1155
1156                // Check if this is an LFO rate parameter
1157                let rate = if id == Self::MOD_LFO1_RATE.id() {
1158                    Some(Self::parameter_update_value(value, &Self::MOD_LFO1_RATE)?)
1159                } else if id == Self::MOD_LFO2_RATE.id() {
1160                    Some(Self::parameter_update_value(value, &Self::MOD_LFO2_RATE)?)
1161                } else {
1162                    None
1163                };
1164                // Check if this is an LFO waveform parameter
1165                let waveform = if id == Self::MOD_LFO1_WAVEFORM.id() {
1166                    let mut waveform_value =
1167                        EnumParameterValue::from_description(Self::MOD_LFO1_WAVEFORM);
1168                    waveform_value.apply_update(value);
1169                    Some(waveform_value.value())
1170                } else if id == Self::MOD_LFO2_WAVEFORM.id() {
1171                    let mut waveform_value =
1172                        EnumParameterValue::from_description(Self::MOD_LFO2_WAVEFORM);
1173                    waveform_value.apply_update(value);
1174                    Some(waveform_value.value())
1175                } else {
1176                    None
1177                };
1178
1179                // Delegate to modulation state
1180                return modulation_state.apply_parameter_update(
1181                    id,
1182                    rate,
1183                    waveform,
1184                    &mut self.voices,
1185                );
1186            }
1187            _ => {}
1188        }
1189        Err(Error::ParameterError(format!(
1190            "Invalid or unknown sampler parameter: '{id}'"
1191        )))
1192    }
1193
1194    fn modulation_sources(&self) -> Vec<ModulationSource> {
1195        if let Some(modulation_state) = &self.modulation_state {
1196            modulation_state.sources()
1197        } else {
1198            Vec::new()
1199        }
1200    }
1201
1202    fn modulation_targets(&self) -> Vec<ModulationTarget> {
1203        if let Some(modulation_state) = &self.modulation_state {
1204            modulation_state.targets()
1205        } else {
1206            Vec::new()
1207        }
1208    }
1209
1210    fn set_modulation(
1211        &mut self,
1212        source: FourCC,
1213        target: FourCC,
1214        amount: f32,
1215        bipolar: bool,
1216    ) -> Result<(), Error> {
1217        if let Some(modulation_state) = &self.modulation_state {
1218            for voice in &mut self.voices {
1219                if let Some(matrix) = voice.modulation_matrix_mut() {
1220                    modulation_state.set_modulation(matrix, source, target, amount, bipolar)?;
1221                }
1222            }
1223            Ok(())
1224        } else {
1225            Err(Error::ParameterError(
1226                "Modulation routing only available when granular playback is enabled".to_string(),
1227            ))
1228        }
1229    }
1230
1231    fn clear_modulation(&mut self, source: FourCC, target: FourCC) -> Result<(), Error> {
1232        if let Some(modulation_state) = &self.modulation_state {
1233            for voice in &mut self.voices {
1234                if let Some(matrix) = voice.modulation_matrix_mut() {
1235                    modulation_state.clear_modulation(matrix, source, target)?;
1236                }
1237            }
1238            Ok(())
1239        } else {
1240            Err(Error::ParameterError(
1241                "Modulation routing only available when granular playback is enabled".to_string(),
1242            ))
1243        }
1244    }
1245
1246    fn process_message(&mut self, message: &GeneratorMessagePayload) -> Result<(), Error> {
1247        if let Some(msg) = message.payload().downcast_ref::<SamplerMessage>() {
1248            match msg {
1249                SamplerMessage::SetLoopRange(range) => {
1250                    // validate range: this comes from a message
1251                    let frame_count = self
1252                        .voices
1253                        .first()
1254                        .map(|v| v.file_source().file_buffer().frame_count() as u64)
1255                        .unwrap_or(0);
1256                    if range.is_none()
1257                        || range
1258                            .as_ref()
1259                            .is_some_and(|r| r.start < frame_count && r.end <= frame_count)
1260                    {
1261                        self.set_loop_range(range.clone());
1262                        Ok(())
1263                    } else {
1264                        Err(Error::ParameterError(format!(
1265                            "Invalid loop range {:?}. Loop must be in range {:?}",
1266                            range,
1267                            0..frame_count
1268                        )))
1269                    }
1270                }
1271            }
1272        } else {
1273            Err(Error::ParameterError(format!(
1274                "Sampler: Received unexpected message payload from '{}'.",
1275                message.generator_name()
1276            )))
1277        }
1278    }
1279}