Skip to content

Instantly share code, notes, and snippets.

@yupferris
Last active November 22, 2018 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yupferris/ba50f489d235f6586400882d09cb58d7 to your computer and use it in GitHub Desktop.
Save yupferris/ba50f489d235f6586400882d09cb58d7 to your computer and use it in GitHub Desktop.
Galactic Pinball sample playback disassembly/notes
// Super high-level playroutine overview:
// Timer reload set to 0x0002
// Timer int fires every ~50us (no, that's not a typo)
// Store 0x00 in timer control (large interval, zero interrupt disable, timer disable)
// Store 0x19 in timer control (small interval, zero interrupt enable, timer enable)
// Output volume to VOICE_1_ENVELOPE_DATA and VOICE_2_ENVELOPE_DATA
// Data stream
// 0xXX where 0xXX != 0x0f => envelope data <- 0xXX << 4 (reload value = 0xXX & 0x0f; count down to 0)
// 0x0f 0xXX 0xYY where 0xXX != 0x0f => voice volume <- 0xXX; envelope data <- 0xYY << 4
// 0x0f 0x0f => End of sample?
// Sample data regions:
// - 0xfff1a15a-0xfff20d5b - Welcome to space world!
// - 0xfff24d30-0xfff2a191 - Cosmic
// Small timer interval: 20us
// 400 clock cycles
// Timer reload value: 0x0002
// Target sample rate: ~20000hz
// 50us sample period; exactly 2.5 * 20us (small timer interval, yields 1000 cycles)
// Sample format appears to be correct
// plays in multiple players/DAW's; hand-checked file sizes and compared math against other .wav files
// Current timer impl
// Sample rate: matches .wav dump at ~16.7khz
// 60us sample period; exactly 3 * 20us
// 3 ticks observed; 400 cycles * 3 = 1200 cycles
// Current timer impl with mednafen hack
// Sample rate: matches .wav dump at 25khz
// 40us sample period; exactly 2 * 20us
// 2 ticks observed; 400 cycles * 2 = 800 cycles
0xfff00032 00bd0005 movhi 0x500, r0, r8
0xfff00036 c8a0e000 movea 0xe0, r8, r6
// if byte [0x050000e0] != 0
0xfff0003e e6c00000 ld.b 0[r6], r7
0xfff00042 070c cmp r7, r0
0xfff00044 43ddecff st.w -20[r3], r10
0xfff00048 fc94 bnz 0xfc (0xfff00144)
// {
0xfff00144 20bc0002 movhi 0x200, r0, r1
0xfff00148 21a02000 movea 0x20, r1, r1 // r1 = 0x02000020 (timer control reg)
0xfff0014c e6cc0400 ld.w 4[r6], r7 // r7 = Stream pointer
0xfff00150 20bd0001 movhi 0x100, r0, r9
0xfff00154 07c10000 ld.b 0[r7], r8 // r8 = Stream data byte
0xfff00158 40a11900 movea 0x19, r0, r10
0xfff0015c 01d00000 st.b 0[r1], r0 // Store 0x00 in timer control reg (large interval, zero interrupt disable, timer disable)
0xfff00160 41d10000 st.b 0[r1], r10 // Store 0x19 in timer control reg (small interval, zero interrupt enable, timer enable)
0xfff00164 0f4d cmp 0x0f, r8
0xfff00166 46c10100 ld.b 1[r6], r10
0xfff0016a 4084 bz 0x40 (0xfff001aa)
0xfff0016c 400d cmp r0, r10
0xfff0016e 29a01004 movea 0x410, r9, r1
0xfff00172 3484 bz 0x34 (0xfff001a6)
0xfff00174 0451 shl 4, r8 // r8 = Envelope value; envelope value <<= 4
0xfff00176 e144 add 1, r7 // Increment stream pointer
0xfff00178 5f45 add 31, r10 // add -1, r10 (sign extend)
0xfff0017a e6dc0400 st.w 4[r6], r7
0xfff0017e 46d10100 st.b 1[r6], r10
// Output envelope value
0xfff00182 01d10000 st.b 0[r1], r8 // r1 = 0x01000410 (VSU VOICE_1_ENVELOPE_DATA), r8 = envelope value
0xfff00186 01d14000 st.b 64[r1], r8
// }
0xfff0018a 43cdecff ld.w -20[r3], r10
0xfff0018e 23cdf0ff ld.w -16[r3], r9
0xfff00192 03cdf4ff ld.w -12[r3], r8
0xfff00196 e3ccf8ff ld.w -8[r3], r7
0xfff0019a c3ccfcff ld.w -4[r3], r6
0xfff0019e 23cc0000 ld.w 0[r3], r1
0xfff001a2 6444 add 4, r3
0xfff001a4 0064 reti
* 0xfff001a6 4145 add 1, r10
0xfff001a8 d68b br 0x1d6 (0xfff0017e)
* 0xfff001aa 07c10100 ld.b 1[r7], r8
0xfff001ae 0f4d cmp 0x0f, r8
0xfff001b0 29a00404 movea 0x404, r9, r1
0xfff001b4 1284 bz 0x12 (0xfff001c6) // 0x0f 0x0f found, end sample playback
0xfff001b6 e244 add 2, r7 // Stream pointer += 2
0xfff001b8 01d10000 st.b 0[r1], r8 // 0x0f 0xXX found; set volume for voice 1 and 2 to 0xXX
0xfff001bc 01d14000 st.b 64[r1], r8
0xfff001c0 07c10000 ld.b 0[r7], r8 // Load next envelope value
0xfff001c4 a08b br 0x1a0 (0xfff00164)
0xfff001c6 01d00000 st.b 0[r1], r0
0xfff001ca 01d04000 st.b 64[r1], r0
0xfff001ce 01d00c00 st.b 12[r1], r0
0xfff001d2 01d04c00 st.b 76[r1], r0
0xfff001d6 20bc0002 movhi 0x200, r0, r1
0xfff001da e1a01800 movea 0x18, r1, r7
0xfff001de 06d00000 st.b 0[r6], r0
0xfff001e2 07d00800 st.b 8[r7], r0
0xfff001e6 00a1c800 movea 0xc8, r0, r8
0xfff001ea 2941 mov 9, r9
0xfff001ec 07d10000 st.b 0[r7], r8
0xfff001f0 07d00400 st.b 4[r7], r0
0xfff001f4 27d10800 st.b 8[r7], r9
0xfff001f8 43cdecff ld.w -20[r3], r10
0xfff001fc 23cdf0ff ld.w -16[r3], r9
0xfff00200 03cdf4ff ld.w -12[r3], r8
0xfff00204 e3ccf8ff ld.w -8[r3], r7
0xfff00208 c3ccfcff ld.w -4[r3], r6
0xfff0020c 23cc0000 ld.w 0[r3], r1
0xfff00210 6444 add 4, r3
0xfff00212 0064 reti
@yupferris
Copy link
Author

yupferris commented Nov 22, 2018

Just had a thought with this and looking over the other timer-based sample playback routines I reverse-engineered. It seems that this might be the only one that disables and re-enables the timer on each timer tick. Since the sample period is exactly 2.5x the small timer interval, perhaps there's some weird 0.5x small timer interval delay on a disabled->enabled transition. The key here though is I figured out a simple way to test this: produce a new ROM that plays back a sample extracted from this game (see https://gist.github.com/yupferris/99238041bde343d20ce2aae6afe3a1ef) and nothing more. Ideally we should be able to build this just from the high-level notes at the top of the file and the extracted sample data. If this plays back the sample at the correct rate on hardware but incorrect rate in rustual boy, then the next test would be to change the 0x00 timer control write to clear interrupts etc but not disable the timer, and if we see the sample period decrease to 2x the small timer interval (at which point this should match the emulator output), then we're onto something. We can then adjust timer parameters to make sure this observation is consistent, and then follow up with subsequent hardware testing and try out new timer implementations accordingly.

I still have yet to come up with a nice way to regression test the timer impl in the emulator; maybe even just having its own test suite based on cycle callbacks with tests backed by hardware evidence is enough, even though we can only observe its behavior indirectly on the actual system. I would have liked to have ROM tests for this but since we don't have another consistent time source (at least while we're not 100% sure of cycle accuracy in the CPU) I'm unsure we'll be able to do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment