YM2612 envelope generator

3use crate::ym2612::phase::PhaseGenerator;
4use bincode::{Decode, Encode};
5use jgenesis_common::num::GetBit;
6use std::cmp;

Envelope updates every third cycle at 53627 Hz

9const ENVELOPE_DIVIDER: u8 = 3;
11const SSG_ATTENUATION_THRESHOLD: u16 = 0x200;

Attenuation is 10 bits

14pub(super) const ATTENUATION_MASK: u16 = 0x03FF;
15pub(super) const MAX_ATTENUATION: u16 = ATTENUATION_MASK;

From http://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386&start=105

18#[rustfmt::skip]
19const ATTENUATION_INCREMENTS: &[[u8; 8]; 64] = &[
20    [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,1,0,1,0,1,0,1], [0,1,0,1,0,1,0,1],  // 0-3
21    [0,1,0,1,0,1,0,1], [0,1,0,1,0,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,0,1,1,1],  // 4-7
22    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 8-11
23    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 12-15
24    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 16-19
25    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 20-23
26    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 24-27
27    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 28-31
28    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 32-35
29    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 36-39
30    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 40-43
31    [0,1,0,1,0,1,0,1], [0,1,0,1,1,1,0,1], [0,1,1,1,0,1,1,1], [0,1,1,1,1,1,1,1],  // 44-47
32    [1,1,1,1,1,1,1,1], [1,1,1,2,1,1,1,2], [1,2,1,2,1,2,1,2], [1,2,2,2,1,2,2,2],  // 48-51
33    [2,2,2,2,2,2,2,2], [2,2,2,4,2,2,2,4], [2,4,2,4,2,4,2,4], [2,4,4,4,2,4,4,4],  // 52-55
34    [4,4,4,4,4,4,4,4], [4,4,4,8,4,4,4,8], [4,8,4,8,4,8,4,8], [4,8,8,8,4,8,8,8],  // 56-59
35    [8,8,8,8,8,8,8,8], [8,8,8,8,8,8,8,8], [8,8,8,8,8,8,8,8], [8,8,8,8,8,8,8,8],  // 60-63
36];
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
39enum EnvelopePhase {
40    Attack,
41    Decay,
42    Sustain,
43    Release,
44}
45
46#[derive(Debug, Clone, Encode, Decode)]
47pub(super) struct EnvelopeGenerator {
48    // Register values
49    pub(super) attack_rate: u8,
50    pub(super) decay_rate: u8,
51    pub(super) sustain_rate: u8,
52    pub(super) release_rate: u8,
53    pub(super) total_level: u8,
54    pub(super) sustain_level: u8,
55    pub(super) key_scale: u8,
56    // Internal state
57    phase: EnvelopePhase,
58    attenuation: u16,
59    pub(super) key_scale_rate: u8,
60    cycle_count: u16,
61    divider: u8,
62    pub(super) ssg_enabled: bool,
63    pub(super) ssg_attack: bool,
64    pub(super) ssg_alternate: bool,
65    pub(super) ssg_hold: bool,
66    ssg_invert_output: bool,
67}
68
69impl EnvelopeGenerator {
70    pub(super) fn new() -> Self {
71        Self {
72            attack_rate: 0,
73            decay_rate: 0,
74            sustain_rate: 0,
75            release_rate: 0,
76            total_level: 0,
77            sustain_level: 0,
78            key_scale: 0,
79            phase: EnvelopePhase::Release,
80            attenuation: MAX_ATTENUATION,
81            key_scale_rate: 0,
82            cycle_count: 1,
83            divider: ENVELOPE_DIVIDER,
84            ssg_enabled: false,
85            ssg_attack: false,
86            ssg_alternate: false,
87            ssg_hold: false,
88            ssg_invert_output: false,
89        }
90    }
91
92    #[inline]
93    pub(super) fn clock(&mut self, phase_generator: &mut PhaseGenerator) {
94        // SSG updates on every clock if enabled
95        if self.ssg_enabled {
96            self.ssg_clock(phase_generator);
97        }
98
99        self.divider -= 1;
100        if self.divider == 0 {
101            self.divider = ENVELOPE_DIVIDER;
102            self.envelope_clock();
103        }
104    }
105
106    #[inline]
107    fn envelope_clock(&mut self) {
108        // It's been verified that actual hardware's envelope cycle counter is 12-bit and skips 0 on overflow
109        self.cycle_count += 1;
110        self.cycle_count = (self.cycle_count & 0xFFF) + (self.cycle_count >> 12);
111
112        // Sustain level applies in increments of 32, with max level special cased to be
113        // the max multiple of 32
114        let sustain_level = match self.sustain_level {
115            15 => (MAX_ATTENUATION >> 5) << 5,
116            sl => u16::from(sl) << 5,
117        };
118
119        // Progress past attack phase if attenuation is at 0
120        if self.phase == EnvelopePhase::Attack && self.attenuation == 0 {
121            self.phase = EnvelopePhase::Decay;
122        }
123
124        // Progress past decay phase if attenuation is at or past sustain level
125        if self.phase == EnvelopePhase::Decay && self.attenuation >= sustain_level {
126            self.phase = EnvelopePhase::Sustain;
127        }
128
129        let r = match self.phase {
130            EnvelopePhase::Attack => self.attack_rate,
131            EnvelopePhase::Decay => self.decay_rate,
132            EnvelopePhase::Sustain => self.sustain_rate,
133            EnvelopePhase::Release => (self.release_rate << 1) | 1,
134        };
135
136        let rate = if r == 0 { 0 } else { cmp::min(63, 2 * r + self.key_scale_rate) };
137
138        let update_frequency_shift = 11_u8.saturating_sub(rate >> 2);
139        if self.cycle_count & ((1 << update_frequency_shift) - 1) == 0 {
140            let increment_idx = (self.cycle_count >> update_frequency_shift) & 7;
141            let increment: u16 =
142                ATTENUATION_INCREMENTS[rate as usize][increment_idx as usize].into();
143
144            match self.phase {
145                EnvelopePhase::Attack => {
146                    // Rates of 62 and 63 do nothing during attack phase; during key on they skip
147                    // this phase entirely
148                    if rate <= 61 {
149                        // Formula from http://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386&start=405
150                        self.attenuation = self
151                            .attenuation
152                            .wrapping_add((!self.attenuation).wrapping_mul(increment) >> 4)
153                            & ATTENUATION_MASK;
154                    }
155                }
156                EnvelopePhase::Decay | EnvelopePhase::Sustain | EnvelopePhase::Release => {
157                    if self.ssg_enabled {
158                        // Attenuation increments 4x as fast in SSG-EG mode, but only if current
159                        // attenuation is below 0x200
160                        if self.attenuation < SSG_ATTENUATION_THRESHOLD {
161                            self.attenuation =
162                                cmp::min(MAX_ATTENUATION, self.attenuation + 4 * increment);
163                        }
164                    } else {
165                        self.attenuation = cmp::min(MAX_ATTENUATION, self.attenuation + increment);
166                    }
167                }
168            }
169        }
170    }
171
172    #[inline]
173    fn ssg_clock(&mut self, phase_generator: &mut PhaseGenerator) {
174        // SSG-EG implementation pretty much entirely based on this:
175        // https://gendev.spritesmind.net/forum/viewtopic.php?t=386&start=405
176
177        // SSG-EG updates only apply when attenuation is greater than or equal to 0x200
178        if self.attenuation < SSG_ATTENUATION_THRESHOLD {
179            return;
180        }
181
182        // Alternate flag causes the output to invert after each attack-decay-sustain pass
183        if self.ssg_alternate {
184            if self.ssg_hold {
185                // If holding, the output is always inverted once the hold begins
186                self.ssg_invert_output = true;
187            } else {
188                // If not holding, flip the invert output flag
189                self.ssg_invert_output = !self.ssg_invert_output;
190            }
191        }
192
193        if !self.ssg_alternate && !self.ssg_hold {
194            // When not alternating and not holding, the phase counter is held at 0 until
195            // attenuation drops below 0x200
196            phase_generator.reset();
197        }
198
199        if matches!(self.phase, EnvelopePhase::Decay | EnvelopePhase::Sustain) && !self.ssg_hold {
200            // If in decay or sustain phase and not holding, start a new attack-decay-sustain pass
201            if 2 * self.attack_rate + self.key_scale_rate >= 62 {
202                // Skip attack phase
203                self.attenuation = 0;
204                self.phase = EnvelopePhase::Decay;
205            } else {
206                self.phase = EnvelopePhase::Attack;
207            }
208        } else if self.phase == EnvelopePhase::Release
209            || (self.phase != EnvelopePhase::Attack && self.ssg_invert_output == self.ssg_attack)
210        {
211            // If in release phase _or_ in decay/sustain phase with invert output flag matching attack flag,
212            // force attenuation to max
213            self.attenuation = MAX_ATTENUATION;
214        }
215    }
216
217    pub(super) fn is_key_on(&self) -> bool {
218        self.phase != EnvelopePhase::Release
219    }
220
221    pub(super) fn key_on(&mut self) {
222        if self.is_key_on() {
223            // Key is already down
224            return;
225        }
226
227        let rate = 2 * self.attack_rate + self.key_scale_rate;
228
229        // Rates of 62 and 63 skip attack phase
230        if rate >= 62 {
231            self.phase = EnvelopePhase::Decay;
232            self.attenuation = 0;
233        } else {
234            self.phase = EnvelopePhase::Attack;
235        }
236
237        self.ssg_invert_output = false;
238
239        log::trace!("State at key on: {self:?}");
240    }
241
242    pub(super) fn key_off(&mut self) {
243        if self.ssg_enabled
244            && self.phase != EnvelopePhase::Release
245            && self.ssg_invert_output != self.ssg_attack
246        {
247            // If SSG-EG is on and output is inverted, keying off applies the inversion to stored
248            // attenuation
249            self.attenuation =
250                SSG_ATTENUATION_THRESHOLD.wrapping_sub(self.attenuation) & ATTENUATION_MASK;
251        }
252
253        self.phase = EnvelopePhase::Release;
254    }
255
256    pub(super) fn update_key_scale_rate(&mut self, f_number: u16, block: u8) {
257        let key_code = super::compute_key_code(f_number, block);
258        self.key_scale_rate = key_code >> (3 - self.key_scale);
259    }
260
261    pub(super) fn current_attenuation(&self) -> u16 {
262        let attenuation = if self.ssg_enabled
263            && self.phase != EnvelopePhase::Release
264            && self.ssg_invert_output != self.ssg_attack
265        {
266            // Apply SSG output inversion, which centers around 0x200
267            SSG_ATTENUATION_THRESHOLD.wrapping_sub(self.attenuation) & ATTENUATION_MASK
268        } else {
269            self.attenuation
270        };
271
272        let total_level = u16::from(self.total_level) << 3;
273        cmp::min(MAX_ATTENUATION, attenuation + total_level)
274    }
275
276    pub(super) fn write_ssg_register(&mut self, value: u8) {
277        self.ssg_enabled = value.bit(3);
278        self.ssg_attack = value.bit(2);
279        self.ssg_alternate = value.bit(1);
280        self.ssg_hold = value.bit(0);
281
282        log::trace!(
283            "SSG-EG register write; enabled={}, attack={}, alternate={}, hold={}",
284            self.ssg_enabled,
285            self.ssg_attack,
286            self.ssg_alternate,
287            self.ssg_hold
288        );
289    }
290}
291
292impl Default for EnvelopeGenerator {
293    fn default() -> Self {
294        Self::new()
295    }
296}