Skip to main content

firewheel_nodes/
sampler.rs

1// TODO: The logic in this has become incredibly complex and error-prone. The
2// sampler engine should probably be rewritten using a state machine.
3//
4// Some features that are currently missing include:
5// * Ability to set loop start/end points
6// * Better quality time/pitch shifting algorithms (and possibly an API where
7//   users can implement their own resampling algorithms)
8// * Ability to stream samples from a network/disk (this could be done using
9//   a custom `SampleResource`).
10
11use firewheel_core::clock::{DurationSamples, DurationSeconds};
12use firewheel_core::collector::{OwnedGc, OwnedGcUnsized};
13use firewheel_core::node::{NodeError, ProcBuffers, ProcExtra, ProcStreamCtx};
14
15use bevy_platform::sync::{Arc, Mutex};
16use bevy_platform::time::Instant;
17use core::{
18    num::{NonZeroU32, NonZeroUsize},
19    ops::Range,
20};
21use firewheel_core::diff::{EventQueue, NotifyID, PatchError, PathBuilder, RealtimeClone};
22use smallvec::SmallVec;
23use triple_buffer::{Input, Output};
24
25#[cfg(not(feature = "std"))]
26use bevy_platform::prelude::Box;
27#[cfg(not(feature = "std"))]
28use num_traits::Float;
29
30use firewheel_core::{
31    StreamInfo,
32    channel_config::{ChannelConfig, ChannelCount, NonZeroChannelCount},
33    clock::InstantSeconds,
34    collector::ArcGc,
35    diff::{Diff, Notify, ParamPath, Patch},
36    dsp::{
37        buffer::InstanceBuffer,
38        declick::{DeclickFadeCurve, Declicker},
39        volume::{DEFAULT_MIN_AMP, Volume},
40    },
41    event::{NodeEventType, ParamData, ProcEvents},
42    mask::{MaskType, SilenceMask},
43    node::{
44        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcInfo,
45        ProcessStatus,
46    },
47    sample_resource::SampleResource,
48};
49
50#[cfg(feature = "scheduled_events")]
51use firewheel_core::clock::EventInstant;
52
53pub const MAX_OUT_CHANNELS: usize = 8;
54pub const DEFAULT_NUM_DECLICKERS: usize = 2;
55pub const MIN_PLAYBACK_SPEED: f64 = 0.0000001;
56
57mod resampler;
58mod resource;
59
60pub use self::resource::{SamplerNodeResource, StreamedSample};
61
62use self::resampler::Resampler;
63
64pub type PlaybackID = NotifyID;
65
66/// The configuration of a [`SamplerNode`]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
69#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub struct SamplerConfig {
72    /// The number of channels in this node.
73    ///
74    /// By default this is set to [`NonZeroChannelCount::STEREO`].
75    pub channels: NonZeroChannelCount,
76    /// The maximum number of "declickers" present on this node.
77    /// The more declickers there are, the more samples that can be declicked
78    /// when played in rapid succession. (Note more declickers will allocate
79    /// more memory).
80    ///
81    /// By default this is set to `2`.
82    pub num_declickers: u32,
83    /// The quality of the resampling algorithm used when changing the playback
84    /// speed.
85    pub speed_quality: PlaybackSpeedQuality,
86}
87
88impl Default for SamplerConfig {
89    fn default() -> Self {
90        Self {
91            channels: NonZeroChannelCount::STEREO,
92            num_declickers: DEFAULT_NUM_DECLICKERS as u32,
93            speed_quality: PlaybackSpeedQuality::default(),
94        }
95    }
96}
97
98/// The quality of the resampling algorithm used for changing the playback
99/// speed of a sampler node.
100#[non_exhaustive]
101#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
102#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104pub enum PlaybackSpeedQuality {
105    #[default]
106    /// Low quality, fast performance. Recommended for most use cases.
107    ///
108    /// More specifically, this uses a linear resampling algorithm with no
109    /// antialiasing filter.
110    LinearFast,
111    // TODO: more quality options
112}
113
114/// A node that plays samples
115///
116/// It supports pausing, resuming, looping, and changing the playback speed.
117#[derive(Debug, Clone, Copy, Diff, Patch, PartialEq)]
118#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
119#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct SamplerNode {
122    /// The volume to play the sample at.
123    ///
124    /// Note, this gain parameter is *NOT* smoothed! If you need the gain to be
125    /// smoothed, please use a [`VolumeNode`] or a [`VolumePanNode`].
126    ///
127    /// [`VolumeNode`]: crate::volume::VolumeNode
128    /// [`VolumePanNode`]: crate::volume_pan::VolumePanNode
129    pub volume: Volume,
130
131    /// Whether or not the current sample should start/restart playing (true), or be
132    /// paused/stopped (false).
133    #[cfg_attr(feature = "serde", serde(skip))]
134    pub play: Notify<bool>,
135
136    /// Defines where the sampler should start playing from when
137    /// [`SamplerNode::play`] is set to `true`.
138    pub play_from: PlayFrom,
139
140    /// How many times a sample should be repeated.
141    pub repeat_mode: RepeatMode,
142
143    /// The speed at which to play the sample at. `1.0` means to play the sound at
144    /// its original speed, `< 1.0` means to play the sound slower (which will make
145    /// it lower-pitched), and `> 1.0` means to play the sound faster (which will
146    /// make it higher-pitched).
147    pub speed: f64,
148
149    /// If `true`, then mono samples will be converted to stereo during playback.
150    ///
151    /// By default this is set to `true`.
152    pub mono_to_stereo: bool,
153    /// If true, then samples will be crossfaded when the playhead or sample is
154    /// changed (if a sample was currently playing when the event was sent).
155    ///
156    /// By default this is set to `true`.
157    pub crossfade_on_seek: bool,
158    /// If the resulting gain (in raw amplitude, not decibels) is less
159    /// than or equal to this value, then the gain will be clamped to
160    /// `0.0` (silence).
161    ///
162    /// By default this is set to `0.00001` (-100 decibels).
163    pub min_gain: f32,
164}
165
166impl Default for SamplerNode {
167    fn default() -> Self {
168        Self {
169            volume: Volume::default(),
170            play: Default::default(),
171            play_from: PlayFrom::default(),
172            repeat_mode: RepeatMode::default(),
173            speed: 1.0,
174            mono_to_stereo: true,
175            crossfade_on_seek: true,
176            min_gain: DEFAULT_MIN_AMP,
177        }
178    }
179}
180
181impl SamplerNode {
182    /// Returns an event to clear the sample resource from a sampler node.
183    pub fn clear_sample_event() -> NodeEventType {
184        NodeEventType::Custom(OwnedGc::new(Box::<Option<SamplerNodeResource>>::new(None)))
185    }
186
187    /// Returns an event to set the sample resource for a sampler node from the
188    /// given sample resource.
189    pub fn set_sample_event<T: SampleResource + Send + Sync + 'static>(sample: T) -> NodeEventType {
190        Self::set_resource_event(SamplerNodeResource::from_sample(sample))
191    }
192
193    /// Returns an event to set the sample resource for a sampler node from the
194    /// given streamed sample resource.
195    pub fn set_streamed_sample_event<T: StreamedSample>(sample: T) -> NodeEventType {
196        Self::set_resource_event(SamplerNodeResource::from_streamed(sample))
197    }
198
199    /// Returns an event to set the sample resource for a sampler node from the
200    /// given type-erased sample resource.
201    pub fn set_dyn_sample_event(
202        sample: ArcGc<dyn SampleResource + Send + Sync + 'static>,
203    ) -> NodeEventType {
204        Self::set_resource_event(sample.into())
205    }
206
207    /// Returns an event to set the sample resource for a sampler node from the
208    /// given type-erased streamed sample resource.
209    pub fn set_dyn_streamed_sample_event(
210        sample: OwnedGcUnsized<dyn StreamedSample>,
211    ) -> NodeEventType {
212        Self::set_resource_event(sample.into())
213    }
214
215    /// Returns an event to set the sample resource for a sampler node.
216    pub fn set_resource_event(sample: SamplerNodeResource) -> NodeEventType {
217        NodeEventType::Custom(OwnedGc::new(Box::new(Some(sample))))
218    }
219
220    /// Returns an event type to sync the `volume` parameter.
221    pub fn sync_volume_event(&self) -> NodeEventType {
222        NodeEventType::Param {
223            data: ParamData::Volume(self.volume),
224            path: ParamPath::Single(0),
225        }
226    }
227
228    /// Returns an event type to sync the `play` parameter.
229    pub fn sync_play_event(&self) -> NodeEventType {
230        // Diff for Notify<bool> is defined here:
231        // https://github.com/BillyDM/Firewheel/blob/380806ce61b3a417eb676a4fd8640da49905ec23/crates/firewheel-core/src/diff/leaf.rs#L247
232        let mut bytes: [u8; 20] = [0; 20];
233        bytes[0..core::mem::size_of::<u64>()].copy_from_slice(&self.play.id().0.to_ne_bytes());
234        bytes[core::mem::size_of::<u64>()] = if *self.play { 1 } else { 0 };
235
236        NodeEventType::Param {
237            data: ParamData::CustomBytes(bytes),
238            path: ParamPath::Single(1),
239        }
240    }
241
242    /// Returns the current playback ID.
243    pub fn playback_id(&self) -> PlaybackID {
244        self.play.id()
245    }
246
247    /// Returns an event type to sync the `play_from` parameter.
248    pub fn sync_play_from_event(&self) -> NodeEventType {
249        NodeEventType::Param {
250            data: self.play_from.as_param_data(),
251            path: ParamPath::Single(2),
252        }
253    }
254
255    /// Returns an event type to sync the `playhead` parameter.
256    pub fn sync_repeat_mode_event(&self) -> NodeEventType {
257        NodeEventType::Param {
258            data: ParamData::any(self.repeat_mode),
259            path: ParamPath::Single(3),
260        }
261    }
262
263    /// Returns an event type to sync the `speed` parameter.
264    pub fn sync_speed_event(&self) -> NodeEventType {
265        NodeEventType::Param {
266            data: ParamData::F64(self.speed),
267            path: ParamPath::Single(4),
268        }
269    }
270
271    /// Returns an event type to sync the `mono_to_stereo` parameter.
272    pub fn sync_mono_to_stereo_event(&self) -> NodeEventType {
273        NodeEventType::Param {
274            data: ParamData::Bool(self.mono_to_stereo),
275            path: ParamPath::Single(5),
276        }
277    }
278
279    /// Returns an event type to sync the `crossfade_on_seek` parameter.
280    pub fn sync_crossfade_on_seek_event(&self) -> NodeEventType {
281        NodeEventType::Param {
282            data: ParamData::Bool(self.crossfade_on_seek),
283            path: ParamPath::Single(6),
284        }
285    }
286
287    /// Returns an event type to sync the `min_gain` parameter.
288    pub fn sync_min_gain_event(&self) -> NodeEventType {
289        NodeEventType::Param {
290            data: ParamData::F32(self.min_gain),
291            path: ParamPath::Single(7),
292        }
293    }
294
295    /// Start/restart the sample in this node.
296    ///
297    /// If a sample is already playing, then it will restart from the beginning.
298    pub fn start_or_restart(&mut self) {
299        self.play_from = PlayFrom::BEGINNING;
300        *self.play = true;
301    }
302
303    /// Play the sample in this node from the given playhead.
304    pub fn start_from(&mut self, from: PlayFrom) {
305        self.play_from = from;
306        *self.play = true;
307    }
308
309    /// Pause sample playback.
310    pub fn pause(&mut self) {
311        self.play_from = PlayFrom::Resume;
312        *self.play = false;
313    }
314
315    /// Resume sample playback.
316    pub fn resume(&mut self) {
317        *self.play = true;
318    }
319
320    /// Stop sample playback.
321    ///
322    /// Calling [`SamplerNode::resume`] after this will restart the sample from
323    /// the beginning.
324    pub fn stop(&mut self) {
325        self.play_from = PlayFrom::BEGINNING;
326        *self.play = false;
327    }
328
329    /// Returns `true` if the current state is set to restart the sample.
330    pub fn start_or_restart_requested(&self) -> bool {
331        *self.play && self.play_from == PlayFrom::BEGINNING
332    }
333
334    /// Returns `true` if the current state is set to resume the sample.
335    pub fn resume_requested(&self) -> bool {
336        *self.play && self.play_from == PlayFrom::Resume
337    }
338
339    /// Returns `true` if the current state is set to pause the sample.
340    pub fn pause_requested(&self) -> bool {
341        !*self.play && self.play_from == PlayFrom::Resume
342    }
343
344    /// Returns `true` if the current state is set to stop the sample.
345    pub fn stop_requested(&self) -> bool {
346        !*self.play && self.play_from != PlayFrom::Resume
347    }
348}
349
350#[derive(Clone)]
351pub struct SamplerState {
352    channel: Arc<Mutex<SharedChannel>>,
353}
354
355impl SamplerState {
356    fn new() -> Self {
357        Self {
358            channel: Arc::new(Mutex::new(SharedChannel::new())),
359        }
360    }
361
362    /// Get the current state of this sampler node's processor at this instant
363    /// in time.
364    pub fn current_processor_state(&self) -> CurrentProcessorState {
365        *self.channel.lock().unwrap().proc_state_output.read()
366    }
367
368    /// Get the current position of the playhead in units of frames (samples of
369    /// a single channel of audio).
370    pub fn playhead_frames(&self) -> DurationSamples {
371        DurationSamples(
372            self.channel
373                .lock()
374                .unwrap()
375                .proc_state_output
376                .read()
377                .playhead_frames as i64,
378        )
379    }
380
381    /// Get the current position of the sample playhead in seconds.
382    ///
383    /// * `sample_rate` - The sample rate of the current audio stream.
384    pub fn playhead_seconds(&self, sample_rate: NonZeroU32) -> DurationSeconds {
385        DurationSeconds(self.playhead_frames().0 as f64 / sample_rate.get() as f64)
386    }
387
388    /// Get the current playback state of the processor at this instant in time.
389    pub fn playback_state(&self) -> PlaybackState {
390        self.channel
391            .lock()
392            .unwrap()
393            .proc_state_output
394            .read()
395            .playback_state
396    }
397
398    /// Get the current playback state as well as the current [`PlaybackID`] at this
399    /// instant in time.
400    ///
401    /// The [`PlaybackID`] is equal to the ID of the latest [`SamplerNode::play`]
402    /// parameter that was set to `true`.
403    pub fn playback_state_and_id(&self) -> (PlaybackState, PlaybackID) {
404        let mut ch_guard = self.channel.lock().unwrap();
405        let state = ch_guard.proc_state_output.read();
406        (state.playback_state, state.playback_id)
407    }
408
409    /// Returns `true` if the current [`PlaybackID`] is equal to the given playback ID
410    /// *and* the playback state is [`PlaybackState::Stopped`].
411    pub fn playback_finished(&self, playback_id: PlaybackID) -> bool {
412        let (playback_state, id) = self.playback_state_and_id();
413        id == playback_id && playback_state == PlaybackState::Stopped
414    }
415
416    /// Returns the last [`PlaybackID`] that has finished.
417    pub fn last_finished_playback_id(&self) -> PlaybackID {
418        self.channel
419            .lock()
420            .unwrap()
421            .proc_state_output
422            .read()
423            .last_finished_playback_id
424    }
425
426    /// Returns `true` if the processor is currently playing a sample at this instant
427    /// in time.
428    pub fn currently_playing(&self) -> bool {
429        self.playback_state() == PlaybackState::Playing
430    }
431
432    /// Returns `true` if the processor is currently paused at this instant in time.
433    pub fn currently_paused(&self) -> bool {
434        self.playback_state() == PlaybackState::Paused
435    }
436
437    /// Returns `true` if the the processor has either not started playing a sample yet
438    /// or it has finished playing its sample at this instant in time.
439    pub fn currently_stopped(&self) -> bool {
440        self.playback_state() == PlaybackState::Stopped
441    }
442
443    /// Get the current position of the playhead in units of frames (samples of
444    /// a single channel of audio), corrected with the delay between when the audio clock
445    /// was last updated and now.
446    ///
447    /// Call `FirewheelCtx::audio_clock_instant()` right before calling this method to get
448    /// the latest update instant.
449    pub fn playhead_frames_corrected(
450        &self,
451        update_instant: Option<Instant>,
452        sample_rate: NonZeroU32,
453    ) -> DurationSamples {
454        let (playhead_frames, playback_state) = {
455            let mut channel = self.channel.lock().unwrap();
456            let s = channel.proc_state_output.read();
457            (s.playhead_frames, s.playback_state)
458        };
459
460        let Some(update_instant) = update_instant else {
461            return DurationSamples(playhead_frames as i64);
462        };
463
464        if playback_state == PlaybackState::Playing {
465            DurationSamples(
466                playhead_frames as i64
467                    + InstantSeconds(update_instant.elapsed().as_secs_f64())
468                        .to_samples(sample_rate)
469                        .0,
470            )
471        } else {
472            DurationSamples(playhead_frames as i64)
473        }
474    }
475
476    /// Get the current position of the playhead in units of seconds, corrected with the
477    /// delay between when the audio clock was last updated and now.
478    ///
479    /// Call `FirewheelCtx::audio_clock_instant()` right before calling this method to get
480    /// the latest update instant.
481    pub fn playhead_seconds_corrected(
482        &self,
483        update_instant: Option<Instant>,
484        sample_rate: NonZeroU32,
485    ) -> DurationSeconds {
486        DurationSeconds(
487            self.playhead_frames_corrected(update_instant, sample_rate)
488                .0 as f64
489                / sample_rate.get() as f64,
490        )
491    }
492}
493
494struct SharedChannel {
495    proc_state_output: Output<CurrentProcessorState>,
496    proc_state_input: Option<Input<CurrentProcessorState>>,
497}
498
499impl SharedChannel {
500    fn new() -> Self {
501        let (proc_state_input, proc_state_output) = triple_buffer::triple_buffer::<
502            CurrentProcessorState,
503        >(&CurrentProcessorState::default());
504
505        Self {
506            proc_state_input: Some(proc_state_input),
507            proc_state_output,
508        }
509    }
510}
511
512/// The current state of a [`SamplerNode`]'s processor.
513#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
514pub struct CurrentProcessorState {
515    /// The current position of the playhead in frames (samples in a single
516    /// channel of audio).
517    pub playhead_frames: u64,
518    /// The current [`PlaybackID`]. This is equal to the ID of the latest
519    /// [`SamplerNode::play`] parameter that was set to `true`.
520    pub playback_id: PlaybackID,
521    /// The last [`PlaybackID`] which has finished.
522    pub last_finished_playback_id: PlaybackID,
523    /// The current playback state.
524    pub playback_state: PlaybackState,
525    /// The age of the current playback in frames (samples in a single channel
526    /// of audio).
527    pub playback_age_frames: u64,
528    /// Whether or not the processor currently has a sample resource.
529    pub has_sample_resource: bool,
530}
531
532/// The current playback state of a [`SamplerNode`]'s processor.
533#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
534pub enum PlaybackState {
535    #[default]
536    /// The processor has either not started playing a sample yet or it has finished
537    /// playing its sample.
538    Stopped,
539    /// The processor is currently paused.
540    Paused,
541    /// The processor is currently playing a sample.
542    Playing,
543}
544
545/// Defines where the sampler should start playing from when
546/// [`SamplerNode::play`] is set to `true`.
547#[derive(Debug, Clone, Copy, PartialEq, RealtimeClone)]
548#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
549#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
550pub enum PlayFrom {
551    /// When [`SamplerNode::play`] is set to `true`, the sampler will resume
552    /// playing from where it last left off.
553    Resume,
554    /// When [`SamplerNode::play`] is set to `true`, the sampler will begin
555    /// playing  from this position in the sample in units of seconds.
556    Seconds(f64),
557    /// When [`SamplerNode::play`] is set to `true`, the sampler will begin
558    /// playing from this position in the sample in units of frames (samples
559    /// in a single channel of audio).
560    Frames(u64),
561}
562
563impl PlayFrom {
564    pub const BEGINNING: Self = Self::Frames(0);
565
566    pub fn as_frames(&self, sample_rate: NonZeroU32) -> Option<u64> {
567        match *self {
568            Self::Resume => None,
569            Self::Seconds(seconds) => Some(if seconds <= 0.0 {
570                0
571            } else {
572                (seconds.floor() as u64 * sample_rate.get() as u64)
573                    + (seconds.fract() * sample_rate.get() as f64).round() as u64
574            }),
575            Self::Frames(frames) => Some(frames),
576        }
577    }
578
579    pub fn as_param_data(&self) -> ParamData {
580        match self {
581            Self::Resume => ParamData::None,
582            Self::Seconds(s) => ParamData::F64(*s),
583            Self::Frames(f) => ParamData::U64(*f),
584        }
585    }
586}
587
588impl Default for PlayFrom {
589    fn default() -> Self {
590        Self::BEGINNING
591    }
592}
593
594impl Diff for PlayFrom {
595    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
596        if self != baseline {
597            match self {
598                Self::Resume => event_queue.push_param(ParamData::None, path),
599                Self::Seconds(seconds) => event_queue.push_param(*seconds, path),
600                Self::Frames(frames) => event_queue.push_param(*frames, path),
601            }
602        }
603    }
604}
605
606impl Patch for PlayFrom {
607    type Patch = Self;
608
609    fn patch(data: &ParamData, _path: &[u32]) -> Result<Self::Patch, PatchError> {
610        match data {
611            ParamData::None => Ok(PlayFrom::Resume),
612            ParamData::F64(s) => Ok(PlayFrom::Seconds(*s)),
613            ParamData::U64(f) => Ok(PlayFrom::Frames(*f)),
614            _ => Err(PatchError::InvalidData),
615        }
616    }
617
618    fn apply(&mut self, value: Self::Patch) {
619        *self = value;
620    }
621}
622
623/// How many times a sample should be repeated.
624#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Diff, Patch)]
625#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
626#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
627pub enum RepeatMode {
628    /// Play the sample once and then stop.
629    #[default]
630    PlayOnce,
631    /// Repeat the sample the given number of times.
632    RepeatMultiple { num_times_to_repeat: u32 },
633    /// Repeat the sample endlessly.
634    RepeatEndlessly,
635}
636
637impl RepeatMode {
638    pub fn do_loop(&self, num_times_looped_back: u64) -> bool {
639        match self {
640            Self::PlayOnce => false,
641            &Self::RepeatMultiple {
642                num_times_to_repeat,
643            } => num_times_looped_back < num_times_to_repeat as u64,
644            Self::RepeatEndlessly => true,
645        }
646    }
647}
648
649impl AudioNode for SamplerNode {
650    type Configuration = SamplerConfig;
651
652    fn info(&self, config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
653        Ok(AudioNodeInfo::new()
654            .debug_name("sampler")
655            .channel_config(ChannelConfig {
656                num_inputs: ChannelCount::ZERO,
657                num_outputs: config.channels.get(),
658            })
659            .custom_state(SamplerState::new()))
660    }
661
662    fn construct_processor(
663        &self,
664        config: &Self::Configuration,
665        mut cx: ConstructProcessorContext,
666    ) -> Result<impl AudioNodeProcessor, NodeError> {
667        let stop_declicker_buffers = if config.num_declickers == 0 {
668            None
669        } else {
670            Some(InstanceBuffer::<f32>::new(
671                config.num_declickers as usize,
672                NonZeroUsize::new(config.channels.get().get() as usize).unwrap(),
673                cx.stream_info.declick_frames.get() as usize,
674            ))
675        };
676
677        let max_block_frames = cx.stream_info.max_block_frames.get() as usize;
678
679        let playing = *self.play;
680        let paused = !*self.play && self.play_from == PlayFrom::Resume;
681        let playback_state = if playing {
682            PlaybackState::Playing
683        } else if paused {
684            PlaybackState::Paused
685        } else {
686            PlaybackState::Stopped
687        };
688        let playback_id = if playing {
689            self.play.id()
690        } else {
691            PlaybackID::DANGLING
692        };
693
694        let proc_state = CurrentProcessorState {
695            playback_id,
696            playback_state,
697            playhead_frames: self
698                .play_from
699                .as_frames(cx.stream_info.sample_rate)
700                .unwrap_or_default(),
701            ..Default::default()
702        };
703        let mut channel = cx
704            .custom_state_mut::<SamplerState>()
705            .unwrap()
706            .channel
707            .lock()
708            .unwrap();
709        let mut shared_proc_state = if let Some(proc_state_input) = channel.proc_state_input.take()
710        {
711            proc_state_input
712        } else {
713            *channel = SharedChannel::new();
714            channel.proc_state_input.take().unwrap()
715        };
716        shared_proc_state.write(proc_state);
717
718        Ok(SamplerProcessor {
719            config: *config,
720            params: *self,
721            proc_state,
722            shared_proc_state,
723            loaded_sample_state: None,
724            declicker: Declicker::SettledAt1,
725            stop_declicker_buffers,
726            stop_declickers: smallvec::smallvec![StopDeclickerState::default(); config.num_declickers as usize],
727            num_active_stop_declickers: 0,
728            resampler: Some(Resampler::new(config.speed_quality)),
729            speed: self.speed.max(MIN_PLAYBACK_SPEED),
730            playing,
731            paused,
732            #[cfg(feature = "scheduled_events")]
733            queued_playback_instant: None,
734            min_gain: self.min_gain.max(0.0),
735            max_block_frames,
736            num_out_channels: config.channels.get().get() as usize,
737            is_first_process: true,
738        })
739    }
740}
741
742struct SamplerProcessor {
743    config: SamplerConfig,
744    params: SamplerNode,
745    proc_state: CurrentProcessorState,
746    shared_proc_state: Input<CurrentProcessorState>,
747
748    loaded_sample_state: Option<LoadedSampleState>,
749
750    declicker: Declicker,
751
752    playing: bool,
753    paused: bool,
754
755    stop_declicker_buffers: Option<InstanceBuffer<f32>>,
756    stop_declickers: SmallVec<[StopDeclickerState; DEFAULT_NUM_DECLICKERS]>,
757    num_active_stop_declickers: usize,
758
759    resampler: Option<Resampler>,
760    speed: f64,
761
762    #[cfg(feature = "scheduled_events")]
763    queued_playback_instant: Option<EventInstant>,
764
765    min_gain: f32,
766
767    max_block_frames: usize,
768    num_out_channels: usize,
769    is_first_process: bool,
770}
771
772impl SamplerProcessor {
773    fn sync_proc_state(&mut self) {
774        self.shared_proc_state.write(self.proc_state);
775    }
776
777    /// Returns `true` if the sample has finished playing, and also
778    /// returns the number of channels that were filled.
779    fn process_internal(
780        &mut self,
781        buffers: &mut [&mut [f32]],
782        frames: usize,
783        looping: bool,
784        extra: &mut ProcExtra,
785    ) -> (bool, usize) {
786        let (finished_playing, mut channels_filled) = if self.speed != 1.0 {
787            // Get around borrow checker.
788            let mut resampler = self.resampler.take().unwrap();
789
790            let (finished_playing, channels_filled) =
791                resampler.resample_linear(buffers, 0..frames, extra, self, looping);
792
793            self.resampler = Some(resampler);
794
795            (finished_playing, channels_filled)
796        } else {
797            self.resampler.as_mut().unwrap().reset();
798
799            self.copy_from_sample(buffers, 0..frames, looping)
800        };
801
802        let Some(state) = self.loaded_sample_state.as_ref() else {
803            return (true, 0);
804        };
805
806        if !self.declicker.has_settled() {
807            self.declicker.process(
808                buffers,
809                0..frames,
810                &extra.declick_values,
811                state.gain,
812                DeclickFadeCurve::EqualPower3dB,
813            );
814        } else if state.gain != 1.0 {
815            for b in buffers[..channels_filled].iter_mut() {
816                for s in b[..frames].iter_mut() {
817                    *s *= state.gain;
818                }
819            }
820        }
821
822        if state.sample_mono_to_stereo {
823            let (b0, b1) = buffers.split_first_mut().unwrap();
824            b1[0][..frames].copy_from_slice(&b0[..frames]);
825
826            channels_filled = 2;
827        }
828
829        (finished_playing, channels_filled)
830    }
831
832    /// Fill the buffer with raw data from the sample, starting from the
833    /// current playhead. Then increment the playhead.
834    ///
835    /// Returns `true` if the sample has finished playing, and also
836    /// returns the number of channels that were filled.
837    fn copy_from_sample(
838        &mut self,
839        buffers: &mut [&mut [f32]],
840        range_in_buffer: Range<usize>,
841        looping: bool,
842    ) -> (bool, usize) {
843        let Some(state) = self.loaded_sample_state.as_mut() else {
844            return (true, 0);
845        };
846
847        assert!(state.playhead_frames <= state.sample_len_frames);
848
849        let block_frames = range_in_buffer.end - range_in_buffer.start;
850        let first_copy_frames =
851            if state.playhead_frames + block_frames as u64 > state.sample_len_frames {
852                (state.sample_len_frames - state.playhead_frames) as usize
853            } else {
854                block_frames
855            };
856
857        if first_copy_frames > 0 {
858            match &mut state.sample {
859                SamplerNodeResource::InMemory(sample) => {
860                    sample.fill_buffers(
861                        buffers,
862                        range_in_buffer.start..range_in_buffer.start + first_copy_frames,
863                        state.playhead_frames,
864                    );
865                }
866                SamplerNodeResource::Streamed(_) => {
867                    todo!()
868                }
869            }
870
871            state.playhead_frames += first_copy_frames as u64;
872        }
873
874        if first_copy_frames < block_frames {
875            if looping {
876                let mut frames_copied = first_copy_frames;
877
878                while frames_copied < block_frames {
879                    let copy_frames = ((block_frames - frames_copied) as u64)
880                        .min(state.sample_len_frames)
881                        as usize;
882
883                    match &mut state.sample {
884                        SamplerNodeResource::InMemory(sample) => {
885                            sample.fill_buffers(
886                                buffers,
887                                range_in_buffer.start + frames_copied
888                                    ..range_in_buffer.start + frames_copied + copy_frames,
889                                0,
890                            );
891                        }
892                        SamplerNodeResource::Streamed(_) => {
893                            todo!()
894                        }
895                    }
896
897                    state.playhead_frames = copy_frames as u64;
898                    state.num_times_looped_back += 1;
899
900                    frames_copied += copy_frames;
901                }
902            } else {
903                let n_channels = buffers.len().min(state.sample_num_channels.get());
904                for b in buffers[..n_channels].iter_mut() {
905                    b[range_in_buffer.start + first_copy_frames..range_in_buffer.end].fill(0.0);
906                }
907
908                return (true, n_channels);
909            }
910        }
911
912        (false, buffers.len().min(state.sample_num_channels.get()))
913    }
914
915    fn currently_processing_sample(&self) -> bool {
916        if self.loaded_sample_state.is_none() {
917            false
918        } else {
919            self.playing || (self.paused && !self.declicker.has_settled())
920        }
921    }
922
923    fn num_channels_filled(&self) -> usize {
924        if let Some(state) = &self.loaded_sample_state {
925            if state.sample_mono_to_stereo {
926                2
927            } else {
928                state.sample_num_channels.get().min(self.num_out_channels)
929            }
930        } else {
931            0
932        }
933    }
934
935    fn stop(&mut self, extra: &mut ProcExtra) {
936        if self.currently_processing_sample() {
937            // Fade out the sample into a temporary look-ahead
938            // buffer to declick.
939
940            self.declicker.fade_to_0(&extra.declick_values);
941
942            // Work around the borrow checker.
943            if let Some(mut stop_declicker_buffers) = self.stop_declicker_buffers.take() {
944                if self.num_active_stop_declickers < stop_declicker_buffers.num_instances() {
945                    let declicker_i = self
946                        .stop_declickers
947                        .iter()
948                        .enumerate()
949                        .find_map(|(i, d)| if d.frames_left == 0 { Some(i) } else { None })
950                        .unwrap();
951
952                    let n_channels = self.num_channels_filled();
953
954                    let fade_out_frames = stop_declicker_buffers.frames();
955
956                    self.stop_declickers[declicker_i].frames_left = fade_out_frames;
957                    self.stop_declickers[declicker_i].channels = n_channels;
958
959                    let mut tmp_buffers = stop_declicker_buffers
960                        .instance_mut::<MAX_OUT_CHANNELS>(declicker_i, n_channels, fade_out_frames)
961                        .unwrap();
962
963                    self.process_internal(&mut tmp_buffers, fade_out_frames, false, extra);
964
965                    self.num_active_stop_declickers += 1;
966                }
967
968                self.stop_declicker_buffers = Some(stop_declicker_buffers);
969            }
970        }
971
972        if let Some(state) = &mut self.loaded_sample_state {
973            state.playhead_frames = 0;
974            state.num_times_looped_back = 0;
975        }
976
977        self.declicker.reset_to_1();
978
979        if let Some(resampler) = &mut self.resampler {
980            resampler.reset();
981        }
982    }
983
984    fn load_sample(&mut self, sample: SamplerNodeResource) {
985        let mut gain = self.params.volume.amp_clamped(self.min_gain);
986        if gain > 0.99999 && gain < 1.00001 {
987            gain = 1.0;
988        }
989
990        let (sample_len_frames, sample_num_channels) = match &sample {
991            SamplerNodeResource::InMemory(s) => (s.len_frames(), s.num_channels()),
992            SamplerNodeResource::Streamed(s) => (s.len_frames(), s.num_channels()),
993        };
994
995        let sample_mono_to_stereo = self.params.mono_to_stereo
996            && self.num_out_channels > 1
997            && sample_num_channels.get() == 1;
998
999        self.loaded_sample_state = Some(LoadedSampleState {
1000            sample,
1001            sample_len_frames,
1002            sample_num_channels,
1003            sample_mono_to_stereo,
1004            gain,
1005            playhead_frames: 0,
1006            num_times_looped_back: 0,
1007        });
1008    }
1009}
1010
1011impl AudioNodeProcessor for SamplerProcessor {
1012    fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, extra: &mut ProcExtra) {
1013        let is_first_process = self.is_first_process;
1014        self.is_first_process = false;
1015
1016        let mut new_playing: Option<bool> = if is_first_process {
1017            Some(self.playing)
1018        } else {
1019            None
1020        };
1021        let mut new_sample = None;
1022        let mut repeat_mode_changed = false;
1023        let mut speed_changed = false;
1024        let mut volume_changed = false;
1025        let mut proc_state_changed = false;
1026
1027        #[cfg(feature = "scheduled_events")]
1028        let mut playback_instant: Option<EventInstant> = None;
1029
1030        #[cfg(feature = "scheduled_events")]
1031        for (mut event, timestamp) in events.drain_with_timestamps() {
1032            let mut s = None;
1033            if event.downcast_swap::<Option<SamplerNodeResource>>(&mut s) {
1034                new_sample = Some(s);
1035                continue;
1036            }
1037
1038            if let Some(patch) = SamplerNode::patch_event(&event) {
1039                match patch {
1040                    SamplerNodePatch::Volume(_) => volume_changed = true,
1041                    SamplerNodePatch::Play(play) => {
1042                        playback_instant = timestamp;
1043                        new_playing = Some(*play);
1044
1045                        if *play {
1046                            self.proc_state.last_finished_playback_id = self.proc_state.playback_id;
1047                            self.proc_state.playback_id = play.id();
1048                            proc_state_changed = true;
1049                        }
1050                    }
1051                    SamplerNodePatch::RepeatMode(_) => repeat_mode_changed = true,
1052                    SamplerNodePatch::Speed(_) => speed_changed = true,
1053                    SamplerNodePatch::MinGain(min_gain) => {
1054                        self.min_gain = min_gain.max(0.0);
1055                    }
1056                    _ => {}
1057                }
1058
1059                self.params.apply(patch);
1060            }
1061        }
1062
1063        #[cfg(not(feature = "scheduled_events"))]
1064        for mut event in events.drain() {
1065            let mut s = None;
1066            if event.downcast_swap::<Option<SamplerNodeResource>>(&mut s) {
1067                new_sample = Some(s);
1068                continue;
1069            }
1070
1071            if let Some(patch) = SamplerNode::patch_event(&event) {
1072                match patch {
1073                    SamplerNodePatch::Volume(_) => volume_changed = true,
1074                    SamplerNodePatch::Play(play) => {
1075                        new_playing = Some(*play);
1076
1077                        if *play {
1078                            self.proc_state.last_finished_playback_id = self.proc_state.playback_id;
1079                            self.proc_state.playback_id = play.id();
1080                            proc_state_changed = true;
1081                        }
1082                    }
1083                    SamplerNodePatch::RepeatMode(_) => repeat_mode_changed = true,
1084                    SamplerNodePatch::Speed(_) => speed_changed = true,
1085                    SamplerNodePatch::MinGain(min_gain) => {
1086                        self.min_gain = min_gain.max(0.0);
1087                    }
1088                    _ => {}
1089                }
1090
1091                self.params.apply(patch);
1092            }
1093        }
1094
1095        if speed_changed {
1096            self.speed = self.params.speed.max(MIN_PLAYBACK_SPEED);
1097
1098            if self.speed > 0.99999 && self.speed < 1.00001 {
1099                self.speed = 1.0;
1100            }
1101        }
1102
1103        if volume_changed && let Some(loaded_sample) = &mut self.loaded_sample_state {
1104            loaded_sample.gain = self.params.volume.amp_clamped(self.min_gain);
1105            if loaded_sample.gain > 0.99999 && loaded_sample.gain < 1.00001 {
1106                loaded_sample.gain = 1.0;
1107            }
1108        }
1109
1110        if repeat_mode_changed && let Some(loaded_sample) = &mut self.loaded_sample_state {
1111            loaded_sample.num_times_looped_back = 0;
1112        }
1113
1114        if let Some(maybe_sample) = new_sample {
1115            self.proc_state.has_sample_resource = maybe_sample.is_some();
1116            proc_state_changed = true;
1117
1118            self.stop(extra);
1119
1120            #[cfg(feature = "scheduled_events")]
1121            if new_playing == Some(true)
1122                && playback_instant.is_none()
1123                && let Some(queued_playback_instant) = self.queued_playback_instant.take()
1124                && queued_playback_instant.to_samples(info).is_some()
1125            {
1126                playback_instant = Some(queued_playback_instant);
1127            }
1128
1129            self.loaded_sample_state = None;
1130
1131            if let Some(sample) = maybe_sample {
1132                self.load_sample(sample);
1133            }
1134        }
1135
1136        if let Some(mut new_playing) = new_playing {
1137            self.paused = false;
1138            self.proc_state.playback_age_frames = 0;
1139            proc_state_changed = true;
1140
1141            if new_playing {
1142                let mut playhead_frames_at_play_instant = None;
1143
1144                if self.params.play_from == PlayFrom::Resume {
1145                    // Resume
1146                    if self.playing && !is_first_process {
1147                        // Sample is already playing, no need to do anything.
1148                        #[cfg(feature = "scheduled_events")]
1149                        {
1150                            self.queued_playback_instant = None;
1151                        }
1152                    } else if let Some(loaded_sample_state) = &self.loaded_sample_state {
1153                        playhead_frames_at_play_instant = Some(loaded_sample_state.playhead_frames);
1154                    }
1155                } else {
1156                    // Play from the given playhead
1157                    if let Some(loaded_sample_state) = &mut self.loaded_sample_state {
1158                        loaded_sample_state.num_times_looped_back = 0;
1159                        playhead_frames_at_play_instant =
1160                            Some(self.params.play_from.as_frames(info.sample_rate).unwrap());
1161                    } else {
1162                        #[cfg(feature = "scheduled_events")]
1163                        {
1164                            self.queued_playback_instant = playback_instant;
1165                        }
1166                    }
1167                }
1168
1169                if let Some(playhead_frames_at_play_instant) = playhead_frames_at_play_instant {
1170                    let loaded_sample_state = self.loaded_sample_state.as_mut().unwrap();
1171                    let prev_playhead_frames = loaded_sample_state.playhead_frames;
1172
1173                    #[cfg(feature = "scheduled_events")]
1174                    let mut new_playhead_frames = if let Some(playback_instant) = playback_instant {
1175                        let playback_instant_samples = playback_instant
1176                            .to_samples(info)
1177                            .unwrap_or(info.clock_samples);
1178                        let delay = if playback_instant_samples < info.clock_samples {
1179                            (info.clock_samples - playback_instant_samples).0 as u64
1180                        } else {
1181                            0
1182                        };
1183
1184                        playhead_frames_at_play_instant + delay
1185                    } else {
1186                        playhead_frames_at_play_instant
1187                    };
1188
1189                    #[cfg(not(feature = "scheduled_events"))]
1190                    let mut new_playhead_frames = playhead_frames_at_play_instant;
1191
1192                    if new_playhead_frames >= loaded_sample_state.sample_len_frames {
1193                        match self.params.repeat_mode {
1194                            RepeatMode::PlayOnce => {
1195                                new_playhead_frames = loaded_sample_state.sample_len_frames
1196                            }
1197                            RepeatMode::RepeatEndlessly => {
1198                                while new_playhead_frames >= loaded_sample_state.sample_len_frames {
1199                                    new_playhead_frames -= loaded_sample_state.sample_len_frames;
1200                                    loaded_sample_state.num_times_looped_back += 1;
1201                                }
1202                            }
1203                            RepeatMode::RepeatMultiple {
1204                                num_times_to_repeat,
1205                            } => {
1206                                while new_playhead_frames >= loaded_sample_state.sample_len_frames {
1207                                    if loaded_sample_state.num_times_looped_back
1208                                        == num_times_to_repeat as u64
1209                                    {
1210                                        new_playhead_frames = loaded_sample_state.sample_len_frames;
1211                                        break;
1212                                    }
1213
1214                                    new_playhead_frames -= loaded_sample_state.sample_len_frames;
1215                                    loaded_sample_state.num_times_looped_back += 1;
1216                                }
1217                            }
1218                        }
1219                    }
1220
1221                    if prev_playhead_frames != new_playhead_frames {
1222                        self.stop(extra);
1223
1224                        self.loaded_sample_state.as_mut().unwrap().playhead_frames =
1225                            new_playhead_frames;
1226
1227                        self.proc_state.playhead_frames = new_playhead_frames;
1228                    }
1229
1230                    if new_playhead_frames
1231                        == self.loaded_sample_state.as_ref().unwrap().sample_len_frames
1232                    {
1233                        self.proc_state.playhead_frames = new_playhead_frames;
1234
1235                        new_playing = false;
1236                    } else if new_playhead_frames != 0
1237                        || (self.num_active_stop_declickers > 0 && self.params.crossfade_on_seek)
1238                    {
1239                        self.declicker.reset_to_0();
1240                        self.declicker.fade_to_1(&extra.declick_values);
1241                    } else {
1242                        self.declicker.reset_to_1();
1243                    }
1244
1245                    #[cfg(feature = "scheduled_events")]
1246                    {
1247                        self.queued_playback_instant = None;
1248                    }
1249                }
1250            } else if self.params.play_from == PlayFrom::Resume {
1251                // Pause
1252                self.declicker.fade_to_0(&extra.declick_values);
1253                self.paused = true;
1254            } else {
1255                // Stop
1256                self.stop(extra);
1257            }
1258
1259            self.playing = new_playing;
1260
1261            self.proc_state.playback_state = if self.playing {
1262                PlaybackState::Playing
1263            } else if self.paused {
1264                PlaybackState::Paused
1265            } else {
1266                self.proc_state.last_finished_playback_id = self.proc_state.playback_id;
1267                PlaybackState::Stopped
1268            };
1269        }
1270
1271        if proc_state_changed {
1272            self.sync_proc_state();
1273        }
1274    }
1275
1276    fn bypassed(&mut self, _bypassed: bool) {
1277        self.declicker.reset_to_target();
1278        self.num_active_stop_declickers = 0;
1279    }
1280
1281    fn process(
1282        &mut self,
1283        info: &ProcInfo,
1284        buffers: ProcBuffers,
1285        extra: &mut ProcExtra,
1286    ) -> ProcessStatus {
1287        let currently_processing_sample = self.currently_processing_sample();
1288
1289        if !currently_processing_sample && self.num_active_stop_declickers == 0 {
1290            return ProcessStatus::ClearAllOutputs;
1291        }
1292
1293        let mut num_filled_channels = 0;
1294
1295        if currently_processing_sample {
1296            let sample_state = self.loaded_sample_state.as_ref().unwrap();
1297
1298            let looping = self
1299                .params
1300                .repeat_mode
1301                .do_loop(sample_state.num_times_looped_back);
1302
1303            let (finished, n_channels) =
1304                self.process_internal(buffers.outputs, info.frames, looping, extra);
1305
1306            num_filled_channels = n_channels;
1307
1308            self.proc_state.playhead_frames =
1309                self.loaded_sample_state.as_ref().unwrap().playhead_frames;
1310
1311            if finished {
1312                self.playing = false;
1313                self.proc_state.playback_state = PlaybackState::Stopped;
1314                self.proc_state.last_finished_playback_id = self.proc_state.playback_id;
1315            } else {
1316                self.proc_state.playback_age_frames = self
1317                    .proc_state
1318                    .playback_age_frames
1319                    .saturating_add(info.frames as u64);
1320            }
1321
1322            self.sync_proc_state();
1323        }
1324
1325        for (i, out_buf) in buffers
1326            .outputs
1327            .iter_mut()
1328            .enumerate()
1329            .skip(num_filled_channels)
1330        {
1331            if !info.out_silence_mask.is_channel_silent(i) {
1332                out_buf[..info.frames].fill(0.0);
1333            }
1334        }
1335
1336        if self.num_active_stop_declickers > 0 {
1337            let tmp_buffers = self.stop_declicker_buffers.as_ref().unwrap();
1338            let fade_out_frames = tmp_buffers.frames();
1339
1340            for (declicker_i, declicker) in self.stop_declickers.iter_mut().enumerate() {
1341                if declicker.frames_left == 0 {
1342                    continue;
1343                }
1344
1345                let tmp_buffers = tmp_buffers
1346                    .instance::<MAX_OUT_CHANNELS>(declicker_i, declicker.channels, fade_out_frames)
1347                    .unwrap();
1348
1349                let copy_frames = info.frames.min(declicker.frames_left);
1350                let start_frame = fade_out_frames - declicker.frames_left;
1351
1352                for (out_buf, tmp_buf) in buffers.outputs.iter_mut().zip(tmp_buffers.iter()) {
1353                    for (os, &ts) in out_buf[..copy_frames]
1354                        .iter_mut()
1355                        .zip(tmp_buf[start_frame..start_frame + copy_frames].iter())
1356                    {
1357                        *os += ts;
1358                    }
1359                }
1360
1361                declicker.frames_left -= copy_frames;
1362                if declicker.frames_left == 0 {
1363                    self.num_active_stop_declickers -= 1;
1364                }
1365
1366                num_filled_channels = num_filled_channels.max(declicker.channels);
1367            }
1368        }
1369
1370        let out_silence_mask = if num_filled_channels >= self.num_out_channels {
1371            SilenceMask::NONE_SILENT
1372        } else {
1373            let mut mask = SilenceMask::new_all_silent(self.num_out_channels);
1374            for i in 0..num_filled_channels {
1375                mask.set_channel(i, false);
1376            }
1377            mask
1378        };
1379
1380        ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask))
1381    }
1382
1383    fn new_stream(&mut self, stream_info: &StreamInfo, _context: &mut ProcStreamCtx) {
1384        if stream_info.sample_rate != stream_info.prev_sample_rate {
1385            self.stop_declicker_buffers = if self.config.num_declickers == 0 {
1386                None
1387            } else {
1388                Some(InstanceBuffer::<f32>::new(
1389                    self.config.num_declickers as usize,
1390                    NonZeroUsize::new(self.config.channels.get().get() as usize).unwrap(),
1391                    stream_info.declick_frames.get() as usize,
1392                ))
1393            };
1394
1395            // The sample rate has changed, meaning that the sample resources now have
1396            // the incorrect sample rate and the user must reload them.
1397            self.loaded_sample_state = None;
1398            self.playing = false;
1399            self.paused = false;
1400            self.proc_state.playback_state = PlaybackState::Stopped;
1401            self.sync_proc_state();
1402        }
1403    }
1404}
1405
1406struct LoadedSampleState {
1407    sample: SamplerNodeResource,
1408    sample_len_frames: u64,
1409    sample_num_channels: NonZeroUsize,
1410    sample_mono_to_stereo: bool,
1411    gain: f32,
1412    playhead_frames: u64,
1413    num_times_looped_back: u64,
1414}
1415
1416#[derive(Default, Clone, Copy)]
1417struct StopDeclickerState {
1418    frames_left: usize,
1419    channels: usize,
1420}