diff --git a/README.md b/README.md index f05c60f6..9e556d5f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you want to learn how to make cool sounds using WereSoCool, you'll find cool at [weresocool.org](https://www.weresocool.org/). -My recommend following the tutorials in order and writing your own composition after completing each one. Additional documentation is currently being worked on, as well as a record featuring a great band, so stay tuned. +I recommend following the tutorials in order and writing your own composition after completing each one. Additional documentation is currently being worked on, as well as a record featuring a great band, so stay tuned. On mobile, you can still view the tutorials, but you won't be able to hear anything. diff --git a/ast/src/datagen/csv1d_test.rs b/ast/src/datagen/csv1d_test.rs new file mode 100644 index 00000000..558aabf6 --- /dev/null +++ b/ast/src/datagen/csv1d_test.rs @@ -0,0 +1,93 @@ +#[cfg(test)] +mod eeg_test { + use num_rational::Rational64; + + use crate::{ + datagen::mod_1d::{ + csv_to_normalform, eeg_data_to_normal_form, eeg_datum_to_point_op, CsvData, + }, + NameSet, NormalForm, PointOp, + }; + #[test] + fn test_eeg_datum_to_point_op() { + let mut names = NameSet::new(); + names.insert("data.csv".to_string()); + let result = eeg_datum_to_point_op(1.0e-14, None, 2.0e14, "data.csv"); + let expected = PointOp { + fa: Rational64::new(2, 1), + l: Rational64::new(1, 50), + names, + ..PointOp::default() + }; + assert_eq!(result, expected); + } + + #[test] + fn test_eeg_datum_to_normal_form() { + let eeg_data = CsvData { + data: vec![0.5e-14, 1.0e-14, 1.5e-14], + }; + let mut names = NameSet::new(); + names.insert("data.csv".to_string()); + let result = eeg_data_to_normal_form(&eeg_data, 2.0e14, "data.csv"); + let expected = NormalForm { + operations: vec![vec![ + PointOp { + fa: Rational64::new(1, 1), + l: Rational64::new(1, 50), + names: names.clone(), + ..PointOp::default() + }, + PointOp { + fa: Rational64::new(3, 2), + l: Rational64::new(1, 50), + names: names.clone(), + ..PointOp::default() + }, + PointOp { + fa: Rational64::new(2, 1), + l: Rational64::new(1, 50), + names, + ..PointOp::default() + }, + ]], + length_ratio: Rational64::new(3, 50), + }; + assert_eq!(result, expected); + } + #[test] + fn test_csv_to_normalform() { + let result = csv_to_normalform( + "./src/datagen/test_data.csv", + Some(Rational64::new(200_000_000_000_000, 1)), + ) + .unwrap(); + + let mut names = NameSet::new(); + names.insert("test_data.csv".to_string()); + let expected = NormalForm { + operations: vec![vec![ + PointOp { + fa: Rational64::new(1, 1), + l: Rational64::new(1, 50), + names: names.clone(), + ..PointOp::default() + }, + PointOp { + fa: Rational64::new(3, 2), + l: Rational64::new(1, 50), + names: names.clone(), + ..PointOp::default() + }, + PointOp { + fa: Rational64::new(2, 1), + l: Rational64::new(1, 50), + names, + ..PointOp::default() + }, + ]], + length_ratio: Rational64::new(3, 50), + }; + assert_eq!(result, expected); + } +} diff --git a/ast/src/datagen/csv_test.rs b/ast/src/datagen/csv2d_test.rs similarity index 99% rename from ast/src/datagen/csv_test.rs rename to ast/src/datagen/csv2d_test.rs index 7189943e..5196ba49 100644 --- a/ast/src/datagen/csv_test.rs +++ b/ast/src/datagen/csv2d_test.rs @@ -14,7 +14,6 @@ mod csv2d_tests { assert_that!(&result, contains(expected).exactly()); } - //TODO: Fix these #[test] fn test_point_to_point_op() { let mut names = NameSet::new(); diff --git a/ast/src/datagen/mod.rs b/ast/src/datagen/mod.rs index 7d2c9dd7..1b01061c 100644 --- a/ast/src/datagen/mod.rs +++ b/ast/src/datagen/mod.rs @@ -6,8 +6,9 @@ use std::{fs::File, path::Path}; use weresocool_error::Error; use weresocool_ring_buffer::RingBuffer; use weresocool_shared::helpers::r_to_f32; -mod csv_test; -mod test; +mod csv1d_test; +mod csv2d_test; +mod mod_1d; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)] pub struct Point { @@ -145,9 +146,7 @@ fn get_data1d(filename: String, length: Rational64) -> Result>, Err .deserialize::>() .map(|datum| datum.expect("Error deserializing datum")) .collect(); - dbg!(&deserialized); let result: Vec> = deserialized[0].iter().map(|v| vec![*v, length]).collect(); - dbg!(&result); Ok(result) } diff --git a/ast/src/datagen/mod_1d.rs b/ast/src/datagen/mod_1d.rs new file mode 100644 index 00000000..aeff5950 --- /dev/null +++ b/ast/src/datagen/mod_1d.rs @@ -0,0 +1,140 @@ +use crate::{NameSet, NormalForm, Normalize, Op, OscType, PointOp, Term, ASR}; +use num_rational::{Ratio, Rational64}; +use scop::Defs; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::{fs::File, path::Path}; +use weresocool_error::Error; +use weresocool_ring_buffer::RingBuffer; +use weresocool_shared::helpers::r_to_f32; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CsvData { + pub data: Vec, +} + +pub fn csv_to_normalform(filename: &str, scale: Option) -> Result { + let data = get_data(filename.into())?; + let path = Path::new(&filename); + Ok(vec_eeg_data_to_normal_form( + data, + if let Some(s) = scale { + r_to_f32(s) + } else { + 1.0 + }, + path.file_name() + .unwrap() + .to_string_lossy() + .to_string() + .as_str(), + )) +} + +fn vec_eeg_data_to_normal_form(data: Vec, scale: f32, filename: &str) -> NormalForm { + let mut nfs: Vec = data + .iter() + .map(|stream| eeg_data_to_normal_form(stream, scale, filename)) + .collect(); + + let overlay = Op::Overlay { + operations: nfs.iter_mut().map(|nf| Term::Nf(nf.to_owned())).collect(), + }; + + let mut nf = NormalForm::init(); + overlay + .apply_to_normal_form(&mut nf, &mut Defs::new()) + .expect("unable to normalize"); + nf +} + +pub fn eeg_data_to_normal_form(data: &CsvData, scale: f32, filename: &str) -> NormalForm { + let mut length_ratio = Rational64::new(0, 1); + + let mut buffer = RingBuffer::::new(50); + + let point_ops: Vec = data + .data + .iter() + .map(|value| { + let op = eeg_datum_to_point_op(*value, Some(&mut buffer), scale, filename); + length_ratio += op.l; + op + }) + .collect(); + + NormalForm { + length_ratio, + operations: vec![point_ops], + } +} + +pub fn f32_to_rational(mut float: f32) -> Rational64 { + if !float.is_finite() || float > 100_000_000.0 { + float = 0.0 + } + let float_string = format!("{:.8}", float); + let decimal = float_string.split('.').collect::>()[1]; + let den = i64::pow(10, decimal.len() as u32); + let num = i64::from_str(&float_string.replace('.', "")) + .unwrap_or_else(|_| panic!("error converting {} to i64", float_string)); + + Ratio::new(num, den) +} + +pub fn eeg_datum_to_point_op( + datum: f32, + buffer: Option<&mut RingBuffer>, + scale: f32, + filename: &str, +) -> PointOp { + let mut nameset = NameSet::new(); + nameset.insert(filename.to_string()); + let mut datum = datum.abs() * scale; + if let Some(b) = buffer { + b.push(datum); + + let b_vec = b.to_vec(); + let sum: f32 = b_vec.iter().sum(); + datum = sum / b_vec.len() as f32; + } + + let fa = f32_to_rational(datum); + PointOp { + // fm, + fm: Rational64::new(1, 1), + fa, + l: Rational64::new(2, 100), + g: Rational64::new(1, 1), + pm: Rational64::new(1, 1), + pa: Rational64::new(0, 1), + asr: ASR::Long, + portamento: Rational64::new(1, 1), + attack: Rational64::new(1, 1), + decay: Rational64::new(1, 1), + reverb: None, + osc_type: OscType::None, + names: nameset, + } +} + +fn get_data(filename: String) -> Result, Error> { + let path = Path::new(&filename); + let cwd = std::env::current_dir()?; + let file = File::open(path).unwrap_or_else(|_| { + panic!( + "unable to read file: {}. current working directory is: {}", + path.display(), + cwd.display() + ) + }); + let mut rdr = csv::ReaderBuilder::new() + .has_headers(false) + .delimiter(b',') + .from_reader(file); + + Ok(rdr + .deserialize::() + .map(|datum| datum.expect("Error deserializing datum")) + .collect()) +} diff --git a/ast/src/datagen/test.rs b/ast/src/datagen/test.rs deleted file mode 100644 index cafce14b..00000000 --- a/ast/src/datagen/test.rs +++ /dev/null @@ -1,91 +0,0 @@ -// #[cfg(test)] -// weresocool_parser -// use num_rational::Rational64; - -// use crate::{ -// datagen::{csv_to_normalform, eeg_data_to_normal_form, eeg_datum_to_point_op, CsvData}, -// NameSet, NormalForm, PointOp, -// }; -// #[test] -// fn test_eeg_datum_to_point_op() { -// let mut names = NameSet::new(); -// names.insert("data.csv".to_string()); -// let result = eeg_datum_to_point_op(1.0e-14, None, 2.0e14, "data.csv"); -// let expected = PointOp { -// fa: Rational64::new(2, 1), -// l: Rational64::new(1, 50), -// names, -// ..PointOp::default() -// }; -// assert_eq!(result, expected); -// } - -// #[test] -// fn test_eeg_datum_to_normal_form() { -// let eeg_data = CsvData { -// data: vec![0.5e-14, 1.0e-14, 1.5e-14], -// }; -// let mut names = NameSet::new(); -// names.insert("data.csv".to_string()); -// let result = eeg_data_to_normal_form(&eeg_data, 2.0e14, "data.csv"); -// let expected = NormalForm { -// operations: vec![vec![ -// PointOp { -// fa: Rational64::new(1, 1), -// l: Rational64::new(1, 50), -// names: names.clone(), -// ..PointOp::default() -// }, -// PointOp { -// fa: Rational64::new(3, 2), -// l: Rational64::new(1, 50), -// names: names.clone(), -// ..PointOp::default() -// }, -// PointOp { -// fa: Rational64::new(2, 1), -// l: Rational64::new(1, 50), -// names, -// ..PointOp::default() -// }, -// ]], -// length_ratio: Rational64::new(3, 50), -// }; -// assert_eq!(result, expected); -// } -// #[test] -// fn test_csv_to_normalform() { -// let result = csv_to_normalform( -// "./src/datagen/test_data.csv", -// Some(Rational64::new(200_000_000_000_000, 1)), -// ) -// .unwrap(); - -// let mut names = NameSet::new(); -// names.insert("test_data.csv".to_string()); -// let expected = NormalForm { -// operations: vec![vec![ -// PointOp { -// fa: Rational64::new(1, 1), -// l: Rational64::new(1, 50), -// names: names.clone(), -// ..PointOp::default() -// }, -// PointOp { -// fa: Rational64::new(3, 2), -// l: Rational64::new(1, 50), -// names: names.clone(), -// ..PointOp::default() -// }, -// PointOp { -// fa: Rational64::new(2, 1), -// l: Rational64::new(1, 50), -// names, -// ..PointOp::default() -// }, -// ]], -// length_ratio: Rational64::new(3, 50), -// }; -// assert_eq!(result, expected); -// } -// }