In the past, I've made a patch for the PAL versions of megaman 1 and 2 that fixed their too slow music, see that blog post for details, and somebody then asked me to take a look at castlevania, so now I did.
While there was a fully proper, well documented disassembly of the music driver of megaman 1 and 2, I did not find something quite like that for castlevania after searching, but I did find a very basic disassembly with a couple functions named at least, most importantly, a function called UpdateMusic.
As you can see, theres no further documentation or comments, but hey, at least I now knew what to look for, as it also pointed out where it was called from.
With the PAL versions of megaman 1 and 2, there were slight differences from the NTSC version when it comes to the music so there I could not just look for those addresses, but in castlevania, they did not adjust music pitch or speed, so when I looked for this particular call in hex, 20 8A 83 in the PAL rom in my hex editor, it came up immediately:
So there was no further searching required which is nice, and I now had an idea of the variables involved too!
Now to give you a demonstration of how bad it sounds like when a pal title does not have speed or pitch adjusted, first have a listen to the intended ntsc speed:
And now, for how the pal version sounded like originally:
Previously with megaman, songs had a speed value attached to them which I modified, but with castlevania I looked at its addresses while it was running and there it already seems to be all based on frame values counting down, the issue with that of course is, the pal version runs at 50 frames per second, the ntsc version at 60, thats why it plays back slower. So with no easy song speed value to change out this time, with a frame counter I would have had to adjust each note timing and it would end up not really syncing up anymore, so I instead wanted to try out a method I saw in another nes music driver in the past that was already adjusted for proper pal playback, which just involves calling the UpdateMusic function more often. For this, I need to add some extra code of course, so that meant to see if there was some leftover space, and luckily, there is a massive area near the existing music code:
that is only a small fraction of it, there is lots of unused space after this point too. Now I just had to replace the existing calls to UpdateMusic, which jumped to address 838A previously, and point it to somewhere in this unused space, in this case, I chose BB8A because that meant I just had to replace 1 byte of the rom:Warning: Spoilers inside!
Now when it comes to what to put in place instead I started out very simple, I just called the UpdateMusic function twice like this:
With that small change in place, it now all sounds like this ingame:
Exactly as hoped, as you can hear everything plays at twice the speed now, so thats some good progress. Now to make this sync up as it should, I needed some counter variable so I know how many frames have passed so far, for that I looked into the lower memory of the game for a while and noticed that address $F0 seems to be unused:
To confirm that, I did set a breakpoint for that address, which basically would interrupt the game as soon as there was a write to it, but it never triggered, not during the intro or gameplay demo, I also finished the game with it hooked and did not see it accessed once, and in that disassembly I mentioned earlier I also so no reference of it, so it was the perfect candidate for this. So, with an address near the start of the memory that I could use, I now wrote up the following bit of code instead of just calling the UpdateMusic function twice:
To explain what this does, I first subtract 1 from whatever number is in address $F0, and I then see if the first 3 bits of $F0 are 0 by using the AND instruction. Because the game never initializes this value I wanted to make sure it checks only as few bits as possible, if I were to just see if the number itself was 0 then at the beginning of the game if there was a really large number in there already from boot then it would take a couple of seconds to sync up the timer properly. With this method though, it will sync up within the first few milliseconds of boot regardless of what is in there, which is quick enough to never run into any issues. Now when it hits 0, it reloads $F0 with a value of 5, and then executes the UpdateMusic function twice, if it was not 0, it only executes UpdateMusic once.
The idea behind this is rather simple, because I have to make up 10 calls a second (50 calls a second in pal vs 60 calls a second in ntsc), after the function was called once for 4 frames, I then call it twice on the next frame and repeat that sequence over and over. This simple method means for 5 frames it gets called 6 times, which of course after 50 frames means it called it 60 times, exactly the number it should be to be as fast as ntsc! While this of course means at times it advances the music more than others, it happens in such a short timespan of just milliseconds that it is unnoticable when listening to it.
To hear that for yourself, with this change in place we get the following result:
That is already so much better, the only thing left now is the pitch of the notes still being off, how this is done is that there is a function that gets a number for a note, and it takes that number and grabs the frequency to use from a list of frequencies:
$BC in this case already contains one half of that grabbed frequency and at the bottom you can see that getting stored into SQ1_HI, which is the upper half of the frequency register of the audio processor, so that is what you hear in the end, that frequency value in $BC was assigned right here:
You can see, it grabs some value from $878A and stores it into $BC as well as $BD, which is the other half of the frequency, and Y in this case was whatever note it wanted to translate into the frequency you hear, now to have a look at the values in $878A in a hex editor, I've marked the relevant bit:
I know that the things after that marked part are not relevant anymore as those follow a different structure so they are probably values for some other functions not related to frequencies, every frequency takes up 2 bytes here, so the first one being 0x6AE, and the last one being 0x38A, 12 frequency values in total. What I now have to do is make the notes play higher, that means in this case those frequency values have to be lower, thats how the audio processor works.
The question now of course is, how much lower do those values have to be? Well, having written a nes emu I know just the place to look for a reference of system clocks:
The one relevant here specifically is this one:
With 1.789773 MHz being the NTSC and 1.662607 MHz being the PAL clock, so now I just divide the NTSC by the PAL clock, resulting in a value of 1.076485904365854, which is the exact difference I now have to divide each of the values of the game by to get a properly adjusted output:
So the last step now was to just put those newly calculated values into the game like this:
With those tiny changes in place that took me less than 2 hours to come up with and implement which really makes me wonder why this wasnt done back in the day, I present to you the resulting ROM played on my actual PAL NES:
Because of that last recording it actually took me more than 2 hours just to get done with the blog post, thanks to my internal capture card suddenly showing a green screen when I wanted to record, after a reboot it wasnt detected anymore so I had to do a full shutdown, take out the card, make sure the pins are clean, replug it and THEN it was back to normal, probably over the years some contact got loose from my constant plugging in of different devices.
As always, this patch is available on my github:
It only changes a few bytes overall which is neat, anyways that is all for now, thanks for reading.
You need to be logged in to comment