hyperion/instance/
smoothing.rs

1use std::time::{Duration, Instant};
2
3use crate::models;
4
5// TODO: Implement decay smoothing
6// TODO: Implement dithering
7
8pub struct Smoothing {
9    config: models::Smoothing,
10    led_data: Vec<models::Color>,
11    current_data: Vec<models::Color16>,
12    target_data: Vec<models::Color16>,
13    target_time: Instant,
14    previous_write_time: Instant,
15    next_update: Option<Instant>,
16}
17
18impl Smoothing {
19    pub fn new(config: models::Smoothing, led_count: usize) -> Self {
20        let now = Instant::now();
21
22        Self {
23            config,
24            led_data: vec![Default::default(); led_count],
25            current_data: vec![Default::default(); led_count],
26            target_data: vec![Default::default(); led_count],
27            target_time: now,
28            previous_write_time: now,
29            next_update: None,
30        }
31    }
32
33    /// Given the current time, prepare the next update
34    fn plan_update(&mut self, now: Instant) -> SmoothingUpdate {
35        if self.config.enable && now < self.target_time {
36            // Smoothing enabled, the continuous update should happen at that time
37            let next_update = self.next_update.unwrap_or(
38                now + Duration::from_micros(
39                    1_000_000_000 / (1000. * self.config.update_frequency) as u64,
40                ),
41            );
42
43            self.next_update = Some(next_update);
44
45            // Compute the led data for the current time
46            let delta_time = (self.target_time - now).as_micros() as u64;
47            let k = 1.
48                - 1. * (delta_time as f32)
49                    / (self.target_time - self.previous_write_time).as_micros() as f32;
50
51            // Update current data with linear smoothing
52            for (tgt, prev) in self.target_data.iter().zip(self.current_data.iter_mut()) {
53                let r_diff = tgt.red as i32 - prev.red as i32;
54                let g_diff = tgt.green as i32 - prev.green as i32;
55                let b_diff = tgt.blue as i32 - prev.blue as i32;
56
57                prev.red = (prev.red as i32 + r_diff.signum() * (k * r_diff.abs() as f32) as i32)
58                    .clamp(0, 65535) as u16;
59                prev.green = (prev.green as i32
60                    + g_diff.signum() * (k * g_diff.abs() as f32) as i32)
61                    .clamp(0, 65535) as u16;
62                prev.blue = (prev.blue as i32 + b_diff.signum() * (k * b_diff.abs() as f32) as i32)
63                    .clamp(0, 65535) as u16;
64            }
65        } else {
66            // Smoothing disabled, update as soon as possible
67            if self.config.enable {
68                self.next_update = None;
69            } else {
70                // Or linear update complete, color is stable
71                self.next_update = Some(now);
72            }
73
74            // Update current data from target data
75            self.current_data.copy_from_slice(&self.target_data);
76        }
77
78        // Convert current data to led data
79        for (src, dst) in self.current_data.iter().zip(self.led_data.iter_mut()) {
80            *dst = crate::color::color_to8(*src);
81        }
82
83        if self.next_update.is_some() {
84            SmoothingUpdate::Running
85        } else {
86            SmoothingUpdate::Settled
87        }
88    }
89
90    pub fn set_target(&mut self, color_data: &[models::Color16]) {
91        // Update our copy of the target data
92        self.target_data.copy_from_slice(color_data);
93
94        // Update times
95        let now = Instant::now();
96        self.previous_write_time = now;
97        self.target_time = now + Duration::from_millis(self.config.time_ms as _);
98
99        self.plan_update(now);
100    }
101
102    pub async fn update(&mut self) -> (&[models::Color], SmoothingUpdate) {
103        if let Some(next_update) = self.next_update {
104            // Wait for the right update time
105            if next_update > Instant::now() {
106                tokio::time::sleep_until(next_update.into()).await
107            }
108
109            // We waited until the update time, return the result and plan the next update
110            self.next_update = None;
111            let update = self.plan_update(Instant::now());
112
113            (&self.led_data, update)
114        } else {
115            // No update pending
116            futures::future::pending().await
117        }
118    }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum SmoothingUpdate {
123    Running,
124    Settled,
125}