SN76489 PSG (programmable sound generator)
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 10#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 11pub enum Sn76489Version { 12 #[default] 13 MasterSystem2, 14 Standard, // Sega-customized version used in SMS, Game Gear, Genesis 15 Discrete, // Stock TI version used in SG-1000 16} 17 18#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 19enum WaveOutput { 20 Negative = 0, 21 Positive = 1, 22} 23 24impl WaveOutput { 25 fn invert(self) -> Self { 26 match self { 27 Self::Negative => Self::Positive, 28 Self::Positive => Self::Negative, 29 } 30 } 31} 32 33impl From<WaveOutput> for f64 { 34 fn from(value: WaveOutput) -> Self { 35 match value { 36 WaveOutput::Negative => 0.0, 37 WaveOutput::Positive => 1.0, 38 } 39 } 40} 41 42#[derive(Debug, Clone, Copy, Encode, Decode)] 43struct SquareWaveGenerator { 44 counter: u16, 45 current_output: WaveOutput, 46 tone: u16, 47 attenuation: u8, 48}
Each step up in attenuation decreases volume by 2dB, except for the step up to 15 which silences A delta of -2dB is equal to a multiplier of 10^(-1/10) ~= 0.7943
52const ATTENUATION_TO_VOLUME: [f64; 16] = [ 53 1.0, 54 0.7943282347242815, 55 0.6309573444801932, 56 0.5011872336272722, 57 0.3981071705534972, 58 0.3162277660168379, 59 0.25118864315095796, 60 0.19952623149688792, 61 0.15848931924611132, 62 0.1258925411794167, 63 0.09999999999999998, 64 0.07943282347242814, 65 0.06309573444801932, 66 0.05011872336272722, 67 0.03981071705534972, 68 0.0, 69];
The SMS2 clips the highest 3 volumes; 0.55 was chosen arbitrarily
72const SMS2_ATTENUATION_TO_VOLUME: [f64; 16] = [ 73 0.55, 74 0.55, 75 0.55, 76 0.5011872336272722, 77 0.3981071705534972, 78 0.3162277660168379, 79 0.25118864315095796, 80 0.19952623149688792, 81 0.15848931924611132, 82 0.1258925411794167, 83 0.09999999999999998, 84 0.07943282347242814, 85 0.06309573444801932, 86 0.05011872336272722, 87 0.03981071705534972, 88 0.0, 89];
91impl SquareWaveGenerator { 92 fn new() -> Self { 93 Self { counter: 1, current_output: WaveOutput::Negative, tone: 0, attenuation: 0x0F } 94 } 95 96 fn update_tone_low_bits(&mut self, data: u8) { 97 self.tone = (self.tone & 0xFFF0) | u16::from(data & 0x0F); 98 } 99 100 fn update_tone_high_bits(&mut self, data: u8) { 101 self.tone = (self.tone & 0x000F) | (u16::from(data & 0x3F) << 4); 102 } 103 104 fn clock(&mut self) { 105 self.counter -= 1; 106 if self.counter == 0 { 107 // Tone of 0 functions as 1, where the output inverts every PSG clock 108 self.counter = cmp::max(self.tone, 1); 109 self.current_output = self.current_output.invert(); 110 } 111 } 112 113 fn sample(self, volume_table: &[f64; 16]) -> f64 { 114 f64::from(self.current_output) * volume_table[self.attenuation as usize] 115 } 116} 117 118#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 119pub enum NoiseMode { 120 Periodic, 121 White, 122} 123 124#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 125pub enum NoiseReload { 126 Value(u16), 127 Tone2, 128} 129 130impl NoiseReload { 131 fn from_noise_register(value: u8) -> Self { 132 match value & 0x03 { 133 0x00 => Self::Value(0x10), 134 0x01 => Self::Value(0x20), 135 0x02 => Self::Value(0x40), 136 0x03 => Self::Tone2, 137 _ => unreachable!("value & 0x03 is always <= 0x03"), 138 } 139 } 140 141 fn value(self, tone2: u16) -> u16 { 142 match self { 143 Self::Value(value) => value, 144 Self::Tone2 => tone2, 145 } 146 } 147} 148 149#[derive(Debug, Clone, Encode, Decode)] 150struct NoiseGenerator { 151 counter: u16, 152 current_counter_output: WaveOutput, 153 counter_reload: NoiseReload, 154 lfsr: u16, 155 current_lfsr_output: WaveOutput, 156 noise_type: NoiseMode, 157 attenuation: u8, 158} 159 160const INITIAL_LFSR: u16 = 0x8000; 161 162impl NoiseGenerator { 163 fn new() -> Self { 164 Self { 165 counter: 0, 166 current_counter_output: WaveOutput::Negative, 167 counter_reload: NoiseReload::from_noise_register(0x00), 168 lfsr: INITIAL_LFSR, 169 current_lfsr_output: WaveOutput::Negative, 170 noise_type: NoiseMode::Periodic, 171 attenuation: 0x0F, 172 } 173 } 174 175 fn shift_lfsr(&mut self, version: Sn76489Version) { 176 self.current_lfsr_output = 177 if self.lfsr.bit(0) { WaveOutput::Positive } else { WaveOutput::Negative }; 178 179 let input_bit = match self.noise_type { 180 NoiseMode::Periodic => self.lfsr.bit(0), 181 NoiseMode::White => match version { 182 Sn76489Version::Standard | Sn76489Version::MasterSystem2 => { 183 self.lfsr.bit(0) ^ self.lfsr.bit(3) 184 } 185 Sn76489Version::Discrete => self.lfsr.bit(0) ^ self.lfsr.bit(2) ^ self.lfsr.bit(15), 186 }, 187 }; 188 189 self.lfsr = (self.lfsr >> 1) | (u16::from(input_bit) << 15); 190 } 191 192 fn write_data(&mut self, data: u8) { 193 self.counter_reload = NoiseReload::from_noise_register(data); 194 self.noise_type = if data.bit(2) { NoiseMode::White } else { NoiseMode::Periodic }; 195 196 self.lfsr = INITIAL_LFSR; 197 } 198 199 fn clock(&mut self, tone2: u16, version: Sn76489Version) { 200 self.counter = self.counter.saturating_sub(1); 201 if self.counter != 0 { 202 return; 203 } 204 205 self.counter = self.counter_reload.value(tone2); 206 self.current_counter_output = self.current_counter_output.invert(); 207 if self.current_counter_output == WaveOutput::Positive { 208 self.shift_lfsr(version); 209 } 210 } 211 212 fn sample(&self, volume_table: &[f64; 16]) -> f64 { 213 f64::from(self.current_lfsr_output) * volume_table[self.attenuation as usize] 214 } 215} 216 217#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 218enum Register { 219 Tone0, 220 Tone1, 221 Tone2, 222 Noise, 223 Volume0, 224 Volume1, 225 Volume2, 226 Volume3, 227} 228 229impl Register { 230 fn from_latch_byte(value: u8) -> Self { 231 match value & 0x70 { 232 0x00 => Self::Tone0, 233 0x10 => Self::Volume0, 234 0x20 => Self::Tone1, 235 0x30 => Self::Volume1, 236 0x40 => Self::Tone2, 237 0x50 => Self::Volume2, 238 0x60 => Self::Noise, 239 0x70 => Self::Volume3, 240 _ => unreachable!("value & 0x70 is always one of the above values"), 241 } 242 } 243} 244 245#[derive(Debug, Clone, Copy, PartialEq, Eq)] 246pub enum Sn76489TickEffect { 247 None, 248 Clocked, 249} 250 251#[derive(Debug, Clone, Copy, Encode, Decode)] 252struct StereoControl { 253 square_0_l: bool, 254 square_1_l: bool, 255 square_2_l: bool, 256 noise_l: bool, 257 square_0_r: bool, 258 square_1_r: bool, 259 square_2_r: bool, 260 noise_r: bool, 261} 262 263impl StereoControl { 264 fn write(&mut self, value: u8) { 265 self.square_0_r = value.bit(0); 266 self.square_1_r = value.bit(1); 267 self.square_2_r = value.bit(2); 268 self.noise_r = value.bit(3); 269 self.square_0_l = value.bit(4); 270 self.square_1_l = value.bit(5); 271 self.square_2_l = value.bit(6); 272 self.noise_l = value.bit(7); 273 } 274} 275 276impl Default for StereoControl { 277 fn default() -> Self { 278 Self { 279 square_0_l: true, 280 square_1_l: true, 281 square_2_l: true, 282 noise_l: true, 283 square_0_r: true, 284 square_1_r: true, 285 square_2_r: true, 286 noise_r: true, 287 } 288 } 289} 290 291#[derive(Debug, Clone, Encode, Decode)] 292pub struct Sn76489 { 293 version: Sn76489Version, 294 square_wave_channels: [SquareWaveGenerator; 3], 295 noise_channel: NoiseGenerator, 296 latched_register: Register, 297 stereo_control: StereoControl, 298 divider: u8, 299} 300 301const SN76489_DIVIDER: u8 = 16; 302 303impl Sn76489 { 304 #[must_use] 305 pub fn new(version: Sn76489Version) -> Self { 306 Self { 307 version, 308 square_wave_channels: array::from_fn(|_| SquareWaveGenerator::new()), 309 noise_channel: NoiseGenerator::new(), 310 latched_register: Register::Tone0, 311 stereo_control: StereoControl::default(), 312 divider: SN76489_DIVIDER, 313 } 314 } 315 316 fn write_register_low_bits(&mut self, data: u8) { 317 match self.latched_register { 318 Register::Tone0 => { 319 self.square_wave_channels[0].update_tone_low_bits(data); 320 } 321 Register::Tone1 => { 322 self.square_wave_channels[1].update_tone_low_bits(data); 323 } 324 Register::Tone2 => { 325 self.square_wave_channels[2].update_tone_low_bits(data); 326 } 327 Register::Noise => { 328 self.noise_channel.write_data(data); 329 } 330 Register::Volume0 => { 331 self.square_wave_channels[0].attenuation = data & 0x0F; 332 } 333 Register::Volume1 => { 334 self.square_wave_channels[1].attenuation = data & 0x0F; 335 } 336 Register::Volume2 => { 337 self.square_wave_channels[2].attenuation = data & 0x0F; 338 } 339 Register::Volume3 => { 340 self.noise_channel.attenuation = data & 0x0F; 341 } 342 } 343 } 344 345 fn write_register_high_bits(&mut self, data: u8) { 346 match self.latched_register { 347 Register::Tone0 => { 348 self.square_wave_channels[0].update_tone_high_bits(data); 349 } 350 Register::Tone1 => { 351 self.square_wave_channels[1].update_tone_high_bits(data); 352 } 353 Register::Tone2 => { 354 self.square_wave_channels[2].update_tone_high_bits(data); 355 } 356 _ => { 357 self.write_register_low_bits(data); 358 } 359 } 360 } 361 362 pub fn write(&mut self, value: u8) { 363 if value.bit(7) { 364 // LATCH/DATA byte 365 self.latched_register = Register::from_latch_byte(value); 366 self.write_register_low_bits(value); 367 } else { 368 // DATA byte 369 self.write_register_high_bits(value); 370 } 371 } 372 373 pub fn write_stereo_control(&mut self, value: u8) { 374 self.stereo_control.write(value); 375 } 376 377 #[inline] 378 pub fn tick(&mut self) -> Sn76489TickEffect { 379 self.divider -= 1; 380 if self.divider == 0 { 381 self.divider = SN76489_DIVIDER; 382 383 for channel in &mut self.square_wave_channels { 384 channel.clock(); 385 } 386 self.noise_channel.clock(self.square_wave_channels[2].tone, self.version); 387 388 Sn76489TickEffect::Clocked 389 } else { 390 Sn76489TickEffect::None 391 } 392 } 393 394 #[must_use] 395 pub fn version(&self) -> Sn76489Version { 396 self.version 397 } 398 399 pub fn set_version(&mut self, version: Sn76489Version) { 400 self.version = version; 401 } 402 403 #[must_use] 404 pub fn sample(&self) -> (f64, f64) { 405 // TODO rewrite to use integer arithmetic as much as possible 406 let volume_table = match self.version { 407 Sn76489Version::MasterSystem2 => &SMS2_ATTENUATION_TO_VOLUME, 408 Sn76489Version::Standard | Sn76489Version::Discrete => &ATTENUATION_TO_VOLUME, 409 }; 410 411 let square_samples = self.square_wave_channels.map(|channel| channel.sample(volume_table)); 412 let noise_sample = self.noise_channel.sample(volume_table); 413 414 let sample_l = (f64::from(self.stereo_control.square_0_l) * square_samples[0] 415 + f64::from(self.stereo_control.square_1_l) * square_samples[1] 416 + f64::from(self.stereo_control.square_2_l) * square_samples[2] 417 + f64::from(self.stereo_control.noise_l) * noise_sample) 418 / 4.0; 419 let sample_r = (f64::from(self.stereo_control.square_0_r) * square_samples[0] 420 + f64::from(self.stereo_control.square_1_r) * square_samples[1] 421 + f64::from(self.stereo_control.square_2_r) * square_samples[2] 422 + f64::from(self.stereo_control.noise_r) * noise_sample) 423 / 4.0; 424 425 (sample_l, sample_r) 426 } 427 428 #[must_use] 429 pub fn tone_frequencies(&self) -> [u16; 3] { 430 self.square_wave_channels.map(|channel| channel.tone) 431 } 432 433 #[must_use] 434 pub fn tone_attenuations(&self) -> [u8; 3] { 435 self.square_wave_channels.map(|channel| channel.attenuation) 436 } 437 438 #[must_use] 439 pub fn noise_mode(&self) -> NoiseMode { 440 self.noise_channel.noise_type 441 } 442 443 #[must_use] 444 pub fn noise_reload(&self) -> NoiseReload { 445 self.noise_channel.counter_reload 446 } 447 448 #[must_use] 449 pub fn noise_attenuation(&self) -> u8 { 450 self.noise_channel.attenuation 451 } 452}