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:
Emil
2026-06-21 23:54:25 +03:00
parent 3e2376ac26
commit 58c47fe286
12 changed files with 243 additions and 68 deletions
+25 -3
View File
@@ -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();
}
}
}