fix clicking
This commit is contained in:
parent
7ef562917a
commit
242f494219
|
|
@ -5,6 +5,15 @@ use std::f32::consts::PI;
|
||||||
/// Maximum number of simultaneous voices
|
/// Maximum number of simultaneous voices
|
||||||
const MAX_VOICES: usize = 16;
|
const MAX_VOICES: usize = 16;
|
||||||
|
|
||||||
|
/// Envelope state for a voice
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum EnvelopeState {
|
||||||
|
Attack,
|
||||||
|
Sustain,
|
||||||
|
Release,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
/// A single synthesizer voice
|
/// A single synthesizer voice
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct SynthVoice {
|
struct SynthVoice {
|
||||||
|
|
@ -15,6 +24,10 @@ struct SynthVoice {
|
||||||
phase: f32,
|
phase: f32,
|
||||||
frequency: f32,
|
frequency: f32,
|
||||||
age: u32, // For voice stealing
|
age: u32, // For voice stealing
|
||||||
|
|
||||||
|
// Envelope
|
||||||
|
envelope_state: EnvelopeState,
|
||||||
|
envelope_level: f32, // 0.0 to 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SynthVoice {
|
impl SynthVoice {
|
||||||
|
|
@ -27,6 +40,8 @@ impl SynthVoice {
|
||||||
phase: 0.0,
|
phase: 0.0,
|
||||||
frequency: 0.0,
|
frequency: 0.0,
|
||||||
age: 0,
|
age: 0,
|
||||||
|
envelope_state: EnvelopeState::Off,
|
||||||
|
envelope_level: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,19 +59,57 @@ impl SynthVoice {
|
||||||
self.frequency = Self::note_to_frequency(note);
|
self.frequency = Self::note_to_frequency(note);
|
||||||
self.phase = 0.0;
|
self.phase = 0.0;
|
||||||
self.age = 0;
|
self.age = 0;
|
||||||
|
self.envelope_state = EnvelopeState::Attack;
|
||||||
|
self.envelope_level = 0.0; // Start from silence
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop playing
|
/// Stop playing (start release phase)
|
||||||
fn note_off(&mut self) {
|
fn note_off(&mut self) {
|
||||||
self.active = false;
|
// Don't stop immediately - start release phase
|
||||||
|
if self.envelope_state != EnvelopeState::Off {
|
||||||
|
self.envelope_state = EnvelopeState::Release;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate one sample
|
/// Generate one sample
|
||||||
fn process_sample(&mut self, sample_rate: f32) -> f32 {
|
fn process_sample(&mut self, sample_rate: f32) -> f32 {
|
||||||
if !self.active {
|
if self.envelope_state == EnvelopeState::Off {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Envelope timing constants (in seconds)
|
||||||
|
const ATTACK_TIME: f32 = 0.005; // 5ms attack
|
||||||
|
const RELEASE_TIME: f32 = 0.05; // 50ms release
|
||||||
|
|
||||||
|
// Update envelope
|
||||||
|
let attack_increment = 1.0 / (ATTACK_TIME * sample_rate);
|
||||||
|
let release_decrement = 1.0 / (RELEASE_TIME * sample_rate);
|
||||||
|
|
||||||
|
match self.envelope_state {
|
||||||
|
EnvelopeState::Attack => {
|
||||||
|
self.envelope_level += attack_increment;
|
||||||
|
if self.envelope_level >= 1.0 {
|
||||||
|
self.envelope_level = 1.0;
|
||||||
|
self.envelope_state = EnvelopeState::Sustain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EnvelopeState::Sustain => {
|
||||||
|
// Stay at full level
|
||||||
|
self.envelope_level = 1.0;
|
||||||
|
}
|
||||||
|
EnvelopeState::Release => {
|
||||||
|
self.envelope_level -= release_decrement;
|
||||||
|
if self.envelope_level <= 0.0 {
|
||||||
|
self.envelope_level = 0.0;
|
||||||
|
self.envelope_state = EnvelopeState::Off;
|
||||||
|
self.active = false; // Now we can truly stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EnvelopeState::Off => {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simple sine wave
|
// Simple sine wave
|
||||||
let sample = (self.phase * 2.0 * PI).sin() * (self.velocity as f32 / 127.0) * 0.3;
|
let sample = (self.phase * 2.0 * PI).sin() * (self.velocity as f32 / 127.0) * 0.3;
|
||||||
|
|
||||||
|
|
@ -67,7 +120,9 @@ impl SynthVoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.age += 1;
|
self.age += 1;
|
||||||
sample
|
|
||||||
|
// Apply envelope
|
||||||
|
sample * self.envelope_level
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,10 +162,17 @@ impl SimpleSynth {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the voice playing a specific note on a specific channel
|
/// Find the voice playing a specific note on a specific channel
|
||||||
|
/// Only matches voices in Attack or Sustain state (not already releasing)
|
||||||
fn find_voice_for_note_off(&mut self, channel: u8, note: u8) -> Option<usize> {
|
fn find_voice_for_note_off(&mut self, channel: u8, note: u8) -> Option<usize> {
|
||||||
self.voices
|
self.voices
|
||||||
.iter()
|
.iter()
|
||||||
.position(|v| v.active && v.channel == channel && v.note == note)
|
.position(|v| {
|
||||||
|
v.active
|
||||||
|
&& v.channel == channel
|
||||||
|
&& v.note == note
|
||||||
|
&& (v.envelope_state == EnvelopeState::Attack
|
||||||
|
|| v.envelope_state == EnvelopeState::Sustain)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a MIDI event
|
/// Handle a MIDI event
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue