Add vibrato support + fix feedback loop + GUI improvements
Vibrato (LFO frequency modulation): - New VibratoSpec in SoundSpec: rate (Hz), depth (cents), delay (s) - ChannelRenderer applies vibrato to frequency each tick - Supported on Pulse and Triangle channels - GUI: vibrato controls (enable, rate, depth) in channel panel - Enables bird chirps, sirens, wobbles Feedback loop fixes: - generate_batch now accepts full SoundSpec array (not just names) so each sound is unique - render_sound returns reference examples from feedback DB - Fixed GUI bug: clicking stars no longer erases feedback text - Feedback text auto-saves on Enter / focus loss - Replaced emoji buttons (✓/🗑) with text (Save/Del) - Green saved indicator when feedback exists in DB 99 tests passing, 0 warnings
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Produces stereo samples (left, right) with volume and pan applied.
|
||||
|
||||
use crate::effect::{Envelope, Filter, Sweep};
|
||||
use crate::effect::{Envelope, Filter, Sweep, Vibrato};
|
||||
use crate::generator::{Generator, Voice};
|
||||
use crate::voice::VoiceKind;
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct ChannelRenderer {
|
||||
initial_frequency: f32,
|
||||
filter: Option<Filter>,
|
||||
filter_sweep: Option<Sweep>,
|
||||
vibrato: Option<Vibrato>,
|
||||
volume: f32,
|
||||
pan: f32,
|
||||
}
|
||||
@@ -27,6 +28,7 @@ impl ChannelRenderer {
|
||||
initial_frequency: 440.0,
|
||||
filter: None,
|
||||
filter_sweep: None,
|
||||
vibrato: None,
|
||||
volume: 1.0,
|
||||
pan: 0.0,
|
||||
}
|
||||
@@ -58,6 +60,12 @@ impl ChannelRenderer {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set vibrato (LFO frequency modulation).
|
||||
pub fn with_vibrato(mut self, vibrato: Vibrato) -> Self {
|
||||
self.vibrato = Some(vibrato);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_volume(mut self, volume: f32) -> Self {
|
||||
self.volume = volume.clamp(0.0, 1.0);
|
||||
self
|
||||
@@ -83,6 +91,9 @@ impl ChannelRenderer {
|
||||
if let Some(s) = &mut self.filter_sweep {
|
||||
s.trigger();
|
||||
}
|
||||
if let Some(v) = &mut self.vibrato {
|
||||
v.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
/// Release the envelope (note off).
|
||||
@@ -101,11 +112,19 @@ impl ChannelRenderer {
|
||||
#[inline]
|
||||
pub fn tick(&mut self) -> (f32, f32) {
|
||||
// Update frequency from sweep
|
||||
let mut current_freq = self.initial_frequency;
|
||||
if let Some(sweep) = &mut self.freq_sweep {
|
||||
let freq = sweep.tick();
|
||||
self.voice.set_frequency(freq);
|
||||
current_freq = sweep.tick();
|
||||
}
|
||||
|
||||
// Apply vibrato (LFO frequency modulation)
|
||||
if let Some(vib) = &mut self.vibrato {
|
||||
let ratio = vib.tick();
|
||||
current_freq *= ratio;
|
||||
}
|
||||
|
||||
self.voice.set_frequency(current_freq);
|
||||
|
||||
// Tick voice
|
||||
let mut sample = self.voice.tick();
|
||||
|
||||
@@ -148,6 +167,9 @@ impl ChannelRenderer {
|
||||
if let Some(f) = &mut self.filter {
|
||||
f.reset();
|
||||
}
|
||||
if let Some(v) = &mut self.vibrato {
|
||||
v.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user