Open Blaziken257 opened 1 year ago
Verified the issue doesn't occur on 0.1.0~57, but occurs on 0.1.0~56. This was just a text update though, so an error in the text update was probably masking the actual root cause.
The whole voiceover setup is super timing dependent... Will probably spend some time disassembling it and maybe reimplementing it.
0A:44AF is what actually does the robattle stuff, including setting up the text and calling the function
3779 calls the playback and sets things up 37c7 is the actual voice sample playback function, calls 3833 which is some arbitrary delay function
https://github.com/Medabots/medarot3/assets/3066132/81f40730-f8c8-4f15-a028-1abb9f64a93d
https://github.com/Medabots/medarot3/assets/3066132/63ab3f2e-de0a-482f-bb72-53747f8b1580
Depending on how you affect the timing it, it actually happens in both the original and our patch. Note the volume differences too...
It's been mentioned that the Japanese version also has this issue, but I looked at part of a YouTube playlist of Medarot 3 (see here: https://www.youtube.com/playlist?list=PLNL-hT-h2KcRnB1E7ZkP8Lokv14Nm1B79), in Japanese, on the Game Boy Player (which is affected by the "Robattle fight" issue in the English patch). This issue doesn't seem to be present in Japanese on real hardware so it might be an emulation issue. Below is a list of timestamps through the end of Chapter 2. This will be updated over time with more timestamps. Unless otherwise noted, a timestamp has "Robattle fight" as its voice clip.
Video in playlist | Link & Timestamp | Remarks |
---|---|---|
1 | 01:41 | Cutscene |
1 | 02:37 | Chapter 1 |
1 | 07:52 | Cutscene |
1 | 06:18 | Cutscene |
1 | 11:08 | |
1 | 13:53 | |
2 | 07:16 | |
2 | 15:53 | |
3 | 02:53 | |
3 | 06:53 | |
3 | 13:27 | |
3 | 17:53 | |
3 | 22:08 | |
3 | 31:49 | Chapter 2 |
4 | 10:41 | |
4 | 19:19 | |
5 | 00:49 | |
5 | 05:16 | |
5 | 10:29 | |
6 | 02:04 | |
6 | 05:10 | |
6 | 11:37 | Cutscene |
6 | 12:02 | Same cutscene |
6 | 12:24 | |
6 | 25:42 | |
6 | 29:13 | Chapter 3 |
The root cause of this issue is that GBC audio on GBA is very different internally from GBC audio on GBC, which causes a problem with the playback method that this and some other games use.
The playback method works by playing ultrasonic tone and varying the volume of the sound channel. The ultrasonic tone is inaudible or filtered out, and what's left is a varying DC offset that's used to play one sample at a time.
For example a square wave at volume F
might output 0F0F0F0F...
and that gets averaged to 7.5 "units of volume". Or volume C
might output 0C0C0C0C...
and that gets averaged to 6 "units of volume". This is used to play back a sample.
On GBC, the audio mixing is analog. The combined signal is happily output from the SoC, and the ultrasonic tone is (hopefully) filtered out by various components on the GBC meant for suppressing interference. (For example the C14 filter capacitor connected in parallel with the internal speaker, or the EM1-EM5 EMI filters connected in series with the headphone jack.)
On GBA, the audio mixing is digital, and what's worse, more or less using the nearest neighbor sampling method. It's sampling periodically at a rate specified by the GBA BIOS during the boot process. This means that in the example above it might only pick every 2nd or 4th sample and happen to always pick the active signal level, in which case the sample is heard. Or it might align so that it always reads zeros. It essentially works or not based on dumb luck.
Except that's not what happens at all, because of what is probably a bug in the code. First look at the initialization code in Sound_PlaySampleFragment
:
ld a, $FF
ldh [H_RegNR13], a
ldh [H_RegNR23], a
ld a, $78
ldh [H_RegNR12], a
ldh [H_RegNR22], a ;Envelope 0xFF, sweep disabled
ld a, $87
ldh [H_RegNR14], a
ldh [H_RegNR24], a ;Frequency 0x2FF, consecutive mode
The comment is actually wrong. The pitch value is initialized to $7FF
. This is the highest frequency the channel can produce, 131072 Hz, and it's what you would expect from this playback method.
For contrast, this is what's done later in .pcmLoop
.
ld a, $FF ; 0 in Telefang.
ldh [H_RegNR13], a
ldh [H_RegNR23], a
ld a, $81 ; $80 in Telefang.
ldh [H_RegNR14], a
ldh [H_RegNR24], a
This sets the pitch value to $1FF
which gives a frequency of 131072/(2048-511) = 85.28 Hz. This was likely meant to be $7FF
. What this means instead is that the programmer, probably unknowingly, discovered a sample playback technique I've been toying with. In this method, you retrigger the channel periodically to both set the volume, and reset the phase position. The channel goes through a sequence of 8 steps, which can be either "high" or "low", and this normally creates a 12.5%, 25%, 50% or 75% square wave. This is described in Pan Docs.
But the phase position can be reset partially, to the start of the closest step. (Unless you turn the APU off and on again.) So if you let the channel run freely for a while without resetting it or keeping precise track of the time that has elapsed, you can get a similar effect as above where you only get zeros as output.
In the first playback method described the issue is that a ultrasonic square wave doesn't align with the sampling if the GBA's audio mixer and only samples zeros. But this method accidentally isn't used.
In the second playback described, the issue is that we might wait the wrong amount of time and lock the channel in a phase position where it only outputs zeros. So we've basically gone from one playback method that might've worked by dumb luck, through a bug, to another playback method that works by dumb luck but in a different way.
So, what's actually happening, exactly? We can reset the APU by turning it off and then on again to reset the phase position of CH1 and CH2. The game does this... kind of:
xor a
ldh [H_RegNR52], a ;Disable sound hardware, resetting all state
call Sound_OpenSampleData
.fragmentLoop
call Sound_ExitSampleMode ; In Telefang this call is missing.
call Sound_PrepareSampleFragment
di ; In Telefang this is before the call to the Sound_PlaySample wrapper function instead.
call Sound_PlaySampleFragment
ei ; In Telefang this is after the call to the Sound_PlaySample wrapper function instead.
; Later loop back to .fragmentLoop.
The problems more specifically are this:
ei
is executed, pending VBlank and LCD interrupts are called. This takes an undefined amount of time, and throws off the timings. If it still happens to work after that, it is (not to repeat myself too much) by dumb luck. The quick fix would be to rearrange the code a little bit:
Sound_PlaySample::
push af
push bc
push de
push hl
call Sound_OpenSampleData
.fragmentLoop
call Sound_PrepareSampleFragment
di ; In Telefang this is before the call to the Sound_PlaySample wrapper function instead.
xor a
ldh [H_RegNR52], a ;Disable sound hardware, resetting all state
call Sound_PlaySampleFragment
call Sound_ExitSampleMode ; In Telefang this call is missing.
ei ; In Telefang this is after the call to the Sound_PlaySample wrapper function instead.
ld a, [W_Sound_SampleFragmentCount]
dec a
ld [W_Sound_SampleFragmentCount], a
jr nz, .fragmentLoop
call Sound_ExitSampleMode
pop hl
pop de
pop bc
pop af
rst $18 ; Absent from Telefang (since Telefang uses a wrapper function instead).
ret
What we're doing here is putting all timing critical code inside the di/ei
fence. I've also moved call Sound_ExitSampleMode
to after the sample fragment is finished playing which should minimize interference noises after a sample fragment is finished playing. Although if you want to have something that doesn't just work by dumb luck, I could rewrite the whole sample playback more properly. My fingers are itching. :)
Thanks for the detailed explanation @nitro2k01 .
Also CC: @andwhyisit
Although if you want to have something that doesn't just work by dumb luck, I could rewrite the whole sample playback more properly. My fingers are itching. :)
If you're interested, a PR is always welcome :). We're also fairly active in the development discord https://discord.gg/VqqGXzXE (#medarot-3-translation)
Hardware used
Flashcarts used
Versions
Has been present since the very first English patch (0.1.0) and remains as of 0.4.1+EN+nightly.20230611, and occurs consistently. Consistently does not occur in the retail Japanese game. Occurs in both Kabuto and Kuwagata versions.
Description
The “Robottle fight” voice sample, which is heard at the start of a Robattle, cuts out halfway on real GBA hardware: Only the “Robottle” part is heard, but the “fight” part is not. This issue happens consistently in all English patches tested. No other voice sample in the game is known to have this issue.
This issue does not occur in BGB in GBC mode, but a different issue occurs when played in GBC on GBA mode. (This can be done by right-clicking BGB -> Options -> System tab -> Check "detect GBA".) Instead of being silent, the "fight" part has a buzzing sound overlapping the normal "fight" voice. Due to the different behavior between BGB and real GBA hardware, this issue appears to be a strange corner case. (Making this even stranger is that Medarot 3 doesn't even check the b register on boot, which is normally done to detect if the game is run on a GBA.)
It is possible that the issue is timing or lag related, as if you hack the game to disable double speed mode (this can be done by unchecking the "double speed" box in the bgb IO map), even more severe buzzing occurs with all voice samples.
This issue has been untested on other flashcarts. It was also untested on an original GBA as I do not have a working one to test with.
Audio files
Various audio (MP3) files are attached.
Robottle Fight.zip