150 lines
4.1 KiB
Rust
150 lines
4.1 KiB
Rust
use ratatui::buffer::Buffer;
|
|
use ratatui::layout::Rect;
|
|
use ratatui::style::Color;
|
|
use ratatui::widgets::Widget;
|
|
|
|
#[allow(dead_code)]
|
|
pub enum Orientation {
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
pub struct Scope<'a> {
|
|
data: &'a [f32],
|
|
orientation: Orientation,
|
|
color: Color,
|
|
gain: f32,
|
|
}
|
|
|
|
impl<'a> Scope<'a> {
|
|
pub fn new(data: &'a [f32]) -> Self {
|
|
Self {
|
|
data,
|
|
orientation: Orientation::Horizontal,
|
|
color: Color::Green,
|
|
gain: 1.0,
|
|
}
|
|
}
|
|
|
|
pub fn orientation(mut self, o: Orientation) -> Self {
|
|
self.orientation = o;
|
|
self
|
|
}
|
|
|
|
pub fn color(mut self, c: Color) -> Self {
|
|
self.color = c;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Widget for Scope<'_> {
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
if area.width == 0 || area.height == 0 || self.data.is_empty() {
|
|
return;
|
|
}
|
|
|
|
match self.orientation {
|
|
Orientation::Horizontal => {
|
|
render_horizontal(self.data, area, buf, self.color, self.gain)
|
|
}
|
|
Orientation::Vertical => render_vertical(self.data, area, buf, self.color, self.gain),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render_horizontal(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gain: f32) {
|
|
let width = area.width as usize;
|
|
let height = area.height as usize;
|
|
let fine_width = width * 2;
|
|
let fine_height = height * 4;
|
|
|
|
let mut patterns = vec![0u8; width * height];
|
|
|
|
for fine_x in 0..fine_width {
|
|
let sample_idx = (fine_x * data.len()) / fine_width;
|
|
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);
|
|
|
|
let fine_y = ((1.0 - sample) * 0.5 * (fine_height - 1) as f32).round() as usize;
|
|
let fine_y = fine_y.min(fine_height - 1);
|
|
|
|
let char_x = fine_x / 2;
|
|
let char_y = fine_y / 4;
|
|
let dot_x = fine_x % 2;
|
|
let dot_y = fine_y % 4;
|
|
|
|
let bit = match (dot_x, dot_y) {
|
|
(0, 0) => 0x01,
|
|
(0, 1) => 0x02,
|
|
(0, 2) => 0x04,
|
|
(0, 3) => 0x40,
|
|
(1, 0) => 0x08,
|
|
(1, 1) => 0x10,
|
|
(1, 2) => 0x20,
|
|
(1, 3) => 0x80,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
patterns[char_y * width + char_x] |= bit;
|
|
}
|
|
|
|
for cy in 0..height {
|
|
for cx in 0..width {
|
|
let pattern = patterns[cy * width + cx];
|
|
if pattern != 0 {
|
|
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
|
buf[(area.x + cx as u16, area.y + cy as u16)]
|
|
.set_char(ch)
|
|
.set_fg(color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render_vertical(data: &[f32], area: Rect, buf: &mut Buffer, color: Color, gain: f32) {
|
|
let width = area.width as usize;
|
|
let height = area.height as usize;
|
|
let fine_width = width * 2;
|
|
let fine_height = height * 4;
|
|
|
|
let mut patterns = vec![0u8; width * height];
|
|
|
|
for fine_y in 0..fine_height {
|
|
let sample_idx = (fine_y * data.len()) / fine_height;
|
|
let sample = (data.get(sample_idx).copied().unwrap_or(0.0) * gain).clamp(-1.0, 1.0);
|
|
|
|
let fine_x = ((sample + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
|
|
let fine_x = fine_x.min(fine_width - 1);
|
|
|
|
let char_x = fine_x / 2;
|
|
let char_y = fine_y / 4;
|
|
let dot_x = fine_x % 2;
|
|
let dot_y = fine_y % 4;
|
|
|
|
let bit = match (dot_x, dot_y) {
|
|
(0, 0) => 0x01,
|
|
(0, 1) => 0x02,
|
|
(0, 2) => 0x04,
|
|
(0, 3) => 0x40,
|
|
(1, 0) => 0x08,
|
|
(1, 1) => 0x10,
|
|
(1, 2) => 0x20,
|
|
(1, 3) => 0x80,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
patterns[char_y * width + char_x] |= bit;
|
|
}
|
|
|
|
for cy in 0..height {
|
|
for cx in 0..width {
|
|
let pattern = patterns[cy * width + cx];
|
|
if pattern != 0 {
|
|
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
|
buf[(area.x + cx as u16, area.y + cy as u16)]
|
|
.set_char(ch)
|
|
.set_fg(color);
|
|
}
|
|
}
|
|
}
|
|
}
|