Skip to main content

figments_render/
smart_leds.rs

1use core::{marker::Copy, ops::Mul};
2use core::convert::AsRef;
3use core::result::Result;
4use core::iter::Iterator;
5
6use smart_leds_trait::{SmartLedsWrite, SmartLedsWriteAsync};
7
8use figments::{liber8tion::interpolate::Fract8, mappings::linear::LinearSpace, prelude::*};
9
10use crate::{gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::*};
11
12#[derive(Debug)]
13pub struct PowerControls {
14    max_mw: Milliwatts,
15    brightness: Fract8,
16    is_on: bool,
17    gamma_curve: GammaCurve,
18    cur_mw: u32
19}
20
21impl PowerControls {
22    pub fn new(max_mw: Milliwatts) -> Self {
23        Self {
24            max_mw,
25            brightness: Fract8::MAX,
26            is_on: true,
27            gamma_curve: GammaCurve::default(),
28            cur_mw: 0
29        }
30    }
31
32    pub fn max_power(&self) -> Milliwatts {
33        self.max_mw
34    }
35
36    pub fn set_max_power(&mut self, max_mw: Milliwatts) {
37        self.max_mw = max_mw;
38    }
39
40    pub fn iter_brightness<'a, Color, P: AsRef<[Color]> + ?Sized>(&'a mut self, pixbuf: &'a P) -> impl Iterator<Item = Color> + use<'a, Color, P> where Color: 'a + Copy + WithGamma + AsMilliwatts + Mul<Fract8, Output = Color> {
41        self.cur_mw = pixbuf.as_ref().iter().map(|x| { x.with_gamma(&self.gamma_curve).as_milliwatts() }).sum();
42        let b = brightness_for_mw(self.cur_mw, self.brightness, self.max_mw);
43        pixbuf.as_ref().iter().map(move |x| { x.with_gamma(&self.gamma_curve) * b })
44    }
45}
46
47impl Brightness for PowerControls {
48    fn set_brightness(&mut self, brightness: Fract8) {
49        self.brightness = brightness;
50    }
51
52    fn set_on(&mut self, is_on: bool) {
53        self.is_on = is_on;
54    }
55}
56
57impl GammaCorrected for PowerControls {
58    fn set_gamma(&mut self, gamma: GammaCurve) {
59        self.gamma_curve = gamma
60    }
61}
62
63#[derive(Debug)]
64pub struct PowerManagedWriter<T> {
65    target: T,
66    controls: PowerControls
67}
68
69impl<T> PowerManagedWriter<T> {
70    pub fn new(target: T, max_mw: Milliwatts) -> Self {
71        Self {
72            target,
73            controls: PowerControls::new(max_mw)
74        }
75    }
76
77    pub fn write<P: AsRef<[T::Color]> + ?Sized>(&mut self, pixbuf: &P) -> Result<(), T::Error> where T: SmartLedsWrite, T::Color: Mul<Fract8, Output = T::Color> + Copy + WithGamma + AsMilliwatts + core::fmt::Debug {
78        if self.controls.is_on {
79            self.target.write(self.controls.iter_brightness(pixbuf))
80        } else {
81            self.target.write(pixbuf.as_ref().iter().map(|x| { *x * Fract8::MIN }))
82        }
83    }
84
85
86    pub async fn write_async<P: AsRef<[T::Color]> + ?Sized>(&mut self, pixbuf: &P) -> Result<(), T::Error> where T: SmartLedsWriteAsync, T::Color: Mul<Fract8, Output = T::Color> + Copy + WithGamma + AsMilliwatts + core::fmt::Debug {
87        if self.controls.is_on {
88            self.target.write(self.controls.iter_brightness(pixbuf)).await
89        } else {
90            self.target.write(pixbuf.as_ref().iter().map(|x| { *x * Fract8::MIN })).await
91        }
92    }
93
94    pub fn controls(&mut self) -> &mut PowerControls {
95        &mut self.controls
96    }
97
98    /// Returns the total power required to display the previous write at full brightness. This is /not/ the actual power consumption, only a theoretical maximum useful for designing power supplies.
99    pub const fn max_mw(&self) -> u32 {
100        self.controls.cur_mw
101    }
102}
103
104pub struct SmartLedsOutput<'a, T, Pixbuf> {
105    writer: PowerManagedWriter<T>,
106    pixbuf: &'a mut Pixbuf,
107    buf_idx: usize,
108    clip: Rectangle<LinearSpace>
109}
110
111impl<'a, T, Pixel, const PIXEL_COUNT: usize> SmartLedsOutput<'a, T, [Pixel; PIXEL_COUNT]> {
112    pub fn new(target: T, pixbuf: &'a mut [Pixel; PIXEL_COUNT], max_mw: Milliwatts) -> Self {
113        Self {
114            writer: PowerManagedWriter::new(target, max_mw),
115            pixbuf,
116            buf_idx: 0,
117            clip: Rectangle::everything()
118        }
119    }
120
121    pub const fn pixbuf(&mut self) -> &mut [Pixel; PIXEL_COUNT] {
122        self.pixbuf
123    }
124
125    pub fn set_clip(&mut self, clip: Rectangle<LinearSpace>) {
126        self.clip = clip;
127    }
128
129    // TODO: We could just put this into a DoubleBufferedPixbuf, then there isn't a need to call this ever with SmartLedsOutput, as you could do output.pixbuf().swap(&mut next) with that.
130    pub fn swap_buffer(&mut self, pixbuf: &'a mut [Pixel; PIXEL_COUNT]) -> &'a mut [Pixel; PIXEL_COUNT] {
131        self.buf_idx = (self.buf_idx + 1) % self.pixbuf.as_ref().len();
132        core::mem::replace(&mut self.pixbuf, pixbuf)
133    }
134}
135
136impl<'a, T: SmartLedsWrite + 'a, Pixbuf: AsRef<[T::Color]>> Output<'a, LinearSpace> for SmartLedsOutput<'a, T, Pixbuf> where Self: Sample<'a, LinearSpace>, T::Color: core::fmt::Debug + AsMilliwatts + Mul<Fract8, Output = T::Color> + Copy + WithGamma {
137    type Error = T::Error;
138
139    type Controls = PowerControls;
140
141    fn commit(&mut self)  -> Result<(), Self::Error> {
142        self.writer.write(&self.pixbuf)
143    }
144
145    fn controls(&mut self) -> Option<&mut Self::Controls> {
146        Some(self.writer.controls())
147    }
148}
149
150impl<'a, T: SmartLedsWriteAsync + 'a, Pixbuf: AsRef<[T::Color]>> OutputAsync<'a, LinearSpace> for SmartLedsOutput<'a, T, Pixbuf> where Self: Sample<'a, LinearSpace>, T::Color: core::fmt::Debug + AsMilliwatts + Mul<Fract8, Output = T::Color> + Copy + WithGamma {
151    type Error = T::Error;
152
153    type Controls = PowerControls;
154
155    async fn commit_async(&mut self)  -> Result<(), Self::Error> {
156        self.writer.write_async(&self.pixbuf).await
157    }
158
159    fn controls(&mut self) -> Option<&mut Self::Controls> {
160        Some(self.writer.controls())
161    }
162}
163
164impl<'a, T, Color, const PIXEL_COUNT: usize> Sample<'a, LinearSpace> for SmartLedsOutput<'a, T, [Color; PIXEL_COUNT]> where Color: 'a {
165    type Output = Color;
166
167    fn sample(&mut self, rect: &figments::prelude::Rectangle<LinearSpace>) -> impl Iterator<Item = (figments::prelude::Coordinates<LinearSpace>, &'a mut Self::Output)> {
168        let start = self.clip.top_left.x.clamp(0, self.pixbuf.len() - 1);
169        let end = self.clip.bottom_right.x.clamp(0, self.pixbuf.len() - 1);
170        self.pixbuf[start..=end].sample(rect)
171    }
172}