diff --git a/src/grid.rs b/src/grid.rs index 2763921..a29f9f2 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -6,6 +6,7 @@ use crate::gui::Showable; use crate::noise::NoteType; use crate::scale::Scale; use crate::tenori::LOOP_LENGTH; +use crate::timbre::Timbre; impl NoteType { fn color(&self) -> Color32 { @@ -38,8 +39,8 @@ pub struct Grid { pub id: Id, pub name: String, pub open: bool, - pub envelope: Envelope, - pub envelope_open: bool + pub timbre: Timbre, + pub timbre_open: bool } impl Grid { @@ -51,8 +52,8 @@ impl Grid { scale: Scale::CMajor, notes: vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize], name: format!("{} Track", note_type.name()), - envelope: Envelope::default(), - envelope_open: false, + timbre: Timbre::default(), + timbre_open: false, id } } @@ -134,8 +135,8 @@ impl Showable for Grid { } }); - if ui.button("Envelope...").clicked() { - self.envelope_open = !self.envelope_open; + if ui.button("Timbre...").clicked() { + self.timbre_open = !self.timbre_open; } }); @@ -149,11 +150,13 @@ impl Showable for Grid { }); }); - if self.envelope_open { - let mut eopen = true; - let (id, title) = (format!("{} envelope", self.id.value()).into(), format!("{} Envelope", self.name)); - (&mut self.envelope, &mut eopen).show(ctx, &(id, title)); - self.envelope_open = eopen; + if self.timbre_open { + let mut topen = true; + let mut name = self.name.clone(); + let (id, title) = (format!("{} timbre", self.id.value()).into(), format!("{} Timbre", self.name)); + (&mut self.timbre, &mut topen, &mut name).show(ctx, &(id, title)); + self.timbre_open = topen; + self.name = name; } self.open = open; diff --git a/src/main.rs b/src/main.rs index 489b19d..6b9fd72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod scale; mod saveload; mod dialog; mod envelope; +mod timbre; use std::time::Duration; use eframe::{App, Frame}; diff --git a/src/noise.rs b/src/noise.rs index 443dc2e..017c9d9 100644 --- a/src/noise.rs +++ b/src/noise.rs @@ -2,6 +2,7 @@ use rodio::mixer::Mixer; use rodio::Source; use serde::{Deserialize, Serialize}; use crate::envelope::Envelope; +use crate::timbre::Timbre; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum NoteType { @@ -24,7 +25,7 @@ pub struct Note { pub volume: f32, /// ADSR envelope - pub envelope: Envelope + pub timbre: Timbre } /// The frequency for a given tone, in Hz. @@ -52,9 +53,9 @@ impl NoteType { impl Note { pub fn play(self, mixer: &Mixer) { - let note = self.note_type.source(self.tone); - let note = self.envelope.modulate(note); - let note = note.amplify(self.volume.clamp(0.0, 1.0)); - mixer.add(note) + let freq = freq(self.tone); + let source = self.timbre.source(freq); + let source = source.amplify_normalized(self.volume); + mixer.add(source) } } diff --git a/src/saveload.rs b/src/saveload.rs index 8a07741..bb3ed6e 100644 --- a/src/saveload.rs +++ b/src/saveload.rs @@ -1,10 +1,10 @@ use eframe::egui::Id; use serde::{Deserialize, Serialize}; -use crate::envelope::Envelope; use crate::grid::Grid; use crate::noise::NoteType; use crate::scale::Scale; use crate::tenori::Tenori; +use crate::timbre::Timbre; #[derive(Serialize, Deserialize)] pub struct PersistedTenori { @@ -37,7 +37,7 @@ struct PersistedGrid { scale: Scale, notes: String, name: String, - envelope: Envelope, + timbre: Timbre, } impl From<&Grid> for PersistedGrid { @@ -48,7 +48,7 @@ impl From<&Grid> for PersistedGrid { volume: value.volume, scale: value.scale, name: value.name.clone(), - envelope: value.envelope, + timbre: value.timbre, notes } } @@ -62,9 +62,9 @@ impl PersistedGrid { volume: self.volume, scale: self.scale, name: self.name, - envelope: self.envelope, + timbre: self.timbre, open: true, - envelope_open: false, + timbre_open: false, notes, id } diff --git a/src/tenori.rs b/src/tenori.rs index 1032294..e13ea43 100644 --- a/src/tenori.rs +++ b/src/tenori.rs @@ -106,7 +106,7 @@ impl Tenori { note_type: grid.note_type, tone, volume: grid.volume, - envelope: grid.envelope + timbre: grid.timbre }) } } diff --git a/src/timbre.rs b/src/timbre.rs new file mode 100644 index 0000000..b713a34 --- /dev/null +++ b/src/timbre.rs @@ -0,0 +1,106 @@ +use std::ops::RangeInclusive; +use eframe::egui; +use eframe::egui::{Context, Id, Label, Slider, Window}; +use rodio::mixer::MixerSource; +use rodio::Source; +use rodio::source::{SawtoothWave, SineWave, SquareWave, TriangleWave, WhiteUniform}; +use serde::{Deserialize, Serialize}; +use crate::envelope::{Envelope, EnvelopeSource}; +use crate::gui::Showable; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct Timbre { + sine: f32, + triangle: f32, + square: f32, + sawtooth: f32, + noise: f32, + envelope: Envelope +} + +impl Default for Timbre { + fn default() -> Self { + Self { + sine: 0.0, + triangle: 0.0, + square: 1.0, + sawtooth: 0.0, + noise: 0.0, + envelope: Default::default(), + } + } +} + +impl Timbre { + pub fn source(&self, frequency: f32) -> EnvelopeSource { + let (mut mixer, source) = rodio::mixer::mixer(1, 44100); + if self.sine > 0.0 { + mixer.add(SineWave::new(frequency).amplify_normalized(self.sine)) + } + if self.triangle > 0.0 { + mixer.add(TriangleWave::new(frequency).amplify_normalized(self.triangle)) + } + if self.square > 0.0 { + mixer.add(SquareWave::new(frequency).amplify_normalized(self.square)) + } + if self.sawtooth > 0.0 { + mixer.add(SawtoothWave::new(frequency).amplify_normalized(self.sawtooth)) + } + if self.noise > 0.0 { + mixer.add(WhiteUniform::new(44100).amplify_normalized(self.noise)) + } + self.envelope.modulate(source) + } +} + +impl Showable<(Id, String)> for (&mut Timbre, &mut bool, &mut String) { + fn show(&mut self, ctx: &Context, (id, title): &(Id, String)) { + let mut open = true; + let window = Window::new(title) + .id(*id) + .open(&mut open) + .resizable([false, false]) + .scroll([false, false]); + + window.show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Name"); + ui.text_edit_singleline(self.2); + }); + + egui::Grid::new(id).show(ui, |ui| { + ui.add(Label::new("Sine")); + ui.add(Slider::new(&mut self.0.sine, RangeInclusive::new(0.0, 1.0))); + ui.add(Label::new("Attack")); + ui.add(Slider::new(&mut self.0.envelope.attack, RangeInclusive::new(0.0, 1.0))); + ui.end_row(); + + ui.add(Label::new("Triangle")); + ui.add(Slider::new(&mut self.0.triangle, RangeInclusive::new(0.0, 1.0))); + ui.add(Label::new("Decay")); + ui.add(Slider::new(&mut self.0.envelope.decay, RangeInclusive::new(0.0, 1.0))); + ui.end_row(); + + ui.add(Label::new("Square")); + ui.add(Slider::new(&mut self.0.square, RangeInclusive::new(0.0, 1.0))); + ui.add(Label::new("Sustain")); + ui.add(Slider::new(&mut self.0.envelope.sustain, RangeInclusive::new(0.0, 1.0))); + ui.end_row(); + + ui.add(Label::new("Sawtooth")); + ui.add(Slider::new(&mut self.0.sawtooth, RangeInclusive::new(0.0, 1.0))); + ui.add(Label::new("Hold")); + ui.add(Slider::new(&mut self.0.envelope.hold, RangeInclusive::new(0.0, 2.0))); + ui.end_row(); + + ui.add(Label::new("Noise")); + ui.add(Slider::new(&mut self.0.noise, RangeInclusive::new(0.0, 1.0))); + ui.add(Label::new("Release")); + ui.add(Slider::new(&mut self.0.envelope.release, RangeInclusive::new(0.0, 1.0))); + ui.end_row(); + }); + }); + + *self.1 = open; + } +} \ No newline at end of file