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:
loki5512344 2026-08-02 22:49:30 +02:00
parent 13f85b4220
commit 72769f4615
Signed by: boba
GPG key ID: 253067914055423B
6 changed files with 361 additions and 101 deletions

View file

@ -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`.
### Зависимости ### Зависимости

View file

@ -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()
}) })

View file

@ -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,

View file

@ -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;
} }
} }
} }

View file

@ -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,
)), )),
} }

View file

@ -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,