maia_wasm/
waterfall.rs

1//! WebGL2 waterfall.
2//!
3//! This module contains the implementation of a WebGL2 waterfall using the
4//! render engine contained in [`crate::render`].
5
6use crate::render::{
7    DrawMode, ProgramSource, RenderEngine, RenderObject, Texture, TextureMagFilter,
8    TextureMinFilter, TextureParameter, TextureWrap, Uniform, UniformValue,
9    texture_formats::{R16f, Rgb},
10};
11use std::cell::Cell;
12use std::rc::Rc;
13use wasm_bindgen::prelude::*;
14use web_sys::{Performance, WebGlProgram, WebGlTexture, WebGlVertexArrayObject};
15
16/// Waterfall.
17///
18/// This object is used to create and add a WebGL2 waterfall display to a
19/// [`RenderEngine`] and to modify the parameters of the waterfall.
20pub struct Waterfall {
21    texture_map: Box<[f32]>,
22    enables: Enables,
23    uniforms: Uniforms,
24    textures: Textures,
25    programs: Programs,
26    vaos: VAOs,
27    performance: Performance,
28    // State for rendering updates
29    current_draw_line: usize,
30    last_draw_line: usize,
31    last_spectrum_timestamp: Option<f32>,
32    waterfall_rate: Option<f32>,
33    waterfall_wraps: usize,
34    center_freq: f64,
35    samp_rate: f64,
36    // Auxiliary for frequency axis
37    num_freqs: Vec<usize>,
38    freq_radixes: Vec<u8>,
39    freq_num_idx: Rc<Cell<u32>>,
40    freq_num_idx_ticks: Rc<Cell<u32>>,
41    zoom_levels: Vec<f32>,
42    waterfall_min: f32,
43    waterfall_max: f32,
44}
45
46#[derive(Default)]
47struct Enables {
48    waterfall: Rc<Cell<bool>>,
49    spectrum_background: Rc<Cell<bool>>,
50    spectrum: Rc<Cell<bool>>,
51    frequency_labels: Rc<Cell<bool>>,
52    frequency_ticks: Rc<Cell<bool>>,
53    channel: Rc<Cell<bool>>,
54}
55
56struct Uniforms {
57    time_translation: Rc<Uniform<f32>>,
58    center_freq: Rc<Uniform<f32>>,
59    zoom: Rc<Uniform<f32>>,
60    waterfall_scale_add: Rc<Uniform<f32>>,
61    waterfall_scale_add_floor: Rc<Uniform<f32>>,
62    waterfall_scale_mult: Rc<Uniform<f32>>,
63    waterfall_brightness: Rc<Uniform<f32>>,
64    aspect_ratio: Rc<Uniform<f32>>,
65    canvas_width: Rc<Uniform<f32>>,
66    freq_labels_width: Rc<Uniform<f32>>,
67    freq_labels_height: Rc<Uniform<f32>>,
68    major_ticks_end: Rc<Uniform<i32>>,
69    channel_freq: Rc<Uniform<f32>>,
70    channel_width: Rc<Uniform<f32>>,
71}
72
73struct Textures {
74    waterfall: Rc<WebGlTexture>,
75    colormap: Rc<WebGlTexture>,
76    text: Rc<WebGlTexture>,
77}
78
79struct Programs {
80    frequency_labels: Rc<WebGlProgram>,
81    frequency_ticks: Rc<WebGlProgram>,
82}
83
84#[derive(Default)]
85struct VAOs {
86    frequency_labels: Option<Rc<WebGlVertexArrayObject>>,
87    frequency_ticks: Option<Rc<WebGlVertexArrayObject>>,
88}
89
90impl Waterfall {
91    // number of indices for waterfall
92    const NUM_INDICES: usize = 12;
93
94    // number of indices for a rectangle
95    const RECTANGLE_NUM_INDICES: usize = 6;
96
97    const TEXTURE_WIDTH: usize = 4096;
98    const TEXTURE_HEIGHT: usize = 512;
99
100    const SPECTRUM_POINTS: usize = Self::TEXTURE_WIDTH;
101
102    // horizontal divisions are spaced by 1 dB; these cover a 200 dB range,
103    // which is more than enough
104    const HORIZONTAL_DIVISIONS: usize = 200;
105
106    // waterfall brightness when spectrum is visible
107    const WATERFALL_BRIGHTNESS_WITH_SPECTRUM: f32 = 0.7;
108
109    /// Creates a new waterfall, adding it to the [`RenderEngine`].
110    ///
111    /// The `performance` parameter should contain a performance object obtained
112    /// with [`web_sys::Window::performance`].
113    pub fn new(engine: &mut RenderEngine, performance: Performance) -> Result<Waterfall, JsValue> {
114        let programs = Programs {
115            frequency_labels: Self::frequency_labels_program(engine)?,
116            frequency_ticks: Self::frequency_ticks_program(engine)?,
117        };
118        // These default values will be overwritten by the UI
119        let samp_rate = 30.72e6;
120        let center_freq = Self::actual_center_freq(2400e6, samp_rate);
121        let mut w = Waterfall {
122            texture_map: vec![0.0; Self::TEXTURE_WIDTH * Self::TEXTURE_HEIGHT].into_boxed_slice(),
123            enables: Enables::default(),
124            uniforms: Uniforms::new(),
125            textures: Textures::new(engine)?,
126            programs,
127            vaos: VAOs::default(),
128            performance,
129            current_draw_line: Self::TEXTURE_HEIGHT - 1,
130            last_draw_line: 0,
131            waterfall_wraps: 0,
132            last_spectrum_timestamp: None,
133            waterfall_rate: None,
134            center_freq,
135            samp_rate,
136            num_freqs: Vec::new(),
137            freq_radixes: Vec::new(),
138            zoom_levels: Vec::new(),
139            freq_num_idx: Rc::new(Cell::new(0)),
140            freq_num_idx_ticks: Rc::new(Cell::new(0)),
141            waterfall_min: 35.0,
142            waterfall_max: 85.0,
143        };
144
145        w.update_canvas_size(engine);
146        w.update_waterfall_scale();
147        w.load_waterfall(engine)?;
148        w.load_colormap(engine, &crate::colormap::turbo::COLORMAP)?;
149        let waterfall_object = w.waterfall_object(engine)?;
150        engine.add_object(waterfall_object);
151        let spectrum_background_object = w.spectrum_background_object(engine)?;
152        engine.add_object(spectrum_background_object);
153        let horizontal_divisions_object = w.horizontal_divisions_object(engine)?;
154        engine.add_object(horizontal_divisions_object);
155        let spectrum_object = w.spectrum_object(engine)?;
156        engine.add_object(spectrum_object);
157        let channel_object = w.channel_object(engine)?;
158        engine.add_object(channel_object);
159        let (frequency_labels_object, frequency_ticks_object) =
160            w.frequency_labels_object(engine)?;
161        engine.add_object(frequency_labels_object);
162        engine.add_object(frequency_ticks_object);
163
164        w.enables.waterfall.set(true);
165        w.enables.frequency_labels.set(true);
166        w.enables.frequency_ticks.set(true);
167
168        Ok(w)
169    }
170
171    /// Adds a spectrum line to the waterfall.
172    ///
173    /// This function updates the waterfall by adding a new spectrum line to
174    /// it. The spectrum is given in linear power units.
175    pub fn put_waterfall_spectrum(&mut self, spectrum_linear: &js_sys::Float32Array) {
176        self.last_spectrum_timestamp = Some(self.performance.now() as f32);
177        self.current_draw_line = (self.current_draw_line + 1) % Self::TEXTURE_HEIGHT;
178        let line = self.current_draw_line;
179        let spectrum_texture =
180            &mut self.texture_map[line * Self::TEXTURE_WIDTH..(line + 1) * Self::TEXTURE_WIDTH];
181        spectrum_linear.copy_to(spectrum_texture);
182        // Convert to "dB". We don't include the 10.0 factor to save us a multiplication.
183        // This will later be taken into account in the shader.
184        for x in spectrum_texture.iter_mut() {
185            // do not compute the log10 of pixels that are zero to avoid the
186            // shader from having to handle -infty
187            if *x != 0.0 {
188                *x = x.log10();
189            }
190        }
191    }
192
193    /// Updates the waterfall for rendering.
194    ///
195    /// This function must be called before each call to
196    /// [`RenderEngine::render`]. It updates and prepares the waterfall render
197    /// objects for rendering. The vale of `dt` should be the timestamp given
198    /// to the `request_animation_frame` callback in which this function is
199    /// called. It is currently unused, since the waterfall scroll rate is
200    /// determined by how often
201    /// [`put_waterfall_spectrum`](Waterfall::put_waterfall_spectrum) is called.
202    pub fn prepare_render(&mut self, engine: &mut RenderEngine, dt: f32) -> Result<(), JsValue> {
203        let draw_lines_coarse = self.current_draw_line as f32;
204        // Fine correction to draw_t_coarse for smooth animation interpolation
205        // between waterfall lines. Only applied when we have the necessary data.
206        let draw_lines_fine = match (self.last_spectrum_timestamp, self.waterfall_rate) {
207            (Some(t0), Some(rate)) => {
208                let elapsed_secs = (dt - t0) * 1e-3;
209                let elapsed_lines = elapsed_secs * rate;
210                // Gives a correction between -0.5 and +0.5 lines
211                elapsed_lines.clamp(0.0, 1.0) - 0.5
212            }
213            _ => 0.0,
214        };
215        let draw_t = (draw_lines_coarse + draw_lines_fine) / Self::TEXTURE_HEIGHT as f32;
216        // TODO use elapsed_ms to effect draw_t. This needs us to know the spectrometer rate.
217        self.uniforms.time_translation.set_data(4.0 * draw_t);
218
219        let end_draw = self.current_draw_line;
220        let start_draw = if end_draw < self.last_draw_line {
221            // wraps around
222            let start_wrap = self.last_draw_line + 1;
223            if start_wrap != Self::TEXTURE_HEIGHT {
224                // Last render didn't finish the bottom of the texture. Update
225                // it and load it.
226                engine.texture_subimage::<R16f>(
227                    &self.textures.waterfall,
228                    &self.texture_map[start_wrap * Self::TEXTURE_WIDTH..],
229                    0,
230                    start_wrap,
231                    Self::TEXTURE_WIDTH,
232                    Self::TEXTURE_HEIGHT - start_wrap,
233                )?;
234            }
235            self.waterfall_wraps += 1;
236            0
237        } else {
238            self.last_draw_line + 1
239        };
240
241        if start_draw != end_draw + 1 {
242            engine.texture_subimage::<R16f>(
243                &self.textures.waterfall,
244                &self.texture_map
245                    [start_draw * Self::TEXTURE_WIDTH..(end_draw + 1) * Self::TEXTURE_WIDTH],
246                0,
247                start_draw,
248                Self::TEXTURE_WIDTH,
249                end_draw - start_draw + 1,
250            )?;
251        }
252
253        self.last_draw_line = end_draw;
254
255        Ok(())
256    }
257
258    /// Updates the waterfall according to the new dimensions of the canvas.
259    ///
260    /// This function should be called each time that the canvas size or the
261    /// device pixel ratio changes, so that the waterfall can be update accordingly.
262    pub fn resize_canvas(&mut self, engine: &mut RenderEngine) -> Result<(), JsValue> {
263        // update frequency labels VAOs and texts texture
264        self.frequency_labels_vao(engine)?;
265        self.update_canvas_size(engine);
266        Ok(())
267    }
268
269    fn update_canvas_size(&mut self, engine: &mut RenderEngine) {
270        let dims = engine.canvas_dims().css_pixels();
271        let aspect_ratio = f64::from(dims.0) / f64::from(dims.1);
272        self.uniforms.aspect_ratio.set_data(aspect_ratio as f32);
273        self.uniforms.canvas_width.set_data(dims.0 as f32);
274    }
275
276    /// Updates the waterfall with a new center frequency and sample rate.
277    ///
278    /// The center frequency and sample rate should be given in units of Hz and
279    /// samples per second.
280    pub fn set_freq_samprate(
281        &mut self,
282        center_freq: f64,
283        samp_rate: f64,
284        engine: &mut RenderEngine,
285    ) -> Result<(), JsValue> {
286        let center_freq = Self::actual_center_freq(center_freq, samp_rate);
287        if center_freq != self.center_freq || samp_rate != self.samp_rate {
288            self.center_freq = center_freq;
289            self.samp_rate = samp_rate;
290            // update frequency labels VAOs and texts texture
291            self.frequency_labels_vao(engine)?;
292        }
293        Ok(())
294    }
295
296    fn actual_center_freq(center_freq: f64, samp_rate: f64) -> f64 {
297        // Take note that the actual center_frequency in the waterfall is not
298        // baseband DC, but rather the frequency between the DC FFT bin and one
299        // bin to the left.
300        let fft_bin_hz = samp_rate / Self::TEXTURE_WIDTH as f64;
301        center_freq - 0.5 * fft_bin_hz
302    }
303
304    /// Returns the current waterfall center frequency and sample rate.
305    ///
306    /// The center frequency and sample rate are given in units of Hz and
307    /// samples per second.
308    pub fn get_freq_samprate(&self) -> (f64, f64) {
309        let samp_rate = self.samp_rate;
310        (
311            Self::inv_actual_center_freq(self.center_freq, samp_rate),
312            samp_rate,
313        )
314    }
315
316    fn inv_actual_center_freq(center_freq: f64, samp_rate: f64) -> f64 {
317        // inverse of acqtual_center_freq
318        let fft_bin_hz = samp_rate / Self::TEXTURE_WIDTH as f64;
319        center_freq + 0.5 * fft_bin_hz
320    }
321
322    /// Returns whether the waterfall is visible.
323    pub fn is_waterfall_visible(&self) -> bool {
324        self.enables.waterfall.get()
325    }
326
327    /// Sets whether the waterfall is visible.
328    ///
329    /// By default the waterfall is visible.
330    pub fn set_waterfall_visible(&self, visible: bool) {
331        self.enables.waterfall.set(visible);
332        self.enables.spectrum_background.set(!visible);
333    }
334
335    /// Returns whether the spectrum is visible.
336    pub fn is_spectrum_visible(&self) -> bool {
337        self.enables.spectrum.get()
338    }
339
340    /// Sets whether the spectrum is visible.
341    ///
342    /// By default the spectrum is not visible.
343    pub fn set_spectrum_visible(&self, visible: bool) {
344        self.enables.spectrum.set(visible);
345        // darken waterfall slightly if the spectrum is visible to make the
346        // spectrum more clear
347        self.uniforms.waterfall_brightness.set_data(if visible {
348            Self::WATERFALL_BRIGHTNESS_WITH_SPECTRUM
349        } else {
350            1.0
351        });
352    }
353
354    /// Returns whether the DDC channel is visible in the waterfall.
355    pub fn is_channel_visible(&self) -> bool {
356        self.enables.channel.get()
357    }
358
359    /// Sets whether the DDC channel is visible in the waterfall.
360    ///
361    /// By default the channel is not visible.
362    pub fn set_channel_visible(&self, visible: bool) {
363        self.enables.channel.set(visible);
364    }
365
366    /// Returns the frequency of the DDC channel in the waterfall.
367    ///
368    /// The frequency is given in Hz of offset with respect to the waterfall
369    /// center frequency.
370    pub fn get_channel_frequency(&self) -> f64 {
371        0.5 * self.uniforms.channel_freq.get_data() as f64 * self.samp_rate
372    }
373
374    /// Sets the frequency of the DDC channel in the waterfall.
375    ///
376    /// This function shall be called when the DDC frequency changes.  The
377    /// `frequency` is given in Hz of offset with respect to the waterfall
378    /// center frequency.
379    ///
380    /// This funciton must also be called when the sample rate is changed using
381    /// `[Self::set_freq_samprate]`.
382    pub fn set_channel_frequency(&mut self, frequency: f64) {
383        // The range for frequency is [-1, 1], so we need to multiply by 2.
384        let frequency = 2.0 * frequency / self.samp_rate;
385        self.uniforms.channel_freq.set_data(frequency as f32);
386    }
387
388    /// Sets the decimation factor of the DDC channel in the waterfall.
389    ///
390    /// This function shall be called when the DDC decimation factor changes.
391    pub fn set_channel_decimation(&mut self, decimation: u32) {
392        self.uniforms
393            .channel_width
394            .set_data(f64::from(decimation).recip() as f32);
395    }
396
397    fn waterfall_object(&self, engine: &mut RenderEngine) -> Result<RenderObject, JsValue> {
398        let program = Self::waterfall_program(engine)?;
399        let vao = self.waterfall_vao(engine, &program)?;
400        Ok(RenderObject {
401            enabled: Rc::clone(&self.enables.waterfall),
402            program,
403            vao,
404            draw_mode: DrawMode::Triangles,
405            draw_num_indices: Rc::new(Cell::new(Self::NUM_INDICES as u32)),
406            draw_offset_elements: Rc::new(Cell::new(0)),
407            uniforms: self.uniforms.waterfall_uniforms(),
408            textures: self.textures.waterfall_textures(),
409        })
410    }
411
412    fn spectrum_background_object(
413        &self,
414        engine: &mut RenderEngine,
415    ) -> Result<RenderObject, JsValue> {
416        let program = Self::spectrum_background_program(engine)?;
417        let vao = self.rectangle_vao(engine, &program)?;
418        Ok(RenderObject {
419            enabled: Rc::clone(&self.enables.spectrum_background),
420            program,
421            vao,
422            draw_mode: DrawMode::Triangles,
423            draw_num_indices: Rc::new(Cell::new(Self::RECTANGLE_NUM_INDICES as u32)),
424            draw_offset_elements: Rc::new(Cell::new(0)),
425            uniforms: Box::new([]),
426            textures: Box::new([]),
427        })
428    }
429
430    fn spectrum_object(&self, engine: &mut RenderEngine) -> Result<RenderObject, JsValue> {
431        let program = Self::spectrum_program(engine)?;
432        let vao = self.spectrum_vao(engine, &program)?;
433        Ok(RenderObject {
434            enabled: Rc::clone(&self.enables.spectrum),
435            program,
436            vao,
437            draw_mode: DrawMode::Triangles,
438            draw_num_indices: Rc::new(Cell::new(6 * (Self::SPECTRUM_POINTS - 1) as u32)),
439            draw_offset_elements: Rc::new(Cell::new(0)),
440            uniforms: self.uniforms.spectrum_uniforms(),
441            textures: self.textures.spectrum_textures(),
442        })
443    }
444
445    fn frequency_labels_object(
446        &mut self,
447        engine: &mut RenderEngine,
448    ) -> Result<(RenderObject, RenderObject), JsValue> {
449        let (vao_labels, vao_ticks) = self.frequency_labels_vao(engine)?;
450
451        let object_labels = RenderObject {
452            enabled: Rc::clone(&self.enables.frequency_labels),
453            program: Rc::clone(&self.programs.frequency_labels),
454            vao: vao_labels,
455            draw_mode: DrawMode::Triangles,
456            draw_num_indices: Rc::clone(&self.freq_num_idx),
457            draw_offset_elements: Rc::new(Cell::new(0)),
458            uniforms: self.uniforms.frequency_labels_uniforms(),
459            textures: self.textures.text_textures(),
460        };
461        let object_ticks = RenderObject {
462            enabled: Rc::clone(&self.enables.frequency_ticks),
463            program: Rc::clone(&self.programs.frequency_ticks),
464            vao: vao_ticks,
465            draw_mode: DrawMode::Lines,
466            draw_num_indices: Rc::clone(&self.freq_num_idx_ticks),
467            draw_offset_elements: Rc::new(Cell::new(0)),
468            uniforms: self.uniforms.frequency_ticks_uniforms(),
469            textures: Box::new([]),
470        };
471        Ok((object_labels, object_ticks))
472    }
473
474    fn horizontal_divisions_object(
475        &mut self,
476        engine: &mut RenderEngine,
477    ) -> Result<RenderObject, JsValue> {
478        let program = Self::horizontal_divisions_program(engine)?;
479        let vao = self.horizontal_divisions_vao(engine, &program)?;
480        Ok(RenderObject {
481            enabled: Rc::clone(&self.enables.spectrum),
482            program,
483            vao,
484            draw_mode: DrawMode::Lines,
485            draw_num_indices: Rc::new(Cell::new(2 * Self::HORIZONTAL_DIVISIONS as u32)),
486            draw_offset_elements: Rc::new(Cell::new(0)),
487            uniforms: self.uniforms.horizontal_divisions_uniforms(),
488            textures: Box::new([]),
489        })
490    }
491
492    fn channel_object(&self, engine: &mut RenderEngine) -> Result<RenderObject, JsValue> {
493        let program = Self::channel_program(engine)?;
494        let vao = self.rectangle_vao(engine, &program)?;
495        Ok(RenderObject {
496            enabled: Rc::clone(&self.enables.channel),
497            program,
498            vao,
499            draw_mode: DrawMode::Triangles,
500            draw_num_indices: Rc::new(Cell::new(Self::RECTANGLE_NUM_INDICES as u32)),
501            draw_offset_elements: Rc::new(Cell::new(0)),
502            uniforms: self.uniforms.channel_uniforms(),
503            textures: Box::new([]),
504        })
505    }
506
507    fn waterfall_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
508        let source = ProgramSource {
509            vertex_shader: r#"#version 300 es
510        in vec2 aPosition;
511        in vec2 aTextureCoordinates;
512        uniform float uTimeTranslation;
513        uniform float uCenterFreq;
514        uniform float uZoom;
515        out vec2 vTextureCoordinates;
516        void main() {
517            gl_Position = vec4(uZoom * (aPosition.x - uCenterFreq),
518                               aPosition.y + uTimeTranslation,
519                               0.0, 1.0);
520            vTextureCoordinates = aTextureCoordinates;
521        }"#,
522            fragment_shader: r#"#version 300 es
523        precision highp float;
524        in vec2 vTextureCoordinates;
525        uniform sampler2D uSampler;
526        uniform sampler2D uColormapSampler;
527        uniform float uWaterfallScaleAdd;
528        uniform float uWaterfallScaleMult;
529        uniform float uWaterfallBrightness;
530        out vec4 color;
531        void main() {
532            float power = texture(uSampler, vTextureCoordinates).x;
533            float normalizedPower = uWaterfallScaleMult * (power + uWaterfallScaleAdd);
534            color = texture(uColormapSampler, vec2(normalizedPower, 0.0))
535                    * vec4(vec3(uWaterfallBrightness), 1.0);
536        }"#,
537        };
538        engine.make_program(source)
539    }
540
541    fn spectrum_background_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
542        let source = ProgramSource {
543            vertex_shader: r#"#version 300 es
544        in vec2 aPosition;
545        void main() {
546            gl_Position = vec4(aPosition.xy, 0.0, 1.0);
547        }"#,
548            fragment_shader: r#"#version 300 es
549        precision highp float;
550        out vec4 color;
551        void main() {
552            color = vec4(vec3(0.1333), 1.0);
553        }"#,
554        };
555        engine.make_program(source)
556    }
557
558    fn spectrum_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
559        let source = ProgramSource {
560            vertex_shader: &format!(
561                r#"#version 300 es
562        in vec2 aPosition;
563        uniform sampler2D uSampler;
564        uniform float uTimeTranslation;
565        uniform float uCenterFreq;
566        uniform float uZoom;
567        uniform float uWaterfallScaleAdd;
568        uniform float uWaterfallScaleMult;
569        uniform float uAspectRatio;
570        uniform float uCanvasWidth;
571        out float vSignedDistance;
572        void main() {{
573            vec2 texturePosition = vec2(0.5 * (aPosition.x + 1.0), 0.25 * uTimeTranslation);
574            float delta = 1.0 / {0:.3};
575            vec2 textureNeighLeft = vec2(texturePosition.x - delta, texturePosition.y);
576            vec2 textureNeighRight = vec2(texturePosition.x + delta, texturePosition.y);
577            float power = texture(uSampler, texturePosition).x;
578            float powerLeft = texture(uSampler, textureNeighLeft).x;
579            float powerRight = texture(uSampler, textureNeighRight).x;
580            float normalizedPower = 2.0 * uWaterfallScaleMult * (power + uWaterfallScaleAdd) - 1.0;
581            float normalizedPowerLeft = 2.0 * uWaterfallScaleMult * (powerLeft + uWaterfallScaleAdd) - 1.0;
582            float normalizedPowerRight = 2.0 * uWaterfallScaleMult * (powerRight + uWaterfallScaleAdd) - 1.0;
583
584            float deltaScreen = uZoom * 2.0 / {0:.3} * uAspectRatio;
585            vec2 leftNormal = normalize(vec2(normalizedPowerLeft - normalizedPower, deltaScreen));
586            vec2 rightNormal = normalize(vec2(normalizedPower - normalizedPowerRight, deltaScreen));
587            vec2 normal = normalize(leftNormal + rightNormal);
588            float maxMiter = 10.0;
589            float miter = min(maxMiter, sqrt(2.0 / (1.0 + dot(leftNormal, rightNormal))));
590
591            vec2 position = vec2(uZoom * (aPosition.x - uCenterFreq), normalizedPower);
592            float thickness = 2.0;
593            vec2 positionExpand = position + thickness / uCanvasWidth * aPosition.y * miter * normal * vec2(1.0, uAspectRatio);
594            gl_Position = vec4(positionExpand, 0.0, 1.0);
595            vSignedDistance = aPosition.y;
596        }}"#,
597                (Self::TEXTURE_WIDTH - 1) as f32
598            ),
599            fragment_shader: r#"#version 300 es
600        precision highp float;
601        in float vSignedDistance;
602        out vec4 color;
603        void main() {
604            float alpha = 1.0 - vSignedDistance * vSignedDistance;
605            color = vec4(alpha);
606        }"#,
607        };
608        engine.make_program(source)
609    }
610
611    fn frequency_ticks_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
612        let source = ProgramSource {
613            vertex_shader: r#"#version 300 es
614        in vec2 aPosition;
615        uniform float uCenterFreq;
616        uniform float uZoom;
617        uniform int uMajorTicksEnd;
618        void main() {
619            bool majorTick = gl_VertexID < uMajorTicksEnd;
620            bool tickStart = (gl_VertexID & 1) == 0;
621            float majorTickOffset = majorTick && !tickStart ? 0.02 : 0.0;
622            gl_Position = vec4(uZoom * (aPosition.x - uCenterFreq),
623                               aPosition.y + majorTickOffset,
624                               0.0, 1.0);
625        }"#,
626            fragment_shader: r#"#version 300 es
627        precision highp float;
628        out vec4 color;
629        void main() {
630            color = vec4(1.0);
631        }"#,
632        };
633        engine.make_program(source)
634    }
635
636    fn frequency_labels_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
637        let source = ProgramSource {
638            vertex_shader: r#"#version 300 es
639        in vec2 aPosition;
640        in vec2 aTextureCoordinates;
641        uniform float uCenterFreq;
642        uniform float uZoom;
643        uniform float uLabelWidth;
644        uniform float uLabelHeight;
645        out vec2 vTextureCoordinates;
646        void main() {
647            float side_offset = (float(gl_VertexID & 1) - 0.5) * uLabelWidth;
648            float vertical_offset = (gl_VertexID & 2) != 0 ? uLabelHeight : 0.0;
649            float center = uZoom * (aPosition.x - uCenterFreq);
650            gl_Position = vec4(center + side_offset,
651                               aPosition.y + vertical_offset,
652                               0.0, 1.0);
653            vTextureCoordinates = aTextureCoordinates;
654        }"#,
655            fragment_shader: r#"#version 300 es
656        precision highp float;
657        in vec2 vTextureCoordinates;
658        uniform sampler2D uSampler;
659        out vec4 color;
660        void main() {
661            color = texture(uSampler, vTextureCoordinates);
662        }"#,
663        };
664        engine.make_program(source)
665    }
666
667    fn horizontal_divisions_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
668        let source = ProgramSource {
669            vertex_shader: r#"#version 300 es
670        in vec2 aPosition;
671        uniform float uWaterfallScaleAdd;
672        uniform float uWaterfallScaleAddFloor;
673        uniform float uWaterfallScaleMult;
674        out float vAlpha;
675        void main() {
676            bool majorDivision = (gl_VertexID >> 1) % 10 == 0;
677            vAlpha = float(majorDivision) * 0.4 + 0.4;
678            // power is in units of 10 dB, because the conversion to power does not include
679            // the 10 factor in the 10*log10 formula
680            //
681            // subtracting uWaterfallScaleAddFloor + 1.0 ensures that power >= 0 for most of
682            // the horizontal divisions, so that few of them are hidden below the lower edge
683            // of the screen
684            float power = aPosition.y - uWaterfallScaleAddFloor - 1.0;
685            float normalizedPower = 2.0 * uWaterfallScaleMult * (power + uWaterfallScaleAdd) - 1.0;
686            gl_Position = vec4(aPosition.x, normalizedPower, 0.0, 1.0);
687        }"#,
688            fragment_shader: r#"#version 300 es
689        precision highp float;
690        in float vAlpha;
691        out vec4 color;
692        void main() {
693            color = vec4(vAlpha);
694        }"#,
695        };
696        engine.make_program(source)
697    }
698
699    fn channel_program(engine: &RenderEngine) -> Result<Rc<WebGlProgram>, JsValue> {
700        let source = ProgramSource {
701            vertex_shader: r#"#version 300 es
702        in vec2 aPosition;
703        uniform float uCenterFreq;
704        uniform float uZoom;
705        uniform float uChannelFreq;
706        uniform float uChannelWidth;
707        void main() {
708            gl_Position = vec4(
709                uZoom * (aPosition.x * uChannelWidth + uChannelFreq - uCenterFreq),
710                aPosition.y, 0.0, 1.0);
711        }"#,
712            fragment_shader: r#"#version 300 es
713        precision highp float;
714        out vec4 color;
715        void main() {
716            color = vec4(0.0, 0.0, 0.0, 0.33);
717        }"#,
718        };
719
720        engine.make_program(source)
721    }
722
723    fn waterfall_vao(
724        &self,
725        engine: &mut RenderEngine,
726        program: &WebGlProgram,
727    ) -> Result<Rc<WebGlVertexArrayObject>, JsValue> {
728        let vertices: [f32; 16] = [
729            -1.0, -1.0, // A
730            1.0, -1.0, // B
731            -1.0, -5.0, // C
732            1.0, -5.0, // D
733            -1.0, 1.0, // E
734            1.0, 1.0, // F
735            -1.0, -1.0, // G
736            1.0, -1.0, // H
737        ];
738        let indices: [u16; Self::NUM_INDICES] = [
739            0, 1, 2, // ABC
740            1, 2, 3, // BCD
741            4, 5, 6, // EFG
742            5, 6, 7, // FGH
743        ];
744        let texture_coordinates: [f32; 16] = [
745            0.0, 0.0, // A
746            1.0, 0.0, // B
747            0.0, 1.0, // C
748            1.0, 1.0, // D
749            0.0, 0.5, // E
750            1.0, 0.5, // F
751            0.0, 1.0, // G
752            1.0, 1.0, // H
753        ];
754        let vao = engine
755            .create_vao()?
756            .create_array_buffer(program, "aPosition", 2, &vertices)?
757            .create_array_buffer(program, "aTextureCoordinates", 2, &texture_coordinates)?
758            .create_element_array_buffer(&indices)?
759            .build();
760        Ok(vao)
761    }
762
763    fn spectrum_vao(
764        &self,
765        engine: &mut RenderEngine,
766        program: &WebGlProgram,
767    ) -> Result<Rc<WebGlVertexArrayObject>, JsValue> {
768        let step = 2.0 / (Self::SPECTRUM_POINTS - 1) as f64;
769        let vertices: Vec<f32> = (0..Self::SPECTRUM_POINTS)
770            .flat_map(|j| {
771                let x = (-1.0 + step * j as f64) as f32;
772                [x, -1.0, x, 1.0]
773            })
774            .collect();
775        let indices: Vec<u16> = (0..2 * u16::try_from(Self::SPECTRUM_POINTS).unwrap() - 2)
776            .flat_map(|j| [j, j + 1, j + 2])
777            .collect();
778        let vao = engine
779            .create_vao()?
780            .create_array_buffer(program, "aPosition", 2, &vertices)?
781            .create_element_array_buffer(&indices)?
782            .build();
783        Ok(vao)
784    }
785
786    fn frequency_labels_vao(
787        &mut self,
788        engine: &mut RenderEngine,
789    ) -> Result<(Rc<WebGlVertexArrayObject>, Rc<WebGlVertexArrayObject>), JsValue> {
790        // Measure the width of a frequency label to determine the width of the
791        // bounding box for the labels. We use 0000.000 as a "template label", since
792        // we don't really know what labels we will use yet.
793        const TEXT_HEIGHT_PX: u32 = 16;
794        let boundingbox_margin_factor = 1.1;
795        let width_boundingbox = boundingbox_margin_factor
796            * engine.text_renderer_text_width("0000.000", TEXT_HEIGHT_PX)?;
797        let mut max_depth_labels = 4;
798        let mut max_depth = max_depth_labels + 2;
799
800        let s = (self.samp_rate * 0.5 * width_boundingbox as f64).log10();
801        let s2 = s.ceil();
802        let s3 = s2 - 2.0_f64.log10();
803        let (mut step, mut radix5) = if s3 >= s {
804            (10.0_f64.powf(s3), true)
805        } else {
806            (10.0_f64.powf(s2), false)
807        };
808        let minfreq = self.center_freq - 0.5 * self.samp_rate;
809        let maxfreq = self.center_freq + 0.5 * self.samp_rate;
810        let start = (minfreq / step).floor() as i32 - 1;
811        let stop = (maxfreq / step).ceil() as i32 + 1;
812        let mut freqs = (start..=stop).map(|k| k as f64 * step).collect::<Vec<_>>();
813        let mut nfreqs = Vec::with_capacity(max_depth + 1);
814        nfreqs.push(freqs.len());
815        let mut freq_radixes = Vec::with_capacity(max_depth);
816        let step_factor = 0.5 * width_boundingbox as f64 * self.samp_rate;
817        let mut zoom_levels = vec![(step_factor / step) as f32];
818        for depth in 0..max_depth {
819            step /= if radix5 { 5.0 } else { 2.0 };
820            freq_radixes.push(if radix5 { 5 } else { 2 });
821            for j in 0..freqs.len() {
822                let f = freqs[j];
823                if radix5 {
824                    for &mult in &[-2.0, -1.0, 1.0, 2.0] {
825                        freqs.push(f + mult * step);
826                    }
827                } else {
828                    if j == 0 {
829                        freqs.push(f - step);
830                    }
831                    freqs.push(f + step);
832                }
833            }
834            radix5 = !radix5;
835            nfreqs.push(freqs.len());
836            if depth < max_depth_labels - 1 {
837                zoom_levels.push((step_factor / step) as f32);
838            }
839        }
840
841        // cull frequencies outside passband
842        let freqs_all = freqs;
843        let mut freqs = Vec::with_capacity(freqs_all.len());
844        let mut j = 0;
845        for nf in nfreqs.iter_mut() {
846            while j < *nf {
847                if freqs_all[j] > minfreq && freqs_all[j] < maxfreq {
848                    freqs.push(freqs_all[j]);
849                }
850                j += 1;
851            }
852            *nf = freqs.len();
853        }
854        drop(freqs_all);
855
856        // We need to have 2 vertices per frequency for the ticks, and we cannot
857        // have more than 1 << 16 vertices, since we index them with a u16.
858        //
859        // Limit depth of frequencies to guarantee that this happens. Typically,
860        // no limiting needs to be done. The limiting is only used at high resolutions.
861        if 2 * freqs.len() > (1 << 16) {
862            let (depth, ndepth) = nfreqs
863                .iter()
864                .copied()
865                .enumerate()
866                .filter(|&(_, n)| 2 * n <= 1 << 16)
867                .next_back()
868                .unwrap();
869            freqs.truncate(ndepth);
870            nfreqs.truncate(depth + 1);
871            max_depth = depth;
872            assert_eq!(nfreqs.len(), max_depth + 1);
873            max_depth_labels = if max_depth > 2 { max_depth - 2 } else { 1 };
874            zoom_levels.truncate(max_depth_labels);
875        }
876        assert!(2 * freqs.len() <= (1 << 16));
877
878        let freqs_labels = &freqs[..nfreqs[max_depth_labels - 1]];
879        // We need to have 4 vertices per frequency label for the labels, and we
880        // cannot have more than 1 << 16 vertices, since we index them with a
881        // u16.
882        assert!(4 * freqs_labels.len() <= (1 << 16));
883
884        let y = -0.96;
885        let vertices_labels = freqs_labels
886            .iter()
887            .flat_map(|f| {
888                let x = (2.0 * (f - self.center_freq) / self.samp_rate) as f32;
889                [x, y, x, y, x, y, x, y]
890            })
891            .collect::<Vec<f32>>();
892
893        let vertices_ticks = freqs
894            .iter()
895            .flat_map(|f| {
896                let x = (2.0 * (f - self.center_freq) / self.samp_rate) as f32;
897                [x, -1.0, x, -0.98]
898            })
899            .collect::<Vec<f32>>();
900
901        let indices_labels = freqs_labels
902            .iter()
903            .enumerate()
904            .flat_map(|(j, _)| {
905                let a = 4 * j as u16;
906                [a, a + 1, a + 2, a + 1, a + 2, a + 3]
907            })
908            .collect::<Vec<u16>>();
909
910        let indices_ticks = (0..vertices_ticks.len())
911            .map(|x| x as u16)
912            .collect::<Vec<u16>>();
913
914        let texture_texts = freqs_labels
915            .iter()
916            .map(|f| format!("{:.03}", f * 1e-6))
917            .collect::<Vec<_>>();
918        let texts_dimensions =
919            engine.render_texts_to_texture(&self.textures.text, &texture_texts, TEXT_HEIGHT_PX)?;
920
921        let vao_labels = match self.vaos.frequency_labels.take() {
922            Some(vao) => engine.modify_vao(vao),
923            None => engine.create_vao()?,
924        }
925        .create_array_buffer(
926            &self.programs.frequency_labels,
927            "aPosition",
928            2,
929            &vertices_labels,
930        )?
931        .create_array_buffer(
932            &self.programs.frequency_labels,
933            "aTextureCoordinates",
934            2,
935            &texts_dimensions.texture_coordinates,
936        )?
937        .create_element_array_buffer(&indices_labels)?
938        .build();
939        self.vaos.frequency_labels = Some(Rc::clone(&vao_labels));
940
941        let vao_ticks = match self.vaos.frequency_ticks.take() {
942            Some(vao) => engine.modify_vao(vao),
943            None => engine.create_vao()?,
944        }
945        .create_array_buffer(
946            &self.programs.frequency_ticks,
947            "aPosition",
948            2,
949            &vertices_ticks,
950        )?
951        .create_element_array_buffer(&indices_ticks)?
952        .build();
953        self.vaos.frequency_ticks = Some(Rc::clone(&vao_ticks));
954
955        self.num_freqs = nfreqs;
956        self.freq_radixes = freq_radixes;
957        self.zoom_levels = zoom_levels;
958        // Update zoom-related variables.
959        self.set_zoom(self.get_zoom());
960        self.uniforms
961            .freq_labels_width
962            .set_data(texts_dimensions.text_width);
963        self.uniforms
964            .freq_labels_height
965            .set_data(texts_dimensions.text_height);
966
967        Ok((vao_labels, vao_ticks))
968    }
969
970    fn horizontal_divisions_vao(
971        &self,
972        engine: &mut RenderEngine,
973        program: &WebGlProgram,
974    ) -> Result<Rc<WebGlVertexArrayObject>, JsValue> {
975        // 1 dB separation in the divisions. The power is in units of 10 dB, so
976        // we space the y coordinates by 0.1
977        let vertices: Vec<f32> = (0..Self::HORIZONTAL_DIVISIONS)
978            .flat_map(|j| [-1.0, 0.1 * j as f32, 1.0, 0.1 * j as f32])
979            .collect();
980        let indices: Vec<u16> = (0..vertices.len()).map(|x| x as u16).collect();
981        let vao = engine
982            .create_vao()?
983            .create_array_buffer(program, "aPosition", 2, &vertices)?
984            .create_element_array_buffer(&indices)?
985            .build();
986        Ok(vao)
987    }
988
989    fn rectangle_vao(
990        &self,
991        engine: &mut RenderEngine,
992        program: &WebGlProgram,
993    ) -> Result<Rc<WebGlVertexArrayObject>, JsValue> {
994        let vertices: [f32; 8] = [
995            -1.0, -1.0, // A
996            1.0, -1.0, // B
997            1.0, 1.0, // C
998            -1.0, 1.0, // D
999        ];
1000        let indices: [u16; Self::RECTANGLE_NUM_INDICES] = [
1001            0, 1, 2, // ABC
1002            2, 3, 0, // CDA
1003        ];
1004        let vao = engine
1005            .create_vao()?
1006            .create_array_buffer(program, "aPosition", 2, &vertices)?
1007            .create_element_array_buffer(&indices)?
1008            .build();
1009        Ok(vao)
1010    }
1011
1012    /// Loads a new colormap for the waterfall.
1013    ///
1014    /// The `colormap` is given as a slice whose length is a multiple of 3 and
1015    /// contains the concatenation of the RGB values of the list of colors that
1016    /// defines the colormap (typically, 256 colors are used for the colormap,
1017    /// so the length of the colormap slice is `3 * 256`).
1018    pub fn load_colormap(&self, engine: &mut RenderEngine, colormap: &[u8]) -> Result<(), JsValue> {
1019        self.textures.load_colormap(engine, colormap)
1020    }
1021
1022    fn load_waterfall(&self, engine: &mut RenderEngine) -> Result<(), JsValue> {
1023        engine.texture_image::<R16f>(
1024            &self.textures.waterfall,
1025            &self.texture_map,
1026            Self::TEXTURE_WIDTH,
1027            Self::TEXTURE_HEIGHT,
1028        )
1029    }
1030
1031    /// Sets the zoom level of the waterfall.
1032    pub fn set_zoom(&mut self, zoom: f32) {
1033        self.uniforms.zoom.set_data(zoom);
1034        // TODO: improve search algorithm
1035        let mut k = 0;
1036        for (j, &z) in self.zoom_levels.iter().enumerate() {
1037            if z <= zoom {
1038                k = j;
1039            } else {
1040                break;
1041            }
1042        }
1043        self.freq_num_idx.set(6 * self.num_freqs[k] as u32);
1044        let next = if self.freq_radixes[k] == 2 { k + 1 } else { k };
1045        self.freq_num_idx_ticks
1046            .set(2 * self.num_freqs[next + 1] as u32);
1047        self.uniforms
1048            .major_ticks_end
1049            .set_data(2 * self.num_freqs[next] as i32);
1050    }
1051
1052    /// Returns the current zoom level of the waterfall.
1053    pub fn get_zoom(&self) -> f32 {
1054        self.uniforms.zoom.get_data()
1055    }
1056
1057    /// Sets the center frequency of the waterfall.
1058    ///
1059    /// This function is used when dragging the waterfall to scroll in
1060    /// frequency. The `frequency` does not use physical units, but rather has a
1061    /// value between -1 and 1 that corresponds to screen coordinates.
1062    pub fn set_center_frequency(&mut self, frequency: f32) {
1063        self.uniforms.center_freq.set_data(frequency);
1064    }
1065
1066    /// Returns the current center frequency of the waterfall.
1067    ///
1068    /// The frequency is defined as in the
1069    /// [`set_center_frequency`](Waterfall::set_center_frequency) function.
1070    pub fn get_center_frequency(&self) -> f32 {
1071        self.uniforms.center_freq.get_data()
1072    }
1073
1074    /// Sets the waterfall minimum power value.
1075    ///
1076    /// The minimum value is used to scale the colormap. The `value` is in dB
1077    /// units.
1078    pub fn set_waterfall_min(&mut self, value: f32) {
1079        self.waterfall_min = value;
1080        self.update_waterfall_scale();
1081    }
1082
1083    /// Sets the waterfall maximum power value.
1084    ///
1085    /// The maximum value is used to scale the colormap. The `value` is in dB
1086    /// units.
1087    pub fn set_waterfall_max(&mut self, value: f32) {
1088        self.waterfall_max = value;
1089        self.update_waterfall_scale();
1090    }
1091
1092    /// Returns the value of the uniform associated with the DDC channel
1093    /// frequency.
1094    pub fn get_channel_frequency_uniform(&self) -> f32 {
1095        self.uniforms.channel_freq.get_data()
1096    }
1097
1098    /// Returns the value of the uniform associated with the DDC channel
1099    /// width.
1100    pub fn get_channel_width_uniform(&self) -> f32 {
1101        self.uniforms.channel_width.get_data()
1102    }
1103
1104    fn update_waterfall_scale(&mut self) {
1105        let waterfall_scale_add = -self.waterfall_min * 0.1;
1106        self.uniforms
1107            .waterfall_scale_add
1108            .set_data(waterfall_scale_add);
1109        self.uniforms
1110            .waterfall_scale_add_floor
1111            .set_data(waterfall_scale_add.floor());
1112        self.uniforms
1113            .waterfall_scale_mult
1114            .set_data(10.0 / (self.waterfall_max - self.waterfall_min));
1115    }
1116
1117    /// Sets the waterfall update rate.
1118    ///
1119    /// The waterfall update rate is used for smooth animation interpolation
1120    /// between waterfall lines. If the rate is not set, smooth animation
1121    /// interpolation is not used.
1122    ///
1123    /// The rate is indicated in Hz (updates per second).
1124    pub fn set_waterfall_update_rate(&mut self, rate: f32) {
1125        self.waterfall_rate = Some(rate);
1126    }
1127}
1128
1129impl Textures {
1130    fn new(engine: &mut RenderEngine) -> Result<Textures, JsValue> {
1131        // We do not use mipmaps for the waterfall texture, to avoid having to
1132        // regenerate the mipmap every time that a small piece of the texture is
1133        // updated.
1134        let waterfall = engine
1135            .create_texture()?
1136            .set_parameter(TextureParameter::MagFilter(TextureMagFilter::Linear))
1137            .set_parameter(TextureParameter::MinFilter(TextureMinFilter::Linear))
1138            .set_parameter(TextureParameter::WrapS(TextureWrap::ClampToEdge))
1139            .set_parameter(TextureParameter::WrapT(TextureWrap::ClampToEdge))
1140            .build();
1141
1142        let colormap = engine
1143            .create_texture()?
1144            .set_parameter(TextureParameter::MagFilter(TextureMagFilter::Linear))
1145            .set_parameter(TextureParameter::MinFilter(
1146                TextureMinFilter::LinearMipmapLinear,
1147            ))
1148            .set_parameter(TextureParameter::WrapS(TextureWrap::ClampToEdge))
1149            .set_parameter(TextureParameter::WrapT(TextureWrap::ClampToEdge))
1150            .build();
1151
1152        let text = engine
1153            .create_texture()?
1154            .set_parameter(TextureParameter::MagFilter(TextureMagFilter::Linear))
1155            .set_parameter(TextureParameter::MinFilter(TextureMinFilter::Linear))
1156            .set_parameter(TextureParameter::WrapS(TextureWrap::ClampToEdge))
1157            .set_parameter(TextureParameter::WrapT(TextureWrap::ClampToEdge))
1158            .build();
1159
1160        Ok(Textures {
1161            waterfall,
1162            colormap,
1163            text,
1164        })
1165    }
1166
1167    fn load_colormap(&self, engine: &mut RenderEngine, colormap: &[u8]) -> Result<(), JsValue> {
1168        engine.texture_image::<Rgb>(&self.colormap, colormap, colormap.len() / 3, 1)?;
1169        engine.generate_mipmap(&self.colormap);
1170        Ok(())
1171    }
1172
1173    fn waterfall_textures(&self) -> Box<[Texture]> {
1174        Box::new([
1175            Texture::new(String::from("uSampler"), Rc::clone(&self.waterfall)),
1176            Texture::new(String::from("uColormapSampler"), Rc::clone(&self.colormap)),
1177        ])
1178    }
1179
1180    fn spectrum_textures(&self) -> Box<[Texture]> {
1181        Box::new([Texture::new(
1182            String::from("uSampler"),
1183            Rc::clone(&self.waterfall),
1184        )])
1185    }
1186
1187    fn text_textures(&self) -> Box<[Texture]> {
1188        Box::new([Texture::new(
1189            String::from("uSampler"),
1190            Rc::clone(&self.text),
1191        )])
1192    }
1193}
1194
1195impl Uniforms {
1196    fn new() -> Uniforms {
1197        Uniforms {
1198            time_translation: Rc::new(Uniform::new(String::from("uTimeTranslation"), 0.0)),
1199            center_freq: Rc::new(Uniform::new(String::from("uCenterFreq"), 0.0)),
1200            zoom: Rc::new(Uniform::new(String::from("uZoom"), 1.0)),
1201            waterfall_scale_add: Rc::new(Uniform::new(String::from("uWaterfallScaleAdd"), 0.0)),
1202            waterfall_scale_add_floor: Rc::new(Uniform::new(
1203                String::from("uWaterfallScaleAddFloor"),
1204                0.0,
1205            )),
1206            waterfall_scale_mult: Rc::new(Uniform::new(String::from("uWaterfallScaleMult"), 0.0)),
1207            waterfall_brightness: Rc::new(Uniform::new(String::from("uWaterfallBrightness"), 1.0)),
1208            aspect_ratio: Rc::new(Uniform::new(String::from("uAspectRatio"), 0.0)),
1209            canvas_width: Rc::new(Uniform::new(String::from("uCanvasWidth"), 0.0)),
1210            freq_labels_width: Rc::new(Uniform::new(
1211                String::from("uLabelWidth"),
1212                Default::default(),
1213            )),
1214            freq_labels_height: Rc::new(Uniform::new(
1215                String::from("uLabelHeight"),
1216                Default::default(),
1217            )),
1218            major_ticks_end: Rc::new(Uniform::new(
1219                String::from("uMajorTicksEnd"),
1220                Default::default(),
1221            )),
1222            channel_freq: Rc::new(Uniform::new(String::from("uChannelFreq"), 0.0)),
1223            channel_width: Rc::new(Uniform::new(String::from("uChannelWidth"), 0.1)),
1224        }
1225    }
1226
1227    fn waterfall_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1228        Box::new([
1229            Rc::clone(&self.time_translation) as _,
1230            Rc::clone(&self.center_freq) as _,
1231            Rc::clone(&self.zoom) as _,
1232            Rc::clone(&self.waterfall_scale_add) as _,
1233            Rc::clone(&self.waterfall_scale_mult) as _,
1234            Rc::clone(&self.waterfall_brightness) as _,
1235        ])
1236    }
1237
1238    fn spectrum_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1239        Box::new([
1240            Rc::clone(&self.time_translation) as _,
1241            Rc::clone(&self.center_freq) as _,
1242            Rc::clone(&self.zoom) as _,
1243            Rc::clone(&self.waterfall_scale_add) as _,
1244            Rc::clone(&self.waterfall_scale_mult) as _,
1245            Rc::clone(&self.aspect_ratio) as _,
1246            Rc::clone(&self.canvas_width) as _,
1247        ])
1248    }
1249
1250    fn frequency_ticks_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1251        Box::new([
1252            Rc::clone(&self.center_freq) as _,
1253            Rc::clone(&self.zoom) as _,
1254            Rc::clone(&self.major_ticks_end) as _,
1255        ])
1256    }
1257
1258    fn frequency_labels_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1259        Box::new([
1260            Rc::clone(&self.center_freq) as _,
1261            Rc::clone(&self.zoom) as _,
1262            Rc::clone(&self.freq_labels_width) as _,
1263            Rc::clone(&self.freq_labels_height) as _,
1264        ])
1265    }
1266
1267    fn horizontal_divisions_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1268        Box::new([
1269            Rc::clone(&self.waterfall_scale_add) as _,
1270            Rc::clone(&self.waterfall_scale_add_floor) as _,
1271            Rc::clone(&self.waterfall_scale_mult) as _,
1272        ])
1273    }
1274
1275    fn channel_uniforms(&self) -> Box<[Rc<dyn UniformValue>]> {
1276        Box::new([
1277            Rc::clone(&self.center_freq) as _,
1278            Rc::clone(&self.zoom) as _,
1279            Rc::clone(&self.channel_freq) as _,
1280            Rc::clone(&self.channel_width) as _,
1281        ])
1282    }
1283}
1284
1285impl Default for Uniforms {
1286    fn default() -> Uniforms {
1287        Uniforms::new()
1288    }
1289}