Skip to main content

rodio/source/
chirp.rs

1//! Chirp/sweep source.
2
3use std::time::Duration;
4
5use crate::{
6    common::{ChannelCount, SampleRate},
7    math::{nz, TAU},
8    source::SeekError,
9    Float, Sample, Source,
10};
11
12/// Convenience function to create a new `Chirp` source.
13#[inline]
14pub fn chirp(
15    sample_rate: SampleRate,
16    start_frequency: Float,
17    end_frequency: Float,
18    duration: Duration,
19) -> Chirp {
20    Chirp::new(sample_rate, start_frequency, end_frequency, duration)
21}
22
23/// Generate a sine wave with an instantaneous frequency that changes/sweeps linearly over time.
24/// At the end of the chirp, once the `end_frequency` is reached, the source is exhausted.
25#[derive(Clone, Debug)]
26pub struct Chirp {
27    start_frequency: Float,
28    end_frequency: Float,
29    sample_rate: SampleRate,
30    total_samples: u64,
31    elapsed_samples: u64,
32}
33
34impl Chirp {
35    fn new(
36        sample_rate: SampleRate,
37        start_frequency: Float,
38        end_frequency: Float,
39        duration: Duration,
40    ) -> Self {
41        Self {
42            sample_rate,
43            start_frequency,
44            end_frequency,
45            total_samples: (duration.as_secs_f64() * sample_rate.get() as f64) as u64,
46            elapsed_samples: 0,
47        }
48    }
49
50    #[allow(dead_code)]
51    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
52        let mut target = (pos.as_secs_f64() * self.sample_rate.get() as f64) as u64;
53        if target >= self.total_samples {
54            target = self.total_samples;
55        }
56
57        self.elapsed_samples = target;
58        Ok(())
59    }
60}
61
62impl Iterator for Chirp {
63    type Item = Sample;
64
65    fn next(&mut self) -> Option<Self::Item> {
66        let i = self.elapsed_samples;
67        if i >= self.total_samples {
68            return None; // Exhausted
69        }
70
71        let ratio = (i as f64 / self.total_samples as f64) as Float;
72        let freq = self.start_frequency * (1.0 - ratio) + self.end_frequency * ratio;
73        let t = (i as f64 / self.sample_rate().get() as f64) as Float * TAU * freq;
74
75        self.elapsed_samples += 1;
76        Some(t.sin())
77    }
78
79    fn size_hint(&self) -> (usize, Option<usize>) {
80        let remaining = self.total_samples - self.elapsed_samples;
81        (remaining as usize, Some(remaining as usize))
82    }
83}
84
85impl ExactSizeIterator for Chirp {}
86
87impl Source for Chirp {
88    fn current_span_len(&self) -> Option<usize> {
89        None
90    }
91
92    fn channels(&self) -> ChannelCount {
93        nz!(1)
94    }
95
96    fn sample_rate(&self) -> SampleRate {
97        self.sample_rate
98    }
99
100    fn total_duration(&self) -> Option<Duration> {
101        let secs = self.total_samples as f64 / self.sample_rate.get() as f64;
102        Some(Duration::from_secs_f64(secs))
103    }
104}