animato_tween/
waveform.rs1use animato_core::math::sin;
4
5#[cfg(any(feature = "std", feature = "alloc"))]
6use crate::keyframe::{Keyframe, KeyframeTrack};
7#[cfg(any(feature = "std", feature = "alloc"))]
8use alloc::vec::Vec;
9#[cfg(any(feature = "std", feature = "alloc"))]
10use animato_core::Easing;
11#[cfg(any(feature = "std", feature = "alloc"))]
12use animato_core::math::ceil;
13
14const TAU: f32 = core::f32::consts::PI * 2.0;
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum Waveform {
20 Sine {
22 frequency: f32,
24 amplitude: f32,
26 phase: f32,
28 },
29 Sawtooth {
31 frequency: f32,
33 amplitude: f32,
35 },
36 Square {
38 frequency: f32,
40 amplitude: f32,
42 duty_cycle: f32,
44 },
45 Triangle {
47 frequency: f32,
49 amplitude: f32,
51 },
52 Noise {
54 seed: u32,
56 smoothness: f32,
58 },
59}
60
61impl Waveform {
62 pub fn sample(&self, time: f32) -> f32 {
64 let time = finite_or(time, 0.0);
65 match *self {
66 Self::Sine {
67 frequency,
68 amplitude,
69 phase,
70 } => finite_or(amplitude, 1.0) * sin(TAU * finite_or(frequency, 1.0) * time + phase),
71 Self::Sawtooth {
72 frequency,
73 amplitude,
74 } => {
75 let cycle = cycle(time, frequency);
76 finite_or(amplitude, 1.0) * (cycle * 2.0 - 1.0)
77 }
78 Self::Square {
79 frequency,
80 amplitude,
81 duty_cycle,
82 } => {
83 let cycle = cycle(time, frequency);
84 let duty = finite_or(duty_cycle, 0.5).clamp(0.0, 1.0);
85 let amp = finite_or(amplitude, 1.0);
86 if cycle < duty { amp } else { -amp }
87 }
88 Self::Triangle {
89 frequency,
90 amplitude,
91 } => {
92 let cycle = cycle(time, frequency);
93 finite_or(amplitude, 1.0) * (1.0 - 4.0 * (cycle - 0.5).abs())
94 }
95 Self::Noise { seed, smoothness } => {
96 let span = finite_or(smoothness, 0.25).max(f32::EPSILON);
97 let scaled = time.max(0.0) / span;
98 let index = scaled as u32;
99 let local = smoothstep(scaled - index as f32);
100 let a = hash_noise(seed, index);
101 let b = hash_noise(seed, index.saturating_add(1));
102 a + (b - a) * local
103 }
104 }
105 }
106
107 #[cfg(any(feature = "std", feature = "alloc"))]
111 pub fn to_keyframe_track(&self, duration: f32, sample_rate: f32) -> KeyframeTrack<f32> {
112 let duration = finite_or(duration, 0.0).max(0.0);
113 let sample_rate = finite_or(sample_rate, 60.0).max(1.0);
114 let samples = ceil(duration * sample_rate).max(1.0) as usize;
115 let mut frames = Vec::with_capacity(samples + 1);
116 for index in 0..=samples {
117 let t = duration * index as f32 / samples as f32;
118 frames.push(Keyframe::new(t, self.sample(t), Easing::Linear));
119 }
120 KeyframeTrack::from_sorted_frames(frames)
121 }
122}
123
124fn finite_or(value: f32, fallback: f32) -> f32 {
125 if value.is_finite() { value } else { fallback }
126}
127
128fn cycle(time: f32, frequency: f32) -> f32 {
129 let scaled = (time.max(0.0) * finite_or(frequency, 1.0).max(0.0)).max(0.0);
130 scaled - scaled as u32 as f32
131}
132
133fn smoothstep(t: f32) -> f32 {
134 let t = t.clamp(0.0, 1.0);
135 t * t * (3.0 - 2.0 * t)
136}
137
138fn hash_noise(seed: u32, index: u32) -> f32 {
139 let mut x = seed ^ index.wrapping_mul(0x9E37_79B9);
140 x ^= x >> 16;
141 x = x.wrapping_mul(0x7FEB_352D);
142 x ^= x >> 15;
143 x = x.wrapping_mul(0x846C_A68B);
144 x ^= x >> 16;
145 let unit = (x as f32) / (u32::MAX as f32);
146 unit * 2.0 - 1.0
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn sine_respects_frequency_amplitude_and_phase() {
155 let wave = Waveform::Sine {
156 frequency: 1.0,
157 amplitude: 2.0,
158 phase: 0.0,
159 };
160 assert!(wave.sample(0.0).abs() < 0.0001);
161 assert!((wave.sample(0.25) - 2.0).abs() < 0.0001);
162 }
163
164 #[test]
165 fn square_respects_duty_cycle() {
166 let wave = Waveform::Square {
167 frequency: 1.0,
168 amplitude: 3.0,
169 duty_cycle: 0.25,
170 };
171 assert_eq!(wave.sample(0.1), 3.0);
172 assert_eq!(wave.sample(0.3), -3.0);
173 }
174
175 #[test]
176 fn noise_is_deterministic_and_bounded() {
177 let wave = Waveform::Noise {
178 seed: 42,
179 smoothness: 0.25,
180 };
181 let a = wave.sample(0.123);
182 let b = wave.sample(0.123);
183 assert_eq!(a, b);
184 assert!((-1.0..=1.0).contains(&a));
185 }
186
187 #[cfg(any(feature = "std", feature = "alloc"))]
188 #[test]
189 fn waveform_converts_to_keyframes() {
190 let wave = Waveform::Triangle {
191 frequency: 1.0,
192 amplitude: 1.0,
193 };
194 let track = wave.to_keyframe_track(1.0, 4.0);
195 assert_eq!(track.frames().len(), 5);
196 assert_eq!(track.duration(), 1.0);
197 }
198}