1use crate::engine::{Automaton, NoAction, Range, Time};
9use rill_core::traits::ParamValue;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum RandomType {
14 Walk,
16 Logistic,
18 Henon,
20 Lorenz,
22 WhiteNoise,
24 PinkNoise,
26 BrownNoise,
28}
29
30#[derive(Debug, Clone)]
32pub struct RandomAutomaton {
33 name: String,
35 rng_type: RandomType,
37 range: Range,
39 rate: f64,
41 chaos_params: (f64, f64, f64), update_rate: f64,
45}
46
47impl RandomAutomaton {
48 pub fn walk(name: &str, rate: f64) -> Self {
50 Self {
51 name: name.to_string(),
52 rng_type: RandomType::Walk,
53 range: Range::bipolar(),
54 rate: rate.max(0.0),
55 chaos_params: (0.0, 0.0, 0.0),
56 update_rate: 0.0,
57 }
58 }
59
60 pub fn logistic(name: &str, r: f64) -> Self {
62 Self {
63 name: name.to_string(),
64 rng_type: RandomType::Logistic,
65 range: Range::unipolar(),
66 rate: 0.0,
67 chaos_params: (r.clamp(3.0, 4.0), 0.0, 0.0),
68 update_rate: 0.0,
69 }
70 }
71
72 pub fn henon(name: &str, a: f64, b: f64) -> Self {
74 Self {
75 name: name.to_string(),
76 rng_type: RandomType::Henon,
77 range: Range::bipolar(),
78 rate: 0.0,
79 chaos_params: (a, b, 0.0),
80 update_rate: 0.0,
81 }
82 }
83
84 pub fn white_noise(name: &str, update_rate: f64) -> Self {
86 Self {
87 name: name.to_string(),
88 rng_type: RandomType::WhiteNoise,
89 range: Range::bipolar(),
90 rate: 0.0,
91 chaos_params: (0.0, 0.0, 0.0),
92 update_rate: update_rate.max(1.0),
93 }
94 }
95
96 pub fn with_range(mut self, range: Range) -> Self {
98 self.range = range;
99 self
100 }
101
102 fn xorshift(&self, state: &mut u64) -> u64 {
104 let mut x = *state;
105 x ^= x << 13;
106 x ^= x >> 7;
107 x ^= x << 17;
108 *state = x;
109 x
110 }
111
112 fn random_f64(&self, state: &mut u64) -> f64 {
114 self.xorshift(state) as f64 / u64::MAX as f64
115 }
116}
117
118impl Automaton for RandomAutomaton {
119 type Internal = (u64, f64, f64); type Action = NoAction;
121
122 fn step(
123 &self,
124 internal: &mut Self::Internal,
125 _current: &ParamValue,
126 time: Time,
127 _action: &Self::Action,
128 ) -> ParamValue {
129 let (rng_state, last_value, last_update_time) = internal;
130 let period = if self.update_rate > 0.0 {
131 1.0 / self.update_rate
132 } else {
133 0.0
134 };
135
136 if period > 0.0 && time - *last_update_time < period {
137 return ParamValue::Float(*last_value as f32);
138 }
139 *last_update_time = time;
140
141 let new_value = match self.rng_type {
142 RandomType::Walk => {
143 let step = (self.random_f64(rng_state) - 0.5) * 2.0 * self.rate;
144 (*last_value + step).clamp(-1.0, 1.0)
145 }
146 RandomType::Logistic => {
147 let r = self.chaos_params.0;
148 r * *last_value * (1.0 - *last_value)
149 }
150 RandomType::Henon => {
151 let a = self.chaos_params.0;
152 let x = *last_value;
153 let y = *last_value - x;
154 1.0 - a * x * x + y
155 }
156 RandomType::WhiteNoise => self.random_f64(rng_state) * 2.0 - 1.0,
157 _ => *last_value,
158 };
159
160 *last_value = new_value;
161 let value = self.range.clamp(new_value);
162 ParamValue::Float(value as f32)
163 }
164
165 fn initial_internal(&self) -> Self::Internal {
166 let initial_value = match self.rng_type {
167 RandomType::Logistic => 0.5,
168 RandomType::Henon => 0.0,
169 _ => 0.0,
170 };
171 (123456789, initial_value, 0.0)
172 }
173
174 fn name(&self) -> &str {
175 &self.name
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_random_walk() {
185 let walk = RandomAutomaton::walk("Walk", 0.1);
186 let mut internal = walk.initial_internal();
187 let current = ParamValue::Float(0.0);
188
189 let value = walk.step(&mut internal, ¤t, 0.01, &NoAction);
190 let val = value.as_f32().unwrap();
191 assert!(val >= -1.0 && val <= 1.0);
192 }
193
194 #[test]
195 fn test_logistic() {
196 let logistic = RandomAutomaton::logistic("Logistic", 3.8);
197 let mut internal = logistic.initial_internal();
198 let current = ParamValue::Float(0.0);
199
200 let value = logistic.step(&mut internal, ¤t, 0.0, &NoAction);
201 let val = value.as_f32().unwrap();
202 assert!(val >= 0.0 && val <= 1.0);
203 }
204}