use crate::color_scheme::{default_color_scheme_data, get_color_count, Rgba};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BarStyle {
Solid, Gradient, Segmented, }
pub struct Spectrum {
pub num_bins: u32,
pub width: u32,
pub height: u32,
pub max_magnitude: f32,
pub min_magnitude: f32,
pub buffer: Vec<f32>,
pub peak_values: Vec<f32>,
pub style: BarStyle,
pub peak_decay: f32,
pub show_peaks: bool,
pub bar_width_factor: f32,
pub background_color: Rgba,
}
impl Spectrum {
pub fn new(bins: u32, width: u32, height: u32) -> Self {
Self {
num_bins: bins,
width,
height,
max_magnitude: f32::NEG_INFINITY,
min_magnitude: f32::INFINITY,
buffer: vec![0.0; bins as usize],
peak_values: vec![0.0; bins as usize],
style: BarStyle::Solid,
peak_decay: 0.0,
show_peaks: false,
bar_width_factor: 0.8,
background_color: [0, 0, 0, 0], }
}
pub fn set_background_color(&mut self, r: u8, g: u8, b: u8, a: u8) {
self.background_color = [r, g, b, a];
}
pub fn update(&mut self, magnitudes: &[f32]) {
let len = magnitudes.len().min(self.buffer.len());
for i in 0..len {
self.buffer[i] = magnitudes[i];
if self.show_peaks {
if magnitudes[i] > self.peak_values[i] {
self.peak_values[i] = magnitudes[i];
} else if self.peak_decay > 0.0 {
self.peak_values[i] *= 1.0 - self.peak_decay;
}
}
if magnitudes[i] > self.max_magnitude {
self.max_magnitude = magnitudes[i];
}
if magnitudes[i] < self.min_magnitude {
self.min_magnitude = magnitudes[i];
}
}
}
pub fn update_bin(&mut self, bin: u32, magnitude_value: f32) {
if bin >= self.num_bins {
return;
}
let idx = bin as usize;
self.buffer[idx] = magnitude_value;
if self.show_peaks {
if magnitude_value > self.peak_values[idx] {
self.peak_values[idx] = magnitude_value;
} else if self.peak_decay > 0.0 {
self.peak_values[idx] *= 1.0 - self.peak_decay;
}
}
if magnitude_value > self.max_magnitude {
self.max_magnitude = magnitude_value;
}
if magnitude_value < self.min_magnitude {
self.min_magnitude = magnitude_value;
}
}
pub fn shift_buffer_to_non_negative(&mut self) {
if self.min_magnitude < 0.0 {
let shift = -self.min_magnitude;
for val in &mut self.buffer {
*val += shift;
}
for val in &mut self.peak_values {
*val += shift;
}
self.max_magnitude += shift;
self.min_magnitude = 0.0;
}
}
pub fn render(&mut self) -> Vec<u8> {
let colors = default_color_scheme_data();
self.render_with_colors(&colors)
}
pub fn render_with_colors(&mut self, colors: &[u8]) -> Vec<u8> {
self.shift_buffer_to_non_negative();
let saturation = if self.max_magnitude > 0.0 {
self.max_magnitude
} else {
1.0
};
self.render_saturated(colors, saturation)
}
pub fn render_saturated(&self, colors: &[u8], saturation: f32) -> Vec<u8> {
assert!(saturation > 0.0);
let total_pixels = (self.width * self.height) as usize;
let mut colorbuf = vec![0u8; total_pixels * 4];
for i in 0..total_pixels {
let idx = i * 4;
colorbuf[idx] = self.background_color[0];
colorbuf[idx + 1] = self.background_color[1];
colorbuf[idx + 2] = self.background_color[2];
colorbuf[idx + 3] = self.background_color[3];
}
let ncolors = get_color_count(colors);
if ncolors == 0 {
return colorbuf;
}
if self.num_bins <= self.width {
self.render_bins_to_pixels(colors, saturation, &mut colorbuf, ncolors);
} else {
self.render_pixels_from_bins(colors, saturation, &mut colorbuf, ncolors);
}
colorbuf
}
fn render_bins_to_pixels(
&self,
colors: &[u8],
saturation: f32,
colorbuf: &mut [u8],
ncolors: usize,
) {
let pixels_per_bin = self.width as f32 / self.num_bins as f32;
let bar_width = (pixels_per_bin * self.bar_width_factor) as u32;
let bar_spacing = ((pixels_per_bin - bar_width as f32) / 2.0) as u32;
for bin in 0..self.num_bins {
let val = self.buffer[bin as usize];
let normalized = (val / saturation).clamp(0.0, 1.0);
let bar_height = (normalized * self.height as f32) as u32;
let bar_start_x = (bin as f32 * pixels_per_bin) as u32 + bar_spacing;
let bar_end_x = (bar_start_x + bar_width).min(self.width);
match self.style {
BarStyle::Solid => {
let color_idx = ((ncolors - 1) as f32 * normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
for y in (self.height - bar_height)..self.height {
for x in bar_start_x..bar_end_x {
let pixel_idx = ((y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
}
}
BarStyle::Gradient => {
for y in (self.height - bar_height)..self.height {
let y_normalized = (self.height - y) as f32 / self.height as f32;
let gradient_val = y_normalized * normalized;
let color_idx = ((ncolors - 1) as f32 * gradient_val + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
for x in bar_start_x..bar_end_x {
let pixel_idx = ((y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
}
}
BarStyle::Segmented => {
let segment_height = 4; let segment_gap = 2; let segment_total = segment_height + segment_gap;
let mut y = self.height;
while y > self.height - bar_height {
let seg_bottom = y;
let seg_top =
(y.saturating_sub(segment_height)).max(self.height - bar_height);
let y_normalized = (self.height - seg_bottom) as f32 / self.height as f32;
let color_idx = ((ncolors - 1) as f32 * y_normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
for seg_y in seg_top..seg_bottom {
for x in bar_start_x..bar_end_x {
let pixel_idx = ((seg_y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
}
if y <= segment_total {
break;
}
y -= segment_total;
}
}
}
if self.show_peaks {
let peak_val = self.peak_values[bin as usize];
let peak_normalized = (peak_val / saturation).clamp(0.0, 1.0);
let peak_y = self.height - (peak_normalized * self.height as f32) as u32;
if peak_y < self.height {
for x in bar_start_x..bar_end_x {
let pixel_idx = ((peak_y * self.width + x) * 4) as usize;
colorbuf[pixel_idx] = 255; colorbuf[pixel_idx + 1] = 255; colorbuf[pixel_idx + 2] = 255; colorbuf[pixel_idx + 3] = 255; }
}
}
}
}
fn render_pixels_from_bins(
&self,
colors: &[u8],
saturation: f32,
colorbuf: &mut [u8],
ncolors: usize,
) {
let bins_per_pixel = self.num_bins as f32 / self.width as f32;
for x in 0..self.width {
let bin_start = (x as f32 * bins_per_pixel) as u32;
let bin_end = ((x + 1) as f32 * bins_per_pixel) as u32;
let bin_end = bin_end.min(self.num_bins);
let mut max_val = 0.0f32;
let mut max_peak = 0.0f32;
for bin in bin_start..bin_end {
max_val = max_val.max(self.buffer[bin as usize]);
if self.show_peaks {
max_peak = max_peak.max(self.peak_values[bin as usize]);
}
}
let normalized = (max_val / saturation).clamp(0.0, 1.0);
let bar_height = (normalized * self.height as f32) as u32;
match self.style {
BarStyle::Solid => {
let color_idx = ((ncolors - 1) as f32 * normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
for y in (self.height - bar_height)..self.height {
let pixel_idx = ((y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
}
BarStyle::Gradient => {
for y in (self.height - bar_height)..self.height {
let y_normalized = (self.height - y) as f32 / self.height as f32;
let gradient_val = y_normalized * normalized;
let color_idx = ((ncolors - 1) as f32 * gradient_val + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
let pixel_idx = ((y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
}
BarStyle::Segmented => {
let segment_height = 2;
let segment_gap = 1;
let segment_total = segment_height + segment_gap;
let mut y = self.height;
while y > self.height - bar_height {
let seg_bottom = y;
let seg_top =
(y.saturating_sub(segment_height)).max(self.height - bar_height);
let y_normalized = (self.height - seg_bottom) as f32 / self.height as f32;
let color_idx = ((ncolors - 1) as f32 * y_normalized + 0.5) as usize;
let color_idx = color_idx.min(ncolors - 1);
let color = &colors[color_idx * 4..color_idx * 4 + 4];
for seg_y in seg_top..seg_bottom {
let pixel_idx = ((seg_y * self.width + x) * 4) as usize;
colorbuf[pixel_idx..pixel_idx + 4].copy_from_slice(color);
}
if y <= segment_total {
break;
}
y -= segment_total;
}
}
}
if self.show_peaks && max_peak > 0.0 {
let peak_normalized = (max_peak / saturation).clamp(0.0, 1.0);
let peak_y = self.height - (peak_normalized * self.height as f32) as u32;
if peak_y < self.height {
let pixel_idx = ((peak_y * self.width + x) * 4) as usize;
colorbuf[pixel_idx] = 255; colorbuf[pixel_idx + 1] = 255; colorbuf[pixel_idx + 2] = 255; colorbuf[pixel_idx + 3] = 255; }
}
}
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.peak_values.fill(0.0);
self.max_magnitude = f32::NEG_INFINITY;
self.min_magnitude = f32::INFINITY;
}
}