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, &amplitudeResolution, 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 }