HPS Hacks
AUDIO HACKS : HPS FILES
Overview of Audio Format - HPS
HPS files are very similar to DSP files - they're just audio. HPS files, though, can loop. Since most of the HPS files are background music files, it makes sense that they loop. Some of the HPS files that don't loop are the victory music files for each character, and the music for the movies (the "How to Play" and the 15 minute long one). HPS files have two channels for audio, a Left and a Right channel.
Let's go over the structure of an HPS file in depth, since they're a bit more complicated imo. It looks like this:
HPS Header [0x00 - 0x80]
-info about the file
-data for 1st channel
-data for 2nd channel
Audio Data [0x80 - end ]
-1st block
---Block Header
---1st channel audio (DSP)
---2nd channel audio (DSP)
-2nd block
---Block Header
---1st channel audio, starting where the last one left off
---2nd channel audio, starting where the last one left off
....continue through blocks until the end.
Starting with the HPS Header, I'll break every bit (and byte) down. The header is like this
[0x00 - 0x07](8bytes) - "HALPST " - magic
[0x08 - 0x0B](4bytes) - sample rate
[0x0C - 0x0F](4bytes) - channels #
[0x10 - 0x13](4bytes) - length of largest block
[0x14 - 0x17](4bytes) - ??
[0x18 - 0x1B](4bytes) - "samples to read" number for left channel
[0x1C - 0x1F](4bytes) - ??
[0x20 - 0x3F](32bytes)- left channel decoder (coefficients)
[0x40 - 0x47](8bytes) - ??
[0x48 - 0x4B](4bytes) - see [0x10 - 0x13]
[0x4C - 0x4F](4bytes) - see [0x14 - 0x17]
[0x50 - 0x53](4bytes) - "samples to read" number for right channel
[0x53 - 0x57](4bytes) - ??
[0x58 - 0x77](32bytes)- right channel decoder (coefficients)
[0x78 - 0x7F](8bytes) - ??
Audio data is broken up into blocks. Once one block is finished playing, the next block immediately starts. The blocks start at 0x80, and continue to the end of the file. Here's what they look like.
First a Block Header:
[0x00 - 0x03](4bytes) - length of the block
[0x04 - 0x07](4bytes) - last byte to read in the block
[0x08 - 0x0B](4bytes) - offset for next block to read (looping done this way)
[0x0C - 0x13](8bytes) - left channel decoder state
[0x14 - 0x1B](8bytes) - right channel decoder state
[0x1B - 0x1F](4bytes) - empty (00 00 00 00)
Then the data for the Left channel in DSP format.
Then the data for the Right channel in DSP format.
Then comes the next block.
--Loops are done via the [0x08 - 0x0B] bytes. These bytes either point to the next block, or to an earlier block. The block that they point to will be the next one to play. When they point to an earlier block, that's basically your loop, as that earlier block is played as well as the ones after it again. You can change this [0x08 - 0x0B] value to a different number, to create a loop that loops to a different point in the audio.
--The "samples to read" numbers from the HPS Header above are very important for loops. This number is a timer for when to loop. It counts down until it hits 0, then, whatever block you're at, the audio will immediately loop. So, if you change the loop to be shorter, you have to change these values as well to be smaller, to make the loop good. Here's a formula for determining what the number is.
(number) = (seconds)(sample rate)(16/14) + 15
where seconds is the time until the loop will start, and sample rate is the sample rate (usually 32000). Round the answer down, then convert it to hexadecimal. That's your number.
HPS Hacking
Right. I finally got around to really figuring out HPS files and true music hacks. So here we go.
I assume that you read through the SSM/DSP overview at least (and the HPS overview), and are familiar with the business of hexing, and using dspadpcm.exe.
Tools:
-You need the same stuff as for the SSM hacks.
-For converting HPS to WAV format, I highly suggest vgmstream, an open-source video game sound player developed by hcs (Halley's Comet Software). It's constantly being updated with new types of video game audio, and it definitely works for HPS. This is very good for testing your HPS hacks, but the real test is on Dolphin or a Wii/GC.
-For hexing the DSP audio data into the HPS files, you can manually do it, but it takes forever. I have created a program that hexes the data automatically, and displays loop points, etc. I provided a link at the LINKS section, use at your own discretion.
-For this guide, I assume that you are using my program, hps_insert.exe.
Things I say a lot in this guide:
-
"loop points": when you run vgmstream, it'll tell you when the audio loops. So, where the audio ends, and where it picks up again. These values are called loop points.
-
"find your own loop points": I think that the audio I want to use would sound good looping from 30.099 secs to 122.901 secs instead of the 1.792 secs to 124.142 secs that its supposed to be. All I do is check out the file in a sound editor and find the times that I thought sounded good. I "found my own loop points" in that situation.
-
"change the starting/ending loop point": In the Block Headers of each block is a value that points to the next block in line. This value, [0x08 - 0x0B], can be messed with to change where your loop points are. Let's say the original loop is 101.585 secs to 0 secs. So the last block starts at offset 0x0037BD00, and it points to 0x00000080 for the loop. I can change the value from 00 00 00 80 (that's 0 secs) to 00 04 01 00 (that's 7.166 secs into the audio). Now the HPS will loop from 101.585 secs to 7.166 secs. I just changed the starting loop point. Let's say I want it to loop from 76.670 secs to 7.166 secs. 76.670 secs, according to hps_insert, is right after the block at offset 0x0029D2C0. This block points to 0x002AD160 as the next block, but I'm going to change it to point to 0x00040100 like before. That's about it in a nutshell for these things. There's a little bit more to it, but I've used up enough space as is. Check out the guide for more detail.
-
"modify the file to reflect the loop point": I want my audio to loop from 30.099 secs to 122.901 secs. Really, I do. But the thing is, the HPS is set up so that I can only point to specific parts of the audio to make my loop. I can point to 30.362 secs exactly, but not 30.099. 30.362 is the closest thing the HPS will let me do. So I have to stretch the audio in my sound editing program to compensate for this. I stretch it, cut it, whatever, so that the loop point I chose matches the loop point that the HPS offers.
-
"samples": What's a sample? Well, the sample rate, like 32000 Hz, is the samples played per second. The higher the sample rate, the better the quality, and the larger the size of the audio file. A general formula relating sample rate, samples, and seconds is (sample rate = samples/seconds). Since vgmstream rounds seconds to two decimal places, you can use this formula to more precisely calculate the seconds.
0. Before actual hacking
I suggest getting WAV files from the HPS files under the "audio" folder in the ISO. Copy all the HPS files to a folder with the vgmstream (mine's called "test") program and cmd.exe. In cmd.exe, type
"test -o outputfile.wav inputfile.hps -i"
to create outputfile.wav from inputfile.hps, ignoring any loop-points. To create a simple loop, type
"test -o outputfile.wav inputfile.hps -l 1"
the last two parameters are "-L" lowercase and the number one. This tells vgmstream to loop once, and it creates outputfile.wav that loops once, before ending.
Do this for each HPS....or use billbaggins' batch program to do it for you. :D
LINK.
With these WAV files, you can figure out what limitations you have with the audio tracks you want to insert. You cannot insert a track that is longer than the size of the original. Period. You can use smaller sized clips, but not longer ones.
EDIT: SleepyK says otherwise. He has 14:02-long clips for each normal stage, since he cut out a lot of unnecessary data from the ISO. I have yet to check this out for myself. Very cool, though.
1. Edit audio
I figure you have a specific audio clip to insert. Open it up in Soundbooth, Goldwave, what have you, and listen to it. Try to find good places to loop from, and work with the audio until you think you have a decent loop that would fit in the HPS you want. When you run vgmstream, it outputs some useful information regarding the HPS file, specifically, the loop points. You can use these points that are originally part of the HPS, or you can create your own!...with some limits, though. So we have two scenarios here - you either stick with the original loop points that the SSBM audio had, or you decide on loop points of your own.
a. Stick to the original
If you decide to use the original loop points, for simplicity, time constraints, laziness, etc. then you have to modify the audio that you want to exactly reflect those points. vgmstream will display the start loop point and the end loop point if you create a loop with it (add "-l 1" when you run it...see step 0.) In fact, it displays the start and end points in seconds, and in samples.
NOTE: What's a sample? Well, the sample rate, like 32000 Hz, is the samples played per second. The higher the sample rate, the better the quality, and the larger the size of the audio file. A general formula relating sample rate, samples, and seconds is (sample rate = samples/seconds). Since vgmstream rounds seconds to two decimal places, you can use this formula to more precisely calculate the seconds.
Since you have the loop points from vgmstream, edit your audio length to exactly match the HPS ones. That's all. If vgmstream says that it loops from 1.79 seconds to 123.49 seconds, then make one loop point at 1.792 seconds (more specific, using the formula) and the other at 2 min, 3.495 seconds. Then go to step 2.
b. Make your own loop points
If you instead decide to loop from 34.100 seconds to 123.000 seconds, then hold on a bit. We need to do a little hexing first. Actually, we just need to take a look at the hex data.
Starting loop points
We're going to run a "dummy" test of hps_insert to check out the HPS info. So copy your HPS to a folder with cmd.exe, dspadpcm stuff, and a WAV file. Make a copy of the WAV file, and run cmd.exe. Type this
"hps_insert hpsname.hps wavname.wav wavcopyname.wav"
which creates a "temp.hps" file, which you can delete. This also spits out a lot of useful info. It tells you where each block is located, the length of each block, the time each one takes, and the total time. With your loop points in mind (say, loop from 34.100 secs to 123.000 secs), take a look at the "total time" column. Find a value that is close to your loop point (if it says 33.897, that's very close to 34.100 - that's what the loop point will be), and write it down or something. You can do this for starting loop points, and for ending loop points. So the program may say that 33.897 and 122.550 are the closest values to what you want. Okay...just modify the audio file to reflect the loop points.
If any of you remember what I wrote earlier about a "counter", forget it. It was meant for manually finding loop points in the HPS file. With the program, you don't need to manually find them.
2. Hex-tastic - part 1
If you're satisfied that the audio clip will fit well into the HPS file, and loop right, then create two mono channels out of it, each at 32000 Hz sample rate, and 16 bit depth. One should be an R channel, the other an L channel. Now, we hex.
hps_insert - I reiterate what you need to run hps_insert here again.
Put your R and L files into a folder with dspadpcm, soundfile.dll, dsptool.dll perhaps, too, and cmd.exe. Oh, and hps_insert.exe...In cmd.exe, type
"hps_insert hpsname.hps lchannel.wav rchannel.wav"
where lchannel is the name of your left channel wav and rchannel is the right channel wav. This creates an HPS file called temp.hps. Rename it however you want. The only thing missing is working out those loop points, if you decided to create your own. If you're using the ones that the HPS originally used, then you're done. Yeah, done.
3. Hex-tastic - part 2
If you created your own loop points, then this is the final step. Open the temp.hps file (or if you renamed it, whatever you called it). Two sections coming up - one for starting loop points, and one for ending loop points.
Starting loops
If you only modified the starting loop, but the audio clip fits the original HPS size perfectly, this is the section. If you modified both starting and ending loop points, go to the next section. Now, navigate to the very last block of data. If you used hps_insert, it tells you the loop point offset, so go to there. The 8th through 11th byte in the block header is the offset that signifies a loop. It calls a previous block of data to be played, which effectively loops the audio. Remember your offset with your starting time? During step 1b., you should have found a time/offset where the seconds came close to your loop point, and you modified the audio to work with the seconds you got. Take that offset and type it in at the loop offset point. EG. If the line read 00010000 0000FFFF 0011A0C0 00500000, then change 0011A0C0 to the offset of your new start loop point.
Ending loops
For this, you need to know the starting loop point offset and the ending loop point offset. Go to the ending loop point offset, and in that line, find the third set of bytes (above, they were 0011A0C0). Replace these with the starting loop point offset. You don't need to edit the last block header in any way, since the audio will never see that block, always looping back to the beginning (Nonetheless, I always fill it with FF's for safety). What we do have to edit, though, is the HPS header, the bytes at the very beginning of the file. So go there. Look at offsets 0x18 and 0x50. They should be the same. They are the number of samples that the HPS goes through before "looping" and counting it as a loop. If they weren't edited correctly, then the audio would loop, and then it would loop again unexpectedly at random points in time. So let's calculate what it should be...Math time.
Info you need: sample rate (R), ending time point in seconds (T)
We'll first find the samples at the loop point:
R*T = samples
Then take that value (S) and multiply by 16/14, then add 15
[S*(16/14)]+15 = answer
Good, now convert that into hexadecimal, and type that value into 0x18 and 0x50 in the new HPS file. ...And we're done.
4 - Extra. The program (Once again, I'm going over how the program works)
The program, hps_insert, will take a HPS file, two WAV files (hopefully L and R channels) and run dspadpcm on both of them, inserting them into a copy of the HPS, called "temp.hps". It also tells you info on each block of audio, such as its offset, its length in bytes, its length in seconds, and the total time up to each offset.
When I change out the audio for a HPS file, I generally run a "dummy" test to see the loop points and the specific time intervals for each block. I copy the HPS to a folder with cmd.exe, hps_insert, the dspadpcm.exe stuff, and two WAVs. I run cmd.exe and type in
"hps_insert hpsname.hps wavL.wav wavR.wav"
hpsname.hps = the name of the HPS file
wavL.wav = the name of one of the WAV files, usually the left channel.
wavR.wav = the name of the other WAV file, usually a right channel.
This makes a "temp.hps", which I delete, but it also gives me incredibly useful info about the HPS, which helps out a lot when determining the best loop points in your audio.
When I actually edited my audio clip to reflect the loop points, then I run hps_insert seriously. I have a different folder for this, with the L and R channel WAVs, hps_insert, the HPS, cmd.exe, and the dspadpcm stuff. If I were editing pura.hps (poke floats), and I used "Pokemon Crystal 14.5 West Coast Radio" from OCRemix, I'd rename the L and R channels to something like "pkL.wav" and "pkR.wav", and I'd run cmd.exe:
"hps_insert pura.hps pkL.wav pkR.wav"
which spits out a temp.hps file. If I wanted to loop from 30.338 to 138.331 seconds, I'd open up temp.hps in a hex editor, look at the hps_insert output, and I'd change the offsets accordingly using the info there.
Troubleshooting HPS
My HPS hack has blips/cracks/pops/static when I listen to it in-game. Why?
Well, I'm sorry to say this, but the program hps_insert.exe is not yet perfect. Basically there are spots in the temp.hps audio file where the decoder coefficient at the start of a block is slightly wrong, so the resulting audio has a small defect. In other words, there's a small problem with the program when it creates the temp.hps file that I haven't been able to fix yet.
The audio plays fine in the game, but I hear silence for a few seconds before it loops.
Check your ending loop point. Chances are that you made the end loop point one or two blocks past where you should have. So open up your HPS hack in a hex editor, find the ending loop block where you told it to loop back near the beginning, and go backwards about 0xFEF0 (65264 in decimal) bytes to try and find the previous block. Then make that block loop back to your starting loop point. Maybe that will help. (btw that 0xFEF0 is just an approximation. But it should get you pretty close to the location of the previous block)
The audio loops sooner than I want it to.
Check your ending loop point. You might have made it loop too soon. So shift it one block back and then try it out. Check out the question above, too, which deals with shifting the ending loop point forward.
The audio loops at random points in time.
I think this is an issue with the HPS timer, also known as the "samples to read" value. This vaue is located at offsets 0x18 and 0x50, and basically counts the samples that are played in the audio. Once a certain number of samples are played, the audio loops automatically. To tell you the truth, I don't know the exact mechanism behind this value, I just tinkered with it until it worked out for me. Then I made a theory about how it works and what it does. Anyway, you just have to fix this value. To do that, follow these steps.
You need the sample rate (R) and the ending time point in seconds (T).
First find the number of samples at the loop point:
R*T = samples
Then take that value (S) and multiply by 16/14, then add 15
[S*(16/14)]+15 = answer.
SSBM crashes when I play my audio hacks.
That's a problem with your coefficients. Assuming you used hps_insert.exe, this shouldn't be happening. Anyway, when the coefficients are way outta whack, the game will think that the data's so messed up that it crashes.