Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wavbrro improvements #69

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions brro-compressor/src/compressor/fft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::BinConfig;
use log::{error, debug, warn, info, trace};

const FFT_COMPRESSOR_ID: u8 = 15;
const DECIMAL_PRECISION: u8 = 5;

/// Struct to store frequencies, since bincode can't encode num_complex Complex format, this one is compatible
// This could be a Generic to support f64, integers, etc...
Expand Down Expand Up @@ -313,9 +314,8 @@ impl FFT {
// We need this for normalization
let len = frame_size as f32;
// We only need the real part
// TODO: Only 1 decimal place is sketchy!
let out_data = data.iter()
.map(|&f| self.round(f.re/len, 1))
.map(|&f| self.round(f.re/len, DECIMAL_PRECISION.into()))
.collect();
out_data
}
Expand Down Expand Up @@ -400,7 +400,7 @@ mod tests {
#[test]
fn test_to_lossy_data() {
let vector1 = vec![1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 3.0, 1.0, 1.0, 5.0];
let lossy_vec = vec![1.0, 1.9, 2.3, 1.0, 1.8, 1.7, 1.8, 1.0, 2.8, 1.2, 1.0, 3.3];
let lossy_vec = vec![1.0, 1.87201, 2.25, 1.0, 1.82735, 1.689, 1.82735, 1.0, 2.75, 1.189, 1.0, 3.311];
let compressed_data = fft(&vector1);
let out = fft_to_data(vector1.len(), &compressed_data);
assert_eq!(lossy_vec, out);
Expand Down
10 changes: 9 additions & 1 deletion brro-compressor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ fn process_directory(arguments: &Args) -> Result<(), Box<dyn Error>> {
for entry in std::fs::read_dir(arguments.input.clone())? {
let path = entry?.path();
if path.is_file() {
match process_single_file(path.clone(), arguments) {
Ok (_) => continue,
//TODO: Files are created while this walks the dir, gives a funny output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this means lol

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you walk a dir with std::fs::read_dir it will call the underlying OS function, and if new files are created while you're walking the dir, they will be returned. Since the compressor is creating files, entry will be one of the created files here and there, and there will be an output WARN/ERROR message.
A check has to be created for this case and ignore those files.

//NOTE: Due to the way read_dir works, it seems we can't do much about this except collecting
// before and then iterating. But that might lead to a MASSIVE array. So it keeps a `funny` output
// output for the time beeing.
Err(err) => error!("{} File: {}", err, path.display()),
}
// We need to make sure we skip anything but BRO and WBRO, this can be done on single file processors
process_single_file(path, arguments)?;
}
Expand All @@ -60,7 +68,7 @@ fn process_single_file(mut file_path: PathBuf, arguments: &Args) -> Result<(), B
}
} else {
// Read an WavBRRO file and compress it
let data = WavBrro::from_file(&file_path);
let data = WavBrro::from_file(&file_path)?;
if arguments.verbose {
println!("Input={:?}", data);
}
Expand Down
6 changes: 6 additions & 0 deletions plot_data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
filename=$1
target/debug/brro-compressor --compressor fft --error 1 --verbose ../../wbro-july/$filename.wbro > ../../$filename.m
target/debug/brro-compressor -u --verbose ../../wbro-july/$filename.bro >> ../../$filename.m
echo "plot(Input,'b', Output,'r')" >> ../../$filename.m
echo "print -dpng $filename.png" >> ../../$filename.m
8 changes: 4 additions & 4 deletions wavbrro/src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use std::fs::File;
use std::path::Path;

// Function to check if a file is a WAV file
pub fn is_wavbrro_file(file_path: &Path) -> bool {
pub fn is_wavbrro_file(file_path: &Path) -> io::Result<bool> {
// Open the file for reading and read the first 12 bytes (header) of the file
let mut file = fs::File::open(file_path).expect("Can't open file!");
let mut file = fs::File::open(file_path)?;
let mut header = [0u8; 12];
file.read_exact(&mut header).expect("File is too small!");
&header[0..4] == b"WBRO" && &header[8..12] == b"WBRO"
file.read_exact(&mut header)?;
Ok(&header[0..4] == b"WBRO" && &header[8..12] == b"WBRO")
}

pub fn read_wavbrro_file(file_path: &Path) -> io::Result<Vec<u8>> {
Expand Down
91 changes: 85 additions & 6 deletions wavbrro/src/wavbrro.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use rkyv::{Archive, Deserialize, Serialize};
use std::{io, fmt, result, error};
use std::path::Path;

use crate::read::{is_wavbrro_file, read_wavbrro_file};
Expand Down Expand Up @@ -83,12 +84,12 @@ impl WavBrro {

// This should be generic, but first implementation is going to be Vec f64
// TODO: This will panic left and right, make it right
pub fn from_file(file_path: &Path) -> Vec<f64> {
pub fn from_file(file_path: &Path) -> Result<Vec<f64>, Error> {
// Check if the header is correct
assert!(is_wavbrro_file(file_path));
let bytes = read_wavbrro_file(file_path).unwrap();
if !is_wavbrro_file(file_path)? {return Err(Error::FormatError);};
let bytes = read_wavbrro_file(file_path)?;
let obj = WavBrro::from_bytes(&bytes);
obj.get_samples()
Ok(obj.get_samples())
}

// TODO: This will panic left and right, make it right
Expand All @@ -114,6 +115,84 @@ impl WavBrro {

}

// Error class is based on https://codeberg.org/ruuda/hound/src/branch/master given the similarities
// between the formats (WAV and WAVBRRO).
#[derive(Debug)]
pub enum Error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.rs/thiserror/latest/thiserror/ can be helpful when writing error enums

/// An IO error occured in the underlying reader or writer.
IoError(io::Error),
/// It's not WAVBRRO
FormatError,
/// The sample has more bits than the destination type.
///
/// When iterating using the `samples` iterator, this means that the
/// destination type (produced by the iterator) is not wide enough to hold
/// the sample. When writing, this means that the sample cannot be written,
/// because it requires more bits than the bits per sample specified.
TooWide,
/// The Sample format is not supported.
Unsupported,
/// The sample format is different than the destination format.
///
/// When iterating using the `samples` iterator, this means the destination
/// type (produced by the iterator) has a different sample format than the
/// samples in the wav file.
///
/// For example, this will occur if the user attempts to produce `i32`
/// samples (which have a `SampleFormat::Int`) from a wav file that
/// contains floating point data (`SampleFormat::Float`).
InvalidSampleFormat,
}

impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
match *self {
Error::IoError(ref err) => err.fmt(formatter),
Error::FormatError => {
formatter.write_str("Wrong WAVBRRO file!")
}
Error::TooWide => {
formatter.write_str("The sample has more bits than the destination type.")
}
Error::Unsupported => {
formatter.write_str("The WAVBRRO format of the file is not supported.")
}
Error::InvalidSampleFormat => {
formatter.write_str("The sample format differs from the destination format.")
}
}
}
}

impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IoError(err)
}
}

impl error::Error for Error {
fn description(&self) -> &str {
match *self {
// TODO: I don't know if this is actually the right way to do!
Error::IoError(ref _err) => "IO Error",
Error::TooWide => "the sample has more bits than the destination type",
Error::Unsupported => "the wave format of the file is not supported",
Error::InvalidSampleFormat => "the sample format differs from the destination format",
Error::FormatError => "the file is not of the WAVBRRO format",
}
}

fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::IoError(ref err) => Some(err),
Error::TooWide => None,
Error::Unsupported => None,
Error::InvalidSampleFormat => None,
Error::FormatError => None,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -152,7 +231,7 @@ mod tests {
wb.add_sample(3.0);
wb.to_file(path);
let result = is_wavbrro_file(path);
assert!(result);
assert!(result.unwrap());
std::fs::remove_file(path).expect("Failed to remove temporary file");
}

Expand All @@ -165,7 +244,7 @@ mod tests {
wb.add_sample(3.0);
wb.to_file(path);
let data = WavBrro::from_file(path);
assert_eq!(data, [1.0, 2.0, 3.0]);
assert_eq!(data.unwrap(), [1.0, 2.0, 3.0]);
std::fs::remove_file(path).expect("Failed to remove temporary file");
}
}