1use 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
16pub 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 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 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 const NUM_INDICES: usize = 12;
93
94 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 const HORIZONTAL_DIVISIONS: usize = 200;
105
106 const WATERFALL_BRIGHTNESS_WITH_SPECTRUM: f32 = 0.7;
108
109 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 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 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 for x in spectrum_texture.iter_mut() {
185 if *x != 0.0 {
188 *x = x.log10();
189 }
190 }
191 }
192
193 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 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 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 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 let start_wrap = self.last_draw_line + 1;
223 if start_wrap != Self::TEXTURE_HEIGHT {
224 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 pub fn resize_canvas(&mut self, engine: &mut RenderEngine) -> Result<(), JsValue> {
263 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 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 self.frequency_labels_vao(engine)?;
292 }
293 Ok(())
294 }
295
296 fn actual_center_freq(center_freq: f64, samp_rate: f64) -> f64 {
297 let fft_bin_hz = samp_rate / Self::TEXTURE_WIDTH as f64;
301 center_freq - 0.5 * fft_bin_hz
302 }
303
304 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 let fft_bin_hz = samp_rate / Self::TEXTURE_WIDTH as f64;
319 center_freq + 0.5 * fft_bin_hz
320 }
321
322 pub fn is_waterfall_visible(&self) -> bool {
324 self.enables.waterfall.get()
325 }
326
327 pub fn set_waterfall_visible(&self, visible: bool) {
331 self.enables.waterfall.set(visible);
332 self.enables.spectrum_background.set(!visible);
333 }
334
335 pub fn is_spectrum_visible(&self) -> bool {
337 self.enables.spectrum.get()
338 }
339
340 pub fn set_spectrum_visible(&self, visible: bool) {
344 self.enables.spectrum.set(visible);
345 self.uniforms.waterfall_brightness.set_data(if visible {
348 Self::WATERFALL_BRIGHTNESS_WITH_SPECTRUM
349 } else {
350 1.0
351 });
352 }
353
354 pub fn is_channel_visible(&self) -> bool {
356 self.enables.channel.get()
357 }
358
359 pub fn set_channel_visible(&self, visible: bool) {
363 self.enables.channel.set(visible);
364 }
365
366 pub fn get_channel_frequency(&self) -> f64 {
371 0.5 * self.uniforms.channel_freq.get_data() as f64 * self.samp_rate
372 }
373
374 pub fn set_channel_frequency(&mut self, frequency: f64) {
383 let frequency = 2.0 * frequency / self.samp_rate;
385 self.uniforms.channel_freq.set_data(frequency as f32);
386 }
387
388 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, 1.0, -1.0, -1.0, -5.0, 1.0, -5.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, ];
738 let indices: [u16; Self::NUM_INDICES] = [
739 0, 1, 2, 1, 2, 3, 4, 5, 6, 5, 6, 7, ];
744 let texture_coordinates: [f32; 16] = [
745 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.5, 1.0, 0.5, 0.0, 1.0, 1.0, 1.0, ];
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 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 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 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 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 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 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, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, ];
1000 let indices: [u16; Self::RECTANGLE_NUM_INDICES] = [
1001 0, 1, 2, 2, 3, 0, ];
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 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 pub fn set_zoom(&mut self, zoom: f32) {
1033 self.uniforms.zoom.set_data(zoom);
1034 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 pub fn get_zoom(&self) -> f32 {
1054 self.uniforms.zoom.get_data()
1055 }
1056
1057 pub fn set_center_frequency(&mut self, frequency: f32) {
1063 self.uniforms.center_freq.set_data(frequency);
1064 }
1065
1066 pub fn get_center_frequency(&self) -> f32 {
1071 self.uniforms.center_freq.get_data()
1072 }
1073
1074 pub fn set_waterfall_min(&mut self, value: f32) {
1079 self.waterfall_min = value;
1080 self.update_waterfall_scale();
1081 }
1082
1083 pub fn set_waterfall_max(&mut self, value: f32) {
1088 self.waterfall_max = value;
1089 self.update_waterfall_scale();
1090 }
1091
1092 pub fn get_channel_frequency_uniform(&self) -> f32 {
1095 self.uniforms.channel_freq.get_data()
1096 }
1097
1098 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 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 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}