timbre controls

This commit is contained in:
2025-10-05 13:57:18 -05:00
parent 03a62ac251
commit 0da8fd9cb2
6 changed files with 133 additions and 22 deletions
+14 -11
View File
@@ -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<f32> 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<f32> 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;
+1
View File
@@ -6,6 +6,7 @@ mod scale;
mod saveload;
mod dialog;
mod envelope;
mod timbre;
use std::time::Duration;
use eframe::{App, Frame};
+6 -5
View File
@@ -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)
}
}
+5 -5
View File
@@ -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
}
+1 -1
View File
@@ -106,7 +106,7 @@ impl Tenori {
note_type: grid.note_type,
tone,
volume: grid.volume,
envelope: grid.envelope
timbre: grid.timbre
})
}
}
+106
View File
@@ -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<MixerSource> {
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;
}
}