Skip to content

Commit 5401b21

Browse files
authored
Merge pull request #76 from Leif-Rydenfalk/main
Synth example fix + key released events
2 parents a4fea74 + acde643 commit 5401b21

File tree

11 files changed

+525
-8750
lines changed

11 files changed

+525
-8750
lines changed

examples/synth/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ publish = false
66

77
[dependencies]
88
rui = { workspace = true }
9-
palette = "0.7.6"
10-
enterpolation = "0.2"
11-
rodio = { version = "0.20.1", default-features = false, features = ["symphonia-all"] }
9+
# palette = "0.7.6"
10+
# enterpolation = "0.2"
11+
rodio = { version = "0.20.1", default-features = false, features = [
12+
"symphonia-all",
13+
] }

examples/synth/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ fn main() {
3030

3131
let mut synth = synth.lock().unwrap();
3232

33+
// let notes = note + 1 + 3 + 5;
34+
3335
// Get the frequency of the note.
3436
let frequency: MidiFrequency = note.frequency();
3537

3638
// Create an audio source for the note.
37-
let audio_source = Oscillator::sawtooth_wave(frequency).amplify(1.0);
39+
let audio_source = Oscillator::sawtooth(frequency).amplify(1.0);
3840

3941
// Get the note id (u8) if you need it. 0 is the lowest note. 127 is the highest note.
4042
let source_id: MidiNoteId = note.id();

examples/synth/src/midi_keyboard.rs

Lines changed: 180 additions & 201 deletions
Large diffs are not rendered by default.

examples/synth/src/synth/mod.rs

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,14 @@ use rodio::Sink;
77
mod oscillator;
88
pub use oscillator::Oscillator;
99

10-
mod osc;
11-
use osc::AnalogOsc;
12-
13-
// The envelope state struct
1410
struct EnvelopeState {
1511
envelope: Envelope,
1612
start_time: Instant,
1713
is_releasing: bool,
1814
release_start_time: Option<Instant>,
15+
release_start_volume: Option<f32>, // Track starting volume for release
1916
}
2017

21-
// The envelope struct
2218
struct Envelope {
2319
attack: f32,
2420
decay: f32,
@@ -45,9 +41,6 @@ pub struct Synth {
4541

4642
impl Synth {
4743
pub fn new(stream_handle: rodio::OutputStreamHandle) -> Synth {
48-
// let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
49-
// For some reason the above code would fail if it was in the new function
50-
5144
Synth {
5245
audio_sinks: HashMap::new(),
5346
envelope_states: HashMap::new(),
@@ -59,12 +52,13 @@ impl Synth {
5952
let sink = Sink::try_new(&self.stream_handle).expect("Failed to create sink");
6053
sink.append(audio_source);
6154

62-
let envelope = Envelope::new(0.1, 0.2, 0.7, 1.3); // example envelope
55+
let envelope = Envelope::new(0.8, 0.2, 0.7, 1.3);
6356
let envelope_state = EnvelopeState {
6457
envelope,
6558
start_time: Instant::now(),
6659
is_releasing: false,
6760
release_start_time: None,
61+
release_start_volume: None,
6862
};
6963

7064
self.audio_sinks.insert(source_id, sink);
@@ -73,48 +67,76 @@ impl Synth {
7367

7468
pub fn release_source(&mut self, source_id: u8) {
7569
if let Some(envelope_state) = self.envelope_states.get_mut(&source_id) {
70+
let now = Instant::now();
71+
let elapsed = now.duration_since(envelope_state.start_time).as_secs_f32();
72+
let envelope = &envelope_state.envelope;
73+
74+
// Calculate current volume at release time
75+
let current_volume = if elapsed < envelope.attack {
76+
// Attack phase
77+
elapsed / envelope.attack
78+
} else if elapsed < envelope.attack + envelope.decay {
79+
// Decay phase
80+
1.0 - (elapsed - envelope.attack) / envelope.decay * (1.0 - envelope.sustain)
81+
} else {
82+
// Sustain phase
83+
envelope.sustain
84+
};
85+
7686
envelope_state.is_releasing = true;
77-
envelope_state.release_start_time = Some(Instant::now());
87+
envelope_state.release_start_time = Some(now);
88+
envelope_state.release_start_volume = Some(current_volume);
7889
}
7990
}
8091

8192
pub fn update(&mut self) {
8293
let now = Instant::now();
83-
8494
let mut to_remove = Vec::new();
8595

8696
for (source_id, envelope_state) in self.envelope_states.iter_mut() {
87-
let elapsed = now.duration_since(envelope_state.start_time).as_secs_f32();
88-
89-
let envelope = &envelope_state.envelope;
9097
let sink = self.audio_sinks.get_mut(source_id).unwrap();
98+
let envelope = &envelope_state.envelope;
9199

92-
let volume = if elapsed < envelope.attack {
93-
// Attack
94-
elapsed / envelope.attack
95-
} else if elapsed < envelope.attack + envelope.decay {
96-
// Decay
97-
1.0 - (elapsed - envelope.attack) / envelope.decay * (1.0 - envelope.sustain)
98-
} else if envelope_state.is_releasing {
99-
// Release
100-
let elapsed_since_released = now
100+
let volume = if envelope_state.is_releasing {
101+
// Release phase - use captured start volume and release time
102+
let elapsed_release = now
101103
.duration_since(envelope_state.release_start_time.unwrap())
102104
.as_secs_f32();
103-
envelope.sustain - elapsed_since_released / envelope.release * envelope.sustain
105+
106+
let start_volume = envelope_state.release_start_volume.unwrap();
107+
let t = (elapsed_release / envelope.release).min(1.0);
108+
start_volume * (1.0 - t)
104109
} else {
105-
// Sustain
106-
envelope.sustain
110+
// Calculate based on ADSR phases
111+
let elapsed = now.duration_since(envelope_state.start_time).as_secs_f32();
112+
113+
if elapsed < envelope.attack {
114+
// Attack phase
115+
elapsed / envelope.attack
116+
} else if elapsed < envelope.attack + envelope.decay {
117+
// Decay phase
118+
1.0 - (elapsed - envelope.attack) / envelope.decay * (1.0 - envelope.sustain)
119+
} else {
120+
// Sustain phase
121+
envelope.sustain
122+
}
107123
};
108124

109125
sink.set_volume(volume);
110126

111-
if envelope_state.is_releasing && elapsed > envelope.release {
112-
// This is done as a separate step to avoid a second mutable borrow of self.envelope_states
113-
// First borrow is when .iter_mut() is called, second is when .remove() is called
114-
to_remove.push(*source_id);
127+
// Check if release is complete
128+
if envelope_state.is_releasing {
129+
let elapsed_release = now
130+
.duration_since(envelope_state.release_start_time.unwrap())
131+
.as_secs_f32();
132+
133+
if elapsed_release >= envelope.release {
134+
to_remove.push(*source_id);
135+
}
115136
}
116137
}
117138

139+
// Cleanup completed sounds
118140
for source_id in to_remove {
119141
self.envelope_states.remove(&source_id);
120142
self.audio_sinks.remove(&source_id);

0 commit comments

Comments
 (0)