blinksy/patterns/noise.rs
1//! # Noise Patterns
2//!
3//! The noise pattern creates flowing animations based on a noise function.
4//!
5//! # What is a noise function?
6//!
7//! A noise function is given a position in 1d, 2d, 3d, or 4d space and returns
8//! a random value between -1.0 and 1.0, where values between nearbly positions are
9//! smoothly interpolated.
10//!
11//! For example, a common use of noise functions is to procedurally generate terrain.
12//! You could give a 2d noise function an (x, y) position and use the resulting value
13//! as an elevation.
14//!
15//! In our case, we will use noise functions to generate `hue` and `value` for [Okhsv]
16//! colors. To animate through time, rather than adding time to our position, we will
17//! input the time to the noise function as an additonal dimension. So a 1d layout will
18//! use a 2d noise function, a 2d layout a 3d noise function, and so on.
19//!
20//! This pattern is the same concept as what you see on [mikey.nz](https://mikey.nz/).
21//!
22//! ## Example
23//!
24//! ```rust,ignore
25//! use blinksy::{
26//! ControlBuilder,
27//! layout2d,
28//! layout::{Layout2d, Shape2d, Vec2},
29//! patterns::noise::{Noise2d, noise_fns, NoiseParams}
30//! };
31//!
32//! // Define a 2D layout
33//! layout2d!(
34//! Layout,
35//! [Shape2d::Grid {
36//! start: Vec2::new(-1., -1.),
37//! horizontal_end: Vec2::new(1., -1.),
38//! vertical_end: Vec2::new(-1., 1.),
39//! horizontal_pixel_count: 16,
40//! vertical_pixel_count: 16,
41//! serpentine: true,
42//! }]
43//! );
44//!
45//! // Create a 2D noise pattern with Perlin noise
46//! let control = ControlBuilder::new_2d()
47//! .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
48//! .with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams {
49//! time_scalar: 0.001,
50//! position_scalar: 0.1,
51//! })
52//! .with_driver(/* Your driver */)
53//! .with_frame_buffer_size::</* Length of frame buffer */>()
54//! .build();
55//! ```
56//!
57//! [`Okhsv`]: crate::color::Okhsv
58//! [mikey.nz]: https://mikey.nz
59
60use noise_functions::{modifiers::Seeded, Noise as NoiseTrait, Sample};
61
62use crate::{
63 color::Okhsv,
64 layout::{Layout1d, Layout2d, Layout3d},
65 markers::{Dim1d, Dim2d, Dim3d},
66 pattern::Pattern,
67};
68
69/// Re-exports of noise functions from the noise crate.
70pub mod noise_fns {
71 pub use noise_functions::{OpenSimplex2, Perlin, Simplex};
72}
73
74/// Configuration parameters for noise patterns.
75#[derive(Debug)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77pub struct NoiseParams {
78 /// Controls the speed of animation (higher = faster)
79 pub time_scalar: f32,
80 /// Controls the spatial scale of the noise (higher = more compressed)
81 pub position_scalar: f32,
82}
83
84impl Default for NoiseParams {
85 fn default() -> Self {
86 const MILLISECONDS_PER_SECOND: f32 = 1e3;
87 Self {
88 time_scalar: 0.75 / MILLISECONDS_PER_SECOND,
89 position_scalar: 0.5,
90 }
91 }
92}
93
94/// One-dimensional noise pattern implementation.
95///
96/// Creates flowing animations based on a 2D noise function, using
97/// time and the 1D position for the input coordinates.
98#[derive(Debug)]
99#[cfg_attr(feature = "defmt", derive(defmt::Format))]
100pub struct Noise1d<Noise> {
101 /// The noise function used to get hue
102 hue_noise: Seeded<Noise>,
103 /// The noise function used to get value
104 value_noise: Seeded<Noise>,
105 /// Configuration parameters
106 params: NoiseParams,
107}
108
109impl<Layout, Noise> Pattern<Dim1d, Layout> for Noise1d<Noise>
110where
111 Layout: Layout1d,
112 Noise: NoiseTrait + Sample<2> + Default,
113{
114 type Params = NoiseParams;
115 type Color = Okhsv;
116
117 /// Creates a new Noise1d pattern with the specified parameters.
118 fn new(params: Self::Params) -> Self {
119 Self {
120 hue_noise: Noise::default().seed(0),
121 value_noise: Noise::default().seed(1),
122 params,
123 }
124 }
125
126 /// Generates colors for a 1D layout using noise.
127 ///
128 /// The pattern uses the LED position and time as inputs to a 2D noise function,
129 /// mapping the noise value to a hue in the Okhsv color space.
130 fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
131 let Self {
132 hue_noise,
133 value_noise,
134 params,
135 } = self;
136
137 let NoiseParams {
138 time_scalar,
139 position_scalar,
140 } = params;
141
142 let noise_time = time_in_ms as f32 * time_scalar;
143
144 Layout::points().map(move |x| {
145 let noise_args = [position_scalar * x, noise_time];
146 let hue = hue_noise.sample2(noise_args);
147 let saturation = 1.;
148 let value = 0.75 + 0.25 * value_noise.sample2(noise_args);
149 Okhsv::new(hue, saturation, value)
150 })
151 }
152}
153
154/// Two-dimensional noise pattern implementation.
155///
156/// Creates flowing animations based on a 3D noise function, using
157/// time and the 2D position for the input coordinates.
158#[derive(Debug)]
159#[cfg_attr(feature = "defmt", derive(defmt::Format))]
160pub struct Noise2d<Noise> {
161 /// The noise function used to get hue
162 hue_noise: Seeded<Noise>,
163 /// The noise function used to get value
164 value_noise: Seeded<Noise>,
165 /// Configuration parameters
166 params: NoiseParams,
167}
168
169impl<Layout, Noise> Pattern<Dim2d, Layout> for Noise2d<Noise>
170where
171 Layout: Layout2d,
172 Noise: NoiseTrait + Sample<3> + Default,
173{
174 type Params = NoiseParams;
175 type Color = Okhsv;
176
177 /// Creates a new Noise2d pattern with the specified parameters.
178 fn new(params: Self::Params) -> Self {
179 Self {
180 hue_noise: Noise::default().seed(0),
181 value_noise: Noise::default().seed(1),
182 params,
183 }
184 }
185
186 /// Generates colors for a 2D layout using noise.
187 ///
188 /// The pattern uses the LED x,y position and time as inputs to a 3D noise function,
189 /// mapping the noise value to a hue in the Okhsv color space.
190 fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
191 let Self {
192 hue_noise,
193 value_noise,
194 params,
195 } = self;
196
197 let NoiseParams {
198 time_scalar,
199 position_scalar,
200 } = params;
201
202 let noise_time = time_in_ms as f32 * time_scalar;
203
204 Layout::points().map(move |point| {
205 let noise_args = [
206 position_scalar * point.x,
207 position_scalar * point.y,
208 noise_time,
209 ];
210 let hue = hue_noise.sample3(noise_args);
211 let saturation = 1.;
212 let value = 0.75 + 0.25 * value_noise.sample3(noise_args);
213 Okhsv::new(hue, saturation, value)
214 })
215 }
216}
217
218/// Three-dimensional noise pattern implementation.
219///
220/// Creates flowing animations based on a 4D noise function, using
221/// time and the 3D position for the input coordinates.
222#[derive(Debug)]
223#[cfg_attr(feature = "defmt", derive(defmt::Format))]
224pub struct Noise3d<Noise>
225where
226 Noise: NoiseTrait,
227{
228 /// The noise function used to get hue
229 hue_noise: Seeded<Noise>,
230 /// The noise function used to get value
231 value_noise: Seeded<Noise>,
232 /// Configuration parameters
233 params: NoiseParams,
234}
235
236impl<Layout, Noise> Pattern<Dim3d, Layout> for Noise3d<Noise>
237where
238 Layout: Layout3d,
239 Noise: NoiseTrait + Sample<4> + Default,
240{
241 type Params = NoiseParams;
242 type Color = Okhsv;
243
244 /// Creates a new Noise2d pattern with the specified parameters.
245 fn new(params: Self::Params) -> Self {
246 Self {
247 hue_noise: Noise::default().seed(0),
248 value_noise: Noise::default().seed(1),
249 params,
250 }
251 }
252
253 /// Generates colors for a 3D layout using noise.
254 ///
255 /// The pattern uses the LED x,y,z position and time as inputs to a 4D noise function,
256 /// mapping the noise value to a hue in the HSV color space.
257 fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
258 let Self {
259 hue_noise,
260 value_noise,
261 params,
262 } = self;
263
264 let NoiseParams {
265 time_scalar,
266 position_scalar,
267 } = params;
268
269 let noise_time = time_in_ms as f32 * time_scalar;
270
271 Layout::points().map(move |point| {
272 let noise_args = [
273 position_scalar * point.x,
274 position_scalar * point.y,
275 position_scalar * point.z,
276 noise_time,
277 ];
278 let hue = hue_noise.sample4(noise_args);
279 let saturation = 1.;
280 let value = 0.75 + 0.25 * value_noise.sample4(noise_args);
281 Okhsv::new(hue, saturation, value)
282 })
283 }
284}