futuresdr_frontend/gui/
frequency.rs

1use futures::StreamExt;
2use gloo_render::request_animation_frame;
3use gloo_render::AnimationFrame;
4use reqwasm::websocket::futures::WebSocket;
5use reqwasm::websocket::Message;
6use std::convert::TryInto;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
9use wasm_bindgen_futures::spawn_local;
10use web_sys::HtmlCanvasElement;
11use web_sys::WebGlBuffer;
12use web_sys::WebGlProgram;
13use web_sys::WebGlRenderingContext as GL;
14use web_sys::WebGlTexture;
15use yew::prelude::*;
16
17#[wasm_bindgen]
18extern "C" {
19    fn get_samples() -> Vec<f32>;
20}
21
22#[wasm_bindgen]
23pub fn add_freq(id: String, url: String, min: f32, max: f32) {
24    let document = gloo_utils::document();
25    let div = document.query_selector(&id).unwrap().unwrap();
26    yew::start_app_with_props_in_element::<Frequency>(div, Props { url, min, max });
27}
28
29pub enum Msg {
30    Data(Vec<u8>),
31    Status(String),
32    Render(f64),
33}
34
35#[derive(Clone, Properties, Default, PartialEq)]
36pub struct Props {
37    pub url: String,
38    pub min: f32,
39    pub max: f32,
40}
41
42pub struct Frequency {
43    canvas_ref: NodeRef,
44    gl: Option<GL>,
45    _render_loop: Option<AnimationFrame>,
46    last_data: [f32; 2048],
47    vertex_buffer: Option<WebGlBuffer>,
48    prog: Option<WebGlProgram>,
49    num_indices: i32,
50    texture_offset: i32,
51    texture: Option<WebGlTexture>,
52    uses_websocket: bool,
53}
54
55const HEIGHT: usize = 256;
56const CANVAS_HEIGHT: usize = 256;
57const CANVAS_WIDTH: usize = 256;
58
59impl Component for Frequency {
60    type Message = Msg;
61    type Properties = Props;
62
63    fn create(ctx: &Context<Self>) -> Self {
64        let uses_websocket = if !ctx.props().url.is_empty() {
65            let link = ctx.link().clone();
66            let url = ctx.props().url.clone();
67
68            spawn_local(async move {
69                let websocket = WebSocket::open(&url).unwrap();
70                let (_, mut rx) = websocket.split();
71
72                while let Some(msg) = rx.next().await {
73                    match msg {
74                        Ok(Message::Text(s)) => link.send_message(Msg::Status(s)),
75                        Ok(Message::Bytes(v)) => link.send_message(Msg::Data(v)),
76                        _ => break,
77                    }
78                }
79            });
80
81            true
82        } else {
83            false
84        };
85
86        Self {
87            canvas_ref: NodeRef::default(),
88            texture: None,
89            vertex_buffer: None,
90            texture_offset: 0,
91            num_indices: 0,
92            gl: None,
93            prog: None,
94            _render_loop: None,
95            last_data: [0f32; 2048],
96            uses_websocket,
97        }
98    }
99
100    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
101        let canvas = self.canvas_ref.cast::<HtmlCanvasElement>().unwrap();
102
103        let gl: GL = canvas
104            .get_context("webgl")
105            .unwrap()
106            .unwrap()
107            .dyn_into()
108            .unwrap();
109
110        let display_width = canvas.client_width() as u32;
111        let display_height = canvas.client_height() as u32;
112
113        let need_resize = canvas.width() != display_width || canvas.height() != display_height;
114
115        if need_resize {
116            canvas.set_width(display_width);
117            canvas.set_height(display_height);
118        }
119
120        gl.viewport(0, 0, display_width as i32, display_height as i32);
121
122        let vert_code = r#"
123attribute vec2 gTexCoord0;
124
125uniform sampler2D frequency_data;
126uniform float yoffset;
127
128varying float power;
129
130void main()
131{
132    vec4 sample = texture2D(frequency_data, vec2(gTexCoord0.x, gTexCoord0.y + yoffset));
133    gl_Position = vec4((gTexCoord0 - 0.5) * 2.0, 0, 1);
134
135    power = sample.a;
136}
137        "#;
138        let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
139        gl.shader_source(&vert_shader, vert_code);
140        gl.compile_shader(&vert_shader);
141
142        let frag_code = r#"
143precision mediump float;
144
145varying float power;
146
147// All components are in the range [0…1], including hue.
148vec3 hsv2rgb(vec3 c)
149{
150    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
151    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
152    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
153}
154
155void main()
156{
157    gl_FragColor = vec4(hsv2rgb(vec3(power, .7, 0.7)), power);
158}
159        "#;
160        let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
161        gl.shader_source(&frag_shader, frag_code);
162        gl.compile_shader(&frag_shader);
163
164        self.prog = Some(gl.create_program().unwrap());
165        gl.attach_shader(self.prog.as_ref().unwrap(), &vert_shader);
166        gl.attach_shader(self.prog.as_ref().unwrap(), &frag_shader);
167        gl.link_program(self.prog.as_ref().unwrap());
168
169        gl.use_program(self.prog.as_ref());
170
171        // ===== prepare texture
172        self.texture = Some(gl.create_texture().unwrap());
173        gl.bind_texture(GL::TEXTURE_2D, Some(self.texture.as_ref().unwrap()));
174        gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_S, GL::REPEAT as i32);
175        gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_WRAP_T, GL::REPEAT as i32);
176        gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MIN_FILTER, GL::NEAREST as i32);
177        gl.tex_parameteri(GL::TEXTURE_2D, GL::TEXTURE_MAG_FILTER, GL::NEAREST as i32);
178
179        let d = vec![0u8; 2048 * HEIGHT];
180        gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
181            GL::TEXTURE_2D,
182            0,
183            GL::ALPHA as i32,
184            2048,
185            HEIGHT as i32,
186            0,
187            GL::ALPHA,
188            GL::UNSIGNED_BYTE,
189            Some(&d),
190        )
191        .unwrap();
192
193        // ===== prepare vertex
194        let mut vertexes = Vec::new();
195        let s = 1.0 / (2.0 * CANVAS_HEIGHT as f32);
196        for h in 0..CANVAS_HEIGHT {
197            for w in 0..CANVAS_WIDTH {
198                vertexes.push(w as f32 / (CANVAS_WIDTH) as f32 + s);
199                vertexes.push(h as f32 / (CANVAS_HEIGHT) as f32 + s);
200            }
201        }
202
203        self.vertex_buffer = Some(gl.create_buffer().unwrap());
204        gl.bind_buffer(GL::ARRAY_BUFFER, self.vertex_buffer.as_ref());
205        let array_buffer = js_sys::Float32Array::from(vertexes.as_slice()).buffer();
206        gl.buffer_data_with_opt_array_buffer(
207            GL::ARRAY_BUFFER,
208            Some(&array_buffer),
209            GL::STATIC_DRAW,
210        );
211
212        let mut indices: Vec<u16> = Vec::new();
213        for h in 0..CANVAS_HEIGHT - 1 {
214            for w in 0..CANVAS_WIDTH - 1 {
215                let o = h * CANVAS_WIDTH;
216                let o1 = (h + 1) * CANVAS_WIDTH;
217                indices.push((o + w) as u16);
218                indices.push((o + w + 1) as u16);
219                indices.push((o1 + w + 1) as u16);
220
221                indices.push((o + w) as u16);
222                indices.push((o1 + w) as u16);
223                indices.push((o1 + w + 1) as u16);
224            }
225        }
226        self.num_indices = indices.len() as i32;
227
228        let indices_buffer = gl.create_buffer().unwrap();
229        gl.bind_buffer(GL::ELEMENT_ARRAY_BUFFER, Some(&indices_buffer));
230        let array_buffer = js_sys::Uint16Array::from(indices.as_slice()).buffer();
231        gl.buffer_data_with_opt_array_buffer(
232            GL::ELEMENT_ARRAY_BUFFER,
233            Some(&array_buffer),
234            GL::STATIC_DRAW,
235        );
236
237        self.gl = Some(gl);
238
239        if first_render {
240            let handle = {
241                let link = ctx.link().clone();
242                request_animation_frame(move |time| link.send_message(Msg::Render(time)))
243            };
244            self._render_loop = Some(handle);
245        }
246    }
247
248    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
249        match msg {
250            Msg::Render(timestamp) => {
251                if !self.uses_websocket {
252                    self.last_data = get_samples().try_into().expect("data has wrong size");
253                }
254                self.render_gl(timestamp, ctx);
255            }
256            Msg::Data(b) => {
257                let v;
258                unsafe {
259                    let s = b.len() / 4;
260                    let p = b.as_ptr();
261                    v = std::slice::from_raw_parts(p as *const f32, s);
262                }
263                self.last_data = v.try_into().expect("data has wrong size");
264            }
265            Msg::Status(s) => {
266                gloo_console::log!(format!("socket status {:?}", &s));
267            }
268        }
269        false
270    }
271
272    fn view(&self, _ctx: &Context<Self>) -> Html {
273        html! {
274            <canvas ref={self.canvas_ref.clone()} />
275        }
276    }
277}
278
279impl Frequency {
280    fn render_gl(&mut self, _timestamp: f64, ctx: &Context<Self>) {
281        let gl = self.gl.as_ref().unwrap();
282
283        gl.bind_texture(GL::TEXTURE_2D, self.texture.as_ref());
284        gl.pixel_storei(GL::UNPACK_ALIGNMENT, 1);
285
286        let props = ctx.props();
287
288        let data: Vec<u8> = self
289            .last_data
290            .iter()
291            .map(|v| {
292                ((v.clamp(props.min, props.max) - props.min) / (props.max - props.min) * 255.0)
293                    as u8
294            })
295            .collect();
296
297        gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
298            GL::TEXTURE_2D,
299            0,
300            0,
301            self.texture_offset,
302            2048,
303            1,
304            GL::ALPHA,
305            GL::UNSIGNED_BYTE,
306            Some(&data),
307        )
308        .unwrap();
309
310        gl.bind_buffer(GL::ARRAY_BUFFER, self.vertex_buffer.as_ref());
311
312        let loc = gl.get_attrib_location(self.prog.as_ref().unwrap(), "gTexCoord0") as u32;
313        gl.enable_vertex_attrib_array(loc);
314        gl.vertex_attrib_pointer_with_i32(loc, 2, GL::FLOAT, false, 0, 0);
315
316        let loc = gl.get_uniform_location(self.prog.as_ref().unwrap(), "yoffset");
317        gl.uniform1f(loc.as_ref(), self.texture_offset as f32 / HEIGHT as f32);
318        let loc = gl.get_uniform_location(self.prog.as_ref().unwrap(), "frequency_data");
319        gl.uniform1i(loc.as_ref(), 0);
320
321        gl.draw_elements_with_i32(GL::TRIANGLES, self.num_indices, GL::UNSIGNED_SHORT, 0);
322
323        self.texture_offset = (self.texture_offset + 1) % HEIGHT as i32;
324
325        let handle = {
326            let link = ctx.link().clone();
327            request_animation_frame(move |time| link.send_message(Msg::Render(time)))
328        };
329        self._render_loop = Some(handle);
330    }
331}