From 3cded50e801f1b96f098047691186687f9fdf764 Mon Sep 17 00:00:00 2001 From: Danny Date: Sat, 11 Mar 2023 23:18:25 -0500 Subject: [PATCH] Dm/init (#770) * do not log imports * update ui * 2d parsing might be ready to try * WIP * csv working...gotta think about it * working on init * new years eve * 1d and 2d csv working * WIP * tests passing * ready to update docs * WIP * hrm maybe no need to track first x in watch? * only create file if needed * readme * improve logging on watch * improve new * csv2d tests passing * cleanup * add back csv1d * demo working * demo working * wip * some lints --- Cargo.lock | 45 ++- Cargo.toml | 36 +- README.md | 4 +- analyze/Cargo.toml | 4 +- ast/Cargo.toml | 12 +- ast/src/ast.rs | 7 +- .../src/datagen/1d_test_data.csv | 0 ast/src/datagen/2d_test_data.csv | 3 + ast/src/datagen/{test.rs => csv1d_test.rs} | 6 +- ast/src/datagen/csv2d_test.rs | 161 ++++++++ ast/src/datagen/mod.rs | 163 ++++---- ast/src/datagen/mod_1d.rs | 140 +++++++ ast/src/generator/coefs.rs | 2 +- ast/src/generator/mod.rs | 6 +- ast/src/lib.rs | 1 + ast/src/lists/indices.rs | 2 +- ast/src/nameset/mod.rs | 8 +- ast/src/operations/get_length_ratio.rs | 9 +- ast/src/operations/normalize.rs | 10 +- bin/snapshot.rs | 4 +- core/Cargo.toml | 28 +- core/src/generation/parsed_to_render.rs | 9 +- core/src/manager/mod.rs | 3 +- core/src/manager/render_manager.rs | 58 ++- core/src/portaudio/duplex.rs | 34 +- core/src/portaudio/real_time.rs | 18 +- .../src/portaudio/real_time_buffer_manager.rs | 26 +- .../src/portaudio/real_time_render_manager.rs | 27 +- core/src/renderable/mod.rs | 28 +- core/src/ui/mod.rs | 3 +- core/src/write.rs | 9 +- error/Cargo.toml | 10 +- instrument/Cargo.toml | 14 +- instrument/reverb/Cargo.toml | 2 +- instrument/src/frequency.rs | 8 +- instrument/src/gain.rs | 30 +- instrument/src/loudness.rs | 6 +- instrument/src/oscillator.rs | 10 +- instrument/src/renderable/mod.rs | 39 +- instrument/src/renderable/render_voice.rs | 13 +- instrument/src/sample.rs | 8 +- instrument/src/test.rs | 20 +- instrument/src/voice.rs | 12 +- lame/Cargo.toml | 2 +- lame/src/lib.rs | 7 +- mocks/data/csv.socool | 13 - mocks/data/csv1d.socool | 16 + mocks/data/csv2d.socool | 12 + mocks/data/data1d.csv | 1 + mocks/data/data2d.csv | 3 + mocks/ops/overtone.socool | 15 + opmap/Cargo.toml | 2 +- parser/Cargo.toml | 10 +- parser/src/parser.rs | 2 +- parser/src/socool.lalrpop | 32 +- portaudio/Cargo.toml | 4 +- portaudio/rust-portaudio-sys/Cargo.toml | 2 +- portaudio/src/stream.rs | 6 +- ring_buffer/Cargo.toml | 4 +- scop/Cargo.toml | 2 +- shared/Cargo.toml | 3 +- shared/src/lib.rs | 2 +- shared/src/settings.rs | 33 +- src/app.rs | 5 + src/demo.rs | 382 ++++++++++-------- src/lib.rs | 7 +- src/new.rs | 18 +- src/play.rs | 61 ++- src/print.rs | 2 + src/test_data/play_unix.mp3 | Bin 17938 -> 17938 bytes src/testing/expect_tests/mod.rs | 7 +- src/testing/hashes.json | 52 +-- src/testing/mod.rs | 55 ++- src/watch.rs | 13 +- test.csv | 6 + test.socool | 9 +- todo/csv.socool | 5 + todo/release.socool | 8 +- vorbis/Cargo.toml | 4 +- vorbis/src/lib.rs | 7 +- 80 files changed, 1206 insertions(+), 644 deletions(-) rename mocks/data/data.csv => ast/src/datagen/1d_test_data.csv (100%) create mode 100644 ast/src/datagen/2d_test_data.csv rename ast/src/datagen/{test.rs => csv1d_test.rs} (94%) create mode 100644 ast/src/datagen/csv2d_test.rs create mode 100644 ast/src/datagen/mod_1d.rs delete mode 100644 mocks/data/csv.socool create mode 100644 mocks/data/csv1d.socool create mode 100644 mocks/data/csv2d.socool create mode 100644 mocks/data/data1d.csv create mode 100644 mocks/data/data2d.csv create mode 100644 mocks/ops/overtone.socool create mode 100644 test.csv create mode 100644 todo/csv.socool diff --git a/Cargo.lock b/Cargo.lock index 08065e115..44c7af0a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1172,9 +1172,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "oorandom" @@ -1190,7 +1190,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "opmap" -version = "1.0.43" +version = "1.0.44" dependencies = [ "drain-while", "hamcrest2", @@ -1275,6 +1275,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "peekread" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978122cdc72f39ed3c6343907453570570699169aace43dd09d46b52cc5f681d" + [[package]] name = "petgraph" version = "0.6.2" @@ -1558,7 +1564,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "reverb" -version = "1.0.43" +version = "1.0.44" [[package]] name = "rustix" @@ -1597,7 +1603,7 @@ dependencies = [ [[package]] name = "scop" -version = "1.0.43" +version = "1.0.44" dependencies = [ "indexmap", "serde", @@ -2037,7 +2043,7 @@ dependencies = [ [[package]] name = "weresocool" -version = "1.0.43" +version = "1.0.44" dependencies = [ "assert_cmd", "clap 4.0.27", @@ -2075,22 +2081,24 @@ dependencies = [ [[package]] name = "weresocool_analyze" -version = "1.0.43" +version = "1.0.44" dependencies = [ "weresocool_shared", ] [[package]] name = "weresocool_ast" -version = "1.0.43" +version = "1.0.44" dependencies = [ "colored", "csv", + "hamcrest2", "indexmap", "meval", "num-integer", "num-rational 0.3.2", "num-traits", + "peekread", "polynomials", "pretty_assertions", "rand", @@ -2103,7 +2111,7 @@ dependencies = [ [[package]] name = "weresocool_core" -version = "1.0.43" +version = "1.0.44" dependencies = [ "bytes", "colored", @@ -2146,7 +2154,7 @@ dependencies = [ [[package]] name = "weresocool_error" -version = "1.0.43" +version = "1.0.44" dependencies = [ "csv", "hound", @@ -2161,7 +2169,7 @@ dependencies = [ [[package]] name = "weresocool_instrument" -version = "1.0.43" +version = "1.0.44" dependencies = [ "num-rational 0.3.2", "num-traits", @@ -2178,7 +2186,7 @@ dependencies = [ [[package]] name = "weresocool_lame" -version = "1.0.43" +version = "1.0.44" dependencies = [ "serde", "thiserror", @@ -2186,7 +2194,7 @@ dependencies = [ [[package]] name = "weresocool_parser" -version = "1.0.43" +version = "1.0.44" dependencies = [ "colored", "indexmap", @@ -2204,7 +2212,7 @@ dependencies = [ [[package]] name = "weresocool_portaudio" -version = "1.0.43" +version = "1.0.44" dependencies = [ "bitflags", "libc", @@ -2214,29 +2222,30 @@ dependencies = [ [[package]] name = "weresocool_portaudio_sys" -version = "1.0.43" +version = "1.0.44" dependencies = [ "cc", ] [[package]] name = "weresocool_ring_buffer" -version = "1.0.43" +version = "1.0.44" dependencies = [ "weresocool_shared", ] [[package]] name = "weresocool_shared" -version = "1.0.43" +version = "1.0.44" dependencies = [ "float-cmp", "num-rational 0.3.2", + "once_cell", ] [[package]] name = "weresocool_vorbis" -version = "1.0.43" +version = "1.0.44" dependencies = [ "hamcrest2", "hound", diff --git a/Cargo.toml b/Cargo.toml index fe9e84c2a..7ba3da0b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool" -version = "1.0.43" +version = "1.0.44" edition = "2021" authors = ["Danny "] description = "***** WereSoCool __!Now In Stereo!__ ****** Make cool sounds. Impress your friends." @@ -24,27 +24,27 @@ depends = ["lame", "vorbis-tools"] provides = ["weresocool"] [target.'cfg(target_os = "windows")'.dependencies] -weresocool_core = { path="core", version = "^1.0.43", features = ["windows"] } -weresocool_error = { path = "error", version = "^1.0.43", features=["windows"] } +weresocool_core = { path="core", version = "^1.0.44", features = ["windows"] } +weresocool_error = { path = "error", version = "^1.0.44", features=["windows"] } [target.'cfg(target_os = "linux")'.dependencies] -weresocool_core = { path="core", version = "^1.0.43", features = ["windows"] } -weresocool_error = { path = "error", version = "^1.0.43", features=["windows"] } +weresocool_core = { path="core", version = "^1.0.44", features = ["windows"] } +weresocool_error = { path = "error", version = "^1.0.44", features=["windows"] } [target.'cfg(target_os = "macos")'.dependencies] -weresocool_core = { path="core", version = "^1.0.43", features = ["app"] } -weresocool_error = { path = "error", version = "^1.0.43", features=["app"] } +weresocool_core = { path="core", version = "^1.0.44", features = ["app"] } +weresocool_error = { path = "error", version = "^1.0.44", features=["app"] } [dependencies] -weresocool_core = { path="core", version = "^1.0.43", default_features=false } -weresocool_error = { path = "error", version = "^1.0.43", default_features=false } -weresocool_parser = { path = "parser", version = "^1.0.43", default_features=false, optional=true } -weresocool_ast = { path = "ast", version = "^1.0.43", default_features=false, optional=true } -weresocool_instrument = { path = "instrument", version = "^1.0.43", default_features=false, optional=true } -weresocool_portaudio = { path = "portaudio", version = "^1.0.43", default_features=false, optional=true } -scop = { path = "scop", version = "^1.0.43" } -opmap = { path = "opmap", version = "^1.0.43" } -weresocool_shared = { path = "shared", version = "^1.0.43" } -weresocool_analyze = { path = "analyze", version = "^1.0.43" } -weresocool_ring_buffer = { path = "ring_buffer", version = "^1.0.43" } +weresocool_core = { path="core", version = "^1.0.44", default_features=false } +weresocool_error = { path = "error", version = "^1.0.44", default_features=false } +weresocool_parser = { path = "parser", version = "^1.0.44", default_features=false, optional=true } +weresocool_ast = { path = "ast", version = "^1.0.44", default_features=false, optional=true } +weresocool_instrument = { path = "instrument", version = "^1.0.44", default_features=false, optional=true } +weresocool_portaudio = { path = "portaudio", version = "^1.0.44", default_features=false, optional=true } +scop = { path = "scop", version = "^1.0.44" } +opmap = { path = "opmap", version = "^1.0.44" } +weresocool_shared = { path = "shared", version = "^1.0.44" } +weresocool_analyze = { path = "analyze", version = "^1.0.44" } +weresocool_ring_buffer = { path = "ring_buffer", version = "^1.0.44" } clap = "4.0.27" thiserror = "1.0.31" notify = "5.0.0-pre.14" diff --git a/README.md b/README.md index 4d9cfc5ef..9e556d5f9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A language for composing mictoronal music built in Rust. You might find this language useful if you want to make cool sounds and impress your friends/pets/plants. -This language does not require familiarity with either microtonal music or computer programming, but experience with either will certainly help. +This language is designed to be easy to use, and you don't need any prior knowledge of microtonal music or computer programming. However, experience in either area will certainly be helpful. The **WereSoCool CLI** is availble on **macOS**, **Linux** and **Windows**. You can also explore the language in a Firefox or Chrome browser on a desktop computer in the [WereSoCool Playground](https://www.weresocool.org/playground). Live coding in the browser currently only works on desktop in a Firefox or Chrome browser. @@ -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 recommended approach to learning the language is to do the tutorials in order and write your own composition after completing each one. I'm currently working on additional documentation as well as a record I've made using WereSoCool featuring a great band. 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/analyze/Cargo.toml b/analyze/Cargo.toml index 03c771774..61ad3679f 100644 --- a/analyze/Cargo.toml +++ b/analyze/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_analyze" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "audio analysis for WereSoCool" @@ -10,4 +10,4 @@ resolver="2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -weresocool_shared = { path = "../shared", version = "^1.0.43" } +weresocool_shared = { path = "../shared", version = "^1.0.44" } diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 0ac322cde..a015e65f8 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_ast" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "AST for WereSoCool" @@ -8,10 +8,10 @@ license = "GPL-3.0" resolver="2" [dependencies] -weresocool_ring_buffer = { path = "../ring_buffer", version = "^1.0.43" } -weresocool_error = { path = "../error", version = "^1.0.43", default_features=false, optional=true } -weresocool_shared = { path = "../shared", version = "^1.0.43" } -scop = { path = "../scop", version = "^1.0.43" } +weresocool_ring_buffer = { path = "../ring_buffer", version = "^1.0.44" } +weresocool_error = { path = "../error", version = "^1.0.44", default_features=false, optional=true } +weresocool_shared = { path = "../shared", version = "^1.0.44" } +scop = { path = "../scop", version = "^1.0.44" } num-rational = { version = "0.3.2", features = ["serde"] } rand = { version="0.7.3", features=["wasm-bindgen"]} serde = { version = "1.0.119", features = ["derive"] } @@ -23,6 +23,8 @@ num-integer = "0.1.44" polynomials = "0.2.4" meval = "0.2.0" csv = "1.1.6" +hamcrest2 = "0.3.0" +peekread = "0.1.1" [features] default=["app"] diff --git a/ast/src/ast.rs b/ast/src/ast.rs index c7c280f6c..ed644f213 100644 --- a/ast/src/ast.rs +++ b/ast/src/ast.rs @@ -1,3 +1,4 @@ +use crate::datagen::Scale; use crate::Term; use num_rational::Rational64; @@ -14,10 +15,14 @@ pub enum Op { Id(String), Tag(String), // - CSV { + CSV1d { path: String, scale: Option, }, + CSV2d { + path: String, + scales: Vec, + }, // FunctionCall { name: String, diff --git a/mocks/data/data.csv b/ast/src/datagen/1d_test_data.csv similarity index 100% rename from mocks/data/data.csv rename to ast/src/datagen/1d_test_data.csv diff --git a/ast/src/datagen/2d_test_data.csv b/ast/src/datagen/2d_test_data.csv new file mode 100644 index 000000000..84dd3fd6d --- /dev/null +++ b/ast/src/datagen/2d_test_data.csv @@ -0,0 +1,3 @@ +2.5,1.0 +1.0,2.0 +1.5,2.0 diff --git a/ast/src/datagen/test.rs b/ast/src/datagen/csv1d_test.rs similarity index 94% rename from ast/src/datagen/test.rs rename to ast/src/datagen/csv1d_test.rs index da8b68cba..e1adffdbc 100644 --- a/ast/src/datagen/test.rs +++ b/ast/src/datagen/csv1d_test.rs @@ -3,7 +3,9 @@ mod eeg_test { use num_rational::Rational64; use crate::{ - datagen::{csv_to_normalform, eeg_data_to_normal_form, eeg_datum_to_point_op, CsvData}, + datagen::mod_1d::{ + csv1d_to_normalform, eeg_data_to_normal_form, eeg_datum_to_point_op, CsvData, + }, NameSet, NormalForm, PointOp, }; #[test] @@ -55,7 +57,7 @@ mod eeg_test { } #[test] fn test_csv_to_normalform() { - let result = csv_to_normalform( + let result = csv1d_to_normalform( "./src/datagen/test_data.csv", Some(Rational64::new(200_000_000_000_000, 1)), ) diff --git a/ast/src/datagen/csv2d_test.rs b/ast/src/datagen/csv2d_test.rs new file mode 100644 index 000000000..5196ba495 --- /dev/null +++ b/ast/src/datagen/csv2d_test.rs @@ -0,0 +1,161 @@ +#[cfg(test)] +mod csv2d_tests { + use hamcrest2::prelude::*; + use num_rational::Rational64; + + use crate::{datagen::*, NameSet, NormalForm, PointOp}; + + #[test] + fn test_get_data_2d() { + let result = get_data2d("./src/datagen/2d_test_data.csv".to_string()).unwrap(); + + let expected = vec![vec![2.5, 1.0], vec![1.0, 2.0], vec![1.5, 2.0]]; + + assert_that!(&result, contains(expected).exactly()); + } + + #[test] + fn test_point_to_point_op() { + let mut names = NameSet::new(); + names.insert("2d_test_data.csv".to_string()); + + let result = point_to_point_op( + &vec![1.0, 2.0], + None, + &vec![ + Scale { + axis: Axis::F, + value: Rational64::new(2, 1), + }, + Scale { + axis: Axis::L, + value: Rational64::new(1, 2), + }, + ], + "2d_test_data.csv", + ); + let expected = Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(2, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(2, 1), + }), + ], + }); + + assert_eq!(result, expected); + } + + #[test] + fn test_csv_data_to_normal_form() { + let csv_data = vec![vec![1.0, 1.0], vec![1.0, 1.0], vec![1.0, 1.0]]; + + let mut names = NameSet::new(); + let filename = "2d_test_data.csv"; + let scales = vec![ + Scale { + axis: Axis::F, + value: Rational64::new(2, 1), + }, + Scale { + axis: Axis::L, + value: Rational64::new(1, 2), + }, + ]; + names.insert(filename.to_string()); + + let result = csv_data_to_normal_form(&csv_data, scales, "2d_test_data.csv"); + let expected = Term::Op(Op::Sequence { + operations: vec![ + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(2, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(1, 1), + }), + ], + }), + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(2, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(1, 1), + }), + ], + }), + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(2, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(1, 1), + }), + ], + }), + ], + }); + + assert_eq!(result, expected); + } + + #[test] + fn test_csv1d_to_normalform() { + let scales = vec![ + Scale { + axis: Axis::F, + value: Rational64::new(2, 1), + }, + Scale { + axis: Axis::L, + value: Rational64::new(1, 2), + }, + ]; + + let result = csv2d_to_normalform("./src/datagen/2d_test_data.csv", scales).unwrap(); + + let mut names = NameSet::new(); + names.insert("2d_test_data.csv".to_string()); + let expected = Term::Op(Op::Sequence { + operations: vec![ + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(5, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(1, 1), + }), + ], + }), + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(2, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(2, 1), + }), + ], + }), + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: Rational64::new(3, 1), + }), + Term::Op(Op::Length { + m: Rational64::new(2, 1), + }), + ], + }), + ], + }); + assert_eq!(result, expected); + } +} diff --git a/ast/src/datagen/mod.rs b/ast/src/datagen/mod.rs index e02789bb3..2fabaa890 100644 --- a/ast/src/datagen/mod.rs +++ b/ast/src/datagen/mod.rs @@ -1,29 +1,44 @@ -use crate::{NameSet, NormalForm, Normalize, Op, OscType, PointOp, Term, ASR}; +use crate::{Axis, NameSet, Op, Term}; 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; -mod test; +mod csv1d_test; +mod csv2d_test; +pub mod mod_1d; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)] +pub struct Point { + fa: f32, + t: f32, +} -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct CsvData { data: Vec, } -pub fn csv_to_normalform(filename: &str, scale: Option) -> Result { - let data = get_data(filename.into())?; +#[derive(Clone, PartialEq, Debug, Hash)] +pub struct Scale { + pub axis: Axis, + pub value: Rational64, +} + +impl Scale { + fn apply(&self, value: f32) -> Rational64 { + self.value * f32_to_rational(value) + } +} + +pub fn csv2d_to_normalform(filename: &str, scales: Vec) -> Result { + let data = get_data2d(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 - }, + Ok(csv_data_to_normal_form( + &data, + scales, path.file_name() .unwrap() .to_string_lossy() @@ -32,42 +47,22 @@ pub fn csv_to_normalform(filename: &str, scale: Option) -> Result, 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 -} - -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); +fn csv_data_to_normal_form(data: &[Vec], scales: Vec, filename: &str) -> Term { + // let mut buffer = RingBuffer::::new(50); - let point_ops: Vec = data - .data + let point_ops: Vec = data .iter() .map(|value| { - let op = eeg_datum_to_point_op(*value, Some(&mut buffer), scale, filename); - length_ratio += op.l; - op + point_to_point_op( + value, None, // Some(&mut buffer), + &scales, filename, + ) }) .collect(); - NormalForm { - length_ratio, - operations: vec![point_ops], - } + Term::Op(Op::Sequence { + operations: point_ops, + }) } pub fn f32_to_rational(mut float: f32) -> Rational64 { @@ -83,43 +78,41 @@ pub fn f32_to_rational(mut float: f32) -> Rational64 { Ratio::new(num, den) } -fn eeg_datum_to_point_op( - datum: f32, +fn point_to_point_op( + point: &[f32], buffer: Option<&mut RingBuffer>, - scale: f32, + scales: &[Scale], filename: &str, -) -> PointOp { +) -> Term { let mut nameset = NameSet::new(); nameset.insert(filename.to_string()); - let mut datum = datum.abs() * scale; + let result: Vec = scales.iter().enumerate().map(|(i, _s)| point[i]).collect(); + + let mut fa = result[0]; if let Some(b) = buffer { - b.push(datum); + b.push(fa); let b_vec = b.to_vec(); let sum: f32 = b_vec.iter().sum(); - datum = sum / b_vec.len() as f32; + fa = 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, - } + let lm = result[1]; + + Term::Op(Op::Compose { + operations: vec![ + Term::Op(Op::TransposeA { + a: scales[0].apply(fa), + }), + Term::Op(Op::Length { + m: f32_to_rational(lm), + }), + ], + }) } -fn get_data(filename: String) -> Result, Error> { +fn _get_data1d(filename: String, length: Rational64) -> Result>, Error> { + let length = r_to_f32(length); let path = Path::new(&filename); let cwd = std::env::current_dir()?; let file = File::open(path).unwrap_or_else(|_| { @@ -129,13 +122,41 @@ fn get_data(filename: String) -> Result, Error> { cwd.display() ) }); + let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .delimiter(b',') .from_reader(file); - Ok(rdr - .deserialize::() + let deserialized: Vec> = rdr + .deserialize::>() .map(|datum| datum.expect("Error deserializing datum")) - .collect()) + .collect(); + let result: Vec> = deserialized[0].iter().map(|v| vec![*v, length]).collect(); + + Ok(result) +} + +fn get_data2d(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); + + let deserialized: Vec> = rdr + .deserialize::>() + .map(|datum| datum.expect("Error deserializing datum")) + .collect(); + + Ok(deserialized) } diff --git a/ast/src/datagen/mod_1d.rs b/ast/src/datagen/mod_1d.rs new file mode 100644 index 000000000..d3610e9da --- /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 csv1d_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/generator/coefs.rs b/ast/src/generator/coefs.rs index 8f131b1b3..6ce5d8102 100644 --- a/ast/src/generator/coefs.rs +++ b/ast/src/generator/coefs.rs @@ -40,7 +40,7 @@ pub enum Coefs { }, } -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derive_ord_xor_partial_ord)] impl Hash for Coefs { fn hash(&self, state: &mut H) { match self { diff --git a/ast/src/generator/mod.rs b/ast/src/generator/mod.rs index db1f3d140..745d13620 100644 --- a/ast/src/generator/mod.rs +++ b/ast/src/generator/mod.rs @@ -32,7 +32,7 @@ impl GenOp { name, seed: match seed { None => rng.gen::(), - Some(s) => s.1.unsigned_abs() as u64, + Some(s) => s.1.unsigned_abs(), }, } } @@ -42,7 +42,7 @@ impl GenOp { gen, seed: match seed { None => rng.gen::(), - Some(s) => s.1.unsigned_abs() as u64, + Some(s) => s.1.unsigned_abs(), }, } } @@ -52,7 +52,7 @@ impl GenOp { gen: Box::new(gen), seed: match seed { None => rng.gen::(), - Some(s) => s.1.unsigned_abs() as u64, + Some(s) => s.1.unsigned_abs(), }, n, } diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 2bc135abe..2dfc59637 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -9,6 +9,7 @@ pub mod operations; pub mod term; pub use crate::{ ast::{FunDef, Op, Op::*, OscType, ASR}, + datagen::Scale, generator::{ coefs::{Coef, Coefs}, Axis, CoefState, GenOp, Generator, diff --git a/ast/src/lists/indices.rs b/ast/src/lists/indices.rs index 23cde3c9b..9a42c1567 100644 --- a/ast/src/lists/indices.rs +++ b/ast/src/lists/indices.rs @@ -98,7 +98,7 @@ impl Index { .filter_map(|(i, n)| { if i % *skip as usize == 0 { Some(IndexVector { - index: *n as usize, + index: *n, index_terms: vec![], }) } else { diff --git a/ast/src/nameset/mod.rs b/ast/src/nameset/mod.rs index 818cda71f..91fb43679 100644 --- a/ast/src/nameset/mod.rs +++ b/ast/src/nameset/mod.rs @@ -22,17 +22,13 @@ impl NameSet { pub fn to_vec(&self) -> Vec { let count_b: BTreeMap<&usize, &String> = self.map.iter().map(|(k, v)| (v, k)).collect(); - count_b - .values() - .into_iter() - .map(|v| v.to_string()) - .collect() + count_b.values().map(|v| v.to_string()).collect() } pub fn to_vec_str(&self) -> Vec<&str> { let count_b: BTreeMap<&usize, &str> = self.map.iter().map(|(k, v)| (v, k.as_str())).collect(); - count_b.values().into_iter().copied().collect() + count_b.values().copied().collect() } pub fn last(&self) -> Option { diff --git a/ast/src/operations/get_length_ratio.rs b/ast/src/operations/get_length_ratio.rs index 2601a1d9e..699159135 100644 --- a/ast/src/operations/get_length_ratio.rs +++ b/ast/src/operations/get_length_ratio.rs @@ -29,7 +29,14 @@ impl GetLengthRatio for Op { | Op::Tag(_) | Op::Gain { .. } => Ok(Ratio::from_integer(1)), - Op::CSV { .. } => { + Op::CSV1d { .. } => { + let mut nf = NormalForm::init(); + self.apply_to_normal_form(&mut nf, defs)?; + + nf.get_length_ratio(normal_form, defs) + } + + Op::CSV2d { .. } => { let mut nf = NormalForm::init(); self.apply_to_normal_form(&mut nf, defs)?; diff --git a/ast/src/operations/normalize.rs b/ast/src/operations/normalize.rs index 0bd58322f..f784bdbc7 100644 --- a/ast/src/operations/normalize.rs +++ b/ast/src/operations/normalize.rs @@ -1,4 +1,4 @@ -use crate::datagen::csv_to_normalform; +use crate::datagen::{csv2d_to_normalform, mod_1d::csv1d_to_normalform}; use crate::operations::Rational64; use crate::operations::{ helpers::*, substitute::insert_function_args, GetLengthRatio, NormalForm, Normalize, Substitute, @@ -36,8 +36,12 @@ impl Normalize for Op { handle_id_error(id, defs)?.apply_to_normal_form(input, defs)?; } - Op::CSV { path, scale } => { - csv_to_normalform(path, *scale)?.apply_to_normal_form(input, defs)?; + Op::CSV1d { path, scale } => { + csv1d_to_normalform(path, *scale)?.apply_to_normal_form(input, defs)?; + } + + Op::CSV2d { path, scales } => { + csv2d_to_normalform(path, scales.clone())?.apply_to_normal_form(input, defs)?; } Op::FunctionCall { name, args } => { diff --git a/bin/snapshot.rs b/bin/snapshot.rs index f255bb2f7..82e409b0c 100644 --- a/bin/snapshot.rs +++ b/bin/snapshot.rs @@ -2,8 +2,10 @@ use weresocool::testing::{ generate_test_table, read_test_table_from_json_file, show_difference, write_test_table_to_json_file, }; +use weresocool_shared::Settings; fn main() { + Settings::init_test(); println!("\nHello Danny's WereSoCool Tests"); let should_rehash = std::env::args().any(|x| x == "--rehash"); @@ -18,7 +20,7 @@ fn main() { if test_table == decoded { println!("All Snapshot Tests Passed"); } else { - show_difference(decoded, test_table); + show_difference(&decoded, &test_table); panic!() } } diff --git a/core/Cargo.toml b/core/Cargo.toml index 34fe14a51..60a363960 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_core" -version = "1.0.43" +version = "1.0.44" edition = "2021" authors = ["Danny "] description = "***** WereSoCool __!Now In Stereo!__ ****** Make cool sounds. Impress your friends." @@ -19,23 +19,23 @@ exclude = [ [target.'cfg(target_os = "macos")'.dependencies] -weresocool_lame = { path = "../lame", version = "^1.0.43" } +weresocool_lame = { path = "../lame", version = "^1.0.44" } [target.'cfg(target_os = "linux")'.dependencies] -weresocool_lame = { path = "../lame", version = "^1.0.43" } +weresocool_lame = { path = "../lame", version = "^1.0.44" } [dependencies] -scop = { path = "../scop", version = "^1.0.43" } -opmap = { path = "../opmap", version = "^1.0.43" } -weresocool_ring_buffer = { path = "../ring_buffer", version = "^1.0.43" } -weresocool_analyze = { path = "../analyze", version = "^1.0.43" } -weresocool_parser = { path = "../parser", version = "^1.0.43", default_features=false, optional=true } -weresocool_ast = { path = "../ast", version = "^1.0.43", default_features=false, optional=true } -weresocool_error = { path = "../error", version = "^1.0.43", default_features=false, optional=true } -weresocool_instrument = { path = "../instrument", version = "^1.0.43", default_features=false, optional=true } -weresocool_shared = { path = "../shared", version = "^1.0.43" } -weresocool_vorbis = { path = "../vorbis", version = "^1.0.43", optional=true, default_features = false } -weresocool_portaudio = { path = "../portaudio", version = "^1.0.43", optional=true } +scop = { path = "../scop", version = "^1.0.44" } +opmap = { path = "../opmap", version = "^1.0.44" } +weresocool_ring_buffer = { path = "../ring_buffer", version = "^1.0.44" } +weresocool_analyze = { path = "../analyze", version = "^1.0.44" } +weresocool_parser = { path = "../parser", version = "^1.0.44", default_features=false, optional=true } +weresocool_ast = { path = "../ast", version = "^1.0.44", default_features=false, optional=true } +weresocool_error = { path = "../error", version = "^1.0.44", default_features=false, optional=true } +weresocool_instrument = { path = "../instrument", version = "^1.0.44", default_features=false, optional=true } +weresocool_shared = { path = "../shared", version = "^1.0.44" } +weresocool_vorbis = { path = "../vorbis", version = "^1.0.44", optional=true, default_features = false } +weresocool_portaudio = { path = "../portaudio", version = "^1.0.44", optional=true } rand = { version="0.7.3", features=["wasm-bindgen"]} hound = "3.4.0" serde = { version = "1.0.119", features = ["derive"] } diff --git a/core/src/generation/parsed_to_render.rs b/core/src/generation/parsed_to_render.rs index 6fcca6d75..accadccaf 100644 --- a/core/src/generation/parsed_to_render.rs +++ b/core/src/generation/parsed_to_render.rs @@ -26,9 +26,7 @@ use weresocool_instrument::renderable::{ }; use weresocool_instrument::{Basis, Oscillator, StereoWaveform}; use weresocool_parser::ParsedComposition; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; #[derive(Clone, Eq, PartialEq, Debug)] pub enum WavType { @@ -182,7 +180,6 @@ pub fn parsed_to_render( if !names.is_subset(&nf_names) { let difference = names .difference(&nf_names) - .into_iter() .cloned() .collect::>() .join(", "); @@ -325,7 +322,7 @@ pub fn render( #[cfg(feature = "wasm")] let iter = voices.iter_mut(); let batch: Vec = iter - .filter_map(|voice| voice.render_batch(SETTINGS.buffer_size, None)) + .filter_map(|voice| voice.render_batch(Settings::global().buffer_size, None)) .collect(); if !batch.is_empty() { @@ -395,7 +392,7 @@ pub fn generate_waveforms( .map(|ref mut vec_render_op: &mut Vec| { #[cfg(feature = "app")] pb.lock().unwrap().add(1_u64); - let mut osc = Oscillator::init(&SETTINGS); + let mut osc = Oscillator::init(); vec_render_op.render(&mut osc, None) }) .collect(); diff --git a/core/src/manager/mod.rs b/core/src/manager/mod.rs index f4b23dee1..3bae54067 100644 --- a/core/src/manager/mod.rs +++ b/core/src/manager/mod.rs @@ -4,6 +4,7 @@ mod render_manager; pub use self::{ buffer_manager::BufferManager, render_manager::{ - prepare_render_outside, render_op_to_normalized_op4d, KillChannel, RenderManager, VisEvent, + prepare_render_outside, render_op_to_normalized_op4d, KillChannel, RenderManager, + RenderManagerSettings, VisEvent, }, }; diff --git a/core/src/manager/render_manager.rs b/core/src/manager/render_manager.rs index 15efc7f91..68daf101b 100644 --- a/core/src/manager/render_manager.rs +++ b/core/src/manager/render_manager.rs @@ -15,9 +15,7 @@ use weresocool_instrument::renderable::{ nf_to_vec_renderable, renderables_to_render_voices, RenderOp, RenderVoice, Renderable, }; use weresocool_instrument::StereoWaveform; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; pub type KillChannel = Option>; @@ -68,19 +66,31 @@ pub fn render_op_to_normalized_op4d(render_op: &RenderOp, normalizer: &Normalize Some(op4d) } +pub struct RenderManagerSettings { + pub sample_rate: f64, + pub buffer_size: usize, +} + impl RenderManager { - pub const fn init( - render_voices: Vec, + pub fn init( visualization_channel: VisualizationChannel, kill_channel: KillChannel, once: bool, + settings: Option, ) -> Self { + if !cfg!(test) { + if let Some(s) = settings { + Settings::init(s.sample_rate, s.buffer_size); + } else { + Settings::init_default(); + }; + } Self { visualization: Visualization { channel: visualization_channel, normalizer: Normalizer::default(), }, - renders: [Some(render_voices), None], + renders: [None, None], past_volume: 0.8, current_volume: 0.8, render_idx: 0, @@ -91,23 +101,6 @@ impl RenderManager { } } - pub const fn init_silent() -> Self { - Self { - visualization: Visualization { - channel: None, - normalizer: Normalizer::default(), - }, - renders: [None, None], - past_volume: 0.8, - current_volume: 0.8, - render_idx: 0, - _read_idx: 0, - kill_channel: None, - once: false, - paused: false, - } - } - pub fn kill(&self) -> Result<(), SendError> { if let Some(kc) = &self.kill_channel { kc.send(true)?; @@ -163,7 +156,7 @@ impl RenderManager { let result: Vec<(_, _)> = iter .filter_map(|voice| { - let ops = voice.get_batch(SETTINGS.buffer_size, None); + let ops = voice.get_batch(Settings::global().buffer_size, None); match ops { Some(mut batch) => Some(( if vtx.is_some() { @@ -258,7 +251,8 @@ impl RenderManager { self.next_render().is_some() } - pub fn push_render(&mut self, render: Vec) { + pub fn push_render(&mut self, render: Vec, once: bool) { + self.once = once; *self.next_render() = Some(render); } } @@ -286,7 +280,7 @@ mod render_manager_tests { #[test] fn test_ramp_to_current_value() { - let mut rm = RenderManager::init_silent(); + let mut rm = RenderManager::init(None, None, false, None); rm.update_volume(0.9); assert!(cmp_f32(rm.current_volume, f32::powf(0.9, 2.0))); let ramp = rm.ramp_to_current_volume(2); @@ -299,7 +293,7 @@ mod render_manager_tests { #[test] fn test_inc_render() { - let mut r = RenderManager::init_silent(); + let mut r = RenderManager::init(None, None, false, None); r.inc_render(); assert_eq!(r.render_idx, 1); r.inc_render(); @@ -312,11 +306,13 @@ mod render_manager_tests { #[test] fn test_push_render() { - let mut r = RenderManager::init(render_voices_mock(), None, None, false); - assert_eq!(*r.current_render(), Some(render_voices_mock())); + Settings::init_test(); + let mut r = RenderManager::init(None, None, false, None); + assert_eq!(*r.current_render(), None); assert_eq!(*r.next_render(), None); - r.push_render(render_voices_mock()); - assert_eq!(*r.current_render(), Some(render_voices_mock())); + r.push_render(render_voices_mock(), false); assert_eq!(*r.next_render(), Some(render_voices_mock())); + assert_eq!(*r.current_render(), None); + r.push_render(render_voices_mock(), false); } } diff --git a/core/src/portaudio/duplex.rs b/core/src/portaudio/duplex.rs index d6ffd292d..f3122519a 100644 --- a/core/src/portaudio/duplex.rs +++ b/core/src/portaudio/duplex.rs @@ -8,9 +8,7 @@ use weresocool_instrument::renderable::{ use weresocool_instrument::StereoWaveform; use weresocool_portaudio as pa; use weresocool_ring_buffer::RingBuffer; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; fn process_detection_result(result: &mut DetectionResult) -> (f64, f64) { if result.gain < 0.005 || result.frequency > 1_000.0 { @@ -30,13 +28,14 @@ fn sing_along_callback( ) { input_buffer.push_vec(args.in_buffer.to_vec()); - let mut detection_result: DetectionResult = input_buffer - .to_vec() - .analyze(SETTINGS.sample_rate as f32, SETTINGS.probability_threshold); + let mut detection_result: DetectionResult = input_buffer.to_vec().analyze( + Settings::global().sample_rate as f32, + Settings::global().probability_threshold, + ); let (freq, gain) = process_detection_result(&mut detection_result); - let offset = if SETTINGS.mic { + let offset = if Settings::global().mic { Some(Offset { freq: freq / basis_f, gain, @@ -47,7 +46,7 @@ fn sing_along_callback( let result: Vec = voices .par_iter_mut() - .filter_map(|voice| voice.render_batch(SETTINGS.buffer_size, offset.as_ref())) + .filter_map(|voice| voice.render_batch(Settings::global().buffer_size, offset.as_ref())) .collect(); let stereo_waveform = sum_all_waveforms(result); write_output_buffer(args.out_buffer, stereo_waveform); @@ -61,7 +60,8 @@ pub fn duplex_setup( let duplex_stream_settings = get_duplex_settings(&pa)?; let mut voices = renderables_to_render_voices(renderables); - let mut input_buffer: RingBuffer = RingBuffer::::new(SETTINGS.yin_buffer_size); + let mut input_buffer: RingBuffer = + RingBuffer::::new(Settings::global().yin_buffer_size); let duplex_stream = pa.open_non_blocking_stream(duplex_stream_settings, move |args| { sing_along_callback(basis_f, args, &mut input_buffer, &mut voices); @@ -79,8 +79,8 @@ fn get_duplex_settings(pa: &pa::PortAudio) -> Result::new( def_input, - SETTINGS.channels, - SETTINGS.interleaved, + Settings::global().channels, + Settings::global().interleaved, latency, ); @@ -89,14 +89,18 @@ fn get_duplex_settings(pa: &pa::PortAudio) -> Result, @@ -21,7 +19,7 @@ pub fn real_time( let result: Vec<(_, _)> = iter .filter_map(|voice| { - let ops = voice.get_batch(SETTINGS.buffer_size, None); + let ops = voice.get_batch(Settings::global().buffer_size, None); match ops { Some(mut batch) => { Some((batch.clone(), batch.render(&mut voice.oscillator, None))) @@ -49,13 +47,17 @@ pub fn get_output_settings(pa: &pa::PortAudio) -> Result>, @@ -14,7 +12,10 @@ pub fn real_time_buffer_manager( let output_stream_settings = get_output_settings(&pa)?; let output_stream = pa.open_non_blocking_stream(output_stream_settings, move |args| { - let sw = buffer_manager.lock().unwrap().read(SETTINGS.buffer_size); + let sw = buffer_manager + .lock() + .unwrap() + .read(Settings::global().buffer_size); match sw { Some(stereo_waveform) => { @@ -22,7 +23,10 @@ pub fn real_time_buffer_manager( pa::Continue } None => { - write_output_buffer(args.buffer, StereoWaveform::new(SETTINGS.buffer_size)); + write_output_buffer( + args.buffer, + StereoWaveform::new(Settings::global().buffer_size), + ); pa::Continue } @@ -37,13 +41,17 @@ pub fn get_output_settings(pa: &pa::PortAudio) -> Result>, @@ -18,14 +16,19 @@ pub fn real_time_render_manager( let output_stream_settings = get_output_settings(&pa)?; let output_stream = pa.open_non_blocking_stream(output_stream_settings, move |args| { - let batch: Option<(StereoWaveform, Vec)> = - render_manager.lock().unwrap().read(SETTINGS.buffer_size); + let batch: Option<(StereoWaveform, Vec)> = render_manager + .lock() + .unwrap() + .read(Settings::global().buffer_size); if let Some((b, ramp)) = batch { new_write_output_buffer(args.buffer, b, ramp); pa::Continue } else { - write_output_buffer(args.buffer, StereoWaveform::new(SETTINGS.buffer_size)); + write_output_buffer( + args.buffer, + StereoWaveform::new(Settings::global().buffer_size), + ); pa::Continue } @@ -39,13 +42,17 @@ pub fn get_output_settings(pa: &pa::PortAudio) -> Result, filename: Option) { "\n**** WereSoCool".truecolor(250, 180, 220).bold(), format!("v{} ****", VERSION).truecolor(250, 180, 220).bold() ); + println!("{}", "--- Make cool sounds. ---".truecolor(250, 134, 200)); println!( "{}", - "--- Make cool sounds. Impress your friends/pets/plants. ---".truecolor(250, 134, 200) + "--- Impress your friends/pets/plants. ---".truecolor(250, 134, 200) ); // println!( // "{}", diff --git a/core/src/write.rs b/core/src/write.rs index 98052f695..6b281b0a0 100644 --- a/core/src/write.rs +++ b/core/src/write.rs @@ -8,9 +8,7 @@ use weresocool_error::Error; use weresocool_instrument::{Normalize, StereoWaveform}; #[cfg(not(any(target_os = "windows", feature = "wasm")))] use weresocool_lame::Lame; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; pub fn write_output_buffer(out_buffer: &mut [f32], stereo_waveform: StereoWaveform) { let mut l_idx = 0; @@ -65,6 +63,7 @@ pub fn write_composition_to_mp3(mut composition: StereoWaveform) -> Result Result"] edition = "2021" description = "Errors for WereSoCool" license = "GPL-3.0" [target.'cfg(target_os = "macos")'.dependencies] -weresocool_lame = { path = "../lame", version = "^1.0.43" } +weresocool_lame = { path = "../lame", version = "^1.0.44" } [target.'cfg(target_os = "linux")'.dependencies] -weresocool_lame = { path = "../lame", version = "^1.0.43" } +weresocool_lame = { path = "../lame", version = "^1.0.44" } [dependencies] -scop = { path = "../scop", version = "^1.0.43" } -weresocool_portaudio = { path = "../portaudio", version = "^1.0.43", optional=true } +scop = { path = "../scop", version = "^1.0.44" } +weresocool_portaudio = { path = "../portaudio", version = "^1.0.44", optional=true } hound = "3.4.0" serde_json = "1.0.64" serde = { version = "1.0.119", features = ["derive"] } diff --git a/instrument/Cargo.toml b/instrument/Cargo.toml index dd3d8b644..864d9083f 100644 --- a/instrument/Cargo.toml +++ b/instrument/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_instrument" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "audio renderer for WereSoCool" @@ -9,13 +9,13 @@ license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -weresocool_ast = { path = "../ast", version = "^1.0.43", default_features=false, optional=true } -weresocool_error = { path = "../error", version = "^1.0.43", default_features=false, optional=true } -weresocool_parser = { path = "../parser", version = "^1.0.43", default_features=false, optional=true } -weresocool_shared = { path = "../shared", version = "^1.0.43" } -scop = { path = "../scop", version = "^1.0.43" } +weresocool_ast = { path = "../ast", version = "^1.0.44", default_features=false, optional=true } +weresocool_error = { path = "../error", version = "^1.0.44", default_features=false, optional=true } +weresocool_parser = { path = "../parser", version = "^1.0.44", default_features=false, optional=true } +weresocool_shared = { path = "../shared", version = "^1.0.44" } +scop = { path = "../scop", version = "^1.0.44" } serde = { version = "1.0.119", features = ["derive"] } -reverb = { path = "reverb", version = "^1.0.43" } +reverb = { path = "reverb", version = "^1.0.44" } rayon = "1.5.1" num-rational = "0.3.2" rand = { version="0.7.3", features=["wasm-bindgen"]} diff --git a/instrument/reverb/Cargo.toml b/instrument/reverb/Cargo.toml index 94ad51c00..e009e3e3b 100644 --- a/instrument/reverb/Cargo.toml +++ b/instrument/reverb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reverb" -version = "1.0.43" +version = "1.0.44" authors = ["josh, mitchell.nordine@gmail.com"] description = "A super-fast mono-to-stereo plate reverberator." readme = "README.md" diff --git a/instrument/src/frequency.rs b/instrument/src/frequency.rs index 154138da4..fbfd4c267 100644 --- a/instrument/src/frequency.rs +++ b/instrument/src/frequency.rs @@ -32,11 +32,12 @@ impl Voice { #[cfg(test)] mod tests { use super::*; - use weresocool_shared::{get_test_settings, helpers::cmp_f64}; + use weresocool_shared::{helpers::cmp_f64, Settings}; #[test] fn test_calculate_portamento_delta() { - let v = Voice::init(0, get_test_settings()); + Settings::init_test(); + let v = Voice::init(0); let result = v.calculate_portamento_delta(10, 0.0, 100.0); assert!(cmp_f64(result, 10.0)); let result = v.calculate_portamento_delta(10, 100.0, 0.0); @@ -46,8 +47,9 @@ mod tests { } #[test] fn test_calculate_frequency() { + Settings::init_test(); for i in 0..10 { - let v = Voice::init(0, get_test_settings()); + let v = Voice::init(0); let result = v.calculate_frequency(i, 4, 25.0, 0.0, 100.0); let expected = std::cmp::min(100, result as usize); assert_eq!(result as usize, expected); diff --git a/instrument/src/gain.rs b/instrument/src/gain.rs index 37d5adede..d18b949af 100644 --- a/instrument/src/gain.rs +++ b/instrument/src/gain.rs @@ -55,11 +55,22 @@ impl Voice { #[cfg(test)] mod tests { use super::*; - use weresocool_shared::get_test_settings; + use std::sync::Once; use weresocool_shared::helpers::cmp_f64; + use weresocool_shared::{get_test_settings, Settings}; + + static INIT: Once = Once::new(); + + fn setup() { + INIT.call_once(|| { + Settings::init_test(); + }); + } + #[test] fn test_get_current_gain_from_op() { - let v = Voice::init(0, get_test_settings()); + setup(); + let v = Voice::init(0); let mut op = RenderOp::init_fglp(200.0, (0.9, 0.9), 1.0, 0.0, &get_test_settings()); op.osc_type = OscType::Noise; @@ -77,7 +88,8 @@ mod tests { #[test] fn test_get_past_gain_from_op() { - let mut v = Voice::init(0, get_test_settings()); + setup(); + let mut v = Voice::init(0); let mut op = RenderOp::init_fglp(200.0, (1.0, 1.0), 1.0, 0.0, &get_test_settings()); op.osc_type = OscType::Noise; @@ -90,20 +102,22 @@ mod tests { #[test] fn test_silence_next() { + setup(); let mut op = RenderOp::init_silent_with_length(1.0); op.next_r_silent = false; op.next_l_silent = true; - let v1 = Voice::init(0, get_test_settings()); + let v1 = Voice::init(0); let result = v1.silence_next(&op); assert!(result); - let v2 = Voice::init(1, get_test_settings()); + let v2 = Voice::init(1); let result = v2.silence_next(&op); assert!(!result); } #[test] fn test_gain_from_index() { + setup(); let mut g = gain_at_index(0.0, 1.0, 5, 10); assert!(cmp_f64(g, 0.5)); @@ -115,7 +129,8 @@ mod tests { } #[test] fn test_silence_now() { - let mut v = Voice::init(0, get_test_settings()); + setup(); + let mut v = Voice::init(0); v.current.frequency = 0.0; v.current.gain = 1.0; assert!(v.silence_now()); @@ -133,7 +148,8 @@ mod tests { } #[test] fn test_silence_to_sound() { - let mut v = Voice::init(0, get_test_settings()); + setup(); + let mut v = Voice::init(0); v.past.frequency = 0.0; v.current.frequency = 100.0; v.past.gain = 1.0; diff --git a/instrument/src/loudness.rs b/instrument/src/loudness.rs index 6398779c8..7ed289e3b 100644 --- a/instrument/src/loudness.rs +++ b/instrument/src/loudness.rs @@ -1,10 +1,8 @@ -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; pub fn freq_to_sones(frequency: f64) -> f64 { // http://www.ukintpress-conferences.com/conf/08txeu_conf/pdf/day_1/01-06-garcia.pdf - if frequency < SETTINGS.min_freq { + if frequency < Settings::global().min_freq { 0.0 } else { 1.0 / (((20.0 * (frequency).log10()) - 40.0) / 10.0).exp2() diff --git a/instrument/src/oscillator.rs b/instrument/src/oscillator.rs index cd71b7a0c..f5cccd99c 100644 --- a/instrument/src/oscillator.rs +++ b/instrument/src/oscillator.rs @@ -4,12 +4,10 @@ use crate::{ }; use num_rational::Rational64; use weresocool_parser::Init; -use weresocool_shared::Settings; #[derive(Clone, Debug, PartialEq)] pub struct Oscillator { pub voices: (Voice, Voice), - pub settings: Settings, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -36,13 +34,9 @@ impl Basis { } impl Oscillator { - pub fn init(settings: &Settings) -> Self { + pub fn init() -> Self { Self { - voices: ( - Voice::init(0, settings.clone()), - Voice::init(1, settings.clone()), - ), - settings: settings.clone(), + voices: (Voice::init(0), Voice::init(1)), } } diff --git a/instrument/src/renderable/mod.rs b/instrument/src/renderable/mod.rs index c5485a4ff..ba9b77070 100644 --- a/instrument/src/renderable/mod.rs +++ b/instrument/src/renderable/mod.rs @@ -11,9 +11,7 @@ use scop::Defs; use serde::{Deserialize, Serialize}; use weresocool_ast::{NormalForm, Normalize, OscType, PointOp, Term, ASR}; use weresocool_error::Error; -use weresocool_shared::{get_settings, lossy_rational_mul, r_to_f64, Settings}; - -const SETTINGS: Settings = get_settings(); +pub(crate) use weresocool_shared::{lossy_rational_mul, r_to_f64, Settings}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RenderOp { @@ -63,7 +61,7 @@ impl RenderOp { names: vec![], } } - pub const fn init_silent_with_length(l: f64) -> Self { + pub fn init_silent_with_length(l: f64) -> Self { Self { f: 0.0, g: (0.0, 0.0), @@ -71,11 +69,11 @@ impl RenderOp { l, t: 0.0, reverb: None, - attack: SETTINGS.sample_rate, - decay: SETTINGS.sample_rate, + attack: Settings::global().sample_rate, + decay: Settings::global().sample_rate, asr: ASR::Long, - samples: SETTINGS.sample_rate as usize, - total_samples: SETTINGS.sample_rate as usize, + samples: Settings::global().sample_rate as usize, + total_samples: Settings::global().sample_rate as usize, index: 0, voice: 0, event: 0, @@ -91,6 +89,7 @@ impl RenderOp { l: f64, osc_type: OscType, reverb: Option, + settings: &Settings, ) -> Self { Self { f: 0.0, @@ -99,11 +98,11 @@ impl RenderOp { l, t: 0.0, reverb, - attack: SETTINGS.sample_rate, - decay: SETTINGS.sample_rate, + attack: settings.sample_rate, + decay: settings.sample_rate, asr: ASR::Long, - samples: SETTINGS.sample_rate as usize, - total_samples: SETTINGS.sample_rate as usize, + samples: settings.sample_rate as usize, + total_samples: settings.sample_rate as usize, index: 0, voice: 0, event: 0, @@ -177,6 +176,7 @@ fn pointop_to_renderop( basis: &Basis, next: Option, ) -> RenderOp { + let settings = Settings::global(); let mut next_l_gain = 0.0; let mut next_r_gain = 0.0; let next_silent; @@ -209,10 +209,10 @@ fn pointop_to_renderop( None }, index: 0, - samples: (l * SETTINGS.sample_rate).round() as usize, - total_samples: (l * SETTINGS.sample_rate).round() as usize, - attack: r_to_f64(point_op.attack * basis.a) * SETTINGS.sample_rate, - decay: r_to_f64(point_op.decay * basis.d) * SETTINGS.sample_rate, + samples: (l * settings.sample_rate).round() as usize, + total_samples: (l * settings.sample_rate).round() as usize, + attack: r_to_f64(point_op.attack * basis.a) * settings.sample_rate, + decay: r_to_f64(point_op.decay * basis.d) * settings.sample_rate, osc_type: point_op.osc_type, asr: point_op.asr, portamento: (r_to_f64(point_op.portamento) * 1024_f64) as usize, @@ -257,6 +257,7 @@ pub fn m_a_and_basis_to_f64(basis: Rational64, m: Rational64, a: Rational64) -> } pub fn calculate_fgpl(basis: &Basis, point_op: &PointOp) -> (f64, (f64, f64), f64, f64) { + let settings = Settings::global(); let (mut f, mut g) = if point_op.is_silent() { (0.0, (0.0, 0.0)) } else { @@ -265,7 +266,7 @@ pub fn calculate_fgpl(basis: &Basis, point_op: &PointOp) -> (f64, (f64, f64), f6 }; let p = m_a_and_basis_to_f64(basis.p, point_op.pm, point_op.pa); let l = r_to_f64(point_op.l * basis.l); - if f < SETTINGS.min_freq { + if f < settings.min_freq { f = 0.0; g = (0.0, 0.0); }; @@ -278,6 +279,7 @@ pub fn nf_to_vec_renderable( defs: &mut Defs, basis: &Basis, ) -> Result>, Error> { + let settings = Settings::global(); let mut normal_form = NormalForm::init(); composition.apply_to_normal_form(&mut normal_form, defs)?; @@ -307,11 +309,12 @@ pub fn nf_to_vec_renderable( ); result.push(op); } - if SETTINGS.pad_end { + if settings.pad_end { result.push(RenderOp::init_silent_with_length_osc_type_and_reverb( 1.0, OscType::None, None, + settings, )); } result diff --git a/instrument/src/renderable/render_voice.rs b/instrument/src/renderable/render_voice.rs index 7aef8c480..4acba4b0f 100644 --- a/instrument/src/renderable/render_voice.rs +++ b/instrument/src/renderable/render_voice.rs @@ -2,9 +2,7 @@ use crate::renderable::{Offset, RenderOp, Renderable}; use crate::{Oscillator, StereoWaveform}; #[cfg(feature = "app")] use rayon::prelude::*; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; #[derive(Debug, Clone, PartialEq)] pub struct RenderVoice { @@ -19,17 +17,12 @@ impl RenderVoice { sample_index: 0, op_index: 0, ops: ops.to_vec(), - oscillator: Oscillator::init(&SETTINGS), + oscillator: Oscillator::init(), } } /// Recursive function to prepare a batch of RenderOps for rendering /// Initially pass in None as result - /// ``` - /// # use weresocool_instrument::renderable::{RenderOp, RenderVoice}; - /// let mut voice = RenderVoice::init(&vec![RenderOp::init_silent_with_length(1.0)]); - /// let batch = voice.get_batch(1024, None); - /// ``` pub fn get_batch( &mut self, samples_left_in_batch: usize, @@ -40,7 +33,7 @@ impl RenderVoice { None => vec![], }; - if SETTINGS.loop_play && self.op_index >= self.ops.len() { + if Settings::global().loop_play && self.op_index >= self.ops.len() { self.op_index = 0; } diff --git a/instrument/src/sample.rs b/instrument/src/sample.rs index 26508a2cc..82782f333 100644 --- a/instrument/src/sample.rs +++ b/instrument/src/sample.rs @@ -2,12 +2,9 @@ use crate::voice::{SampleInfo, Voice}; use num_rational::Rational64; use rand::{thread_rng, Rng}; use std::f64::consts::PI; -use weresocool_shared::{get_settings, r_to_f64, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::{r_to_f64, Settings}; const TAU: f64 = PI * 2.0; -const FACTOR: f64 = TAU / SETTINGS.sample_rate; fn random_offset() -> f64 { thread_rng().gen_range(-0.5, 0.5) @@ -61,7 +58,8 @@ impl Voice { if info.gain == 0.0 { 0.0 } else { - (FACTOR.mul_add(info.frequency, self.phase) + rand) % TAU + ((TAU / Settings::global().sample_rate).mul_add(info.frequency, self.phase) + rand) + % TAU } } } diff --git a/instrument/src/test.rs b/instrument/src/test.rs index c04567f23..939cea5c0 100644 --- a/instrument/src/test.rs +++ b/instrument/src/test.rs @@ -7,15 +7,15 @@ pub mod tests { voice::{ReverbState, Voice, VoiceState}, }; use weresocool_ast::ast::{OscType, ASR}; - use weresocool_shared::get_test_settings; use weresocool_shared::helpers::{cmp_f64, cmp_vec_f64}; + use weresocool_shared::{get_test_settings, Settings}; pub mod voice { use super::*; #[test] fn test_voice_init() { let index = 1; let settings = get_test_settings(); - let voice = Voice::init(index, settings.clone()); + let voice = Voice::init(index); let result = Voice { index, @@ -57,7 +57,7 @@ pub mod tests { #[test] fn test_deltas() { let index = 1; - let mut voice = Voice::init(index, get_test_settings()); + let mut voice = Voice::init(index); let op = RenderOp::init_fglp(200.0, (0.5, 0.5), 1.0, 0.0, &get_test_settings()); voice.update(&op, &Offset::identity()); @@ -72,7 +72,7 @@ pub mod tests { #[test] fn test_generate_waveform() { let index = 1; - let mut voice = Voice::init(index, get_test_settings()); + let mut voice = Voice::init(index); let mut op = RenderOp::init_fglp(100.0, (0.5, 0.5), 1.0, 0.0, &get_test_settings()); op.samples = 3; voice.update(&op, &Offset::identity()); @@ -85,7 +85,7 @@ pub mod tests { #[test] fn test_sound_silence() { - let mut voice = Voice::init(1, get_test_settings()); + let mut voice = Voice::init(1); let op1 = RenderOp::init_fglp(100.0, (0.5, 0.5), 1.0, 0.0, &get_test_settings()); let op2 = RenderOp::init_fglp(100.0, (0.5, 0.5), 1.0, 0.0, &get_test_settings()); voice.update(&op1, &Offset::identity()); @@ -105,9 +105,8 @@ pub mod tests { #[test] fn oscillator_init_test() { - let osc = Oscillator::init(&get_test_settings()); + let osc = Oscillator::init(); let expected = Oscillator { - settings: get_test_settings(), voices: ( Voice { index: 0, @@ -182,7 +181,7 @@ pub mod tests { #[test] fn oscillator_update_test() { - let mut osc = Oscillator::init(&get_test_settings()); + let mut osc = Oscillator::init(); let render_op = RenderOp::init_fglp(100.0, (0.75, 0.25), 1.0, 0.0, &get_test_settings()); @@ -206,7 +205,7 @@ pub mod tests { #[ignore] #[test] fn oscillator_generate_sine_test() { - let mut osc = Oscillator::init(&get_test_settings()); + let mut osc = Oscillator::init(); let mut render_op = RenderOp::init_fglp(100.0, (0.75, 0.25), 1.0, 0.0, &get_test_settings()); @@ -224,12 +223,11 @@ pub mod tests { assert_eq!(osc.generate(&render_op, &Offset::identity()), expected); } - // TODO: Pass config around #[ignore] #[test] fn oscillator_generate_sine_power_test() { let settings = get_test_settings(); - let mut osc = Oscillator::init(&settings); + let mut osc = Oscillator::init(); let mut render_op = RenderOp::init_fglp(100.0, (0.75, 0.25), 1.0, 0.0, &settings); diff --git a/instrument/src/voice.rs b/instrument/src/voice.rs index 646fa726c..3ae774ecd 100644 --- a/instrument/src/voice.rs +++ b/instrument/src/voice.rs @@ -5,9 +5,7 @@ use crate::{ use reverb::Reverb; use weresocool_ast::{OscType, ASR}; -use weresocool_shared::{get_settings, Settings}; - -const SETTINGS: Settings = get_settings(); +use weresocool_shared::Settings; #[derive(Clone, Debug, PartialEq)] pub struct Voice { @@ -51,7 +49,7 @@ impl VoiceState { /// Check if the previous voice state was silent pub fn silent(&self) -> bool { - self.frequency < SETTINGS.min_freq || self.gain == 0.0 + self.frequency < Settings::global().min_freq || self.gain == 0.0 } } @@ -71,7 +69,7 @@ impl ReverbState { } impl Voice { - pub fn init(index: usize, settings: Settings) -> Self { + pub fn init(index: usize) -> Self { Self { index, reverb: ReverbState::init(), @@ -81,8 +79,8 @@ impl Voice { offset_current: VoiceState::init(), phase: 0.0, osc_type: OscType::Sine { pow: None }, - attack: settings.sample_rate as usize, - decay: settings.sample_rate as usize, + attack: Settings::global().sample_rate as usize, + decay: Settings::global().sample_rate as usize, asr: ASR::Long, } } diff --git a/lame/Cargo.toml b/lame/Cargo.toml index 2ec5ee965..0ce8853b1 100644 --- a/lame/Cargo.toml +++ b/lame/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_lame" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "Lame FFI for WereSoCool" diff --git a/lame/src/lib.rs b/lame/src/lib.rs index a9b50417b..f6deedb0a 100644 --- a/lame/src/lib.rs +++ b/lame/src/lib.rs @@ -79,6 +79,11 @@ impl Lame { pub fn new() -> Option { let ctx = unsafe { ffi::lame_init() }; + unsafe { + crate::ffi::lame_set_in_samplerate(ctx, 48000); + dbg!(crate::ffi::lame_get_in_samplerate(ctx)); + } + if ctx.is_null() { None } else { @@ -123,7 +128,7 @@ impl Lame { /// Returns the output bitrate in kilobits per second. pub fn kilobitrate(&self) -> i32 { - unsafe { ffi::lame_get_brate(self.ptr) as i32 } + unsafe { ffi::lame_get_brate(self.ptr) } } /// Sets the target output bitrate. This value is in kilobits per second, diff --git a/mocks/data/csv.socool b/mocks/data/csv.socool deleted file mode 100644 index 15688af8f..000000000 --- a/mocks/data/csv.socool +++ /dev/null @@ -1,13 +0,0 @@ -{ f: 220, l: 1, g: 1, p: 0 } - - -main = { - csv(2.0e14) ./mocks/data/data.csv -} - -expect = { - Seq [ - Fa 1/1, Fa 3/2, Fa 2/1 - ] | Lm 1/50 - | #data.csv -} diff --git a/mocks/data/csv1d.socool b/mocks/data/csv1d.socool new file mode 100644 index 000000000..5ab64b5f6 --- /dev/null +++ b/mocks/data/csv1d.socool @@ -0,0 +1,16 @@ +{ f: 220, l: 1, g: 1, p: 0 } + + +main = { + Csv1d(2.0) ./mocks/data/data1d.csv +} + +expect = { + -- remember there is a ring buffer of 50 samples involved here :) + Seq [ + Fa 2/1, Fa 3/1, Fa 4/1 + ] + | Lm 1/50 + | #data1d.csv + +} diff --git a/mocks/data/csv2d.socool b/mocks/data/csv2d.socool new file mode 100644 index 000000000..c7d93518d --- /dev/null +++ b/mocks/data/csv2d.socool @@ -0,0 +1,12 @@ +{ f: 220, l: 1, g: 1, p: 0 } + + +main = { + Csv2d(f: 2.0, l: 1.0) ./mocks/data/data2d.csv +} + +expect = { + Seq [ + Fa 2/1, Fa 4/1, Fa 6/1 + ] +} diff --git a/mocks/data/data1d.csv b/mocks/data/data1d.csv new file mode 100644 index 000000000..633d7cf8a --- /dev/null +++ b/mocks/data/data1d.csv @@ -0,0 +1 @@ +1.0,2.0,3.0 diff --git a/mocks/data/data2d.csv b/mocks/data/data2d.csv new file mode 100644 index 000000000..4440e144c --- /dev/null +++ b/mocks/data/data2d.csv @@ -0,0 +1,3 @@ +1.0,1.0 +2.0,1.0 +3.0,1.0 diff --git a/mocks/ops/overtone.socool b/mocks/ops/overtone.socool new file mode 100644 index 000000000..6764f797f --- /dev/null +++ b/mocks/ops/overtone.socool @@ -0,0 +1,15 @@ +{ f: 220, l: 1, g: 1, p: 0 } + +main = { + Overlay [ + {2, 2, 1/2, 1}, + {1, -1, 2/3, -1}, + ] +} + +expect = { + Overlay [ + Fm 2 | Fa 2 | Gm 1/2 | Pa 1, + Fm 1 | Fa -1 | Gm 2/3 | Pa -1, + ] +} diff --git a/opmap/Cargo.toml b/opmap/Cargo.toml index 6f5836fa8..14edcb8f2 100644 --- a/opmap/Cargo.toml +++ b/opmap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opmap" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "OpMap Datastructure for WereSoCool" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e1215b55b..4c68741ef 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_parser" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" build = "build.rs" # LALRPOP preprocessing @@ -13,10 +13,10 @@ version = "0.19.6" features = ["lexer"] [dependencies] -weresocool_ast = { path = "../ast", version = "^1.0.43", default_features=false, optional=true } -weresocool_error = { path = "../error", version = "^1.0.43", default_features=false, optional=true } -scop = { path = "../scop", version = "^1.0.43" } -lalrpop-util = { version="0.19.6", features=["lexer"]} +weresocool_ast = { path = "../ast", version = "^1.0.44", default_features=false, optional=true } +weresocool_error = { path = "../error", version = "^1.0.44", default_features=false, optional=true } +scop = { path = "../scop", version = "^1.0.44" } +lalrpop-util = { version="0.19.8", features=["lexer"]} regex = "1.5.4" colored = "2.0.0" num-rational = "0.3.2" diff --git a/parser/src/parser.rs b/parser/src/parser.rs index d04e3b43e..75a4548b1 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -111,7 +111,7 @@ pub fn parse_file( wd.push(filepath); filepath = wd.clean().display().to_string(); } - dbg!(&filepath); + // dbg!(&filepath); let vec_string = filename_to_vec_string(&filepath.to_string())?; let parsed_composition = parse_file(vec_string, Some(defs.clone()), working_path.clone())?; diff --git a/parser/src/socool.lalrpop b/parser/src/socool.lalrpop index 2ce03d9ac..acca811a1 100644 --- a/parser/src/socool.lalrpop +++ b/parser/src/socool.lalrpop @@ -17,6 +17,7 @@ use weresocool_ast::{ GenOp, Indices, Index, + Scale, }; use crate::parser::{Init, handle_fit_length_recursively}; use crate::indices::{et, random_seed}; @@ -77,6 +78,7 @@ Operation: Term = { ">" "FitGain" => { unimplemented!() }, + // TODO: Revisit Focus/Lens ">" "@" => { Term::Op( @@ -228,11 +230,6 @@ Index: Index = { "|" => Index::IndexAndTerm {index: Box::new(index), term}, } -SuperCompose: Term = { - => o, - => Term::Gen(o) -} - ComposeOperation: Term = { > => { Term::Op(Compose { operations: handle_fit_length_recursively(terms) }) @@ -247,8 +244,14 @@ Composeable: Term = { => Term::Gen(generator), } +Scale: Scale = { + => Scale { axis, value } +} + BaseOperation: Term = { - "csv" ?> => Term::Op(CSV { path, scale }), + + "Csv1d" ?> => Term::Op(CSV1d { path, scale }), + "Csv2d" >> => Term::Op(CSV2d { path, scales }), // r"\\|Lambda" ?> "{" "}" => Term::Op(Lambda { term: Box::new(term), input_name, scope: uuid::Uuid::new_v4().to_string()}), @@ -338,6 +341,7 @@ BaseOperation: Term = { Term::Op(Length { m: length }), ]}) }, + BracedOvertone }; @@ -357,6 +361,22 @@ Overtone: Term = { }) } +BracedOvertone: Term = { + "{" + "," + "," + "," + + "}" + => Term::Op(Compose { operations: vec! [ + Term::Op(TransposeM { m: fm }), + Term::Op(TransposeA { a: fa }), + Term::Op(Gain { m: g }), + Term::Op(PanA { a: p }), + ] + }) +} + Overtones = Comma; diff --git a/portaudio/Cargo.toml b/portaudio/Cargo.toml index ba6c78079..e4562406e 100644 --- a/portaudio/Cargo.toml +++ b/portaudio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_portaudio" -version = "1.0.43" +version = "1.0.44" authors = ["", "Mitchell Nordine ", "Danny Meyer " @@ -14,4 +14,4 @@ repository = "https://github.com/xasopheno/weresocool.git" bitflags = "1.3.2" libc = "0.2.51" num = { version = "0.2.0", default-features = false } -weresocool_portaudio_sys = { path = "./rust-portaudio-sys", version = "^1.0.43" } +weresocool_portaudio_sys = { path = "./rust-portaudio-sys", version = "^1.0.44" } diff --git a/portaudio/rust-portaudio-sys/Cargo.toml b/portaudio/rust-portaudio-sys/Cargo.toml index 9f8f93b6e..a3808d61a 100644 --- a/portaudio/rust-portaudio-sys/Cargo.toml +++ b/portaudio/rust-portaudio-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_portaudio_sys" -version = "1.0.43" +version = "1.0.44" authors = ["Jeremy Letang ", "Mitchell Nordine ", "Danny Meyer " diff --git a/portaudio/src/stream.rs b/portaudio/src/stream.rs index a80e286f7..1980cd0f0 100644 --- a/portaudio/src/stream.rs +++ b/portaudio/src/stream.rs @@ -1251,7 +1251,8 @@ where pub fn read_available(&self) -> Result { match unsafe { ffi::Pa_GetStreamReadAvailable(self.pa_stream) } { n if n >= 0 => Ok(Available::Frames(n)), - n => match FromPrimitive::from_i64(n as i64) { + #[allow(clippy::useless_conversion)] + n => match FromPrimitive::from_i64(n.into()) { Some(Error::InputOverflowed) => Ok(Available::InputOverflowed), Some(Error::OutputUnderflowed) => Ok(Available::OutputUnderflowed), Some(err) => Err(err), @@ -1309,7 +1310,8 @@ where pub fn write_available(&self) -> Result { match unsafe { ffi::Pa_GetStreamWriteAvailable(self.pa_stream) } { n if n >= 0 => Ok(Available::Frames(n)), - n => match FromPrimitive::from_i64(n as i64) { + #[allow(clippy::useless_conversion)] + n => match FromPrimitive::from_i64(n.into()) { Some(Error::InputOverflowed) => Ok(Available::InputOverflowed), Some(Error::OutputUnderflowed) => Ok(Available::OutputUnderflowed), Some(err) => Err(err), diff --git a/ring_buffer/Cargo.toml b/ring_buffer/Cargo.toml index 53e55c8b3..f44a76dce 100644 --- a/ring_buffer/Cargo.toml +++ b/ring_buffer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_ring_buffer" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "ring_buffer for WereSoCool" @@ -9,4 +9,4 @@ license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -weresocool_shared = { path = "../shared", version = "^1.0.43" } +weresocool_shared = { path = "../shared", version = "^1.0.44" } diff --git a/scop/Cargo.toml b/scop/Cargo.toml index b0bc5617a..2d8e89863 100644 --- a/scop/Cargo.toml +++ b/scop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scop" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "Lame FFI for WereSoCool" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ea23d0817..feb22e464 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "weresocool_shared" -version = "1.0.43" +version = "1.0.44" authors = ["Danny Meyer "] edition = "2021" description = "shared for WereSoCool" @@ -12,3 +12,4 @@ resolver="2" [dependencies] float-cmp = "0.8.0" num-rational= "0.3" +once_cell = "1.16.0" diff --git a/shared/src/lib.rs b/shared/src/lib.rs index cf59ff4b5..5c9f1f869 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -2,4 +2,4 @@ pub mod helpers; mod settings; pub use helpers::{f32_string_to_rational, lossy_rational_mul, r_to_f64}; -pub use settings::{default_settings, get_settings, get_test_settings, Settings}; +pub use settings::{default_settings, get_test_settings, Settings}; diff --git a/shared/src/settings.rs b/shared/src/settings.rs index 0361f1b5c..aefa64ca2 100644 --- a/shared/src/settings.rs +++ b/shared/src/settings.rs @@ -1,8 +1,31 @@ -pub const fn get_settings() -> Settings { - if cfg!(test) { - get_test_settings() - } else { - default_settings() +use once_cell::sync::OnceCell; + +static SETTINGS: OnceCell = if cfg!(test) { + OnceCell::with_value(get_test_settings()) +} else { + // OnceCell::with_value(default_settings()) + OnceCell::new() +}; + +impl Settings { + pub fn global() -> &'static Settings { + SETTINGS.get().expect("Oh no! Settings are not initialized") + } + + pub fn init(sample_rate: f64, buffer_size: usize) { + _ = SETTINGS.set(Settings { + sample_rate, + buffer_size, + ..default_settings() + }); + } + + pub fn init_default() { + _ = SETTINGS.set(default_settings()); + } + + pub fn init_test() { + _ = SETTINGS.set(get_test_settings()); } } diff --git a/src/app.rs b/src/app.rs index 17e8f7159..b12c8a89e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -22,6 +22,11 @@ pub fn app() -> clap::Command { .help("On file save, the composition will be re-rendered"), ), ) + .subcommand( + Command::new("watch") + .about("Same as play --watch") + .arg(arg!([filename]).required(true)), + ) .subcommand(Command::new("demo").about("Hear a cool sound")) .subcommand( Command::new("print") diff --git a/src/demo.rs b/src/demo.rs index 7de1887f7..4af189020 100644 --- a/src/demo.rs +++ b/src/demo.rs @@ -1,185 +1,243 @@ -use crate::play::play_once; use crate::Error; use indoc::indoc; +use std::sync::Arc; +use std::sync::Mutex; use weresocool::interpretable::InputType::Language; use weresocool::manager::prepare_render_outside; +use weresocool::manager::RenderManager; +use weresocool::portaudio::real_time_render_manager; +use weresocool::ui::were_so_cool_logo; pub fn demo() -> Result<(), Error> { - let render_voices = prepare_render_outside(Language(DEMO), None); - play_once(render_voices?, "demo.socool".to_string())?; + were_so_cool_logo(Some("Playing"), Some("Demo".to_owned())); + + let (tx, rx) = std::sync::mpsc::channel::(); + let render_manager = Arc::new(Mutex::new(RenderManager::init(None, Some(tx), true, None))); + + let render_voices = prepare_render_outside(Language(DEMO), None)?; + + render_manager + .lock() + .unwrap() + .push_render(render_voices, true); + + let mut stream = real_time_render_manager(Arc::clone(&render_manager))?; + + stream.start()?; + // rx.recv blocks until it receives data and + // after that, the function will complete, + // stream will be dropped, and the application + // will exit. + match rx.recv() { + Ok(_) => {} + Err(e) => { + println!("{}", e); + std::process::exit(1); + } + }; Ok(()) } const DEMO: &str = indoc! {" -{ f: 440, l: 1, g: 1/3, p: 0 } -thing1 = { - Overlay [ - O[ - (1/1, 2, 1, 2/5), - (1/1, 0, 1, -2/5), - ] - | Seq [ - Fm 1, Fm 0, Fm 0 - ] | Lm 1/3, - Seq [ - O[ - (1/2, 0.1, 1, 1/8), - (1/2, 0, 1, -1/8), - ] - | Seq [Lm 7, Lm 0] | Lm 1/8 - ] - ] - | Seq [ - Fm 1, Fm 3/2, Fm 5/6, Fm 1/2, - Fm 5/4, Fm 3/4, Fm 15/8, Fm 1/2, - Fm 2, Fm 5/8, Fm 2/3 | Pa 1, Fm 9/4, - Fm 5/6, Fm 2/3, Fm 9/16, Fm 1/2, - Fm 5/2, Fm 11/4, Fm 1/2, Fm 3, - Fm 25/12, Fm 5/4, Fm 5/3, Fm 25/24, - Fm 9/8, Fm 8/4, Fm 5/6, Fm 2/3 | Pa -1, - Fm 3/4 | Pa -1, Fm 4/3, Fm 15/8, Fm 8/3, - Fm 7/3, Fm 7/8, Fm 3/4, Fm 9/8, - Fm 5/8 | Pa 1, Fm 11/8, Fm 5/2, Fm 1/2 | Pa -1, - Fm 5/2 | Pa -1, Fm 2/3, Fm 5/2, Fm 8/3 | Pa 1, - Fm 9/8, Fm 5/4, Fm 1/2, Fm 8/3 | Pa -1, - Fm 3/2, Fm 1, Fm 5/8, Fm 3, - Fm 10/3 | Pa -1, Fm 4/3, Fm 1/2, Fm 16/5 | Pa 1 - ] - | Lm 1/8 - | Repeat 3 +{ f: 311.127, l: 1, g: 1/3, p: 0 } + +chord = { + Overlay [ + {9/2, 0, 1/2, -1}, + {9/2, 0, 1/2, -1}, + {4, 0, 1/2, -1}, + {4, 0, 1/2, -1}, + {10/3, 0, 1/2, -1}, + {16/5, 0, 1/2, -1}, + {5/4, 4, 1/2, 1}, + {8/3, 0, 1/2, -1}, + {5/4, 4, 1/2, 1}, + {7/3, 0, 1/2, -1}, + {9/4, 4, 1/2, 1}, + {3/2, 4, 1/2, 1}, + {11/8, 0, 1/2, -1}, + {5/4, 7, 1/2, 1}, + {7/6, 0, 1/2, -1}, + {3/2, 4, 1, 1/4}, + {3/2, 0, 1, -1/4}, + {1/3, 4, 1, 1/4}, + {1/3, 0, 1, -1/4}, + ] + | Fm 9/8 } -thing2 = { - Overlay [ - Seq [ - Fm 0 | Lm 4, Fm 1, Fm 0 | Lm 3 - ], - Seq [ - Fm 0 | Lm 5, Fm 2, Fm 0 | Lm 3 - ] | Gm 1/8, - Seq [ - Fm 0 | Lm 6, Fm 3, Fm 0 | Lm 3 - ] | Gm 1/32, - Seq [ - Fm 0 | Lm 7, Fm 4, Fm 0 | Lm 3 - ] | Gm 1/64, - Seq [ - Fm 0 | Lm 8, Fm 1/2, Fm 0 | Lm 3 - ] | Gm 1/32 - ] - | Seq [ - O[ - (3, 0, 1/8, 1/8), - (5/4, 2, 1, 1/2), - (1/2, 0, 1, -1/2), - (1/4, 0, 1, 1/2), - ], - O[ - (10/3, 0, 1/8, -1/8), - (4/3, 2, 1, 1/2), - (9/16, 0, 1, -1/2), - (3/16, 0, 1, 1/2), - ], - O[ - (15/4, 0, 1/8, -1/8), - (3/2, 2, 1, 1/2), - (5/8, 0, 1, -1/2), - (3/8, 0, 1, 1/2), - ], - O[ - (15/4, 0, 1/8, 1/8), - (3/2, 2, 1, 1/2), - (3/8, 0, 1, -1/2), - (3/16, 0, 1, 1/2), - ], - O[ - (5/2, 0, 1/8, 1/8), - (5/3, 2, 1, 1/2), - (1/3, 0, 1, -1/2), - (5/12, 0, 1, -1/2), - ], - O[ - (8/3, 0, 1/8, -1/8), - (15/8, 2, 1, 1/2), - (9/16, 0, 1, -1/2), - (3/8, 0, 1, -1/2), - ], - O[ - (9/4, 0, 1/4, 1/8), - (2, 2, 1, 1/2), - (2/3, 0, 1, -1/2), - (1/3, 0, 1, -1/2), - ], - O[ - (5/2, 0, 1, -1/8), - (2, 2, 1, 1/2), - (5/8, 0, 1, -1/2), - (1/2, 0, 1, 0), - (1/4, 0, 1, 0), - ] | Lm 2, - ] - | Overlay [ - Sine | Fa -3 | Pm -1 | Gm 1/5, - Sine | Gm 1/2, - Sine 1.6 | Gm 1/8 | Fm 1/2 | Fa -7, - Pm -1/8 | Sine 1.75 | Gm 1/8 | Fm 1/2, - ] - | FitLength thing1 +overtones1 = { + Overlay [ + {1/1, 2, 1, 1/2}, + {1/1, 0, 1, -1/2}, + {3/4, 3, 1/8, 1}, + {3/4, 0, 1/8, -1}, + {0/1, 7, 1, 1}, + {0/1, 0, 1, -1}, + ] +} + +overtones2 = { + Overlay [ + {4, -9, 1/7, 1/4}, + {4, 0, 1/7, -1/4}, + {1/2, 2, 1, 1}, + {1/2, 0, 1, -1}, + {1/4, 2, 1, 1}, + {1/4, 0, 1, -1}, + ] } bass = { - O[ - (3/4, 2, 1, 1), - (3/4, 0, 1, 1), - (1/1, 1, 1, -1), - (1/1, 0, 1, -1), - ] - | Fa 10 - | Fm 2 - | Gm 1/2 - | Seq [Fm 1, Fm 0, Fm 0] | Repeat 8 - | Seq [ - Fm 1 - ] - | Seq [Pm 1, Pm -1] - | Repeat 2 - | Repeat 4 - | FitLength thing1 + Seq [ + Fm 7/8, Fm 1, Fm 3/2, Fm 9/8, Fm 5/4, Fm 4/3 | Lm 2, Fm 5/4, Fm 7/8, + Fm 1, Fm 5/4, Fm 4/3, Fm 1, Fm 5/4, Fm 5/3, Fm 9/8, Fm 5/6, Fm 7/8, + ] + | Overlay [Sine, Sine 3/2 | Fm 1/2 | Gm 1/7] + | Seq [Repeat 2, Fm 5/6, Reverse] +} + +thing2 = { + Overlay [ + {2, 7, 1/2, 1/5}, + {2, 0, 1/2, -1/5}, + {3/2, 4, 1, 1/4}, + {3/2, 0, 1, -1/4}, + {1/1, -7, 1, 1/7}, + {1/1, 0, 1, -1/7}, + ] + | Overlay [ + Square | Gm 1/7, + Sine | Gm 1/7 | Pm 2, + ] + | Seq [ + Fm 3/4, Fm 5/6, Fm 7/8, Fm 9/8, Fm 1, Fm 7/8, Fm 9/8, Fm 1, Fm 5/6 + ] + | Overlay [ + {1/1, -3, 1, 1}, + {1/1, 0, 1, -1}, + ] + | Gm 1/2 + | Repeat 2 + | FitLength bass } -drums = { - Noise | - O[ - (1/1, 1, 1, 1/6), - (2, 0, 1, 1/8), - ] - | Gm 1/2 - | Seq [Fm 1, Fm 0, Fm 0] - | Seq [Fm 1, Fm 1/4] - | Seq [ - Fm 1 - ] - | Seq [Pm 1, Pm -1] - | Repeat 6 - | Repeat 8 - | ModBy [ - Gm 0, Gm 1 - ] - | Gm 1/6 - | FitLength thing1 +thing6 = { + Overlay [ + {1/1, 3, 1/2, -1}, + {1/1, -2, 1/2, 1}, + ] + | Seq [ + Fm 2, Fm 2, Fm 2, Fm 2, Fm 5/2, Fm 5/2, Fm 9/4, Fm 9/4, + Fm 3, Fm 2, Fm 2, Fm 9/4, Fm 5/2, Fm 5/2, Fm 9/4, Fm 9/4, + ] + | Lm 2 } +thing3 = { + Overlay [ + {1/1, 5, 1/2, -1}, + {1/1, -2, 1/2, 1}, + ] + | Seq [ + Fm 3/2, Fm 4/3, Fm 5/3, Fm 3/2, Fm 2, Fm 3/2, Fm 5/3, Fm 15/8, + Fm 1, Fm 1, Fm 4/3, Fm 5/3, Fm 4/3, Fm 3/2, Fm 5/3, Fm 15/8, + ] + | Lm 2 +} + +thing4 = { + Overlay [ + {1/1, 2, 1/2, -1}, + {1/1, 0, 1/2, 1}, + {1/2, 4, 1/2, 1}, + {1/2, 0, 1/2, -1}, + ] + | Seq [ + Fm 5/4, Fm 9/8, Fm 4/3, Fm 5/4, Fm 5/3, Fm 5/4, Fm 4/3, Fm 3/2, + Fm 1, Fm 5/6, Fm 9/8, Fm 4/3, Fm 9/8, Fm 5/4, Fm 4/3, Fm 3/2, + ] + | Lm 2 +} + +thing5 = { + Overlay [ + {1/2, -1, 1/2, 1}, + {1/2, 0, 1/2, -1}, + {1/4, -1, 1/2, 1}, + {1/4, 0, 1/2, -1}, + ] + | Seq [ + Fm 1, Fm 1, Fm 1, Fm 3/4, Fm 2/3, Fm 5/8, Fm 9/16, Fm 9/16, + Fm 2/3, Fm 2/3, Fm 3/4, Fm 3/4, Fm 9/16, Fm 5/8, Fm 2/3, Fm 3/4, + ] + | Lm 2 +} main = { - Overlay [ - thing1, - thing2, - bass, - drums - ] - | Seq[ - Lm 1, - Fa 8 | Lm 9/11 - ] + Seq [ + Seq [ + Fm 25/24 | chord | Lm 1/4 | ModBy [Fm 1, Fm 3/4, Fm 1/2, Fm 1/3, Fm 1/4] | Gm 1/3, + Fm 0 | Lm 1/100, + Overlay [ + Overlay [ + Seq [ + Fm 0, Fm 1 + ] | Lm 1/2, + Seq [ + Fm 1, Fm 0 + ] | Lm 1/3 + ] + | overtones1 | bass, + thing2 + ] + | Fm 25/24 + | Lm 1/7, + Fm 0 | Lm 1/100, + chord | Lm 1/4 | ModBy [Fm 1, Fm 3/4, Fm 1/2, Fm 1/3, Fm 1/4] | Gm 1/3, + Fm 0 | Lm 1/8, + Overlay [ + overtones2 | bass, + thing2 + ] + | Repeat 2 + | Lm 1/5, + Fm 0 | Lm 1/100, + ] + | Repeat 2, + Seq [ + Overlay [ + thing6, + thing3, + thing4, + thing5, + ] + | Repeat 2 + | Overlay [ + {1/1, -0.07, 1/3, -1}, + {1/1, 0, 1/2, 0}, + {1/1, 0.03, 1/3, 1}, + ], + ], + Fm 0 | Lm 1/100, + Fm 25/24 | chord | Lm 1/4 | ModBy [Fm 1, Fm 3/4, Fm 1/2, Fm 1/3, Fm 1/4] | Gm 1/3, + Fm 0 | Lm 1/100, + Overlay [ + Overlay [ + Seq [ + Fm 0, Fm 1 + ] | Lm 1/2, + Seq [ + Fm 1, Fm 0 + ] | Lm 1/3 + ] + | overtones1 | bass, + thing2 + ] + | Fm 25/24 + | Lm 1/7, + Fm 0 | Lm 1/100, + chord | Lm 1/4 | ModBy [Fm 1, Fm 3/4, Fm 1/2, Fm 1/3, Fm 1/4] | Gm 1/3, + ] } "}; diff --git a/src/lib.rs b/src/lib.rs index f07c5be0c..a115e92a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,11 @@ #![warn( clippy::nursery, - //clippy::restriction, + clippy::suspicious, + clippy::correctness, + clippy::complexity, + // clippy::restriction, // clippy::pedantic, - //clippy::cargo, + // clippy::cargo, )] pub use weresocool_ast as ast; diff --git a/src/new.rs b/src/new.rs index 799ed51bb..82a537948 100644 --- a/src/new.rs +++ b/src/new.rs @@ -10,20 +10,20 @@ pub fn new(filename: &String, cwd: PathBuf) -> Result<(), Error> { } fn new_socool_file(filename: String, working_path: PathBuf) -> Result<(), Error> { - let path = working_path.join(format!("{filename}.socool")); + let path = working_path.join(filename.clone()); fs::write(path, DEFAULT_SOCOOL).expect("Unable to write file"); - play_file(format!("{filename}.socool"), working_path, Play::Once)?; + play_file(filename, working_path, Play::Once)?; Ok(()) } -const DEFAULT_SOCOOL: &str = indoc! {" +pub const DEFAULT_SOCOOL: &str = indoc! {" { f: 311.127, l: 1, g: 1/3, p: 0 } thing1 = { - O[ - (1/1, 2, 1, 1), - (1/1, 0, 1, -1), + Overlay [ + {1/1, 2, 1, 1}, + {1/1, 0, 1, -1}, ] | Seq [ Fm 1, Fm 9/8, Fm 5/4 @@ -31,9 +31,9 @@ thing1 = { } thing2 = { - O[ - (1/1, 2, 1, 1), - (1/1, 0, 1, -1), + Overlay [ + {1/1, 3, 1, 1}, + {1/1, 0, 1, -1}, ] | Seq [ Fm 3/4 diff --git a/src/play.rs b/src/play.rs index 5cd4a22ba..f5a77e32b 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,5 +1,7 @@ +use crate::new::DEFAULT_SOCOOL; use crate::watch::watch; use crate::Error; +use std::io; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; @@ -8,7 +10,6 @@ use weresocool::manager::prepare_render_outside; use weresocool::manager::RenderManager; use weresocool::portaudio::real_time_render_manager; use weresocool::ui::were_so_cool_logo; -use weresocool_instrument::RenderVoice; pub enum Play { Once, @@ -22,25 +23,24 @@ pub fn play(filename: &String, cwd: PathBuf, play: Play) -> Result<(), Error> { pub fn play_file(filename: String, working_path: PathBuf, play: Play) -> Result<(), Error> { match play { - Play::Once => { - let render_voices = prepare_render_outside(Filename(&filename), Some(working_path)); - - play_once(render_voices?, filename) - } + Play::Once => play_once(filename, working_path), Play::Watch => play_watch(filename, working_path), } } -pub fn play_once(render_voices: Vec, filename: String) -> Result<(), Error> { - were_so_cool_logo(Some("Playing"), Some(filename)); +pub fn play_once(filename: String, working_path: PathBuf) -> Result<(), Error> { + were_so_cool_logo(Some("Playing"), Some(filename.clone())); let (tx, rx) = std::sync::mpsc::channel::(); - let render_manager = Arc::new(Mutex::new(RenderManager::init( - render_voices, - None, - Some(tx), - true, - ))); + let render_manager = Arc::new(Mutex::new(RenderManager::init(None, Some(tx), true, None))); + + let render_voices = prepare_render_outside(Filename(&filename), Some(working_path))?; + + render_manager + .lock() + .unwrap() + .push_render(render_voices, true); + let mut stream = real_time_render_manager(Arc::clone(&render_manager))?; stream.start()?; @@ -59,10 +59,41 @@ pub fn play_once(render_voices: Vec, filename: String) -> Result<() } fn play_watch(filename: String, working_path: PathBuf) -> Result<(), Error> { - let render_manager = Arc::new(Mutex::new(RenderManager::init(vec![], None, None, false))); + maybe_create_file_if_needed(filename.clone(), working_path.clone()); + let render_manager = Arc::new(Mutex::new(RenderManager::init(None, None, false, None))); + let render_voices = prepare_render_outside(Filename(&filename), Some(working_path.clone()))?; + render_manager + .lock() + .unwrap() + .push_render(render_voices, false); watch(filename, working_path, render_manager.clone())?; let mut stream = real_time_render_manager(Arc::clone(&render_manager))?; stream.start()?; std::thread::park(); Ok(()) } + +pub fn maybe_create_file_if_needed(filename: String, working_path: PathBuf) { + let mut input = String::new(); + let path = working_path.join(filename); + if !path.exists() { + println!("{{filename}} does not exist. Create it? (y/n)"); + + io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + + let input = input.trim(); + + match input { + "y" => { + std::fs::write(path, DEFAULT_SOCOOL).expect("Unable to write file"); + } + "n" => { + println!("Aborting"); + std::process::exit(0); + } + _ => println!("Invalid input"), + } + } +} diff --git a/src/print.rs b/src/print.rs index 1b43d800a..b984c7823 100644 --- a/src/print.rs +++ b/src/print.rs @@ -4,8 +4,10 @@ use std::path::PathBuf; use weresocool::generation::{RenderType, WavType}; use weresocool::interpretable::InputType; use weresocool::interpretable::Interpretable; +use weresocool_shared::Settings; pub fn print(print_args: &ArgMatches) -> Result<(), Error> { + Settings::init_default(); let mut printed: Vec<&str> = vec![]; let should_print = |target: &[&str]| -> bool { let result = target.iter().any(|arg| print_args.get_flag(arg)); diff --git a/src/test_data/play_unix.mp3 b/src/test_data/play_unix.mp3 index cc4e8cc4f7c75a17dad33d2fa22982abd3dfb4b6..b2bdf4679554162f93c6c4690c3e4108a623467a 100644 GIT binary patch delta 1505 zcmZWpdrVtZ9KNS5m&Y~=9k>G0%WD%hDwnPZ<27I=l!xGkV~H9k8-$Y3QBbiC3>|E> zEi6U8sROdXMzUmRB8rPnMLH)W!v_*W!bIJolFejhtM6t=y{EP73g?fLoBST%@B4n= zxqUsPuZKuncTYU}PIx=%5I$-dMjR)fIQGJI5yNbV8)k^CCSDZfWQ3#c60WSJKQrDN zR2Ay8iVkOg`a$-d$1|^()S3Zat5ju;s=hv_imOczOXamPKAGjiM1%<#W~wFpUbEq1 z@4KxPpZDw4p-0o#S8t27P92^&b@%Vv*Cq{)C82f4W#QO`k4XNnslkl6xl8=_-_7R! zT6|g+$<^%)PHN%xh0pLJpFI##~S_*$SNrpqr1x#^L_Yf~ND)=bA-LY zUKc#S$u_WsRCD?T+1XoTi_`M6FE7^Q*bWvn_qkSb!&=1s)<9 zepMDIgJ@MAz6@&ftFmD>m_txN1&krj!)46Gjh>FB5v^z!D!uBMkrzoL8r+Q3lLbF4E3Q4@ z!Rpzx8taIhM#{lQHTsl`rA*CI$7$AnAJf1H#Y(8_StG(_doUNRBO!h$W=zFU)r!&d zU^6-A7qg;1mTIkmOreK_?YuX)6~P>xyA1DhI5|d3IM8-?j?PhQ|_U#}YUb zny{4PbAtu96X_hgF_KhIUi0gC46knH(3Vz-U0WJ_{&MpbHhZ3(ypJ#26TK|`J&6(c zR^i8o8p&2aPG-y}Q|(G%OlcyEUL3c#EjAdoDg>Z1ByH2JS-m`!7M5W;RY!E*!vAgY zp^^@$LEXdpRdrT8okr_42;}uXKzE%LE4+u;SbD>%K}{G|dGIQ>>+qboM-X4`HnZ90 z7-1*6mahmu3%8jA`RLy;#O{YvUF%zhJNKy6k;#4{$RqdWzNNS&Zj+nf)YY5G!MyP~6 zzhRXsCXDU$JeU0itzIk-bE)H-z>DI^1>p&Mp79gWH=$3f!oP)jGCJN|bVfXGEb60| GNc11k_G>c$ delta 1397 zcmZ9MYe-Xl9LLXpXJ<~)XEw2BvojSfnXHa&x{IfI=?2~CViZx3WtOqIxoJ*~wB!)a z^Q=gJP)2l9qv%BxMnt_Ss#Qd9xg%F7Hoae8e|JhmR#dhGs_xJt&E?ZX^ zb%l{yoH((pe~>?m+VER7MbRCZywF6cC@LlE0)_PVEvk%h3O}|E{84hP(;j|lo%PN2 zzRYbrzT{u6t3q9L`vrGpsZ~2wvEmNiT{;_uQG?3@0YzPXP1 z#=ED#p1m>_spuWPJeYfY#b4Tj2(=#7@ZI%`(`MzDyrgVsgO<{M=?o5`C>=pt>Ab=~ zr>Z-}k$lnB^J{F`J@bQ~Mpb3qGynUDp(pX<$j#QdbodloI@xjZ>8^v8Tie9R^X^K! zJM>W$-JyZ;&rQ5f6erH<+Mg$U5XXb%(Tv`jwQZs}-fxC8sy%Zi_nnSZ|6Q>w`mid0 z-8Q7#D^7G(m*_x4v#1x=FY1P7dN;ZXJD9`F6bgW#HbGzN7HDT2Fsk;#fbqyQX#z%L zf?Te$OYMR>O&v1Jq7VoPL}Z4+gjTd1KF~7-4*`=X$=nb~Y=9v;G+pli2uTtsg|Vbs z^hnk;0+TGEGH6KNr^wd`LJF~RLNvvL_Q*1vP!~(63&~bEU#bLF!bEBn+9*e_2Sy*O z=hq*A8fGS669{R96v1$sAH9{Gnt@4=Ii<%r&6WTU(b*owkZ?K!spkNn!Hd%6de|#N zlG))$Mm@SDBWz`qkOMqMKWt}a_R$PNW~|<5rWfs(yTV>9VP>deTNPf1Krl%KoiJkZ z!9ej$!4NRn5-5kp?7iq8xn2VZIiwyBV>z|xoy_pEatU*SKi7jc%i(cfd2;xXJm0kJ zh-T_%+>CE<0V`da9efMCahnwKC0z-O=Ks-}nb;M9$Lv?g@JI?|yWs-AA|)Q9g~)ga zT51(3@l7p^xh?cU1#QRYznYvqM}*}N;;I#64uVifm=(Sk22hVYmKI>FgyD5t<9iTn zgxTSzEhHZu-a#=T#o#UW!S*b&9l=frK74!pm>H+kKq&;B@!Jtx3YklJ-waO(9whHa KukoL3AN@b2pf>IR diff --git a/src/testing/expect_tests/mod.rs b/src/testing/expect_tests/mod.rs index f834068d4..df272b093 100644 --- a/src/testing/expect_tests/mod.rs +++ b/src/testing/expect_tests/mod.rs @@ -1,5 +1,5 @@ #[cfg(test)] -mod expect { +mod generated_tests { use crate::{ generation::{RenderReturn, RenderType}, interpretable::{InputType::Filename, Interpretable}, @@ -67,6 +67,11 @@ mod expect { expect(resource); } + #[test_resources("mocks/ops/*.socool")] + fn __ops_generated_(resource: &str) { + expect(resource); + } + #[test_resources("mocks/data/*.socool")] fn __data_generated_(resource: &str) { expect(resource); diff --git a/src/testing/hashes.json b/src/testing/hashes.json index c6c5c357a..823b13fa0 100644 --- a/src/testing/hashes.json +++ b/src/testing/hashes.json @@ -2,157 +2,157 @@ "./src/testing/snapshot_tests/a_440.socool": { "op": 16336396711429778121, "normal_form": 17768612753770439874, - "stereo_waveform": -0.000904279, + "stereo_waveform": -0.000904277, "pop_check": true }, "./src/testing/snapshot_tests/asd_test_4.socool": { "op": 1269305847159649816, "normal_form": 2398934495796138558, - "stereo_waveform": 0.0245386648, + "stereo_waveform": 0.0224631703, "pop_check": true }, "./src/testing/snapshot_tests/asr_test.socool": { "op": 6271611633143483213, "normal_form": 13547630763816978151, - "stereo_waveform": 1.898387869, + "stereo_waveform": 2.0893641401, "pop_check": true }, "./src/testing/snapshot_tests/asr_test_2.socool": { "op": 1269305847159649816, "normal_form": 2398934495796138558, - "stereo_waveform": 0.0245386648, + "stereo_waveform": 0.0224631703, "pop_check": true }, "./src/testing/snapshot_tests/asr_test_3.socool": { "op": 12705210924752734841, "normal_form": 2323698220205907016, - "stereo_waveform": 0.0292538197, + "stereo_waveform": -0.114617793, "pop_check": true }, "./src/testing/snapshot_tests/drew_test.socool": { "op": 5719640757974486592, "normal_form": 15752908550389006017, - "stereo_waveform": 3.9059502081, + "stereo_waveform": 2.8586368965, "pop_check": true }, "./src/testing/snapshot_tests/fit_length_ratio.socool": { "op": 16838375962941339535, "normal_form": 5567540626974557350, - "stereo_waveform": -4.370315476, + "stereo_waveform": -0.231264336, "pop_check": true }, "./src/testing/snapshot_tests/fun1.socool": { "op": 8086664920684890768, "normal_form": 9318730544015173364, - "stereo_waveform": 33.427902357, + "stereo_waveform": -236.2023494, "pop_check": true }, "./src/testing/snapshot_tests/fun2.socool": { "op": 7370449157984831155, "normal_form": 3721480520940659992, - "stereo_waveform": 12.5500326, + "stereo_waveform": 10.02697954, "pop_check": true }, "./src/testing/snapshot_tests/fun_and_focus.socool": { "op": 2413583612346742067, "normal_form": 665617543197401601, - "stereo_waveform": -2.530482354, + "stereo_waveform": -0.336236322, "pop_check": true }, "./src/testing/snapshot_tests/fun_simple.socool": { "op": 7920898999699649515, "normal_form": 4829599383910238654, - "stereo_waveform": -0.017118117, + "stereo_waveform": 0.0140725211, "pop_check": true }, "./src/testing/snapshot_tests/gain_test.socool": { "op": 16859655603875107055, "normal_form": 17321821434433600374, - "stereo_waveform": -0.698801615, + "stereo_waveform": 0.8915591543, "pop_check": true }, "./src/testing/snapshot_tests/import_test.socool": { "op": 5345273636124313774, "normal_form": 1051910468317329445, - "stereo_waveform": -0.019581846, + "stereo_waveform": -0.093617003, "pop_check": true }, "./src/testing/snapshot_tests/import_test_2.socool": { "op": 3437025384493809597, "normal_form": 1925400759312066369, - "stereo_waveform": -0.000449995, + "stereo_waveform": -0.00044999, "pop_check": true }, "./src/testing/snapshot_tests/lee_test.socool": { "op": 9776597960894490032, "normal_form": 16276491339713013197, - "stereo_waveform": 7.327720855, + "stereo_waveform": 0.893586754, "pop_check": true }, "./src/testing/snapshot_tests/length_test.socool": { "op": 14537244801501606905, "normal_form": 3447354791378253623, - "stereo_waveform": -0.287539803, + "stereo_waveform": -0.410002616, "pop_check": true }, "./src/testing/snapshot_tests/live.socool": { "op": 7097496857557948851, "normal_form": 8939914983534101017, - "stereo_waveform": 0.0096451483, + "stereo_waveform": -0.007485638, "pop_check": true }, "./src/testing/snapshot_tests/mod_by_test.socool": { "op": 2198486307084650048, "normal_form": 11237003685707282441, - "stereo_waveform": 6.7063052338, + "stereo_waveform": 8.9159018922, "pop_check": true }, "./src/testing/snapshot_tests/pan_test.socool": { "op": 15443559336654576423, "normal_form": 10885953379247223407, - "stereo_waveform": -0.001246886, + "stereo_waveform": -0.001350129, "pop_check": true }, "./src/testing/snapshot_tests/render_op.socool": { "op": 11031979695295257999, "normal_form": 1808133604392464584, - "stereo_waveform": 0.0599205582, + "stereo_waveform": -0.080921758, "pop_check": true }, "./src/testing/snapshot_tests/render_op_get_batch.socool": { "op": 2854411143078330821, "normal_form": 14677070783010194738, - "stereo_waveform": -3576.209166, + "stereo_waveform": -2613.226217, "pop_check": false }, "./src/testing/snapshot_tests/render_op_get_batch_simple.socool": { "op": 501563646986136987, "normal_form": 16005874538258155555, - "stereo_waveform": 0.0253619746, + "stereo_waveform": -0.009089222, "pop_check": true }, "./src/testing/snapshot_tests/std_test.socool": { "op": 3437025384493809597, "normal_form": 1925400759312066369, - "stereo_waveform": -0.000449995, + "stereo_waveform": -0.00044999, "pop_check": true }, "./src/testing/snapshot_tests/ta_test.socool": { "op": 6124024574779547510, "normal_form": 12585823049523185143, - "stereo_waveform": -0.089334341, + "stereo_waveform": 0.4862787636, "pop_check": true }, "./src/testing/snapshot_tests/template.socool": { "op": 14739783389100950629, "normal_form": 7787792194686407629, - "stereo_waveform": -0.042878703, + "stereo_waveform": 0.0297207799, "pop_check": true }, "./src/testing/snapshot_tests/tm_test.socool": { "op": 3496273567399834240, "normal_form": 16215155583902622536, - "stereo_waveform": 0.0673266386, + "stereo_waveform": -0.418864025, "pop_check": true } } \ No newline at end of file diff --git a/src/testing/mod.rs b/src/testing/mod.rs index e06ef811a..40a3d9422 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -129,7 +129,7 @@ fn calculate_hash(t: &T) -> u64 { } #[allow(unused_must_use)] -pub fn show_difference(tt1: TestTable, tt2: TestTable) { +pub fn show_difference(tt1: &TestTable, tt2: &TestTable) { let Changeset { diffs, .. } = Changeset::new( &to_string_pretty(&tt1).unwrap(), &to_string_pretty(&tt2).unwrap(), @@ -139,45 +139,42 @@ pub fn show_difference(tt1: TestTable, tt2: TestTable) { let mut terminal = term::stdout().unwrap(); for i in 0..diffs.len() { - match diffs[i] { + match diffs.get(i).unwrap() { Difference::Same(ref x) => { terminal.reset().unwrap(); - writeln!(terminal, " {}", x); + writeln!(terminal, " {x}"); } Difference::Add(ref x) => { - match diffs[i - 1] { - Difference::Rem(ref y) => { - terminal.fg(term::color::GREEN).unwrap(); - write!(terminal, "+"); - let Changeset { diffs, .. } = Changeset::new(y, x, " "); - for c in diffs { - match c { - Difference::Same(ref z) => { - terminal.fg(term::color::GREEN).unwrap(); - write!(terminal, "{}", z); - write!(terminal, " "); - } - Difference::Add(ref z) => { - terminal.fg(term::color::WHITE).unwrap(); - terminal.bg(term::color::GREEN).unwrap(); - write!(terminal, "{}", z); - terminal.reset().unwrap(); - write!(terminal, " "); - } - _ => (), + if let Difference::Rem(ref y) = diffs.get(i - 1).unwrap() { + terminal.fg(term::color::GREEN).unwrap(); + write!(terminal, "+"); + let Changeset { diffs, .. } = Changeset::new(y, x, " "); + for c in diffs { + match c { + Difference::Same(ref z) => { + terminal.fg(term::color::GREEN).unwrap(); + write!(terminal, "{z}"); + write!(terminal, " "); } + Difference::Add(ref z) => { + terminal.fg(term::color::WHITE).unwrap(); + terminal.bg(term::color::GREEN).unwrap(); + write!(terminal, "{z}"); + terminal.reset().unwrap(); + write!(terminal, " "); + } + Difference::Rem(_) => (), } - writeln!(terminal); - } - _ => { - terminal.fg(term::color::BRIGHT_GREEN).unwrap(); - writeln!(terminal, "+{}", x); } + writeln!(terminal); + } else { + terminal.fg(term::color::BRIGHT_GREEN).unwrap(); + writeln!(terminal, "+{x}"); }; } Difference::Rem(ref x) => { terminal.fg(term::color::RED).unwrap(); - writeln!(terminal, "-{}", x); + writeln!(terminal, "-{x}"); } } } diff --git a/src/watch.rs b/src/watch.rs index 53c03d58e..32cc6e128 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -20,20 +20,15 @@ pub fn watch( render_manager: Arc>, ) -> Result<(), Error> { were_so_cool_logo(Some("Watching"), Some(filename.clone())); - let mut first_iteration = true; + + let path = Path::new(&working_path).join(Path::new(&filename)); + std::thread::spawn(move || -> Result<(), Error> { loop { - if first_iteration { - render(&filename, &working_path, &render_manager); - first_iteration = false; - } - let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(tx, Config::default()).unwrap(); - let path = Path::new(&working_path).join(Path::new(&filename)); - watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?; if let Ok(_event) = rx.recv() { @@ -58,7 +53,7 @@ fn render(filename: &str, working_path: &Path, render_manager: &Arc, r: Vec) -> Vec { if l.len() != r.len() { @@ -11,7 +9,8 @@ pub fn encode_lr_channels_to_ogg_vorbis(l: Vec, r: Vec) -> Vec { let interleaved: Vec = interleave_channels(l, r); let veci16 = pcm_f64_to_i16(interleaved); - let mut encoder = vorbis_encoder::Encoder::new(2, SETTINGS.sample_rate as u64, 1.0).unwrap(); + let mut encoder = + vorbis_encoder::Encoder::new(2, Settings::global().sample_rate as u64, 1.0).unwrap(); let mut encoded = encoder.encode(&veci16).unwrap(); encoded.append(&mut encoder.flush().unwrap()); encoded