1use crate::blip_buf::BlipBuf;
2use crate::pyxel::TONES;
3use crate::settings::{
4 CLOCKS_PER_TICK, CLOCK_RATE, EFFECT_FADEOUT, EFFECT_HALF_FADEOUT, EFFECT_NONE,
5 EFFECT_QUARTER_FADEOUT, EFFECT_SLIDE, EFFECT_VIBRATO, INITIAL_NOISE_REG, OSCILLATOR_RESOLUTION,
6 TONE_TRIANGLE, VIBRATO_DEPTH, VIBRATO_FREQUENCY,
7};
8
9pub type Gain = f64;
10pub type ToneIndex = u16;
11pub type Effect = u16;
12
13const VIBRATO_PERIOD: u32 =
14 (CLOCK_RATE as f64 / VIBRATO_FREQUENCY / OSCILLATOR_RESOLUTION as f64) as u32;
15
16struct Slide {
17 pitch: f64,
18}
19
20struct Vibrato {
21 clock: u32,
22 phase: u32,
23}
24
25struct FadeOut {
26 duration: u32,
27 delta: Gain,
28}
29
30pub struct Oscillator {
31 pitch: f64,
32 tone: ToneIndex,
33 gain: Gain,
34 effect: Effect,
35 duration: u32,
36 clock: u32,
37 phase: u32,
38 amplitude: i16,
39 noise_reg: u16,
40 slide: Slide,
41 vibrato: Vibrato,
42 fadeout: FadeOut,
43}
44
45impl Oscillator {
46 pub fn new() -> Self {
47 Self {
48 pitch: Self::note_to_pitch(0.0),
49 tone: TONE_TRIANGLE,
50 gain: 0.0,
51 effect: EFFECT_NONE,
52 duration: 0,
53 clock: 0,
54 phase: 0,
55 amplitude: 0,
56 noise_reg: INITIAL_NOISE_REG,
57 slide: Slide { pitch: 0.0 },
58 vibrato: Vibrato { clock: 0, phase: 0 },
59 fadeout: FadeOut {
60 duration: 0,
61 delta: 0.0,
62 },
63 }
64 }
65
66 pub fn play(&mut self, note: f64, tone: ToneIndex, gain: Gain, effect: Effect, duration: u32) {
67 let last_pitch = self.pitch;
68 self.pitch = Self::note_to_pitch(note);
69 self.tone = tone;
70 self.gain = gain;
71 self.effect = effect;
72 self.duration = duration;
73
74 match effect {
75 EFFECT_NONE | EFFECT_VIBRATO => {}
76 EFFECT_SLIDE => {
77 self.slide.pitch = (self.pitch - last_pitch) / self.duration as f64;
78 self.pitch = last_pitch;
79 }
80 EFFECT_FADEOUT | EFFECT_HALF_FADEOUT | EFFECT_QUARTER_FADEOUT => {
81 self.fadeout.duration = duration;
82 if effect == EFFECT_HALF_FADEOUT {
83 self.fadeout.duration /= 2;
84 } else if effect == EFFECT_QUARTER_FADEOUT {
85 self.fadeout.duration /= 4;
86 }
87 self.fadeout.delta = -self.gain / self.fadeout.duration as f64;
88 }
89 _ => panic!("Invalid effect '{}'", self.effect),
90 }
91 }
92
93 pub fn stop(&mut self) {
94 self.duration = 0;
95 }
96
97 pub fn update(&mut self, blip_buf: &mut BlipBuf) {
98 if self.duration == 0 {
100 if self.amplitude != 0 {
101 let delta = if self.amplitude > 0 { -1 } else { 1 };
102 for i in 0..CLOCKS_PER_TICK {
103 self.amplitude += delta;
104 blip_buf.add_delta(i as u64, delta as i32);
105 if self.amplitude == 0 {
106 break;
107 }
108 }
109 }
110
111 self.clock = 0;
112 self.phase = 0;
113 self.vibrato.clock = 0;
114 self.vibrato.phase = 0;
115 return;
116 }
117
118 match self.effect {
120 EFFECT_SLIDE => {
121 self.pitch += self.slide.pitch;
122 }
123 EFFECT_VIBRATO => {
124 self.vibrato.clock += CLOCKS_PER_TICK;
125 self.vibrato.phase = (self.vibrato.phase + self.vibrato.clock / VIBRATO_PERIOD)
126 % OSCILLATOR_RESOLUTION;
127 self.vibrato.clock %= VIBRATO_PERIOD;
128 }
129 _ => {}
130 }
131
132 let pitch = self.pitch
134 + if self.effect == EFFECT_VIBRATO {
135 self.pitch
136 * (if self.vibrato.phase < OSCILLATOR_RESOLUTION / 2 {
137 self.vibrato.phase as f64 / (OSCILLATOR_RESOLUTION / 4) as f64 - 1.0
138 } else {
139 3.0 - self.vibrato.phase as f64 / (OSCILLATOR_RESOLUTION / 4) as f64
140 })
141 * VIBRATO_DEPTH
142 } else {
143 0.0
144 };
145
146 let period = (CLOCK_RATE as f64 / pitch / OSCILLATOR_RESOLUTION as f64) as u32;
147
148 let tones = TONES.lock();
149 let tone = tones[self.tone as usize].lock();
150
151 let fade_delta = if self.effect >= EFFECT_FADEOUT && self.duration <= self.fadeout.duration
152 {
153 self.fadeout.delta / ((CLOCKS_PER_TICK - self.clock) / period) as f64
154 } else {
155 0.0
156 };
157
158 while self.clock < CLOCKS_PER_TICK {
159 let last_amplitude = self.amplitude;
160 self.phase = (self.phase + 1) % OSCILLATOR_RESOLUTION;
161 self.amplitude = (tone.amplitude(self.phase, &mut self.noise_reg)
162 * self.gain
163 * i16::MAX as f64) as i16;
164 blip_buf.add_delta(
165 self.clock as u64,
166 self.amplitude as i32 - last_amplitude as i32,
167 );
168 self.clock += period;
169 self.gain += fade_delta;
170 }
171
172 self.duration -= 1;
173 if self.duration == 0 && self.effect >= EFFECT_FADEOUT {
174 self.clock = 0;
175 self.phase = 0;
176 self.vibrato.clock = 0;
177 self.vibrato.phase = 0;
178 } else {
179 self.clock -= CLOCKS_PER_TICK;
180 }
181 }
182
183 fn note_to_pitch(note: f64) -> f64 {
184 440.0 * ((note - 33.0) / 12.0).exp2()
185 }
186}