• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Forum moderator applications are now open! Click here for details.
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Research: Looping .SSEQ files is possible! However, offsets may set you off...

23
Posts
13
Years
  • Seen May 30, 2011
.SSEQ looping. Many people have been wondering what the heck to do to make their songs loop. Well, I may have found the answer. Today, I was looking at an information consolidation on the .SSEQ file type, and I found out that it showed where "jumping" or "looping" occurs. So I cracked open HxD and modified a .SSEQ file. I started out small, editing the loop point of one track as opposed to all of them (the first track to be precise), and I found that the jump was located near (but not at) the end of the track for the normal wild pokemon encounter/battle (oddly this does not apply for version two of the encounter; it's at the very end for all of them to my knowledge). The file, as you may know or not know, is SEQ_BGM_VS_NORAPOKE.SSEQ, and the "jump (with count)" for the first track is 94 1D 02 00 FF (note: in VGMTrans, "jumps" highlighted yellow and starting with 95 are actually "calls"). I concluded that the "FF" was the end byte (as in, it was the byte that marked the end of the call command). In addition, 94 was what initiated the jump/loop command in the first place. What's self it the 1D 02 00. Well, I found out this is actually the offset in the .SSEQ file in little-endian (or refersed hex if you prefer) that marks the offset where the track will jump to for that one track. So, the offset would actually be 00 02 1D. 21D, as I found out, was the beginning of a volume change in the song, but after a program change (or instrument change). I assumed it was put there because the tracks instruments shift before the volume of the overall track shifts. So, I looked for a similar value to this, and I found that 3FB started a volume change, and was also after a program/instrument change. Therefore, I changed the string of text from 94 1D 02 00 FF to 94 FB 03 00 FF. The end result? When the music hit its loop point, the first track, noticeably, shifted to a later part of the track (it ended up looping to a point ahead of the other tracks). So, I have successfully edited a loop. I am unsure if my findings were previously recorded or tested on a Pokémon game, but in any case I hope for this to be helpful to any composers out there.

Now something I didn't figure out that is crucial to making this work correctly... Track offsets. I don't know how these work at all, but I hope someone here does. Apparently, the track offset is 93 01 5F 07 00 for the first offset of the first track in SEQ_BGM_VS_NORAPOKE.SSEQ. 93 is the command for pointing out the track, 01 is the track's number in hex form (obviously), and 5F 07 00 is the tracks offset. In what? I have no clue. I assumed little-endian at first but 75F isn't anywhere NEAR the beginning of the track! The track's actual beginning, according to VGMTrans, is suppose to be 51, but the way it got that calculation dumbfounds me. I looked into the source code, but no dice. See if you can decipher this:

Code:
while (b == 0x93)        //Track/Channel assignment and pointer.  Channel # is irrelevant
        {
            TrkPtrs->AddSimpleItem(offset, 5, L"Track Pointer");
            ULONG trkOffset = GetByte(offset+2) + (GetByte(offset+3)<<8) + 
                                (GetByte(offset+4)<<16) + dwOffset + 0x1C;
            NDSTrack* newTrack = new NDSTrack(this, trkOffset);
            aTracks.push_back(newTrack);
            //newTrack->
            offset += 5;
            b = GetByte(offset);
        }

If you can understand what that means in C++ you win a cookie and my respect, because if you can't reconfigure the track pointers, you can't add the loop points. At all. I asked my friend JTE for help but this is all he could tell me...

I'm assuming b is an unsigned char, it reads hexadecimal 93, ... it adds the offset to something called TrkPtrs which I assume is a list of track pointers... the trkOffset is the three bytes after the 0x93 plus the dwOffset and static 0x1C.. it uses that offset to read it into an NDSTrack structure and pushes that into the list of tracks, then continues happily on its way reading the next track pointer...

Personally that didn't help me at all, but if it helps you guys, please leave a response. I'm sure you'd help not only clueless me, but any other composers wishing to put in loops.

Regards,
-Chimera

Edit: of course, I reached an error. I tried adding the loop code at the end of a .sseq file generated by midi2sseq (there was a bunch of random useless things scattered into the end of the file), and no dice. It didn't even hint a loop, meaning either something extra has to be done to loop the music, or my assumption about the offsetting was incorrect. :(
 
Last edited:
23
Posts
13
Years
  • Seen May 30, 2011
Yes, however I found out that you actually have to subtract 1C from the offset you want the song to jump to :P That's why it wasn't working; my calculation was off by 3.

I even made a video of the loop working :D
 

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
That's really impressive. You say in your video comments that you're working on a tutorial, correct? I'm curious about how this worked - I literally have no idea where I'm supposed to add the loop - not to mention my converted file ends up larger than the original despite being a shorter song... did you use midi2sseq at all?
 

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
Alright, after downloading a whole bunch of programs, learning how to use FL Studio, reading up on .sseq file documentation and changing hex values around for hours on end, I've got it work - loops and all! I'd post a video but look at this pitiful post count.
 

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
EDIT: Okay, I'm home now. Let's go over the process.


What I used:

- FL Studio
- Awave Studio
- VGMTrans
- NitroExplorer2b
- midi2sseq
- Hex Workshop
- Windows Calculator (Programmer mode)


The process of creating a midi is still the same. However, you need to account for some things in order to make it loop correctly. Since an sseq file is a series of instructions (play this note, wait this amount of time, change pitch to this amount, etc) and looping works with offsets, you need to have a command of some kind in all tracks where the loop happens (i.e. the end of the song) and in the spot it loops to. This can be a note, an instrument change, a volume change, or a pitch change. The reason for this is so the game knows exactly how much time to wait until it loops the track and until the notes start playing after the track looped.

If there are no notes at the loop points you'll need to add something else there - I believe I used a volume change to 0%, which is inconsequential during playback but worked as a good reference point. It was followed by a change back to normal before the first note played, of course.


Once you make your sseq file and fixed the C0s, you'll need to make further changes to the file in hex - specifically, at the header. If you open an unmodified sound_data.sdat with VGMTrans and open an sseq, you'll notice the header works like this:

SSEQ Chunk Header (16 bytes)

Signature (8 bytes) - should be '53 53 45 51 FF FE 00 01';
Size (4 bytes) - the size of your sseq file in little endian. You'll need to alter this after you add the loop commands;
Header Size (2 bytes) - should be '10 00';
Unknown (2 bytes) - sdat documentation says this is the "number of blocks". In any case it's always '01 00';

DATA Chunk Header (12 bytes)

Signature (4 bytes) - should be '44 41 54 41';
Size (4 bytes) - this should be the filesize minus 10 (as in, 10 in hex). Again in little endian. In other words, if your size in the SSEQ chunk header is 'B8 33 00 00', here it should be 'A8 33 00 00';
Data Pointer (4 bytes) - should always be '1C 00 00 00';

Track Pointers (Variable according to how many tracks your song has)

Valid Tracks (3 bytes) - first byte is always 'FE'. The second and third bytes indicate which tracks this song uses, in binary. Bits that are set represent tracks that are used. Like so:

First byte: 76543210
Second byte: FEDCBA98

You can use the Windows calculator to turn bits around according to what you want. Normally you'll want to use the tracks in order. Track 0 should always be set.

A song that uses tracks 0, 1, 2, 3, 4, 5, 6, then skips to 8 and 9 would have the bytes as '7F03', which in binary is '01111111 00000011';

Track Pointers (5 bytes) - first byte is always '93'. Second byte is the track number, from 01 to 0F (track 0 does not need to be listed). The remaining three bytes are the offset where the track starts, not counting the header - meaning once you obtain the track's offset in you hex editor you'll have to subtract 1C from it. The value should be in little endian. You should only change the offsets after you've added the loop commands.


After this, the first track should be determined. All tracks begin with 'C7 00'. The first track (which is track 0) should have the song's tempo. In a song converted with midi2sseq, this track is going to have the song's tempo and other information, probably multiple times, and nothing else. I'm not sure if it's necessary, but I moved the tempo (a 3-byte command starting with 'E1') to the beginning of the first real track and removed this track - which is how the songs are in-game.

To add loops, you'll need to go to the end of each track (assuming your song ends exactly when it should loop, which I recommend). It should be an 'FF'. Add four bytes between the previous byte and this 'FF': The first one should be '94', followed by the offset of the point in this track you want the song to jump to. Again, this needs to account for the header (subtract 1C) and be in little endian.

In order to find the loop point, I used Awave Studio, but any method in which you can check a list of track events works. I looked up the command I added to the loop point in FL Studio - a note, patch change, whatever - and identified its overall position in a song. Then I did a search for this command in hex. Here's a couple of hex commands (from the sdat documentation):

Note On: First byte is a value from 00 to 7F, followed by one byte for velocity, and as many bites are needed for the duration;
Note Off(Rest): First byte is 80, followed by the duration;
Program (Instrument) Change: 81 followed by the new instrument value minus 1 (Meaning instrument 30, "Overdriven Guitar", is actually 29, or 1D);
Panning: C0, followed by a value between 0 and 7F. 64 is the middle;
Volume: C1 xx;
Master Volume: C2 xx;
Pitch Bend: C4 xx.

It should be noted that the values will be in decimal in Awave Studio/other programs, and should be converted to hex before you search for them. For example, if I determine that my loop point starts with a volume change to 100, I'll search the file for 'C1 64'.

Once you find the offset, write it down, subtract 1C from it, and convert to little endian. That's what you should put in your jump command after the 94.

Once you've finished adding the loops, make sure that you change the header to reflect the new filesize and track offsets.


Phew! After all this, you can finally add the track to your sdat. Here's what you should do:

- Open your new sseq and your sdat in Hex Workshop;
- Copy the entirety of your song to the end of the sdat, and write down the offset you pasted it to;
- Open your sdat or a copy of it in VGMTrans, then open up the song you want the new music to replace;
- Check the song's offset. It should be the first line in the hex view. For example, in Diamond and Pearl, the offset to Cynthia's battle theme is '00100160';
- Search your sdat for the song's offset in little endian ('60011000'). If it's the only stance of your string it probably is the song pointer;
- Replace that string with your new song's offset in little endian;
- Right after the song offset, the next couple of bytes determine the size of your sseq. For Cynthia's theme it says '98 2D'. Replace it with the size of your new song, again in little endian;
- Save and reinsert the sdat with NitroExplorer.


If you did everything correctly, the song should be working! If you end up with notes that never stop, it's because you had some very short notes that after the sseq conversion ended up as notes with 0 duration - which the game interprets as never-ending. You'll have to find those and change the duration to a suitable value - like, say, 2.


I'm hoping this is clear enough.
 
Last edited:
28
Posts
16
Years
  • Seen Aug 19, 2013
93 01 5F 07 00

From my own attempts, this is what I think the above HEX line means:

93: Begin track pointer syntax.
01: Pointing to track 1.
5F 07: This is little endian for 075F, denoting how far beyond the header the track's start point is located in the file. Keep in mind that the header is of length 1C, as lmdst so kindly informed us.
00: End track pointer syntax.

This means that Track 1 starts at (0x075F+0x001C=0x077B). If you jump to that offset in a HEX editor, you will find the value 'C700', denoting the start of that track.

To show the start of Track 0, the real first track, is redundant, as it happens immediately after the header, at offset 00; a pointer code for that would be "93 00 00 00 00".

Oh, and by the way, the loop points in Black/White SSEQs are denoted by '94', not '93'.

Note On codes are formatted as such: The first byte is the pitch value; the second is the velocity; and the third is the duration. Breaks between events are denoted by '80' followed by one or two bytes to denote the span of time between events. One-byte breaks are easy to understand as they translate directly from HEX to Decimal (i.e. 7F = 127), but I have yet to interpret two-byte breaks. For example, how does "80 8C 00" translate to a break of length 1536 MIDI ticks? (1536 = 0x0600).
 
Last edited:

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
93 isn't for a loop, it's defining the tracks at the beginning of the song (the offset used, thus, is the very beginning of the track). 94 is used for loops - this is true for every game that uses sseq files.

As for the issue with ticks, I'm not entirely convinced that sseq ticks correspond exactly to MIDI ticks. Most of the values I've checked seemed to be half of the MIDI file's, and my notes with a length of 4 ticks got changed into 0 ticks. It's possible the conversion is not perfectly accurate.
 
98
Posts
16
Years
This is very confusing. I have been following this closing but looping seems to be too complex. I pretty sure I did everything right except the last part which lmdst's talks about the header.

Do you substract 1C from the header equal to the amount of tracks you want to loop? For example, I have 4C 2D in the size of the SSEQ Chunk Header and since I have 8 tracks I want to loop do I substract 1C from it 8 times? Or do I need to subtract 4C 2D 00 00 which includes the other two bytes?

If you can't understand my question it's alright, I might have done the whole process wrong from the start. I wish I could upload the .sseq, but the atttachments won't support that type of file.

Edit: Never mind, got the looping to work.
 
Last edited:

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
Geez, I'm sorry. Both that it's not clear enough and that I kind of disappeared for a while after writing it.


What I meant to say you need to subtract it "for every track" is that you need to do it once for every track pointer you use. This means the pointers at the beginning, and the pointers at the loop points (each track is looped individually).

1C is the size of the header, which the game doesn't consider when looking up offsets, hence the need for subtracting it.
 
98
Posts
16
Years
It's okay, I got it working thanks to your guys knowledge on looping. However, I didn't need to make any changes to the header for the file to work. I just inserted the loop commands at the end of each track and the .sseq looped.
 

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
You mean you didn't need to repoint the tracks? I imagine that if a track's starting offset got changed, it would not play properly if the new offset isn't pointed correctly. (as in, it might be missing parts of it or loop incorrectly). Same deal with a filesize change not being accounted for.

Though I suppose the header stuff may not actually be used in the game and is there merely for organizational purposes. I recommend changing them just to be sure - it's not as difficult as it sounds. (This is a general tip rather than being addressed to you specifically. Sorry for the lateness again)

EDIT: Augh, what the hell? Apparently I made a mistake when I wrote the tutorial. A loop command starts with 94, not 93 as the tutorial used to say (Of course, if you saw the first post in this topic you'd know this). 93 is used by the track pointers at the beginning of the file.
 
Last edited:
23
Posts
13
Years
  • Seen May 30, 2011


And thus I advance further into music hacking in Black and White.

By the way, after school ends (wait for the end of June guys; it's only a month away), I'll make a video tutorial saying how I did all this. :V
 
Last edited:
98
Posts
16
Years
ChimeraReiax: Great job on editing the soundbank and I like the new remix as well :D

lmdst: Yeah, I noticed that the loop command was 94 because 93 did not work for any of the songs. I think another time I might try to figure out how to change the other track offsets as well. But for now I am satisfied.
 
33
Posts
13
Years
is very usefulll for me, i was done with looping, and my problem is how to match with instrument (sbank, swar), is just replace or on midi must adapted from sbank from main ROM?
 

lmdst

Blast From the Past
13
Posts
15
Years
  • Age 33
  • Seen Feb 26, 2014
Well, if you want to use the soundbanks from the game, you can extract them with VGMTrans. You'll get soundbank files you can use with FLStudio if you have that.
 
15
Posts
13
Years
  • Seen Aug 9, 2016
BW sound fonts

In Black and White, all of the sseq files have their own sound bank and sound fonts.

for example, a .sseq may have 5 sound fonts
00--piano
03--vib or piano
36--some harp
56--trumpet
80--drumkit

bank 80 is always a drumkit with a single exception
 
Last edited:
Back
Top