YM2612 envelope generator
Envelope updates every third cycle at 53627 Hz
9const ENVELOPE_DIVIDER: u8 = 3;
11const SSG_ATTENUATION_THRESHOLD: u16 = 0x200;
Attenuation is 10 bits
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}