1 module gbaid.gba.sound; 2 3 import std.meta : AliasSeq; 4 5 import gbaid.gba.io; 6 import gbaid.gba.dma; 7 8 import gbaid.util; 9 10 public alias AudioReceiver = void delegate(short[]); 11 12 private enum uint SYSTEM_CLOCK_FREQUENCY = 2 ^^ 24; 13 private enum uint PSG_FREQUENCY = 2 ^^ 18; 14 public enum uint SOUND_OUTPUT_FREQUENCY = 2 ^^ 16; 15 private enum size_t CYCLES_PER_PSG_SAMPLE = SYSTEM_CLOCK_FREQUENCY / PSG_FREQUENCY; 16 private enum size_t PSG_PER_AUDIO_SAMPLE = PSG_FREQUENCY / SOUND_OUTPUT_FREQUENCY; 17 public enum size_t CYCLES_PER_AUDIO_SAMPLE = SYSTEM_CLOCK_FREQUENCY / SOUND_OUTPUT_FREQUENCY; 18 private enum int OUTPUT_AMPLITUDE_RESCALE = (short.max + 1) / 2 ^^ 9; 19 20 public class SoundChip { 21 private static enum uint SAMPLE_BATCH_SIZE = 256 * 2; 22 private IoRegisters* ioRegisters; 23 private AudioReceiver _receiver = null; 24 private SquareWaveGenerator!true tone1; 25 private SquareWaveGenerator!false tone2; 26 private PatternWaveGenerator wave; 27 private NoiseGenerator noise; 28 private DirectSound!'A' directA; 29 private DirectSound!'B' directB; 30 private bool masterEnable = false; 31 private int psgRightVolume = 0; 32 private int psgLeftVolume = 0; 33 private int psgRightEnableFlags = 0; 34 private int psgLeftEnableFlags = 0; 35 private int psgGlobalVolume = 0; 36 private int directAVolume = 0; 37 private int directBVolume = 0; 38 private int directAEnableFlags = 0; 39 private int directBEnableFlags = 0; 40 private int biasLevel = 0x200; 41 private int amplitudeResolution = 0; 42 private int psgRightReSample = 0; 43 private int psgLeftReSample = 0; 44 private uint psgCount = 0; 45 private short[SAMPLE_BATCH_SIZE] sampleBatch; 46 private uint sampleBatchIndex = 0; 47 48 public this(IoRegisters* ioRegisters, DMAs dmas) { 49 this.ioRegisters = ioRegisters; 50 directA = DirectSound!'A'(dmas); 51 directB = DirectSound!'B'(dmas); 52 53 // These registers are mapped regardless of the master enable flag 54 // Final mixing and direct sound control register 55 ioRegisters.mapAddress(0x80, &psgGlobalVolume, 0b11, 16); 56 ioRegisters.mapAddress(0x80, &directAVolume, 0b1, 18); 57 ioRegisters.mapAddress(0x80, &directBVolume, 0b1, 19); 58 ioRegisters.mapAddress(0x80, &directAEnableFlags, 0b11, 24); 59 ioRegisters.mapAddress(0x80, &directA.timerIndex, 0b1, 26); 60 ioRegisters.mapAddress(0x80, null, 0b1, 27, false, true).preWriteMonitor(&onDirectSoundClearPreWrite!'A'); 61 ioRegisters.mapAddress(0x80, &directBEnableFlags, 0b11, 28); 62 ioRegisters.mapAddress(0x80, &directB.timerIndex, 0b1, 30); 63 ioRegisters.mapAddress(0x80, null, 0b1, 31, false, true).preWriteMonitor(&onDirectSoundClearPreWrite!'B'); 64 // Sound status register 65 ioRegisters.mapAddress(0x84, &tone1.playbackEnabled, 0b1, 0, true, false); 66 ioRegisters.mapAddress(0x84, &tone2.playbackEnabled, 0b1, 1, true, false); 67 ioRegisters.mapAddress(0x84, &wave.playbackEnabled, 0b1, 2, true, false); 68 ioRegisters.mapAddress(0x84, &noise.playbackEnabled, 0b1, 3, true, false); 69 ioRegisters.mapAddress(0x84, &masterEnable, 0b1, 7).postWriteMonitor(&onMasterEnablePostWrite); 70 // Digital to analog converter control register 71 ioRegisters.mapAddress(0x88, &biasLevel, 0x3FF, 0); 72 ioRegisters.mapAddress(0x88, &litudeResolution, 0b11, 14); 73 } 74 75 private void mapChannelRegisters() { 76 // The registers for the sound channels are only mapped when the sound is enabled 77 // First tone channel control register 78 ioRegisters.mapAddress(0x60, &tone1.sweepShift, 0b111, 0); 79 ioRegisters.mapAddress(0x60, &tone1.decreasingShift, 0b1, 3); 80 ioRegisters.mapAddress(0x60, &tone1.sweepStep, 0b111, 4); 81 ioRegisters.mapAddress(0x60, &tone1.duration, 0x3F, 16, false, true); 82 ioRegisters.mapAddress(0x60, &tone1.duty, 0b11, 22); 83 ioRegisters.mapAddress(0x60, &tone1.envelopeStep, 0b111, 24); 84 ioRegisters.mapAddress(0x60, &tone1.increasingEnvelope, 0b1, 27); 85 ioRegisters.mapAddress(0x60, &tone1.initialVolume, 0b1111, 28); 86 ioRegisters.mapAddress(0x64, &tone1.rate, 0x7FF, 0, false, true); 87 ioRegisters.mapAddress(0x64, &tone1.useDuration, 0b1, 14); 88 ioRegisters.mapAddress(0x64, null, 0b1, 15, false, true).preWriteMonitor(&onToneEnablePreWrite!1); 89 // Second tone channel control register 90 ioRegisters.mapAddress(0x68, &tone2.duration, 0x3F, 0, false, true); 91 ioRegisters.mapAddress(0x68, &tone2.duty, 0b11, 6); 92 ioRegisters.mapAddress(0x68, &tone2.envelopeStep, 0b111, 8); 93 ioRegisters.mapAddress(0x68, &tone2.increasingEnvelope, 0b1, 11); 94 ioRegisters.mapAddress(0x68, &tone2.initialVolume, 0b1111, 12); 95 ioRegisters.mapAddress(0x6C, &tone2.rate, 0x7FF, 0, false, true); 96 ioRegisters.mapAddress(0x6C, &tone2.useDuration, 0b1, 14); 97 ioRegisters.mapAddress(0x6C, null, 0b1, 15, false, true).preWriteMonitor(&onToneEnablePreWrite!2); 98 // Wave channel control register 99 ioRegisters.mapAddress(0x70, &wave.combinePatterns, 0b1, 5); 100 ioRegisters.mapAddress(0x70, &wave.selectedPattern, 0b1, 6); 101 ioRegisters.mapAddress(0x70, &wave.channelEnabled, 0b1, 7); 102 ioRegisters.mapAddress(0x70, &wave.duration, 0xFF, 16, false, true); 103 ioRegisters.mapAddress(0x70, &wave.volume, 0b111, 29); 104 ioRegisters.mapAddress(0x74, &wave.rate, 0x7FF, 0, false, true); 105 ioRegisters.mapAddress(0x74, &wave.useDuration, 0b1, 14); 106 ioRegisters.mapAddress(0x74, null, 0b1, 15, false, true).preWriteMonitor(&onWaveEnablePreWrite); 107 // Noise channel control register 108 ioRegisters.mapAddress(0x78, &noise.duration, 0x3F, 0, false, true); 109 ioRegisters.mapAddress(0x78, &noise.envelopeStep, 0b111, 8); 110 ioRegisters.mapAddress(0x78, &noise.increasingEnvelope, 0b1, 11); 111 ioRegisters.mapAddress(0x78, &noise.initialVolume, 0b1111, 12); 112 ioRegisters.mapAddress(0x7C, &noise.divider, 0b111, 0); 113 ioRegisters.mapAddress(0x7C, &noise.use7Bits, 0b1, 3); 114 ioRegisters.mapAddress(0x7C, &noise.preScaler, 0b1111, 4); 115 ioRegisters.mapAddress(0x7C, &noise.useDuration, 0b1, 14); 116 ioRegisters.mapAddress(0x7C, null, 0b1, 15, false, true).preWriteMonitor(&onNoiseEnablePreWrite); 117 // PSG sound mixing register 118 ioRegisters.mapAddress(0x80, &psgRightVolume, 0b111, 0); 119 ioRegisters.mapAddress(0x80, &psgLeftVolume, 0b111, 4); 120 ioRegisters.mapAddress(0x80, &psgRightEnableFlags, 0b1111, 8); 121 ioRegisters.mapAddress(0x80, &psgLeftEnableFlags, 0b1111, 12); 122 // Wave pattern data registers 123 foreach (i; AliasSeq!(0, 1, 2, 3)) { 124 ioRegisters.mapAddress(0x90 + i * 4, null, 0xFFFFFFFF, 0) 125 .readMonitor(&onWavePatternRead!i) 126 .preWriteMonitor(&onWavePatternPreWrite!i); 127 } 128 // Direct sound queue registers 129 ioRegisters.mapAddress(0xA0, null, 0xFFFFFFFF, 0).preWriteMonitor(&onDirectSoundQueuePreWrite!'A'); 130 ioRegisters.mapAddress(0xA4, null, 0xFFFFFFFF, 0).preWriteMonitor(&onDirectSoundQueuePreWrite!'B'); 131 } 132 133 private void unmapChannelRegisters() { 134 // Channel control registers 135 for (uint address = 0x60; address < 0x80; address += 4) { 136 ioRegisters.unmapAddress(address, 0xFFFFFFFF, 0); 137 } 138 // PSG sound mixing register 139 ioRegisters.unmapAddress(0x80, 0xFFFF, 0); 140 // Wave pattern data and direct sound queue registers 141 for (uint address = 0x90; address < 0xA8; address += 4) { 142 ioRegisters.unmapAddress(address, 0xFFFFFFFF, 0); 143 } 144 } 145 146 @property public void receiver(AudioReceiver receiver) { 147 _receiver = receiver; 148 } 149 150 public void addTimerOverflows(int timer)(size_t overflows) { 151 directA.addTimerOverflows!timer(overflows); 152 directB.addTimerOverflows!timer(overflows); 153 } 154 155 public size_t emulate(size_t cycles) { 156 if (_receiver is null) { 157 return 0; 158 } 159 // Update the direct sound channels 160 directA.updateSample(); 161 directB.updateSample(); 162 // Compute the PSG cycles 163 while (cycles >= CYCLES_PER_PSG_SAMPLE) { 164 cycles -= CYCLES_PER_PSG_SAMPLE; 165 // Accumulate the PSG channel value for the left and right samples 166 int rightPsgSample = 0; 167 int leftPsgSample = 0; 168 // Add the tone 1 channel if enabled 169 auto tone1Sample = tone1.nextSample(); 170 if (psgRightEnableFlags & 0b1) { 171 rightPsgSample += tone1Sample; 172 } 173 if (psgLeftEnableFlags & 0b1) { 174 leftPsgSample += tone1Sample; 175 } 176 // Add the tone 2 channel if enabled 177 auto tone2Sample = tone2.nextSample(); 178 if (psgRightEnableFlags & 0b10) { 179 rightPsgSample += tone2Sample; 180 } 181 if (psgLeftEnableFlags & 0b10) { 182 leftPsgSample += tone2Sample; 183 } 184 // Add the wave channel if enabled 185 auto waveSample = wave.nextSample(); 186 if (psgRightEnableFlags & 0b100) { 187 rightPsgSample += waveSample; 188 } 189 if (psgLeftEnableFlags & 0b100) { 190 leftPsgSample += waveSample; 191 } 192 // Add the noise channel if enabled 193 auto noiseSample = noise.nextSample(); 194 if (psgRightEnableFlags & 0b1000) { 195 rightPsgSample += noiseSample; 196 } 197 if (psgLeftEnableFlags & 0b1000) { 198 leftPsgSample += noiseSample; 199 } 200 // Apply the final volume adjustements and accumulate the samples 201 psgRightReSample += rightPsgSample * psgRightVolume >> 2 - psgGlobalVolume; 202 psgLeftReSample += leftPsgSample * psgLeftVolume >> 2 - psgGlobalVolume; 203 psgCount += 1; 204 // Check if we have accumulated all the PSG samples for a single output sample 205 if (psgCount == PSG_PER_AUDIO_SAMPLE) { 206 // Average out the PSG samples to start forming the final output sample 207 auto outputRight = psgRightReSample / cast(int) PSG_PER_AUDIO_SAMPLE; 208 auto outputLeft = psgLeftReSample / cast(int) PSG_PER_AUDIO_SAMPLE; 209 // Now get the samples for the direct sound 210 auto directASample = directA.nextSample() >> 1 - directAVolume; 211 if (directAEnableFlags & 0b1) { 212 outputRight += directASample; 213 } 214 if (directAEnableFlags & 0b10) { 215 outputLeft += directASample; 216 } 217 auto directBSample = directB.nextSample() >> 1 - directBVolume; 218 if (directBEnableFlags & 0b1) { 219 outputRight += directBSample; 220 } 221 if (directBEnableFlags & 0b10) { 222 outputLeft += directBSample; 223 } 224 // Copy the final left and right values of the sample to the output batch buffer 225 sampleBatch[sampleBatchIndex++] = cast(short) (outputLeft * OUTPUT_AMPLITUDE_RESCALE); 226 sampleBatch[sampleBatchIndex++] = cast(short) (outputRight * OUTPUT_AMPLITUDE_RESCALE); 227 // If our output batch buffer is full, then send it to the audio receiver 228 if (sampleBatchIndex >= SAMPLE_BATCH_SIZE) { 229 _receiver(sampleBatch); 230 sampleBatchIndex = 0; 231 } 232 // Finally we can reset the accumulator 233 psgRightReSample = 0; 234 psgLeftReSample = 0; 235 psgCount = 0; 236 } 237 } 238 return cycles; 239 } 240 241 private bool onToneEnablePreWrite(int channel)(int mask, ref int enable) { 242 if (enable) { 243 static if (channel == 1) { 244 tone1.restart(); 245 } else { 246 tone2.restart(); 247 } 248 } 249 return false; 250 } 251 252 private bool onWaveEnablePreWrite(int mask, ref int enable) { 253 if (enable) { 254 wave.restart(); 255 } 256 return false; 257 } 258 259 private void onWavePatternRead(int index)(int mask, ref int pattern) { 260 pattern = *wave.patternData!index; 261 } 262 263 private bool onWavePatternPreWrite(int index)(int mask, ref int pattern) { 264 *wave.patternData!index = *wave.patternData!index & ~mask | pattern; 265 return false; 266 } 267 268 private bool onNoiseEnablePreWrite(int mask, ref int enable) { 269 if (enable) { 270 noise.restart(); 271 } 272 return false; 273 } 274 275 private bool onDirectSoundQueuePreWrite(char channel)(int mask, ref int newData) { 276 alias Direct = DirectSound!channel; 277 static if (channel == 'A') { 278 alias direct = directA; 279 } else static if (channel == 'B') { 280 alias direct = directB; 281 } 282 // Write the bytes to the sound queue 283 foreach (i; 0 .. int.sizeof) { 284 // Only write the new bytes to the queue 285 if (mask & 0xFF) { 286 // Flush out a sample if the queue is full 287 if (direct.queueSize >= Direct.QUEUE_BYTE_SIZE) { 288 direct.queueIndex += 1; 289 direct.queueSize--; 290 } 291 // Write the byte at the next index and increment the size 292 direct.queue[(direct.queueIndex + direct.queueSize) % Direct.QUEUE_BYTE_SIZE] = cast(byte) newData; 293 direct.queueSize += 1; 294 } 295 // Shift to the next byte (from least to most significant) 296 mask >>>= 8; 297 newData >>>= 8; 298 } 299 return false; 300 } 301 302 private bool onDirectSoundClearPreWrite(char channel)(int mask, ref int clear) { 303 if (clear) { 304 static if (channel == 'A') { 305 directA.clearQueue(); 306 } else { 307 directB.clearQueue(); 308 } 309 } 310 return false; 311 } 312 313 private void onMasterEnablePostWrite(int mask, int oldMasterEnable, int newMasterEnable) { 314 directA.channelEnabled = cast(bool) newMasterEnable; 315 directB.channelEnabled = cast(bool) newMasterEnable; 316 if (oldMasterEnable && !newMasterEnable) { 317 // On disable: clear the PSG sound channels and unmap the sound channel registers 318 tone1 = SquareWaveGenerator!true.init; 319 tone2 = SquareWaveGenerator!false.init; 320 wave = PatternWaveGenerator.init; 321 noise = NoiseGenerator.init; 322 psgRightVolume = 0; 323 psgLeftVolume = 0; 324 psgRightEnableFlags = 0; 325 psgLeftEnableFlags = 0; 326 unmapChannelRegisters(); 327 } else if (!oldMasterEnable && newMasterEnable) { 328 // On enable: map the sound channel registers 329 mapChannelRegisters(); 330 } 331 } 332 } 333 334 private struct SquareWaveGenerator(bool sweep) { 335 private static enum size_t SQUARE_WAVE_FREQUENCY = 2 ^^ 17; 336 private bool playbackEnabled = false; 337 private int rate = 0; 338 private int duty = 0; 339 private int envelopeStep = 0; 340 private bool increasingEnvelope = false; 341 private int initialVolume = 0; 342 static if (sweep) { 343 private int sweepShift = 0; 344 private bool decreasingShift = false; 345 private int sweepStep = 0; 346 } 347 private int duration = 0; 348 private bool useDuration = false; 349 private size_t tDuration = 0; 350 private size_t tPeriod = 0; 351 private int envelope = 0; 352 353 private void restart() { 354 playbackEnabled = true; 355 tDuration = 0; 356 tPeriod = 0; 357 envelope = initialVolume; 358 } 359 360 private int nextSample() { 361 // Don't play if disabled 362 if (!playbackEnabled) { 363 return 0; 364 } 365 // Find the period and the edge (in ticks) 366 auto period = (2048 - rate) * (PSG_FREQUENCY / SQUARE_WAVE_FREQUENCY); 367 size_t edge = void; 368 final switch (duty) { 369 case 0: 370 edge = period / 8; 371 break; 372 case 1: 373 edge = period / 4; 374 break; 375 case 2: 376 edge = period / 2; 377 break; 378 case 3: 379 edge = 3 * (period / 4); 380 break; 381 } 382 // Generate the sample 383 auto sample = tPeriod >= edge ? -envelope : envelope; 384 // Increment the period time value 385 tPeriod += 1; 386 if (tPeriod >= period) { 387 tPeriod = 0; 388 } 389 // Disable for the next sample if the duration expired 390 tDuration += 1; 391 if (useDuration && tDuration >= (64 - duration) * (PSG_FREQUENCY / 256)) { 392 playbackEnabled = false; 393 } 394 // Update the envelope if enabled 395 if (envelopeStep > 0 && tDuration % (envelopeStep * (PSG_FREQUENCY / 64)) == 0) { 396 if (increasingEnvelope) { 397 if (envelope < 15) { 398 envelope += 1; 399 } 400 } else { 401 if (envelope > 0) { 402 envelope -= 1; 403 } 404 } 405 } 406 // Update the frequency sweep if enabled 407 static if (sweep) { 408 if (sweepStep > 0 && tDuration % (sweepStep * (PSG_FREQUENCY / 128)) == 0) { 409 if (decreasingShift) { 410 rate -= rate >> sweepShift; 411 } else { 412 rate += rate >> sweepShift; 413 } 414 if (rate < 0 || rate >= 2048) { 415 rate = 0; 416 playbackEnabled = false; 417 } 418 } 419 } 420 return sample; 421 } 422 } 423 424 private struct PatternWaveGenerator { 425 private static enum size_t PATTERN_FREQUENCY = 2 ^^ 21; 426 private static enum size_t BYTES_PER_PATTERN = 4 * int.sizeof; 427 private void[BYTES_PER_PATTERN][2] patterns; 428 private bool channelEnabled = false; 429 private bool playbackEnabled = false; 430 private bool combinePatterns = false; 431 private int selectedPattern = 0; 432 private int volume = 0; 433 private int duration = 0; 434 private bool useDuration = false; 435 private int rate = 0; 436 private size_t tDuration = 0; 437 private size_t tPeriod = 0; 438 private int patternIndex = 0; 439 private int rotateCount = 0; 440 private int sample = 0; 441 442 @property private int* patternData(int index)() { 443 return cast(int*) patterns[1 - selectedPattern].ptr + index; 444 } 445 446 private void restart() { 447 if (channelEnabled) { 448 playbackEnabled = true; 449 tDuration = 0; 450 tPeriod = 0; 451 patternIndex = selectedPattern; 452 rotateCount = 0; 453 } 454 } 455 456 private int nextSample() { 457 // Don't play if disabled 458 if (!channelEnabled || !playbackEnabled) { 459 return 0; 460 } 461 // Check if we should generate a new sample 462 auto period = 2048 - rate; 463 int newSampleCount = cast(int) tPeriod / period; 464 if (newSampleCount > 0) { 465 // Accumulate samples 466 int newSample = 0; 467 foreach (i; 0 .. newSampleCount) { 468 auto pattern = cast(byte*) patterns[patternIndex]; 469 // The samples are ordered so that the upper nibble is first, followed by the lower 470 int unsignedSample = pattern[0] >>> 4 & 0xF; 471 // Rotate the entire bank by 4 bits to the left 472 foreach (b; 0 .. BYTES_PER_PATTERN - 1) { 473 pattern[b] = cast(byte) (pattern[b] << 4 | pattern[b + 1] >>> 4 & 0xF); 474 } 475 pattern[BYTES_PER_PATTERN - 1] = cast(byte) (pattern[BYTES_PER_PATTERN - 1] << 4 | unsignedSample); 476 // Increment the rotate counter, and reset pattern playback once all the samples have been output 477 rotateCount += 1; 478 if (rotateCount >= BYTES_PER_PATTERN * 2) { 479 // When combining we play the other pattern, otherwise we replay the same one 480 patternIndex = combinePatterns ? 1 - patternIndex : selectedPattern; 481 rotateCount = 0; 482 } 483 // Apply the volume setting and accumulate 484 final switch (volume) { 485 case 0b000: 486 // 0% 487 break; 488 case 0b001: 489 // 100% 490 newSample += unsignedSample * 2 - 16; 491 break; 492 case 0b010: 493 // 50% 494 newSample += unsignedSample - 8; 495 break; 496 case 0b011: 497 // 25% 498 newSample += unsignedSample / 2 - 4; 499 break; 500 case 0b100: 501 case 0b101: 502 case 0b110: 503 case 0b111: 504 // 75% 505 newSample += (3 * unsignedSample) / 2 - 12; 506 break; 507 } 508 } 509 // Set the new sample as the average of the accumulated ones 510 sample = newSample / newSampleCount; 511 // Leave the time period remainder 512 tPeriod %= period; 513 } 514 // Increment the period time value 515 tPeriod += PATTERN_FREQUENCY / PSG_FREQUENCY; 516 // Disable for the next sample if the duration expired 517 tDuration += 1; 518 if (useDuration && tDuration >= (256 - duration) * (PSG_FREQUENCY / 256)) { 519 playbackEnabled = false; 520 } 521 return sample; 522 } 523 } 524 525 private struct NoiseGenerator { 526 private static enum size_t NOISE_FREQUENCY = 2 ^^ 19; 527 private bool playbackEnabled = false; 528 private bool use7Bits = false; 529 private int divider = 0; 530 private int preScaler = 0; 531 private int envelopeStep = 0; 532 private bool increasingEnvelope = false; 533 private int initialVolume = 0; 534 private int duration = 0; 535 private bool useDuration = false; 536 private size_t tDuration = 0; 537 private size_t tPeriod = 0; 538 private int shifter = 0x4000; 539 private int envelope = 0; 540 private int sample = 0; 541 542 private void restart() { 543 playbackEnabled = true; 544 tDuration = 0; 545 tPeriod = 0; 546 envelope = initialVolume; 547 shifter = use7Bits ? 0x40 : 0x4000; 548 } 549 550 private int nextSample() { 551 // Don't play if disabled 552 if (!playbackEnabled) { 553 return 0; 554 } 555 // Calculate the period by applying the inverse of the divider and pre-scaler 556 auto period = 1 << preScaler + 1; 557 if (divider == 0) { 558 period /= 2; 559 } else { 560 period *= divider; 561 } 562 // Check if we should generate a new sample 563 int newSampleCount = cast(int) tPeriod / period; 564 if (newSampleCount > 0) { 565 // Accumulate samples 566 int newSample = 0; 567 foreach (i; 0 .. newSampleCount) { 568 // Generate the new "random" bit and convert it to a sample 569 auto outBit = shifter & 0b1; 570 shifter >>= 1; 571 if (outBit) { 572 newSample += envelope; 573 shifter ^= use7Bits ? 0x60 : 0x6000; 574 } else { 575 newSample -= envelope; 576 } 577 } 578 // Set the new sample as the average of the accumulated ones 579 sample = newSample / newSampleCount; 580 // Leave the time period remainder 581 tPeriod %= period; 582 } 583 // Increment the period time value 584 tPeriod += NOISE_FREQUENCY / PSG_FREQUENCY; 585 // Disable for the next sample if the duration expired 586 tDuration += 1; 587 if (useDuration && tDuration >= (64 - duration) * (PSG_FREQUENCY / 256)) { 588 playbackEnabled = false; 589 } 590 // Update the envelope if enabled (using the duration before it was incremented) 591 if (envelopeStep > 0 && tDuration % (envelopeStep * (PSG_FREQUENCY / 64)) == 0) { 592 if (increasingEnvelope) { 593 if (envelope < 15) { 594 envelope += 1; 595 } 596 } else { 597 if (envelope > 0) { 598 envelope -= 1; 599 } 600 } 601 } 602 return sample; 603 } 604 } 605 606 private struct DirectSound(char channel) { 607 private static enum size_t QUEUE_BYTE_SIZE = 32; 608 private byte[QUEUE_BYTE_SIZE] queue; 609 private DMAs dmas; 610 private bool channelEnabled = false; 611 private int timerIndex = 0; 612 private size_t timerOverflows = 0; 613 private size_t queueIndex = 0; 614 private size_t queueSize = 0; 615 private int reSample = 0; 616 private int reSampleCount = 0; 617 private int sample = 0; 618 619 private this(DMAs dmas) { 620 this.dmas = dmas; 621 } 622 623 private void addTimerOverflows(int timer)(size_t overflows) if (timer == 0 || timer == 1) { 624 if (channelEnabled && timerIndex == timer) { 625 timerOverflows += overflows; 626 } 627 } 628 629 private void clearQueue() { 630 queueIndex = 0; 631 queueSize = 0; 632 reSample = 0; 633 reSampleCount = 0; 634 sample = 0; 635 } 636 637 private void updateSample() { 638 if (timerOverflows <= 0) { 639 return; 640 } 641 // Take one sample from the queue for each timer overflow 642 while (timerOverflows > 0 && queueSize > 0) { 643 timerOverflows -= 1; 644 // Take the sample from the queue and accumulate it (after amplifying it) 645 reSample += queue[queueIndex] * 4; 646 reSampleCount += 1; 647 // Update the queue index and size 648 queueIndex = (queueIndex + 1) % QUEUE_BYTE_SIZE; 649 queueSize -= 1; 650 } 651 // Clear the timer overflow count 652 timerOverflows = 0; 653 // If the queue is half empty then signal the DMAs to transfer more 654 if (queueSize <= QUEUE_BYTE_SIZE / 2) { 655 mixin ("dmas.signalSoundQueue" ~ channel ~ "();"); 656 } 657 } 658 659 private int nextSample() { 660 if (!channelEnabled) { 661 return 0; 662 } 663 // Generate a new sample if we have any 664 if (reSampleCount > 0) { 665 // Average the samples 666 sample = reSample / reSampleCount; 667 // Clear the re-sample and count 668 reSample = 0; 669 reSampleCount = 0; 670 } 671 return sample; 672 } 673 }