From 42b98167e021e26c49632f350f69f2a6eb1ea1ad Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Wed, 24 Sep 2025 00:34:54 -0500 Subject: [PATCH] noise --- Cargo.lock | 415 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/grid.rs | 80 ++----- src/gui.rs | 6 +- src/main.rs | 15 +- src/noise.rs | 60 ++++++ src/{nome.rs => tenori.rs} | 38 +++- 7 files changed, 538 insertions(+), 79 deletions(-) create mode 100644 src/noise.rs rename src/{nome.rs => tenori.rs} (72%) diff --git a/Cargo.lock b/Cargo.lock index 780a4ab..29a830a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,28 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "android-activity" version = "0.6.0" @@ -677,6 +699,46 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17" +dependencies = [ + "bitflags 1.3.2", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "cpal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f" +dependencies = [ + "alsa", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -704,6 +766,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "dispatch" version = "0.2.0" @@ -892,6 +960,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -986,6 +1063,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fastrand" version = "2.3.0" @@ -1548,6 +1631,12 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.175" @@ -1621,6 +1710,15 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1783,6 +1881,47 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1878,6 +2017,21 @@ dependencies = [ "objc2-foundation 0.3.1", ] +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07" +dependencies = [ + "bitflags 2.9.4", + "libc", + "objc2 0.6.2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -1902,6 +2056,28 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82" +dependencies = [ + "dispatch2", + "objc2 0.6.2", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.2", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -2279,6 +2455,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -2358,6 +2543,45 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "range-alloc" version = "0.1.4" @@ -2394,11 +2618,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rodio" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40ecf59e742e03336be6a3d53755e789fd05a059fa22dfa0ed624722319e183" +dependencies = [ + "cpal", + "dasp_sample", + "num-rational", + "rand", + "rand_distr", + "symphonia", +] + [[package]] name = "rossnome" version = "0.1.0" dependencies = [ "eframe", + "rodio", "tokio", ] @@ -2653,6 +2892,153 @@ dependencies = [ "syn", ] +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "2.0.106" @@ -3363,6 +3749,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.58.0" @@ -3395,6 +3791,16 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.58.0" @@ -3498,6 +3904,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index c8a4864..a5c943e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] eframe = "0.32.3" -tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros"] } \ No newline at end of file +tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros"] } +rodio = { version = "0.21.1", features = ["noise"] } \ No newline at end of file diff --git a/src/grid.rs b/src/grid.rs index ebecd67..4c4f56b 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,15 +1,8 @@ use std::ops::RangeInclusive; use eframe::egui; -use eframe::egui::{Color32, Context, Id, Margin, PointerButton, Pos2, Rangef, Response, Sense, Stroke, TopBottomPanel, Ui, Vec2, Widget}; -use crate::nome::LOOP_LENGTH; - -pub enum NoteType { - Sine, - Triangle, - Sawtooth, - Square, - Noise -} +use eframe::egui::{Color32, Context, Id, PointerButton, Pos2, Rangef, Sense, Ui, Vec2, Widget}; +use crate::noise::NoteType; +use crate::tenori::LOOP_LENGTH; impl NoteType { fn color(&self) -> Color32 { @@ -24,29 +17,40 @@ impl NoteType { } pub struct Grid { - note_type: NoteType, - volume: f32, + pub note_type: NoteType, + pub volume: f32, + pub length: u64, notes: Vec, - pub id: String + id: String } impl Grid { pub fn for_note_type(note_type: NoteType, counter: usize) -> Self { Self { note_type, - volume: 50.0, + volume: 1.0, + length: 250, notes: vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize], id: format!("Track {}", counter) } } pub fn show(&mut self, ctx: &Context, cursor: f32) { - let id = Id::from(self.id.clone()); let win = egui::Window::new(&self.id).resizable(false).scroll([false, false]); win.show(ctx, |ui| { + egui::MenuBar::new().ui(ui, |ui| { + if ui.button("Clear").clicked() { + self.notes = vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize] + } + }); + egui::MenuBar::new().ui(ui, |ui| { ui.label("Volume"); - ui.add(egui::Slider::new(&mut self.volume, RangeInclusive::new(0.0, 100.0))); + ui.add(egui::Slider::new(&mut self.volume, RangeInclusive::new(0.0, 2.0)).show_value(false)); + + ui.label("Length"); + ui.add(egui::Slider::new(&mut self.length, RangeInclusive::new(0, 2000)).show_value(false)); + }); egui::Frame::new().inner_margin(3).show(ui, |ui| { @@ -81,7 +85,6 @@ impl Grid { ui.input(|input| { if input.pointer.button_clicked(PointerButton::Primary) { if let Some(pos) = input.pointer.latest_pos() { - println!("window: {} pos: {} {}", self.id, pos.x - rect.left(), pos.y - rect.top()); let x = ((pos.x - rect.left()) / 20.0).floor() as usize; let y = ((pos.y - rect.top()) / 20.0).floor() as usize; let n = x + y * LOOP_LENGTH as usize; @@ -92,52 +95,11 @@ impl Grid { } } - // fn frame(&mut self, ui: &mut Ui, cursor: f32) { - // egui::Frame::new().inner_margin(3).show(ui, |ui| { - // let dim = 20.0 * LOOP_LENGTH as f32; - // ui.set_width(dim); - // ui.set_height(dim); - // let rect = ui.clip_rect().translate(Vec2::new(6.0, 6.0)); - // //ui.painter().debug_rect(rect, Color32::from_rgb(0, 255, 0), "blah"); - // for (i, lit) in self.notes.iter().enumerate() { - // let (x, y) = (i as u32 % LOOP_LENGTH, i as u32 / LOOP_LENGTH); - // let center = Pos2::new( - // (x * 20 + 10) as f32 + rect.left(), - // (y * 20 + 10) as f32 + rect.top()); - // if *lit { - // ui.painter().circle_filled(center, 10.0, self.note_type.color()); - // } else { - // ui.painter().circle_stroke(center, 10.0, (1.0, Color32::from_gray(0x88))); - // } - // } - // - // ui.painter().vline( - // cursor * dim + rect.left(), - // Rangef::new(rect.top(), rect.top() + dim), - // (1.0, self.note_type.color()) - // ); - // - // if ui.response().contains_pointer() { - // ui.input(|input| { - // if input.pointer.button_clicked(PointerButton::Primary) { - // if let Some(pos) = input.pointer.latest_pos() { - // println!("window: {} pos: {} {}", self.id, pos.x - rect.left(), pos.y - rect.top()); - // let x = ((pos.x - rect.left()) / 20.0).floor() as usize; - // let y = ((pos.y - rect.top()) / 20.0).floor() as usize; - // let n = x + y * LOOP_LENGTH as usize; - // self.notes[n] = !self.notes[n] - // } - // } - // }) - // } - // }); - // } - pub fn notes(&self, beat: u32) -> Vec { let mut notes = vec![]; for y in 0..LOOP_LENGTH { if self.notes[(y * LOOP_LENGTH + beat) as usize] { - notes.push(y) + notes.push(LOOP_LENGTH - y) } } notes diff --git a/src/gui.rs b/src/gui.rs index 11ce2a0..88960b7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,10 +1,10 @@ use std::ops::RangeInclusive; use eframe::egui; use eframe::egui::{Context, TopBottomPanel}; -use crate::grid::{Grid, NoteType}; -use crate::Nome; +use crate::noise::NoteType; +use crate::Tenori; -impl Nome { +impl Tenori { pub fn menu(&mut self, ctx: &Context) { TopBottomPanel::top("menu_panel").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { diff --git a/src/main.rs b/src/main.rs index 1027d25..e8ce426 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,22 @@ mod gui; -mod nome; +mod tenori; mod grid; +mod noise; use std::time::Duration; use eframe::{App, Frame}; use eframe::egui::Context; -use crate::nome::Nome; +use crate::tenori::Tenori; #[tokio::main] async fn main() { let native_options = eframe::NativeOptions::default(); - eframe::run_native("Fleen", native_options, Box::new(|_cc| { - Ok(Box::new(Nome::default())) + eframe::run_native("Tenori-ish", native_options, Box::new(|_cc| { + Ok(Box::new(Tenori::default())) })).expect("Error running application"); } -impl App for Nome { +impl App for Tenori { fn update(&mut self, ctx: &Context, frame: &mut Frame) { let play = self.tick(); let cursor = self.ratio(); @@ -25,8 +26,8 @@ impl App for Nome { } if play { - for (track, row) in self.notes_for_beat() { - println!("{}: {}", track, row) + for note in self.notes_for_beat() { + self.play(note) } } ctx.request_repaint_after(Duration::from_millis(17)) diff --git a/src/noise.rs b/src/noise.rs new file mode 100644 index 0000000..a7a7b23 --- /dev/null +++ b/src/noise.rs @@ -0,0 +1,60 @@ +use std::time::Duration; +use rodio::mixer::Mixer; +use rodio::Source; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum NoteType { + Sine, + Triangle, + Sawtooth, + Square, + Noise +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Note { + /// What kind of timbre + pub note_type: NoteType, + + /// Which semitone up / down from A4 + pub tone: i32, + + /// How loud, 0.0 .. 2.0 + pub volume: f32, + + /// How long to fade out + pub duration: Duration +} + +/// The frequency for a given tone, in Hz. +/// Tone 0 is A4, A above middle C, which is defined at 440 Hz. +/// Moving up or down one is a single semitone, which means a +/// change in `tone` of `12` is one octave. +fn freq(tone: i32) -> f32 { + 440.0 * 1.0595f32.powf(tone as f32) +} + +impl NoteType { + fn source(self, tone: i32) -> Box { + let freq = freq(tone); + let source: Box = match self { + NoteType::Sine => Box::new(rodio::source::SineWave::new(freq)), + NoteType::Triangle => Box::new(rodio::source::TriangleWave::new(freq)), + NoteType::Sawtooth => Box::new(rodio::source::SawtoothWave::new(freq)), + NoteType::Square => Box::new(rodio::source::SquareWave::new(freq)), + NoteType::Noise => Box::new(rodio::source::noise::WhiteUniform::new(44100) + .low_pass_with_q(freq as u32, 2.0)) + }; + source + } +} + +impl Note { + pub fn play(self, mixer: &Mixer) { + let note = self.note_type.source(self.tone); + let note = note.fade_out(self.duration); + let note = note.amplify(self.volume.clamp(0.0, 1.0)); + let note = note.take_duration(self.duration); + mixer.add(note) + } +} diff --git a/src/nome.rs b/src/tenori.rs similarity index 72% rename from src/nome.rs rename to src/tenori.rs index 31cc8ad..76e23e3 100644 --- a/src/nome.rs +++ b/src/tenori.rs @@ -1,9 +1,11 @@ -use std::time::Instant; -use crate::grid::{Grid, NoteType}; +use std::time::{Duration, Instant}; +use rodio::OutputStream; +use crate::grid::Grid; +use crate::noise::{Note, NoteType}; pub const LOOP_LENGTH: u32 = 16; -pub struct Nome { +pub struct Tenori { /// Tempo in beats per minute pub tempo: u32, @@ -20,23 +22,30 @@ pub struct Nome { pub grids: Vec, // Running count of windows created (for ids) - window_counter: usize + window_counter: usize, + + // The audio output stream to which we will play notes + output_stream: OutputStream } -impl Default for Nome { +impl Default for Tenori { fn default() -> Self { + let output_stream = rodio::OutputStreamBuilder::open_default_stream() + .expect("Open audio output stream"); + Self { tempo: 90, timer: 0.0, playing: true, last_tick: None, grids: vec![], - window_counter: 0 + window_counter: 0, + output_stream } } } -impl Nome { +impl Tenori { /// Call this every frame to update the timer / last tick based on the current instant /// and the tempo pub fn tick(&mut self) -> bool { @@ -76,15 +85,26 @@ impl Nome { self.grids.push(Grid::for_note_type(note_type, self.window_counter)); } - pub fn notes_for_beat(&self) -> Vec<(String, u32)> { + pub fn notes_for_beat(&self) -> Vec { let mut notes = vec![]; let beat = self.beat(); for grid in self.grids.iter() { for note in grid.notes(beat).into_iter() { - notes.push((grid.id.clone(), note)) + let tone: i32 = note as i32 - 8; + + notes.push(Note { + note_type: grid.note_type, + tone, + volume: grid.volume, + duration: Duration::from_millis(grid.length as u64), + }) } } notes } + + pub fn play(&mut self, note: Note) { + note.play(self.output_stream.mixer()) + } } \ No newline at end of file