Last active
June 26, 2019 02:32
-
-
Save ISSOtm/5cf96e4cf385ca048fc0f190a29688f0 to your computer and use it in GitHub Desktop.
Prototype for a Game Boy sample player, eventually ended up at https://github.com/ISSOtm/smooth-player
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Here's some technical explanation about Game Boy sample playing: | |
; The "usual" way of playing sound samples on the Game Boy is to use its wave channel | |
; Basically, let that channel play its 32 4-bit samples, then refill it | |
; Problem: to refill the channel, you have to disable it then restart it | |
; However, when doing so, the "sample buffer" is set to 0 **and not updated** | |
; This means the channel outputs a spike as its first sample, creating a buzzing sound | |
; | |
; Why? That's because of how the Game Boy generates sound: each channel produces | |
; a digital value between 0 and 15, which is fed to a DAC (Digital to Analog Converter) | |
; which converts it to an analog value I don't know the range of | |
; And finally, all analog values get sent to the SO1 and SO2 terminals, where they get | |
; mixed together and scaled based on NR51 and NR50 respectively, then sent to speakers | |
; The problem is that DIGITAL zero maps to ANALOG maximum; therefore the channel | |
; doesn't play silence when starting up. (The GB's APU appears to be poorly designed) | |
; | |
; What this means is that using CH3 inherently has this spiky problem | |
; No solution has been found to use another channel to compensate, | |
; so we need to think outside the box... | |
; The solution used here is to make all osund channels play a DC offset (a constant) | |
; but a different one for each channel; then, we pick which ones get added to reach | |
; any constant, by selecting which channels are fed to the mixed via NR51 | |
; This gives 4-bit PCM at a selectable frequency (nice!) that also does stereo (ooh!) | |
; but that hogs all sound channels (ah.) and requires more CPU (erm) | |
StartSample:: | |
; Prevent sample from playing while in inconsistent state | |
xor a | |
ldh [rTAC], a | |
ld a, l | |
ldh [hSampleReadPtr], a | |
ld a, h | |
ldh [hSampleReadPtr+1], a | |
; We need to reset the APU quickly to reset the pulse channels' phases | |
xor a | |
ldh [rAUDENA], a | |
ld a, $80 | |
ldh [rAUDENA], a | |
; As far as currently known, the values of all sound registers are unknown after powering on | |
; Therefore it must be done first | |
; Disconnect all channels to play silence while we're setting up | |
xor a | |
ldh [rAUDTERM], a | |
; ~ Setting up CH3 ~ | |
; CH3 can be made to output a constant value without trickery | |
; We just define its sample to be, well, a constant wave | |
; We do want to do this as early as possible, because the first sample CH3 puts out | |
; Disable channel to unlock buffer | |
xor a | |
ldh [rAUD3ENA], a | |
ld a, $CC | |
ld bc, 16 << 8 | LOW(_AUD3WAVERAM) | |
.writeWave | |
ldh [c], a | |
inc c | |
dec b | |
jr nz, .writeWave | |
; Re-enable channel so it can play | |
ld a, $80 | |
ldh [rAUD3ENA], a | |
; Retrigger channel so it starts playing | |
; ld a, $80 | |
ldh [rAUD3HIGH], a | |
; ~ Setting up CH4 ~ | |
; CH4 will output digital 0 == analog max | |
; This is done by turning on its DAC, but not the LFSR circuitry | |
; The DAC is turned on by writing a non-zero value to the envelope, the LFSR by "restarting" the channel via NR44 | |
ld a, $F0 | |
ldh [rAUD4ENV], a | |
; ~ Setting up CH1 and CH2 ~ | |
; Those two are more complicated, because we can't use the same trick | |
; as for CH4, and they will keep playing squares | |
; The trick is to keep restarting them so they always play the same offset | |
; It's here that we hit a difficulty, though: two duty cycles start the channel | |
; at digital 0, which is NOT what we want; | |
; instead, we want to stay in the "1" part of the square wave | |
; So we use duty cycle 25% or 50% | |
; We will also obviously need the frequency to be as low as possible | |
; And finally, we want CH1 to output digital 9, and CH2 digital 10 | |
ld a, $90 | |
ldh [rAUD1ENV], a | |
ld a, $A0 | |
ldh [rAUD2ENV], a | |
xor a | |
ldh [rAUD1LOW], a | |
ldh [rAUD2LOW], a | |
; Don't write to rAUDxHIGH, that's done by the sample player | |
; Enable timer interrupt | |
ldh a, [rIE] | |
or IEF_TIMER | |
ldh [rIE], a | |
xor a | |
ldh [rTMA], a | |
; Make a sample trigger right after, to ensure the pulse channels don't | |
ld a, $FF | |
ldh [rTIMA], a | |
; Start counting down | |
ld a, $04 | |
ldh [rTAC], a | |
ret |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Reset phase of pulse channels to make them play constant offsets | |
; Done first as it may be part of the process of setting up the | |
ld a, $80 | |
ldh [rAUD1HIGH], a | |
ldh [rAUD2HIGH], a | |
; Play one byte of the sound sample | |
ldh a, [hSampleReadPtr] | |
ld l, a | |
ldh a, [hSampleReadPtr+1] | |
ld h, a | |
ld a, [hli] | |
ldh [rAUDTERM], a | |
ld a, l | |
ldh [hSampleReadPtr], a | |
ld a, h | |
ldh [hSampleReadPtr+1], a |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment