Rework presets + add Flanger/Tremolo/Overdrive, per-preset gate, vocoder carrier Hz
- New effects: Overdrive (soft atan saturation), Tremolo (LFO AM), Flanger (modulated delay + feedback) - Vocoder carrier is now parametrizable (carrier_hz) and CarrierKind::Input is available again; internal makeup raised so no giant external gains - Per-preset gate_db: Demon/Ghost/etc. gate room noise; user gate wins - Preset rework per review: Girl gets chorus+overdrive shimmer, Boy neutral formant + compressor, Demon less dirty (overdrive, gate, tremolo), Robot/Cyborg differentiated, Alien ring-mod 140Hz + flanger, Vader gets overdrive growl, Ghost tremolo, Walkie-Talkie bitcrush moved last, Growl compressor, Chipmunk drops bitcrush - Gains normalized to moderate values (no more 6x clip factories)
This commit is contained in:
parent
13f85b4220
commit
72769f4615
6 changed files with 361 additions and 101 deletions
56
README.md
56
README.md
|
|
@ -33,7 +33,7 @@ vois.rs turns your microphone into a voice changer in real time: shift pitch and
|
||||||
| Real-time pitch shift | Phase-vocoder, −12…+12 semitones, formants preserved |
|
| Real-time pitch shift | Phase-vocoder, −12…+12 semitones, formants preserved |
|
||||||
| Formant shift | Change the timbre (girl / deep / bright) independently of pitch |
|
| Formant shift | Change the timbre (girl / deep / bright) independently of pitch |
|
||||||
| 12 presets | Clean, Girl/Anime, Boy, Manly/Deep, Demon, Robot, 8-bit, Alien, Radio, Megaphone, Ghost, Cyborg |
|
| 12 presets | Clean, Girl/Anime, Boy, Manly/Deep, Demon, Robot, 8-bit, Alien, Radio, Megaphone, Ghost, Cyborg |
|
||||||
| Effects | Vocoder, bitcrush, distortion, reverb, chorus, ring-mod, bandpass, noise, compressor, noise gate |
|
| Effects | Vocoder, bitcrush, distortion, overdrive, reverb, chorus, flanger, tremolo, ring-mod, bandpass, noise, compressor, noise gate |
|
||||||
| Virtual microphone | Built-in `vois.rs` mic (PipeWire/PulseAudio) — no VB-CABLE needed on Linux |
|
| Virtual microphone | Built-in `vois.rs` mic (PipeWire/PulseAudio) — no VB-CABLE needed on Linux |
|
||||||
| Auto-Tune | Optional pitch snap to the nearest semitone (smooth, no wobble) |
|
| Auto-Tune | Optional pitch snap to the nearest semitone (smooth, no wobble) |
|
||||||
| Config file | Defaults + custom presets in `~/.config/vois/config.toml` |
|
| Config file | Defaults + custom presets in `~/.config/vois/config.toml` |
|
||||||
|
|
@ -47,23 +47,23 @@ vois.rs turns your microphone into a voice changer in real time: shift pitch and
|
||||||
| Preset | Pitch | Formant | Effects |
|
| Preset | Pitch | Formant | Effects |
|
||||||
|--------|-------|---------|---------|
|
|--------|-------|---------|---------|
|
||||||
| Clean | 0 st | 1.00× | passthrough (A/B compare) |
|
| Clean | 0 st | 1.00× | passthrough (A/B compare) |
|
||||||
| Girl / Anime | +5 st | 1.25× | — |
|
| Girl / Anime | +5 st | 1.25× | chorus + overdrive |
|
||||||
| Boy | +3 st | 1.12× | — |
|
| Boy | +2 st | 1.08× | compressor |
|
||||||
| Manly / Deep | −4 st | 0.78× | compressor |
|
| Manly / Deep | −4 st | 0.78× | compressor |
|
||||||
| Demon | −9 st | 0.85× | distortion + reverb |
|
| Demon | −9 st | 0.85× | overdrive + reverb + tremolo |
|
||||||
| Robot | 0 st | 1.00× | channel vocoder + bitcrush |
|
| Robot | 0 st | 1.00× | channel vocoder + bitcrush |
|
||||||
| 8-bit | 0 st | 1.00× | bitcrush (5-bit, decimate ×4, auto-level) |
|
| 8-bit | 0 st | 1.00× | bitcrush (5-bit, decimate ×4, auto-level) |
|
||||||
| Alien | −2 st | 1.15× | ring-mod + chorus |
|
| Alien | −2 st | 1.15× | ring-mod + flanger |
|
||||||
| Radio | 0 st | 1.00× | bandpass + noise + compressor |
|
| Radio | 0 st | 1.00× | bandpass + noise + compressor |
|
||||||
| Megaphone | 0 st | 1.00× | distortion + narrow EQ + compressor |
|
| Megaphone | 0 st | 1.00× | distortion + narrow EQ + compressor |
|
||||||
| Ghost | −3 st | 1.05× | big reverb + chorus |
|
| Ghost | −3 st | 1.05× | big reverb + chorus + tremolo |
|
||||||
| Cyborg | +2 st | 1.00× | vocoder + bitcrush |
|
| Cyborg | +2 st | 1.00× | saw vocoder + overdrive + bitcrush |
|
||||||
| Chipmunk | +7 st | 1.40× | bitcrush |
|
| Chipmunk | +7 st | 1.40× | chorus |
|
||||||
| Helium | +10 st | 1.50× | chorus |
|
| Helium | +10 st | 1.50× | chorus |
|
||||||
| Vader | −10 st | 0.60× | reverb |
|
| Vader | −10 st | 0.60× | overdrive + reverb + tremolo |
|
||||||
| Telephone | 0 st | 1.00× | bandpass + noise + compressor |
|
| Telephone | 0 st | 1.00× | bandpass + noise + compressor |
|
||||||
| Growl | −5 st | 0.70× | distortion + reverb |
|
| Growl | −5 st | 0.70× | distortion + compressor + reverb |
|
||||||
| Walkie-Talkie | 0 st | 1.05× | narrow bandpass + bitcrush + squelch noise + compressor |
|
| Walkie-Talkie | 0 st | 1.05× | narrow bandpass + squelch noise + compressor + bitcrush |
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
|
|
@ -107,9 +107,9 @@ virtual_mic = true
|
||||||
] }
|
] }
|
||||||
```
|
```
|
||||||
|
|
||||||
Any CLI option overrides the file. Effects: `Distortion`, `Bitcrush`, `Reverb`,
|
Any CLI option overrides the file. Effects: `Distortion`, `Overdrive`, `Bitcrush`, `Reverb`, `Chorus`, `Flanger`,
|
||||||
`Chorus`, `RingMod`, `Bandpass`, `Noise`, `Compressor`, `Vocoder` (carrier:
|
`Tremolo`, `RingMod`, `Bandpass`, `Noise`, `Compressor`, `Vocoder` (carrier:
|
||||||
`Noise` / `Saw`).
|
`Noise` / `Saw` / `Input`). Custom presets also accept `gate_db`.
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ vois.rs превращает твой микрофон в войс-чендже
|
||||||
| Сдвиг высоты | Фазовый вокодер, −12…+12 полутонов, форманты сохраняются |
|
| Сдвиг высоты | Фазовый вокодер, −12…+12 полутонов, форманты сохраняются |
|
||||||
| Сдвиг формант | Меняет тембр (девушка / глубокий / звонкий) независимо от высоты |
|
| Сдвиг формант | Меняет тембр (девушка / глубокий / звонкий) независимо от высоты |
|
||||||
| 12 пресетов | Clean, Girl/Anime, Boy, Manly/Deep, Demon, Robot, 8-bit, Alien, Radio, Megaphone, Ghost, Cyborg |
|
| 12 пресетов | Clean, Girl/Anime, Boy, Manly/Deep, Demon, Robot, 8-bit, Alien, Radio, Megaphone, Ghost, Cyborg |
|
||||||
| Эффекты | Вокодер, биткраш, дисторшн, реверб, хорус, кольцевая модуляция, полосовой фильтр, шум, компрессор, шумоподавитель |
|
| Эффекты | Вокодер, биткраш, дисторшн, овердрайв, реверб, хорус, флэнджер, тремоло, кольцевая модуляция, полосовой фильтр, шум, компрессор, шумоподавитель |
|
||||||
| Виртуальный микрофон | Встроенный `vois.rs` (PipeWire/PulseAudio) — на Linux не нужен VB-CABLE |
|
| Виртуальный микрофон | Встроенный `vois.rs` (PipeWire/PulseAudio) — на Linux не нужен VB-CABLE |
|
||||||
| Автолюн (Auto-Tune) | Снап высоты к ближайшему полутону (плавно, без «плавания») |
|
| Автолюн (Auto-Tune) | Снап высоты к ближайшему полутону (плавно, без «плавания») |
|
||||||
| Конфиг-файл | Дефолты и свои пресеты в `~/.config/vois/config.toml` |
|
| Конфиг-файл | Дефолты и свои пресеты в `~/.config/vois/config.toml` |
|
||||||
|
|
@ -168,23 +168,23 @@ vois.rs превращает твой микрофон в войс-чендже
|
||||||
| Пресет | Высота | Форманты | Эффекты |
|
| Пресет | Высота | Форманты | Эффекты |
|
||||||
|--------|--------|----------|---------|
|
|--------|--------|----------|---------|
|
||||||
| Clean | 0 пт | 1.00× | passthrough (сравнение A/B) |
|
| Clean | 0 пт | 1.00× | passthrough (сравнение A/B) |
|
||||||
| Girl / Anime | +5 пт | 1.25× | — |
|
| Girl / Anime | +5 пт | 1.25× | хорус + овердрайв |
|
||||||
| Boy | +3 пт | 1.12× | — |
|
| Boy | +2 пт | 1.08× | компрессор |
|
||||||
| Manly / Deep | −4 пт | 0.78× | компрессор |
|
| Manly / Deep | −4 пт | 0.78× | компрессор |
|
||||||
| Demon | −9 пт | 0.85× | дисторшн + реверб |
|
| Demon | −9 пт | 0.85× | овердрайв + реверб + тремоло |
|
||||||
| Robot | 0 пт | 1.00× | вокодер + биткраш |
|
| Robot | 0 пт | 1.00× | вокодер + биткраш |
|
||||||
| 8-bit | 0 пт | 1.00× | биткраш (5 бит, децимация ×4, автолевел) |
|
| 8-bit | 0 пт | 1.00× | биткраш (5 бит, децимация ×4, автолевел) |
|
||||||
| Alien | −2 пт | 1.15× | кольцевая модуляция + хорус |
|
| Alien | −2 пт | 1.15× | кольцевая модуляция + флэнджер |
|
||||||
| Radio | 0 пт | 1.00× | полосовой фильтр + шум + компрессор |
|
| Radio | 0 пт | 1.00× | полосовой фильтр + шум + компрессор |
|
||||||
| Megaphone | 0 пт | 1.00× | дисторшн + узкий EQ + компрессор |
|
| Megaphone | 0 пт | 1.00× | дисторшн + узкий EQ + компрессор |
|
||||||
| Ghost | −3 пт | 1.05× | большой реверб + хорус |
|
| Ghost | −3 пт | 1.05× | большой реверб + хорус + тремоло |
|
||||||
| Cyborg | +2 пт | 1.00× | вокодер + биткраш |
|
| Cyborg | +2 пт | 1.00× | saw-вокодер + овердрайв + биткраш |
|
||||||
| Chipmunk | +7 пт | 1.40× | биткраш |
|
| Chipmunk | +7 пт | 1.40× | хорус |
|
||||||
| Helium | +10 пт | 1.50× | хорус |
|
| Helium | +10 пт | 1.50× | хорус |
|
||||||
| Vader | −10 пт | 0.60× | реверб |
|
| Vader | −10 пт | 0.60× | овердрайв + реверб + тремоло |
|
||||||
| Telephone | 0 пт | 1.00× | полосовой фильтр + шум + компрессор |
|
| Telephone | 0 пт | 1.00× | полосовой фильтр + шум + компрессор |
|
||||||
| Growl | −5 пт | 0.70× | дисторшн + реверб |
|
| Growl | −5 пт | 0.70× | дисторшн + компрессор + реверб |
|
||||||
| Walkie-Talkie | 0 пт | 1.05× | узкий bandpass + биткраш + сквилч-шум + компрессор |
|
| Walkie-Talkie | 0 пт | 1.05× | узкий bandpass + сквилч-шум + компрессор + биткраш |
|
||||||
|
|
||||||
### Запуск
|
### Запуск
|
||||||
|
|
||||||
|
|
@ -228,9 +228,9 @@ virtual_mic = true
|
||||||
] }
|
] }
|
||||||
```
|
```
|
||||||
|
|
||||||
Любой CLI-флаг перекрывает файл. Эффекты: `Distortion`, `Bitcrush`, `Reverb`,
|
Любой CLI-флаг перекрывает файл. Эффекты: `Distortion`, `Overdrive`, `Bitcrush`, `Reverb`, `Chorus`, `Flanger`,
|
||||||
`Chorus`, `RingMod`, `Bandpass`, `Noise`, `Compressor`, `Vocoder` (carrier:
|
`Tremolo`, `RingMod`, `Bandpass`, `Noise`, `Compressor`, `Vocoder` (carrier:
|
||||||
`Noise` / `Saw`).
|
`Noise` / `Saw` / `Input`). Свои пресеты также принимают `gate_db`.
|
||||||
|
|
||||||
### Зависимости
|
### Зависимости
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ impl FileConfig {
|
||||||
formant_ratio: c.formant.unwrap_or(1.0),
|
formant_ratio: c.formant.unwrap_or(1.0),
|
||||||
effects: c.effects.clone().unwrap_or_default(),
|
effects: c.effects.clone().unwrap_or_default(),
|
||||||
gain: 1.0,
|
gain: 1.0,
|
||||||
|
gate_db: c.gate_db.unwrap_or(-100.0),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,12 @@ impl Processor {
|
||||||
ctrl.preset_idx.min(presets().len() - 1),
|
ctrl.preset_idx.min(presets().len() - 1),
|
||||||
preset.pitch_semitones + ctrl.pitch_delta,
|
preset.pitch_semitones + ctrl.pitch_delta,
|
||||||
preset.formant_ratio * ratio_from_semitones(ctrl.formant_delta),
|
preset.formant_ratio * ratio_from_semitones(ctrl.formant_delta),
|
||||||
ctrl.gate_threshold_db,
|
// User-set gate wins; otherwise fall back to the preset's gate.
|
||||||
|
if ctrl.gate_threshold_db > -90.0 {
|
||||||
|
ctrl.gate_threshold_db
|
||||||
|
} else {
|
||||||
|
preset.gate_db
|
||||||
|
},
|
||||||
ctrl.gain,
|
ctrl.gain,
|
||||||
preset.gain,
|
preset.gain,
|
||||||
ctrl.mute,
|
ctrl.mute,
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,138 @@ impl Effect for Compressor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Overdrive (soft tube-style saturation, unity gain at low levels)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct Overdrive {
|
||||||
|
drive: f32,
|
||||||
|
output: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Overdrive {
|
||||||
|
pub fn new(drive: f32, output: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
drive: drive.max(0.01),
|
||||||
|
output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Effect for Overdrive {
|
||||||
|
fn process(&mut self, mono: &mut [f32]) {
|
||||||
|
let d = self.drive;
|
||||||
|
let o = self.output;
|
||||||
|
for s in mono.iter_mut() {
|
||||||
|
*s = ((*s * d).atan() / d) * o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Tremolo (LFO amplitude modulation)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct Tremolo {
|
||||||
|
depth: f32,
|
||||||
|
rate: f32,
|
||||||
|
phase: f32,
|
||||||
|
fs: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tremolo {
|
||||||
|
pub fn new(sample_rate: u32, depth: f32, rate_hz: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
depth: depth.clamp(0.0, 1.0),
|
||||||
|
rate: rate_hz,
|
||||||
|
phase: 0.0,
|
||||||
|
fs: sample_rate as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Effect for Tremolo {
|
||||||
|
fn process(&mut self, mono: &mut [f32]) {
|
||||||
|
for s in mono.iter_mut() {
|
||||||
|
let lfo = (2.0 * PI * self.phase).sin();
|
||||||
|
let amp = 1.0 - self.depth * (0.5 - 0.5 * lfo);
|
||||||
|
*s *= amp;
|
||||||
|
self.phase += self.rate / self.fs;
|
||||||
|
if self.phase >= 1.0 {
|
||||||
|
self.phase -= 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Flanger (short modulated delay with feedback)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct Flanger {
|
||||||
|
delay: Vec<f32>,
|
||||||
|
idx: usize,
|
||||||
|
cap: usize,
|
||||||
|
base: f32,
|
||||||
|
depth: f32,
|
||||||
|
rate: f32,
|
||||||
|
feedback: f32,
|
||||||
|
wet: f32,
|
||||||
|
phase: f32,
|
||||||
|
fs: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flanger {
|
||||||
|
pub fn new(sample_rate: u32, depth_ms: f32, rate_hz: f32, feedback: f32, wet: f32) -> Self {
|
||||||
|
let depth = (depth_ms / 1000.0 * sample_rate as f32).max(1.0);
|
||||||
|
let cap = (sample_rate as f32 * 0.1) as usize + (depth * 2.0) as usize + 2;
|
||||||
|
Self {
|
||||||
|
delay: vec![0.0; cap],
|
||||||
|
idx: 0,
|
||||||
|
cap,
|
||||||
|
base: depth * 0.5,
|
||||||
|
depth: depth * 0.5,
|
||||||
|
rate: rate_hz,
|
||||||
|
feedback: feedback.clamp(0.0, 0.9),
|
||||||
|
wet: wet.clamp(0.0, 1.0),
|
||||||
|
phase: 0.0,
|
||||||
|
fs: sample_rate as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn read(&self, offset: f32) -> f32 {
|
||||||
|
let mut pos = self.idx as f32 - offset;
|
||||||
|
if pos < 0.0 {
|
||||||
|
pos += self.cap as f32;
|
||||||
|
}
|
||||||
|
let i = pos.floor() as usize % self.cap;
|
||||||
|
let j = (i + 1) % self.cap;
|
||||||
|
let t = pos - pos.floor();
|
||||||
|
self.delay[i] * (1.0 - t) + self.delay[j] * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Effect for Flanger {
|
||||||
|
fn process(&mut self, mono: &mut [f32]) {
|
||||||
|
let wet = self.wet;
|
||||||
|
let dry = 1.0 - wet;
|
||||||
|
for s in mono.iter_mut() {
|
||||||
|
let lfo = (2.0 * PI * self.phase).sin();
|
||||||
|
let offset = self.base + self.depth * lfo;
|
||||||
|
let delayed = self.read(offset);
|
||||||
|
let y = *s * dry + delayed * wet;
|
||||||
|
self.delay[self.idx] = *s + delayed * self.feedback;
|
||||||
|
*s = y;
|
||||||
|
self.idx = (self.idx + 1) % self.cap;
|
||||||
|
self.phase += self.rate / self.fs;
|
||||||
|
if self.phase >= 1.0 {
|
||||||
|
self.phase -= 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Channel vocoder (classic robot voice)
|
// Channel vocoder (classic robot voice)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -504,6 +636,7 @@ impl Effect for Compressor {
|
||||||
pub enum CarrierKind {
|
pub enum CarrierKind {
|
||||||
Noise,
|
Noise,
|
||||||
Saw,
|
Saw,
|
||||||
|
Input,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChannelVocoder {
|
pub struct ChannelVocoder {
|
||||||
|
|
@ -511,6 +644,9 @@ pub struct ChannelVocoder {
|
||||||
carrier: CarrierKind,
|
carrier: CarrierKind,
|
||||||
bands_count: usize,
|
bands_count: usize,
|
||||||
wet: f32,
|
wet: f32,
|
||||||
|
/// Output makeup so vocoder presets stay loud without giant external gain.
|
||||||
|
makeup: f32,
|
||||||
|
carrier_hz: f32,
|
||||||
phase: f32,
|
phase: f32,
|
||||||
noise_state: u32,
|
noise_state: u32,
|
||||||
fs: f32,
|
fs: f32,
|
||||||
|
|
@ -523,7 +659,13 @@ struct VocoderBand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelVocoder {
|
impl ChannelVocoder {
|
||||||
pub fn new(sample_rate: u32, bands_count: usize, carrier: CarrierKind, wet: f32) -> Self {
|
pub fn new(
|
||||||
|
sample_rate: u32,
|
||||||
|
bands_count: usize,
|
||||||
|
carrier: CarrierKind,
|
||||||
|
carrier_hz: f32,
|
||||||
|
wet: f32,
|
||||||
|
) -> Self {
|
||||||
let fs = sample_rate as f32;
|
let fs = sample_rate as f32;
|
||||||
let low: f32 = 150.0;
|
let low: f32 = 150.0;
|
||||||
let high: f32 = 4500.0;
|
let high: f32 = 4500.0;
|
||||||
|
|
@ -546,6 +688,8 @@ impl ChannelVocoder {
|
||||||
carrier,
|
carrier,
|
||||||
bands_count: n,
|
bands_count: n,
|
||||||
wet: wet.clamp(0.0, 1.0),
|
wet: wet.clamp(0.0, 1.0),
|
||||||
|
makeup: 5.0,
|
||||||
|
carrier_hz: carrier_hz.max(20.0),
|
||||||
phase: 0.0,
|
phase: 0.0,
|
||||||
noise_state: 0x2545F491,
|
noise_state: 0x2545F491,
|
||||||
fs,
|
fs,
|
||||||
|
|
@ -553,7 +697,7 @@ impl ChannelVocoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn carrier_sample(&mut self) -> f32 {
|
fn carrier_sample(&mut self, input: f32) -> f32 {
|
||||||
match self.carrier {
|
match self.carrier {
|
||||||
CarrierKind::Noise => {
|
CarrierKind::Noise => {
|
||||||
let mut x = self.noise_state;
|
let mut x = self.noise_state;
|
||||||
|
|
@ -565,12 +709,13 @@ impl ChannelVocoder {
|
||||||
}
|
}
|
||||||
CarrierKind::Saw => {
|
CarrierKind::Saw => {
|
||||||
let s = self.phase * 2.0 - 1.0;
|
let s = self.phase * 2.0 - 1.0;
|
||||||
self.phase += 1.0 / self.fs * 120.0;
|
self.phase += self.carrier_hz / self.fs;
|
||||||
if self.phase >= 1.0 {
|
if self.phase >= 1.0 {
|
||||||
self.phase -= 1.0;
|
self.phase -= 1.0;
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
CarrierKind::Input => input,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,7 +730,7 @@ impl Effect for ChannelVocoder {
|
||||||
|
|
||||||
for s in mono.iter_mut() {
|
for s in mono.iter_mut() {
|
||||||
let input = *s;
|
let input = *s;
|
||||||
let carrier = self.carrier_sample();
|
let carrier = self.carrier_sample(input);
|
||||||
|
|
||||||
let mut acc = 0.0;
|
let mut acc = 0.0;
|
||||||
for band in &mut self.bands {
|
for band in &mut self.bands {
|
||||||
|
|
@ -599,7 +744,7 @@ impl Effect for ChannelVocoder {
|
||||||
acc += band.synthesis.tick(carrier) * band.env;
|
acc += band.synthesis.tick(carrier) * band.env;
|
||||||
}
|
}
|
||||||
|
|
||||||
*s = input * dry + acc * wet * inv_n * 4.0;
|
*s = input * dry + acc * wet * inv_n * self.makeup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ pub struct Preset {
|
||||||
pub effects: Vec<EffectSpec>,
|
pub effects: Vec<EffectSpec>,
|
||||||
/// Output makeup gain so every preset sounds equally loud.
|
/// Output makeup gain so every preset sounds equally loud.
|
||||||
pub gain: f32,
|
pub gain: f32,
|
||||||
|
/// Noise gate threshold for this preset (-100 = off).
|
||||||
|
pub gate_db: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
|
@ -24,6 +26,10 @@ pub enum EffectSpec {
|
||||||
drive: f32,
|
drive: f32,
|
||||||
output: f32,
|
output: f32,
|
||||||
},
|
},
|
||||||
|
Overdrive {
|
||||||
|
drive: f32,
|
||||||
|
output: f32,
|
||||||
|
},
|
||||||
Bitcrush {
|
Bitcrush {
|
||||||
bits: u8,
|
bits: u8,
|
||||||
sample_rate_div: u32,
|
sample_rate_div: u32,
|
||||||
|
|
@ -40,6 +46,16 @@ pub enum EffectSpec {
|
||||||
rate_hz: f32,
|
rate_hz: f32,
|
||||||
wet: f32,
|
wet: f32,
|
||||||
},
|
},
|
||||||
|
Flanger {
|
||||||
|
depth_ms: f32,
|
||||||
|
rate_hz: f32,
|
||||||
|
feedback: f32,
|
||||||
|
wet: f32,
|
||||||
|
},
|
||||||
|
Tremolo {
|
||||||
|
depth: f32,
|
||||||
|
rate_hz: f32,
|
||||||
|
},
|
||||||
RingMod {
|
RingMod {
|
||||||
freq_hz: f32,
|
freq_hz: f32,
|
||||||
depth: f32,
|
depth: f32,
|
||||||
|
|
@ -60,6 +76,7 @@ pub enum EffectSpec {
|
||||||
Vocoder {
|
Vocoder {
|
||||||
bands: usize,
|
bands: usize,
|
||||||
carrier: CarrierKind,
|
carrier: CarrierKind,
|
||||||
|
carrier_hz: f32,
|
||||||
wet: f32,
|
wet: f32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +87,7 @@ pub struct CustomPresetCfg {
|
||||||
pub pitch: Option<f32>,
|
pub pitch: Option<f32>,
|
||||||
pub formant: Option<f32>,
|
pub formant: Option<f32>,
|
||||||
pub effects: Option<Vec<EffectSpec>>,
|
pub effects: Option<Vec<EffectSpec>>,
|
||||||
|
pub gate_db: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PRESETS: OnceLock<Vec<Preset>> = OnceLock::new();
|
static PRESETS: OnceLock<Vec<Preset>> = OnceLock::new();
|
||||||
|
|
@ -81,49 +99,79 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
pitch_semitones: 0.0,
|
pitch_semitones: 0.0,
|
||||||
formant_ratio: 1.0,
|
formant_ratio: 1.0,
|
||||||
effects: vec![],
|
effects: vec![],
|
||||||
gain: 1.10,
|
gain: 1.1,
|
||||||
|
gate_db: -100.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Girl / Anime".into(),
|
name: "Girl / Anime".into(),
|
||||||
pitch_semitones: 5.0,
|
pitch_semitones: 5.0,
|
||||||
formant_ratio: 1.25,
|
formant_ratio: 1.25,
|
||||||
effects: vec![],
|
effects: vec![
|
||||||
gain: 1.50,
|
EffectSpec::Chorus {
|
||||||
|
depth_ms: 4.0,
|
||||||
|
rate_hz: 0.6,
|
||||||
|
wet: 0.25,
|
||||||
|
},
|
||||||
|
EffectSpec::Overdrive {
|
||||||
|
drive: 1.4,
|
||||||
|
output: 1.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
gain: 1.3,
|
||||||
|
gate_db: -45.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Boy".into(),
|
name: "Boy".into(),
|
||||||
pitch_semitones: 3.0,
|
pitch_semitones: 2.0,
|
||||||
formant_ratio: 1.12,
|
formant_ratio: 1.08,
|
||||||
effects: vec![],
|
effects: vec![EffectSpec::Compressor {
|
||||||
gain: 1.00,
|
threshold_db: -20.0,
|
||||||
|
ratio: 3.0,
|
||||||
|
makeup_db: 4.0,
|
||||||
|
}],
|
||||||
|
gain: 1.1,
|
||||||
|
gate_db: -45.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Manly / Deep".into(),
|
name: "Manly / Deep".into(),
|
||||||
pitch_semitones: -4.0,
|
pitch_semitones: -4.0,
|
||||||
formant_ratio: 0.78,
|
formant_ratio: 0.78,
|
||||||
effects: vec![EffectSpec::Compressor {
|
effects: vec![
|
||||||
|
EffectSpec::Compressor {
|
||||||
threshold_db: -20.0,
|
threshold_db: -20.0,
|
||||||
ratio: 3.0,
|
ratio: 3.0,
|
||||||
makeup_db: 2.0,
|
makeup_db: 2.0,
|
||||||
}],
|
},
|
||||||
gain: 1.50,
|
EffectSpec::Reverb {
|
||||||
|
room: 0.25,
|
||||||
|
damp: 0.5,
|
||||||
|
wet: 0.15,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
gain: 1.3,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Demon".into(),
|
name: "Demon".into(),
|
||||||
pitch_semitones: -9.0,
|
pitch_semitones: -9.0,
|
||||||
formant_ratio: 0.85,
|
formant_ratio: 0.85,
|
||||||
effects: vec![
|
effects: vec![
|
||||||
EffectSpec::Distortion {
|
EffectSpec::Overdrive {
|
||||||
drive: 6.0,
|
drive: 2.5,
|
||||||
output: 0.7,
|
output: 0.8,
|
||||||
},
|
},
|
||||||
EffectSpec::Reverb {
|
EffectSpec::Reverb {
|
||||||
room: 0.6,
|
room: 0.5,
|
||||||
damp: 0.4,
|
damp: 0.45,
|
||||||
wet: 0.4,
|
wet: 0.3,
|
||||||
|
},
|
||||||
|
EffectSpec::Tremolo {
|
||||||
|
depth: 0.15,
|
||||||
|
rate_hz: 4.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -38.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Robot".into(),
|
name: "Robot".into(),
|
||||||
|
|
@ -133,16 +181,18 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
EffectSpec::Vocoder {
|
EffectSpec::Vocoder {
|
||||||
bands: 16,
|
bands: 16,
|
||||||
carrier: CarrierKind::Noise,
|
carrier: CarrierKind::Noise,
|
||||||
|
carrier_hz: 110.0,
|
||||||
wet: 1.0,
|
wet: 1.0,
|
||||||
},
|
},
|
||||||
EffectSpec::Bitcrush {
|
EffectSpec::Bitcrush {
|
||||||
bits: 8,
|
bits: 8,
|
||||||
sample_rate_div: 2,
|
sample_rate_div: 2,
|
||||||
max_gain: 8.0,
|
max_gain: 4.0,
|
||||||
target: 0.6,
|
target: 0.6,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 6.00,
|
gain: 1.8,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "8-bit".into(),
|
name: "8-bit".into(),
|
||||||
|
|
@ -154,7 +204,8 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
max_gain: 32.0,
|
max_gain: 32.0,
|
||||||
target: 0.6,
|
target: 0.6,
|
||||||
}],
|
}],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -45.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Alien".into(),
|
name: "Alien".into(),
|
||||||
|
|
@ -162,16 +213,18 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
formant_ratio: 1.15,
|
formant_ratio: 1.15,
|
||||||
effects: vec![
|
effects: vec![
|
||||||
EffectSpec::RingMod {
|
EffectSpec::RingMod {
|
||||||
freq_hz: 55.0,
|
freq_hz: 140.0,
|
||||||
depth: 0.5,
|
depth: 0.5,
|
||||||
},
|
},
|
||||||
EffectSpec::Chorus {
|
EffectSpec::Flanger {
|
||||||
depth_ms: 6.0,
|
depth_ms: 2.5,
|
||||||
rate_hz: 0.5,
|
rate_hz: 0.4,
|
||||||
wet: 0.4,
|
feedback: 0.5,
|
||||||
|
wet: 0.5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 5.50,
|
gain: 2.0,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Radio".into(),
|
name: "Radio".into(),
|
||||||
|
|
@ -190,7 +243,8 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
makeup_db: 6.0,
|
makeup_db: 6.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Megaphone".into(),
|
name: "Megaphone".into(),
|
||||||
|
|
@ -212,7 +266,8 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
makeup_db: 4.0,
|
makeup_db: 4.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.60,
|
gain: 1.2,
|
||||||
|
gate_db: -38.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Ghost".into(),
|
name: "Ghost".into(),
|
||||||
|
|
@ -220,17 +275,22 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
formant_ratio: 1.05,
|
formant_ratio: 1.05,
|
||||||
effects: vec![
|
effects: vec![
|
||||||
EffectSpec::Reverb {
|
EffectSpec::Reverb {
|
||||||
room: 0.95,
|
room: 0.85,
|
||||||
damp: 0.2,
|
damp: 0.3,
|
||||||
wet: 0.8,
|
wet: 0.6,
|
||||||
},
|
},
|
||||||
EffectSpec::Chorus {
|
EffectSpec::Chorus {
|
||||||
depth_ms: 8.0,
|
depth_ms: 5.0,
|
||||||
rate_hz: 0.4,
|
rate_hz: 0.5,
|
||||||
wet: 0.5,
|
wet: 0.3,
|
||||||
|
},
|
||||||
|
EffectSpec::Tremolo {
|
||||||
|
depth: 0.2,
|
||||||
|
rate_hz: 1.5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.00,
|
gain: 0.80,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Cyborg".into(),
|
name: "Cyborg".into(),
|
||||||
|
|
@ -240,28 +300,34 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
EffectSpec::Vocoder {
|
EffectSpec::Vocoder {
|
||||||
bands: 16,
|
bands: 16,
|
||||||
carrier: CarrierKind::Saw,
|
carrier: CarrierKind::Saw,
|
||||||
|
carrier_hz: 220.0,
|
||||||
wet: 0.9,
|
wet: 0.9,
|
||||||
},
|
},
|
||||||
|
EffectSpec::Overdrive {
|
||||||
|
drive: 1.5,
|
||||||
|
output: 1.0,
|
||||||
|
},
|
||||||
EffectSpec::Bitcrush {
|
EffectSpec::Bitcrush {
|
||||||
bits: 8,
|
bits: 8,
|
||||||
sample_rate_div: 2,
|
sample_rate_div: 2,
|
||||||
max_gain: 8.0,
|
max_gain: 4.0,
|
||||||
target: 0.6,
|
target: 0.6,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.20,
|
gain: 1.8,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Chipmunk".into(),
|
name: "Chipmunk".into(),
|
||||||
pitch_semitones: 7.0,
|
pitch_semitones: 7.0,
|
||||||
formant_ratio: 1.4,
|
formant_ratio: 1.4,
|
||||||
effects: vec![EffectSpec::Bitcrush {
|
effects: vec![EffectSpec::Chorus {
|
||||||
bits: 8,
|
depth_ms: 3.0,
|
||||||
sample_rate_div: 2,
|
rate_hz: 0.8,
|
||||||
max_gain: 8.0,
|
wet: 0.2,
|
||||||
target: 0.5,
|
|
||||||
}],
|
}],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -45.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Helium".into(),
|
name: "Helium".into(),
|
||||||
|
|
@ -272,18 +338,30 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
rate_hz: 0.6,
|
rate_hz: 0.6,
|
||||||
wet: 0.3,
|
wet: 0.3,
|
||||||
}],
|
}],
|
||||||
gain: 1.20,
|
gain: 1.0,
|
||||||
|
gate_db: -45.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Vader".into(),
|
name: "Vader".into(),
|
||||||
pitch_semitones: -10.0,
|
pitch_semitones: -10.0,
|
||||||
formant_ratio: 0.6,
|
formant_ratio: 0.6,
|
||||||
effects: vec![EffectSpec::Reverb {
|
effects: vec![
|
||||||
room: 0.7,
|
EffectSpec::Overdrive {
|
||||||
damp: 0.3,
|
drive: 2.0,
|
||||||
wet: 0.35,
|
output: 0.8,
|
||||||
}],
|
},
|
||||||
gain: 2.80,
|
EffectSpec::Reverb {
|
||||||
|
room: 0.6,
|
||||||
|
damp: 0.4,
|
||||||
|
wet: 0.3,
|
||||||
|
},
|
||||||
|
EffectSpec::Tremolo {
|
||||||
|
depth: 0.1,
|
||||||
|
rate_hz: 2.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
gain: 1.4,
|
||||||
|
gate_db: -38.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Telephone".into(),
|
name: "Telephone".into(),
|
||||||
|
|
@ -302,7 +380,8 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
makeup_db: 8.0,
|
makeup_db: 8.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -42.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Walkie-Talkie".into(),
|
name: "Walkie-Talkie".into(),
|
||||||
|
|
@ -314,20 +393,21 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
high_hz: 2400.0,
|
high_hz: 2400.0,
|
||||||
mix: 1.0,
|
mix: 1.0,
|
||||||
},
|
},
|
||||||
EffectSpec::Bitcrush {
|
|
||||||
bits: 8,
|
|
||||||
sample_rate_div: 2,
|
|
||||||
max_gain: 4.0,
|
|
||||||
target: 0.6,
|
|
||||||
},
|
|
||||||
EffectSpec::Noise { gain: 0.05 },
|
EffectSpec::Noise { gain: 0.05 },
|
||||||
EffectSpec::Compressor {
|
EffectSpec::Compressor {
|
||||||
threshold_db: -20.0,
|
threshold_db: -20.0,
|
||||||
ratio: 4.0,
|
ratio: 4.0,
|
||||||
makeup_db: 4.0,
|
makeup_db: 4.0,
|
||||||
},
|
},
|
||||||
|
EffectSpec::Bitcrush {
|
||||||
|
bits: 8,
|
||||||
|
sample_rate_div: 2,
|
||||||
|
max_gain: 4.0,
|
||||||
|
target: 0.6,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
gain: 1.30,
|
gain: 1.00,
|
||||||
|
gate_db: -40.0,
|
||||||
},
|
},
|
||||||
Preset {
|
Preset {
|
||||||
name: "Growl".into(),
|
name: "Growl".into(),
|
||||||
|
|
@ -338,18 +418,23 @@ fn builtin_presets() -> Vec<Preset> {
|
||||||
drive: 4.0,
|
drive: 4.0,
|
||||||
output: 0.6,
|
output: 0.6,
|
||||||
},
|
},
|
||||||
|
EffectSpec::Compressor {
|
||||||
|
threshold_db: -18.0,
|
||||||
|
ratio: 4.0,
|
||||||
|
makeup_db: 4.0,
|
||||||
|
},
|
||||||
EffectSpec::Reverb {
|
EffectSpec::Reverb {
|
||||||
room: 0.5,
|
room: 0.3,
|
||||||
damp: 0.4,
|
damp: 0.5,
|
||||||
wet: 0.3,
|
wet: 0.2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
gain: 1.00,
|
gain: 1.0,
|
||||||
|
gate_db: -38.0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register custom presets (from the config file). Call once at startup.
|
|
||||||
pub fn init_custom(customs: Vec<Preset>) {
|
pub fn init_custom(customs: Vec<Preset>) {
|
||||||
let _ = PRESETS.get_or_init(|| {
|
let _ = PRESETS.get_or_init(|| {
|
||||||
let mut all = builtin_presets();
|
let mut all = builtin_presets();
|
||||||
|
|
@ -386,6 +471,9 @@ fn build_effect(spec: &EffectSpec, sample_rate: u32) -> Box<dyn Effect> {
|
||||||
EffectSpec::Distortion { drive, output } => {
|
EffectSpec::Distortion { drive, output } => {
|
||||||
Box::new(effects::Distortion::new(*drive, *output))
|
Box::new(effects::Distortion::new(*drive, *output))
|
||||||
}
|
}
|
||||||
|
EffectSpec::Overdrive { drive, output } => {
|
||||||
|
Box::new(effects::Overdrive::new(*drive, *output))
|
||||||
|
}
|
||||||
EffectSpec::Bitcrush {
|
EffectSpec::Bitcrush {
|
||||||
bits,
|
bits,
|
||||||
sample_rate_div,
|
sample_rate_div,
|
||||||
|
|
@ -405,6 +493,21 @@ fn build_effect(spec: &EffectSpec, sample_rate: u32) -> Box<dyn Effect> {
|
||||||
rate_hz,
|
rate_hz,
|
||||||
wet,
|
wet,
|
||||||
} => Box::new(effects::Chorus::new(sample_rate, *depth_ms, *rate_hz, *wet)),
|
} => Box::new(effects::Chorus::new(sample_rate, *depth_ms, *rate_hz, *wet)),
|
||||||
|
EffectSpec::Flanger {
|
||||||
|
depth_ms,
|
||||||
|
rate_hz,
|
||||||
|
feedback,
|
||||||
|
wet,
|
||||||
|
} => Box::new(effects::Flanger::new(
|
||||||
|
sample_rate,
|
||||||
|
*depth_ms,
|
||||||
|
*rate_hz,
|
||||||
|
*feedback,
|
||||||
|
*wet,
|
||||||
|
)),
|
||||||
|
EffectSpec::Tremolo { depth, rate_hz } => {
|
||||||
|
Box::new(effects::Tremolo::new(sample_rate, *depth, *rate_hz))
|
||||||
|
}
|
||||||
EffectSpec::RingMod { freq_hz, depth } => {
|
EffectSpec::RingMod { freq_hz, depth } => {
|
||||||
Box::new(effects::RingMod::new(sample_rate, *freq_hz, *depth))
|
Box::new(effects::RingMod::new(sample_rate, *freq_hz, *depth))
|
||||||
}
|
}
|
||||||
|
|
@ -427,11 +530,13 @@ fn build_effect(spec: &EffectSpec, sample_rate: u32) -> Box<dyn Effect> {
|
||||||
EffectSpec::Vocoder {
|
EffectSpec::Vocoder {
|
||||||
bands,
|
bands,
|
||||||
carrier,
|
carrier,
|
||||||
|
carrier_hz,
|
||||||
wet,
|
wet,
|
||||||
} => Box::new(effects::ChannelVocoder::new(
|
} => Box::new(effects::ChannelVocoder::new(
|
||||||
sample_rate,
|
sample_rate,
|
||||||
*bands,
|
*bands,
|
||||||
*carrier,
|
*carrier,
|
||||||
|
*carrier_hz,
|
||||||
*wet,
|
*wet,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,11 @@ impl App {
|
||||||
preset.name.to_string(),
|
preset.name.to_string(),
|
||||||
preset.pitch_semitones + c.pitch_delta,
|
preset.pitch_semitones + c.pitch_delta,
|
||||||
preset.formant_ratio * 2f32.powf(c.formant_delta / 12.0),
|
preset.formant_ratio * 2f32.powf(c.formant_delta / 12.0),
|
||||||
c.gate_threshold_db,
|
if c.gate_threshold_db > -90.0 {
|
||||||
|
c.gate_threshold_db
|
||||||
|
} else {
|
||||||
|
preset.gate_db
|
||||||
|
},
|
||||||
c.gain,
|
c.gain,
|
||||||
c.mute,
|
c.mute,
|
||||||
c.record,
|
c.record,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue