Skip to main content

firewheel_sampler/
lib.rs

1/*
2use std::{
3    fmt::Debug,
4    num::NonZeroUsize,
5    ops::Range,
6    sync::{atomic::Ordering, Arc},
7};
8
9use firewheel_core::{
10    clock::{ClockTime, EventDelay},
11    node::{AudioNode, AudioNodeInfo, AudioNodeProcessor, ProcInfo, ProcessStatus},
12    param::{range::percent_volume_to_raw_gain, smoother::ParamSmoother},
13    sample_resource::SampleResource,
14    ChannelConfig, ChannelCount, SilenceMask, StreamInfo,
15};
16
17/// Configuration of a [`SamplerNode`]
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SamplerConfig {
20    /// The size of the command queue.
21    ///
22    /// By default this is set to `128`.
23    pub command_queue_size: NonZeroUsize,
24
25    /// The maximum number of samples (voiced) that can be played
26    /// concurrently in this node.
27    ///
28    /// By default this is set to `4`.
29    pub voice_limit: NonZeroUsize,
30}
31
32impl Default for SamplerConfig {
33    fn default() -> Self {
34        Self {
35            command_queue_size: NonZeroUsize::new(128).unwrap(),
36            voice_limit: NonZeroUsize::new(4).unwrap(),
37        }
38    }
39}
40
41/// Additional options for playing a sample.
42#[derive(Debug, Clone, PartialEq)]
43pub struct PlaySampleOpts {
44    /// The range of the sample to play.
45    ///
46    /// By default this is set to [`SampleRange::FullSample`].
47    pub range: SampleRange,
48
49    /// If `true`, then all previously playing samples on this node will
50    /// be stopped.
51    ///
52    /// By default this is set to `false`.
53    pub stop_previous: bool,
54
55    /// The percent volume to play this sample at (where `0.0` is silence
56    /// and `100.0` is unity gain).
57    ///
58    /// Note, this is more for creating advanced sequences when chaining
59    /// commands together, not for simple sample playback. It is
60    /// recommended to use the `Volume` node instead in that case as it
61    /// can be dynamically changed.
62    ///
63    /// If this value is less than or equal to `0.0`, then this command
64    /// will be ignored.
65    ///
66    /// By default this is set to `100.0`.
67    pub percent_volume: f32,
68
69    /// The stereo panning of this sample. (Where `0.0` is center, `-1.0`
70    /// is full-left, and `1.0` is full-right).
71    ///
72    /// This has no effect if the sample is not stereo.
73    ///
74    /// Note, this is more for creating advanced sequences when chaining
75    /// commands together, not for simple sample playback. It is
76    /// recommended to use the `Volume` node instead in that case as it
77    /// can be dynamically changed.
78    ///
79    /// By default this is set to `0.0`.
80    pub pan: f32,
81    // TODO: Pitch (doppler stretching) parameter.
82}
83
84impl Default for PlaySampleOpts {
85    fn default() -> Self {
86        Self {
87            range: SampleRange::FullSample,
88            stop_previous: false,
89            percent_volume: 100.0,
90            pan: 0.0,
91        }
92    }
93}
94
95/// Additional options for looping a sample.
96#[derive(Debug, Clone, PartialEq)]
97pub struct LoopSampleOpts {
98    /// The range in the sample to loop in.
99    ///
100    /// By default this is set to [`SampleRange::FullSample`].
101    pub range: SampleRange,
102
103    /// How many times to repeat the loop.
104    ///
105    /// By default this is set to [`LoopMode::Endless`].
106    pub mode: LoopMode,
107
108    /// Where to begin playing in the sample relative to the start of the
109    /// loop range in units of seconds. If the given time it outside the
110    /// loop range, then the beginning of the loop range will be used
111    /// instead.
112    ///
113    /// By default this is set to `0.0`.
114    pub start_from_secs: f64,
115
116    /// If `true`, then the previous loop iteration will be stopped (choked)
117    /// when the range is less than the length of the sample.
118    ///
119    /// If `false`, then the previous loop iteration will continue playing
120    /// until completion, overlapping with the next loop iteration. This
121    /// can be useful for example making a "rapid fire" of gunshot sounds.
122    ///
123    /// This has no effect when the range is [`SampleRange::FullSample`] or
124    /// when it is longer than the length of the sample.
125    ///
126    /// By default this is set to `true`.
127    pub choke: bool,
128
129    /// The percent volume to play this sample at (where `0.0` is silence
130    /// and `100.0` is unity gain).
131    ///
132    /// Note that gain and pan cannot be changed later for this sample
133    /// playback. Dynamic gain and pan are done using separate audio
134    /// nodes.
135    ///
136    /// If this value is less than or equal to `0.0`, then this command
137    /// will be ignored.
138    ///
139    /// By default this is set to `100.0`.
140    pub percent_volume: f32,
141
142    /// The stereo panning of this sample. (Where `0.0` is center, `-1.0`
143    /// is full-left, and `1.0` is full-right).
144    ///
145    /// This has no effect if the the output of this node is not stereo.
146    ///
147    /// Note that gain and pan cannot be changed later for this sample
148    /// playback. Dynamic gain and pan are done using separate audio
149    /// nodes.
150    ///
151    /// By default this is set to `0.0`.
152    pub pan: f32,
153    // TODO: Pitch (doppler stretching) parameter.
154}
155
156impl Default for LoopSampleOpts {
157    fn default() -> Self {
158        Self {
159            range: SampleRange::FullSample,
160            mode: LoopMode::Endless,
161            start_from_secs: 0.0,
162            choke: true,
163            percent_volume: 100.0,
164            pan: 0.0,
165        }
166    }
167}
168
169/// A command for a [`SamplerNode`].
170#[derive(Debug, Clone, PartialEq)]
171pub struct SamplerCommand<S: SampleResource> {
172    /// When the command should occur.
173    pub delay: EventDelay,
174    /// The type of command to execute.
175    pub command: SamplerCommandType<S>,
176}
177
178/// The type of command for a [`SamplerNode`].
179#[derive(Debug, Clone, PartialEq)]
180pub enum SamplerCommandType<S: SampleResource> {
181    /// Play a sample to completion.
182    ///
183    /// If the number of samples being played in this node exceeds the sample
184    /// (voice) limit, the the oldest voice will be stopped and replaced with
185    /// this one.
186    PlaySample {
187        /// The sample to play.
188        ///
189        /// If this is `None`, then the previously played sample will be played
190        /// again. If there was no previously played sample, then this command
191        /// will be ignored.
192        sample: Option<S>,
193        /// Additional options for playing a sample.
194        opts: PlaySampleOpts,
195    },
196    /// Play a sample, seamlessly looping back to the start of the range.
197    ///
198    /// If the number of samples being played in this node exceeds the sample
199    /// (voice) limit, the the oldest voice will be stopped and replaced with
200    /// this one.
201    LoopSample {
202        /// The sample to play.
203        ///
204        /// If this is `None`, then the previously played sample will be played
205        /// again. If there was no previously played sample, then this command
206        /// will be ignored.
207        sample: Option<S>,
208        /// Additional options for looping a sample.
209        opts: LoopSampleOpts,
210    },
211    /// Pause sample playback.
212    Pause,
213    /// Resume sample playback.
214    Resume,
215    /// Stop all currently playing samples on this node.
216    ///
217    /// Sending [`SamplerCommandType::Resume` afterwards will *NOT* resume
218    /// playback of the previously playing samples.
219    Stop,
220}
221
222/// The range in a sample resource [`SampleResource`]
223#[derive(Default, Debug, Clone, PartialEq)]
224pub enum SampleRange {
225    /// Use the full length of the sample.
226    #[default]
227    FullSample,
228    /// Use only a section of the sample (units are in seconds).
229    ///
230    /// The start of the range must be greater than or equal to 0.0.
231    ///
232    /// The end of the range may extend past the length of the sample,
233    /// in which case silence will be played to fill in the gaps.
234    RangeSecs(Range<f64>),
235}
236
237/// The number of times to loop an audio sample.
238#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
239pub enum LoopMode {
240    /// Loop endlessly. Useful for music playback.
241    #[default]
242    Endless,
243    /// Loop the given number of times
244    Count(u32),
245}
246
247#[derive(Debug, Clone, PartialEq)]
248pub enum CommandType<S: SampleResource> {
249    Single(SamplerCommand<S>),
250    Group(Vec<SamplerCommand<S>>),
251}
252
253enum ProcessorToNodeMsg<S: SampleResource> {
254    ReturnSample(S),
255    ReturnCommandGroup(Vec<SamplerCommand<S>>),
256}
257
258pub struct ActiveSamplerNode<S: SampleResource> {
259    // TODO: Find a good solution for webassembly.
260    to_processor_tx: rtrb::Producer<CommandType<S>>,
261    from_processor_rx: rtrb::Consumer<ProcessorToNodeMsg<S>>,
262}
263
264impl<S: SampleResource> ActiveSamplerNode<S> {
265    /// Play a sample to completion.
266    ///
267    /// If the number of samples being played in this node exceeds the sample
268    /// (voice) limit, the the oldest voice will be stopped and replaced with
269    /// this one.
270    ///
271    /// * `sample` - The sample to play. If this is `None`, then the previously
272    /// played sample will be played again. If there was no previously played
273    /// sample, then this command will be ignored.
274    /// * `delay` - When the command should occur.
275    /// * `opts` - Additional options for playing a sample.
276    ///
277    /// Returns an error if the command queue is full.
278    pub fn play_sample(
279        &mut self,
280        sample: Option<S>,
281        delay: EventDelay,
282        opts: PlaySampleOpts,
283    ) -> Result<(), rtrb::PushError<CommandType<S>>> {
284        self.push_command(SamplerCommand {
285            delay,
286            command: SamplerCommandType::PlaySample { sample, opts },
287        })
288    }
289
290    /// Play a sample, seamlessly looping back to the start of the range.
291    ///
292    /// If the number of samples being played in this node exceeds the sample
293    /// (voice) limit, the the oldest voice will be stopped and replaced with
294    /// this one.
295    ///
296    /// * `sample` - The sample to loop. If this is `None`, then the previously
297    /// played sample will be played again. If there was no previously played
298    /// sample, then this command will be ignored.
299    /// * `delay` - When the command should occur.
300    /// * `opts` - Additional options for looping a sample.
301    ///
302    /// Returns an error if the command queue is full.
303    pub fn loop_sample(
304        &mut self,
305        sample: Option<S>,
306        delay: EventDelay,
307        opts: LoopSampleOpts,
308    ) -> Result<(), rtrb::PushError<CommandType<S>>> {
309        self.push_command(SamplerCommand {
310            delay,
311            command: SamplerCommandType::LoopSample { sample, opts },
312        })
313    }
314
315    /// Pause sample playback.
316    ///
317    /// * `delay` - When the command should occur.
318    ///
319    /// Returns an error if the command queue is full.
320    pub fn pause(&mut self, delay: EventDelay) -> Result<(), rtrb::PushError<CommandType<S>>> {
321        self.push_command(SamplerCommand {
322            delay,
323            command: SamplerCommandType::Pause,
324        })
325    }
326
327    /// Resume sample playback.
328    ///
329    /// * `delay` - When the command should occur.
330    ///
331    /// Returns an error if the command queue is full.
332    pub fn resume(&mut self, delay: EventDelay) -> Result<(), rtrb::PushError<CommandType<S>>> {
333        self.push_command(SamplerCommand {
334            delay,
335            command: SamplerCommandType::Resume,
336        })
337    }
338
339    /// Stop all currently playing samples on this node.
340    ///
341    /// Calling [`ActiveSamplerNode::resume()`] afterwards will *NOT* resume
342    /// playback of the previously playing samples.
343    ///
344    /// * `delay` - When the command should occur.
345    ///
346    /// Returns an error if the command queue is full.
347    pub fn stop(&mut self, delay: EventDelay) -> Result<(), rtrb::PushError<CommandType<S>>> {
348        self.push_command(SamplerCommand {
349            delay,
350            command: SamplerCommandType::Stop,
351        })
352    }
353
354    /// Push a new [`SamplerCommand`] to execute.
355    ///
356    /// Returns an error if the command queue is full.
357    pub fn push_command(
358        &mut self,
359        command: SamplerCommand<S>,
360    ) -> Result<(), rtrb::PushError<CommandType<S>>> {
361        self.to_processor_tx.push(CommandType::Single(command))
362    }
363
364    /// Push a new group of [`SamplerCommand`]s to execute.
365    ///
366    /// Returns an error if the command queue is full.
367    pub fn push_command_group(
368        &mut self,
369        commands: Vec<SamplerCommand<S>>,
370    ) -> Result<(), rtrb::PushError<CommandType<S>>> {
371        self.to_processor_tx.push(CommandType::Group(commands))
372    }
373}
374
375pub struct SamplerNode<S: SampleResource> {
376    active_state: Option<ActiveSamplerNode<S>>,
377    config: SamplerConfig,
378}
379
380impl<S: SampleResource> SamplerNode<S> {
381    pub fn new(config: SamplerConfig) -> Self {
382        Self {
383            active_state: None,
384            config,
385        }
386    }
387
388    /// Get an immutable reference the active context.
389    ///
390    /// Returns `None` if this node is not currently activated.
391    pub fn get(&self) -> Option<&ActiveSamplerNode<S>> {
392        self.active_state.as_ref()
393    }
394
395    /// Get a mutable reference the active context.
396    ///
397    /// Returns `None` if this node is not currently activated.
398    pub fn get_mut(&mut self) -> Option<&mut ActiveSamplerNode<S>> {
399        self.active_state.as_mut()
400    }
401}
402
403impl<S: SampleResource> AudioNode for SamplerNode<S> {
404    fn debug_name(&self) -> &'static str {
405        "sampler"
406    }
407
408    fn info(&self) -> AudioNodeInfo {
409        AudioNodeInfo {
410            num_min_supported_outputs: ChannelCount::MONO,
411            num_max_supported_outputs: ChannelCount::MAX,
412            updates: true,
413            ..Default::default()
414        }
415    }
416
417    fn activate(
418        &mut self,
419        stream_info: StreamInfo,
420        _channel_config: ChannelConfig,
421    ) -> Result<Box<dyn AudioNodeProcessor>, Box<dyn std::error::Error>> {
422        let (to_processor_tx, from_node_rx) =
423            rtrb::RingBuffer::<CommandType<S>>::new(self.config.command_queue_size.get());
424        let (to_node_tx, from_processor_rx) =
425            rtrb::RingBuffer::<ProcessorToNodeMsg<S>>::new(self.config.command_queue_size.get());
426
427        self.active_state = Some(ActiveSamplerNode {
428            to_processor_tx,
429            from_processor_rx,
430        });
431
432        Ok(Box::new(SamplerProcessor::new(
433            stream_info.sample_rate,
434            stream_info.stream_latency_samples as usize,
435            stream_info.max_block_samples as usize,
436            from_node_rx,
437            to_node_tx,
438        )))
439    }
440
441    fn update(&mut self) {
442        if let Some(active_state) = &mut self.active_state {
443            while let Ok(msg) = active_state.from_processor_rx.pop() {
444                // Clean up resources.
445                match msg {
446                    ProcessorToNodeMsg::ReturnSample(_smp) => {}
447                    ProcessorToNodeMsg::ReturnCommandGroup(_cmds) => {}
448                }
449            }
450        }
451    }
452}
453
454struct SamplerProcessor<S: SampleResource> {
455    playing: bool,
456    sample_rate: u32,
457    stream_latency_samples: usize,
458    playhead: u64,
459    loop_range: Option<ProcSampleRange>,
460
461    sample: Option<S>,
462
463    from_node_rx: rtrb::Consumer<CommandType<S>>,
464    to_node_tx: rtrb::Producer<ProcessorToNodeMsg<S>>,
465}
466
467impl<S: SampleResource> SamplerProcessor<S> {
468    fn new(
469        sample_rate: u32,
470        stream_latency_samples: usize,
471        max_block_samples: usize,
472        from_node_rx: rtrb::Consumer<CommandType<S>>,
473        to_node_tx: rtrb::Producer<ProcessorToNodeMsg<S>>,
474    ) -> Self {
475        Self {
476            playing: false,
477            sample_rate,
478            stream_latency_samples,
479            playhead: 0,
480            loop_range: None,
481            sample: None,
482            from_node_rx,
483            to_node_tx,
484        }
485    }
486}
487
488impl<S: SampleResource> AudioNodeProcessor for SamplerProcessor<S> {
489    fn process(
490        &mut self,
491        samples: usize,
492        _inputs: &[&[f32]],
493        outputs: &mut [&mut [f32]],
494        _proc_info: ProcInfo,
495    ) -> ProcessStatus {
496        while let Ok(msg) = self.from_node_rx.pop() {
497            match msg {
498                NodeToProcessorMsg::SetSample {
499                    sample,
500                    stop_playback,
501                } => {
502                    if let Some(old_sample) = self.sample.take() {
503                        let _ = self
504                            .to_node_tx
505                            .push(ProcessorToNodeMsg::ReturnSample(old_sample));
506                    }
507
508                    self.sample = Some(sample);
509
510                    if let Some(loop_range) = &mut self.loop_range {
511                        loop_range.update_sample(&self.sample);
512                    }
513
514                    if stop_playback {
515                        self.playhead = self
516                            .loop_range
517                            .as_ref()
518                            .map(|l| l.playhead_range.start)
519                            .unwrap_or(0);
520
521                        if self.playing {
522                            self.playing = false;
523
524                            // TODO
525                        }
526                    }
527
528                    // TODO: Declick
529                }
530                NodeToProcessorMsg::Play => {
531                    if !self.playing {
532                        self.playing = true;
533
534                        // TODO: Declick
535                    }
536                }
537                NodeToProcessorMsg::Pause => {
538                    if self.playing {
539                        self.playing = false;
540
541                        // TODO: Declick
542                    }
543                }
544                NodeToProcessorMsg::Stop => {
545                    self.playhead = self
546                        .loop_range
547                        .as_ref()
548                        .map(|l| l.playhead_range.start)
549                        .unwrap_or(0);
550
551                    if self.playing {
552                        self.playing = false;
553
554                        // TODO: Declick
555                    }
556                }
557                NodeToProcessorMsg::SetPlayheadSecs(playhead_secs) => {
558                    let sample = (playhead_secs * f64::from(self.sample_rate)).round() as u64;
559
560                    if sample != self.playhead {
561                        self.playhead = sample;
562                        // TODO: Declick
563                    }
564                }
565                NodeToProcessorMsg::SetSampleRange(loop_range) => {
566                    self.loop_range = loop_range.map(|loop_range| {
567                        ProcSampleRange::new(loop_range, self.sample_rate, &self.sample)
568                    });
569
570                    if let Some(loop_range) = &self.loop_range {
571                        if loop_range.playhead_range.contains(&self.playhead) {
572                            self.playhead = loop_range.playhead_range.start;
573
574                            // TODO: Declick
575                        }
576                    }
577                }
578            }
579        }
580
581        let Some(sample) = &self.sample else {
582            // TODO: Declick
583
584            // No sample data, output silence.
585            return ProcessStatus::NoOutputsModified;
586        };
587
588        if !self.playing {
589            // TODO: Declick
590
591            // Not playing, output silence.
592            return ProcessStatus::NoOutputsModified;
593        }
594
595        let raw_gain = self.raw_gain.load(Ordering::Relaxed);
596        let gain = self.gain_smoother.set_and_process(raw_gain, samples);
597        // Hint to the compiler to optimize loop.
598        assert_eq!(gain.values.len(), samples);
599
600        if !gain.is_smoothing() && gain.values[0] < 0.00001 {
601            // TODO: Reset declick.
602
603            // Muted, so there is no need to process.
604            return ProcessStatus::NoOutputsModified;
605        }
606
607        if let Some(loop_range) = &self.loop_range {
608            if self.playhead >= loop_range.playhead_range.end {
609                // Playhead is out of range. Return to the start.
610                self.playhead = self
611                    .loop_range
612                    .as_ref()
613                    .map(|l| l.playhead_range.start)
614                    .unwrap_or(0);
615            }
616
617            // Copy first block of samples.
618
619            let samples_left = if loop_range.playhead_range.end - self.playhead <= usize::MAX as u64
620            {
621                (loop_range.playhead_range.end - self.playhead) as usize
622            } else {
623                usize::MAX
624            };
625            let first_copy_samples = samples.min(samples_left);
626
627            sample.fill_buffers(outputs, 0..first_copy_samples, self.playhead);
628
629            if first_copy_samples < samples {
630                // Loop back to the start.
631                self.playhead = self
632                    .loop_range
633                    .as_ref()
634                    .map(|l| l.playhead_range.start)
635                    .unwrap_or(0);
636
637                // Copy second block of samples.
638
639                let second_copy_samples = samples - first_copy_samples;
640
641                sample.fill_buffers(outputs, first_copy_samples..samples, self.playhead);
642
643                self.playhead += second_copy_samples as u64;
644            } else {
645                self.playhead += samples as u64;
646            }
647        } else {
648            if self.playhead >= sample.len_samples() {
649                // Playhead is out of range. Output silence.
650                return ProcessStatus::NoOutputsModified;
651
652                // TODO: Notify node that sample has finished.
653            }
654
655            let copy_samples = samples.min((sample.len_samples() - self.playhead) as usize);
656
657            sample.fill_buffers(outputs, 0..copy_samples, self.playhead);
658
659            if copy_samples < samples {
660                // Finished playing sample.
661                self.playing = false;
662                self.playhead = 0;
663
664                // Fill any remaining samples with zeros
665                for out_ch in outputs.iter_mut() {
666                    out_ch[copy_samples..].fill(0.0);
667                }
668
669                // TODO: Notify node that sample has finished.
670            } else {
671                self.playhead += samples as u64;
672            }
673        }
674
675        let sample_channels = sample.num_channels().get();
676
677        // Apply gain and declick
678        // TODO: Declick
679        if outputs.len() >= 2 && sample_channels == 2 {
680            // Provide an optimized stereo loop.
681
682            // Hint to the compiler to optimize loop.
683            assert_eq!(outputs[0].len(), samples);
684            assert_eq!(outputs[1].len(), samples);
685
686            for i in 0..samples {
687                outputs[0][i] *= gain.values[i];
688                outputs[1][i] *= gain.values[i];
689            }
690        } else {
691            for (out_ch, _) in outputs.iter_mut().zip(0..sample_channels) {
692                // Hint to the compiler to optimize loop.
693                assert_eq!(out_ch.len(), samples);
694
695                for i in 0..samples {
696                    out_ch[i] *= gain.values[i];
697                }
698            }
699        }
700
701        let mut out_silence_mask = SilenceMask::NONE_SILENT;
702
703        if outputs.len() > sample_channels {
704            if outputs.len() == 2 && sample_channels == 1 {
705                // If the output of this node is stereo and the sample is mono,
706                // assume that the user wants both channels filled with the
707                // sample data.
708                let (out_first, outs) = outputs.split_first_mut().unwrap();
709                outs[0].copy_from_slice(out_first);
710            } else {
711                // Fill the rest of the channels with zeros.
712                for (i, out_ch) in outputs.iter_mut().enumerate().skip(sample_channels) {
713                    out_ch.fill(0.0);
714                    out_silence_mask.set_channel(i, true);
715                }
716            }
717        }
718
719        ProcessStatus::outputs_modified(out_silence_mask)
720    }
721}
722
723impl<S: SampleResource> Drop for SamplerProcessor<S> {
724    fn drop(&mut self) {
725        if let Some(sample) = self.sample.take() {
726            let _ = self
727                .to_node_tx
728                .push(ProcessorToNodeMsg::ReturnSample(sample));
729        }
730    }
731}
732
733impl<S: SampleResource> Into<Box<dyn AudioNode>> for SamplerNode<S> {
734    fn into(self) -> Box<dyn AudioNode> {
735        Box::new(self)
736    }
737}
738
739struct Voice<S: SampleResource> {
740    sample: S,
741
742    raw_gain_l: f32,
743    raw_gain_r: Option<f32>,
744}
745
746impl<S: SampleResource> Voice<S> {
747
748}
749
750struct ProcSampleRange {
751    playhead_range: Range<u64>,
752    full_range: bool,
753}
754
755impl ProcSampleRange {
756    fn new<S: SampleResource>(
757        loop_range: SampleRange,
758        sample_rate: u32,
759        sample: &Option<S>,
760    ) -> Self {
761        let (start_frame, end_frame, full_range) = match &loop_range {
762            SampleRange::FullSample => {
763                let end_frame = if let Some(sample) = sample {
764                    sample.len_samples()
765                } else {
766                    0
767                };
768
769                (0, end_frame, true)
770            }
771            SampleRange::RangeSecs(range) => (
772                (range.start * f64::from(sample_rate)).round() as u64,
773                (range.end * f64::from(sample_rate)).round() as u64,
774                false,
775            ),
776        };
777
778        Self {
779            playhead_range: start_frame..end_frame,
780            full_range,
781        }
782    }
783
784    fn update_sample<S: SampleResource>(&mut self, sample: &Option<S>) {
785        let Some(sample) = sample else {
786            return;
787        };
788
789        if !self.full_range {
790            return;
791        }
792
793        let end_frame = sample.len_samples();
794
795        self.playhead_range = 0..end_frame;
796    }
797}
798*/