const EIGHTHS: [char; 9] = [
' ', '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', '\u{2586}', '\u{2587}',
'\u{2588}',
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fill {
Empty,
Rms,
Peak,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cell {
pub glyph: char,
pub fg: Fill,
pub behind: Fill,
}
pub fn envelope_rows(columns: &[(f32, f32)], height: usize) -> Vec<Vec<Cell>> {
let eighths = |v: f32| (v.clamp(0.0, 1.0) * (8 * height) as f32).round() as usize;
let steps: Vec<(usize, usize)> = columns
.iter()
.map(|&(rms, peak)| {
let rms = eighths(rms);
(rms, eighths(peak).max(rms))
})
.collect();
(0..height)
.map(|row| {
let below = 8 * (height - 1 - row);
steps
.iter()
.map(|&(rms, peak)| {
let cell = |glyph, fg, behind| Cell { glyph, fg, behind };
if rms >= below + 8 {
cell(EIGHTHS[8], Fill::Rms, Fill::Empty)
} else if rms > below {
let behind = if peak >= below + 8 {
Fill::Peak
} else {
Fill::Empty
};
cell(EIGHTHS[rms - below], Fill::Rms, behind)
} else if peak > below {
cell(EIGHTHS[(peak - below).min(8)], Fill::Peak, Fill::Empty)
} else {
cell(' ', Fill::Empty, Fill::Empty)
}
})
.collect()
})
.collect()
}
pub fn braille_rows(extents: &[(f32, f32)], height: usize) -> Vec<String> {
let dots = 4 * height;
let row_of = |v: f32| ((1.0 - v.clamp(-1.0, 1.0)) / 2.0 * (dots - 1) as f32).round() as usize;
const BITS: [[u32; 4]; 2] = [[0x01, 0x02, 0x04, 0x40], [0x08, 0x10, 0x20, 0x80]];
let cells = extents.len().div_ceil(2);
let mut grid = vec![vec![0u32; cells]; height];
for (i, &(lo, hi)) in extents.iter().enumerate() {
if lo > hi {
continue;
}
for dot in row_of(hi)..=row_of(lo) {
grid[dot / 4][i / 2] |= BITS[i % 2][dot % 4];
}
}
grid.iter()
.map(|row| {
row.iter()
.map(|&bits| char::from_u32(0x2800 + bits).expect("in the Braille block"))
.collect()
})
.collect()
}
pub const UPPER_HALF: char = '\u{2580}';
const SHADES: [char; 5] = [' ', '\u{2591}', '\u{2592}', '\u{2593}', '\u{2588}'];
pub fn spectrum_rows(columns: &[Vec<f32>], height: usize) -> Vec<Vec<Option<(f32, f32)>>> {
(0..height)
.map(|row| {
let upper = 2 * (height - row) - 1;
columns
.iter()
.map(|levels| Some((*levels.get(upper)?, *levels.get(upper - 1)?)))
.collect()
})
.collect()
}
pub fn shade(level: f32) -> char {
SHADES[(level.clamp(0.0, 1.0) * (SHADES.len() - 1) as f32).round() as usize]
}