use ndarray::{s, Array2};
use std::fs::File;
use std::io::{self, Read, Write};
#[derive(Debug)]
pub struct QuantizationRange {
pub min: f32,
pub max: f32,
}
pub fn save_tga_8bit(data: &[f32], n_mels: usize, path: &str) -> io::Result<()> {
let width = (data.len() / n_mels) as u16;
assert!(
width < u16::MAX,
"width greater than TARGA max, use [`tga_8bit`]"
);
let data = tga_8bit_data(&data, n_mels);
let mut file = File::create(path)?;
file.write_all(&data)?;
Ok(())
}
pub fn tga_8bit(data: &[f32], n_mels: usize) -> Vec<Vec<u8>> {
let mut result = Vec::new();
for chunk in chunk_frames_into_strides(data.to_vec(), n_mels, u16::MAX as usize) {
result.push(tga_8bit_data(&chunk, n_mels));
}
result
}
pub fn tga_8bit_data(data: &[f32], n_mels: usize) -> Vec<u8> {
let (tga_data, range) = quantize(&data.to_vec());
let width = (data.len() / n_mels) as u16;
let height = n_mels as u16;
let mut tga_header = Vec::with_capacity(18);
tga_header.push(8u8); tga_header.push(0u8); tga_header.push(3u8); tga_header.extend_from_slice(&[0u8; 5]); tga_header.extend_from_slice(&[0u8; 4]); tga_header.extend_from_slice(&width.to_le_bytes()); tga_header.extend_from_slice(&height.to_le_bytes()); tga_header.push(8u8); tga_header.push(0u8);
tga_header.extend_from_slice(&range.min.to_le_bytes());
tga_header.extend_from_slice(&range.max.to_le_bytes());
let mut tga_image = Vec::new();
tga_image.extend_from_slice(&tga_header);
tga_image.extend_from_slice(&tga_data);
tga_image
}
pub fn parse_tga_8bit(data: &[u8]) -> io::Result<Vec<f32>> {
let mut cursor = io::Cursor::new(data);
let mut tga_data = Vec::new();
let mut min_bytes = [0u8; 4];
let mut max_bytes = [0u8; 4];
cursor.set_position(18);
cursor.read_exact(&mut min_bytes)?;
cursor.read_exact(&mut max_bytes)?;
let min = f32::from_le_bytes(min_bytes);
let max = f32::from_le_bytes(max_bytes);
let range = QuantizationRange { min, max };
cursor.read_to_end(&mut tga_data)?;
let mel = dequantize(&tga_data, &range);
Ok(mel)
}
pub fn load_tga_8bit(path: &str) -> io::Result<Vec<f32>> {
let mut file = File::open(path)?;
let mut tga_data = Vec::new();
file.read_to_end(&mut tga_data)?;
parse_tga_8bit(&tga_data)
}
pub fn chunk_frames_into_strides(
frames: Vec<f32>,
n_mels: usize,
stride_size: usize,
) -> Vec<Vec<f32>> {
let width = frames.len() / n_mels;
if stride_size == width {
return vec![frames];
}
let height = n_mels;
let ndarray_image = Array2::from_shape_vec((height, width), frames).unwrap();
let mut chunks = Vec::new();
for y in (0..height).step_by(stride_size) {
for x in (0..width).step_by(stride_size) {
let end_y = (y + stride_size).min(height);
let end_x = (x + stride_size).min(width);
let chunk = ndarray_image
.slice(s![y..end_y, x..end_x])
.to_owned()
.into_raw_vec_and_offset()
.0;
chunks.push(chunk);
}
}
chunks
}
pub fn quantize(frame: &[f32]) -> (Vec<u8>, QuantizationRange) {
let mut result: Vec<u8> = Vec::new();
let min = frame.iter().copied().fold(f32::INFINITY, f32::min);
let max = frame.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let scale = 255.0 / (max - min);
for &value in frame {
let scaled_value = ((value - min) * scale).round().max(0.0).min(255.0);
result.push(scaled_value as u8);
}
(result, QuantizationRange { min, max })
}
pub fn dequantize(data: &[u8], range: &QuantizationRange) -> Vec<f32> {
let mut result = Vec::with_capacity(data.len());
let scale = (range.max - range.min) / 255.0;
for &value in data {
let scaled_value = value as f32 * scale + range.min;
result.push(scaled_value);
}
result
}
pub fn to_array2(frames: &[f32], n_mels: usize) -> Array2<f64> {
Array2::from_shape_vec(
(n_mels, frames.len() / n_mels),
frames.iter().map(|v| *v as f64).collect::<Vec<_>>(),
)
.unwrap()
}