1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
use xmrs::period_helper::{FrequencyType, PeriodHelper};
/// An Instrument Vibrato State
use xmrs::vibrato::Vibrato;
use xmrs::waveform::WaveformState;
#[derive(Clone)]
pub struct StateAutoVibrato<'a> {
vibrato: &'a Vibrato,
wfs: WaveformState,
period_helper: PeriodHelper,
phase: f32,
pub current_modulation: f32,
}
impl<'a> StateAutoVibrato<'a> {
pub fn new(vibrato: &'a Vibrato, period_helper: PeriodHelper) -> Self {
let mut sv = Self {
vibrato,
wfs: WaveformState::new(vibrato.waveform),
period_helper,
phase: 0.0,
current_modulation: 0.0,
};
sv.reset();
sv
}
pub fn reset(&mut self) {
self.retrig();
}
pub fn retrig(&mut self) {
self.phase = 0.0;
self.current_modulation = 0.0;
}
pub fn tick(&mut self, _sustain: bool) {
self.phase += self.vibrato.speed;
// Sweep-in: auto-vibrato depth ramps from 0 to `depth`
// over the first `sweep` units of phase accumulation. Once
// `phase >= sweep` the LFO runs at full depth for the rest
// of the voice's life — including after key-off (sustain =
// false). Previously the `!sustain` gate made the ramp
// only apply during release, which was exactly backwards.
//
// `sweep == 0` means "no ramp, go straight to full depth"
// in IT (ITTECH) — handled explicitly to avoid a division
// that would NaN out `current_modulation`.
let current_depth = if self.vibrato.sweep <= 0.0 || self.phase >= self.vibrato.sweep {
self.vibrato.depth
} else {
(self.phase / self.vibrato.sweep) * self.vibrato.depth
};
self.current_modulation = current_depth * self.wfs.value(self.phase);
if let FrequencyType::AmigaFrequencies = self.period_helper.freq_type {
self.current_modulation /= 4.0;
}
}
}