Tuesday, September 7, 2021

Finally Breaking Through Is Always Nice!

I've been fighting a small problem for the last two days, and I finally just figured it out. It's such a relief to finally have it make sense that I decided to write it up before I even coded the fix, which it was still fresh. It was a small issue, but in a very large project, which made debugging difficult.

Back in the beginning of this whole pandemic I published version 2 of my VGM compression toolset, this one much more flexible and able to import many more formats. Last month someone finally used it. Yes, retro development is sure rewarding! Anyway...

This guy ported all the tunes from the NES version of Smurfs over to the TI, and released individual programs with my Quickplayer, which wraps a tune with a standalone player and lets you add some text. It was sweet, but awkward... and since he asked if anyone could make a cartridge, I wrote a tool that would take a folder full of program images, and build a loader cart with a menu.

This was a good start, but I decided that I also wanted to take all the visualizers I've done over the years (four of them), and make them available in the Quickplayer too. I adapted all of them to fit in the 8k low memory block on the TI, and ported them to Coleco as well. Then I coded a couple of flags that could be externally set that could be used to find a selection program for chaining (with random play as my intent.) It took some fiddling, but that all came together eventually.

So before diving into the issues that the random player gave me, let's recap...

First, we have the music player libraries themselves.

They are wrapped around a song in a standalone player program -- there are FIVE player programs (four with visualizers, and one just text like I originally released). Plus the program that does the actual wrapping and generates the final code.

Then we have a tool that packages these programs up, and auto-generates a loader program for them (so, the tool, and the loader program).

And we have a menu that selects which loader will be executed. So we're up to 9 programs, and then I wrote the randomizer.

I expected this part to be easy, because I had carefully compartmentalized everything. To explain a bit, the TI file system loads program files in individual 8kb chunks. In addition, the TI's memory map splits the RAM - an 8k block at 0x2000 is where I store the player, and a 24k block at 0xA000 gets the music data. So, the player was always in a separate copy block to the data.

The autogenerated code thus always looks a lot like this:

>  60B6  0200  li   R0,>6004 Ball
   60BA  0201  li   R1,>6000              
   60BE  0202  li   R2,>2000              
   60C2  0203  li   R3,>0cb4              
   60C6  06A0  bl   @>619c      (Copy data)          

   60CA  0200  li   R0,>6006    Data      
   60CE  0201  li   R1,>6000              
   60D2  0202  li   R2,>a000              
   60D6  0203  li   R3,>058a              
   60DA  06A0  bl   @>619c                

   60DE  06A0  bl   @>600c      Trampoline to start          
   60E2  2000  data >2000 Start address

There's a bit of an extra wrinkle in there. The TI cartridge memory space is only 8k, so we're also paging 8k banks. So, R0 is loaded with a page reference, R1 is a source address, R2 is a destination address, and R3 is the number of words to copy. 619C is a subroutine that banks the page in and does the copy. The first destination address is also the start address, and so it's also stored in the data at the end.

This all worked pretty well, so when I did the randomizer, I decided to have it pull the copy data for the first part, which is always the program, do the copy itself, and then randomly jump to the data copy for a different one. This would allow the music and visualizers to mix and match, so to speak.

It tended to work... sometimes. It was very random. Of course, I did introduce two random numbers. I was able to try hard coded values through the debugger and generate combinations that worked and combinations that didn't.

If you have already worked out why, damn you. You should have told me yesterday and saved me some time! ;)

If not, my next clue was that when it didn't work, it was jumping to incorrect addresses - addresses that tended to correlate with different programs than the one that appeared to be loaded in RAM. But out of all five player programs, I only used two start addresses, >2000 and >2100. So there was always a chance of it working.

A sensible fellow would have sorted it at this point, but it took me just a little longer!

I took a lot of notes about start and load addresses, observed patterns, stepped through the code, and probably the fifth time I stepped through a broken start it suddenly clicked.

This is your last chance to tell me what it was! ;)

So yeah. The program is copied in the FIRST block, and the FIRST block contains the start address. JUMPING to the data copy in the last block means that we would use the start address contained at the end there -- which may or may not be for the code we actually loaded, since we are playing mix and match.

To clarify, here's the loader for Piano:

   616E  0200  li   R0,>600e   Piano <-- no page increment 
   6172  0201  li   R1,>6b0e              
   6176  0202  li   R2,>2100              
   617A  0203  li   R3,>0883              
   617E  06A0  bl   @>619c                

   6182  0200  li   R0,>6010   Data         
   6186  0201  li   R1,>6000              
   618A  0202  li   R2,>a000              
   618E  0203  li   R3,>058a              
   6192  06A0  bl   @>619c                

   6196  06A0  bl   @>600c     Tramp      
   619A  2100  data >2100      Start address

Note the different start address!! So, if we copy the Ball program, then jump to the data copy for Piano, we'll end up starting in the middle of the Piano code!

Of course.. the "right" way to do this wouldn't be hacking around your own code... you'd store the player programs in one place, and the music in another... but I wanted to be able to work with programs anyone generated -- plus I expected it to be faster to do. ;) (It probably wasn't!)

The lesson is - beware of red herrings. Validate your assumptions. And for goodness sake, pay attention when you're doing hacky things like running your own code out of order, even if it was on purpose!

1 comment:

  1. FWIW, my fix is to just not use the "data" mechanism at all. Instead I'll store the address in an unused register during program copy and use that when it's time to start.