Jump to content

Pokemon Mystery Dungeon 2 - Psy_commando's Tools and research notes


Recommended Posts

So I tried doing something with this (inserting portrait), but I am such a noob that I don't know how to use the program (inb4 I missed obvious tutorial/manual). It says about Input and Output, but...

Like evandixon said, you'll probably want to try his sky editor. He's using the utility I made in the background, but he made a more intuitive user interface for it. It would probably be easier for you since you've mentioned you're just starting out.

Link to comment
Share on other sites

Tags are pretty interesting. Have you tried each of those tags to see what they do ?

I tried out a bunch of them in the past and figured what some of them did : https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/FileFormats/text__str.txt

I've finally gotten around to mostly doing that, sorry for being weeks late. https://www.dropbox.com/s/w8p07cl76zu21sm/Tag%20codes.rtf

I found all(?) of the game's tag codes at 0x9D9AC/near the first result for searching for "tag code" in the rom. Some of those codes are never used, yet work. A few others crash the game, and probably aren't real.

I compared your notes to mine, and I removed tags that either we mutually had no idea about or that you already knew, if I had nothing to add to those.

Link to comment
Share on other sites

I've made a lot more progress with the smdl and swdl format!

I can parse entire songs now, and I can export midis. Except that I still have a bug to deal with, thanks to the midi lib I'm using..

I'm still looking for a C++ SF2 lib to export a soundfont, and not have to write everything by myself ! I wasn't very lucky finding any..

I also took a look at how the timings for the notes, and the pauses work. I was able to calculate the duration in ticks of each types of prefix delays. And the default PPQN value for smd files is stored in the "song" chunk, its set to 48 by default, the same default as MIDI files.

**Disclaimer: Note durations names may make no sense..**

The following values under the column "Engine ticks 48 PPQN" were all taken directly from the game, 
by running 2 tracks, with 2 repeating notes simultaneously, one with prefixed delays and the other 
with silences with a set duration in ticks instead.
Note events in track 1 had all the same pitch, and played note 0. 
Note events in track 2 had all the same pitch and played note 5.
|======|==================================|========================|======================|
| Code | Duration                         | Midi Ticks at 960 PPQN | Engine ticks 48 PPQN |
|======|==================================|========================|======================|
| NA   | Whole note                       | 3840                   | 192                  | *Whole note included as reference only !
|------|----------------------------------|------------------------|----------------------|
| 0x80 | half note                        | 1920                   | 96                   |
| 0x81 | dotted quarter note              | 1440                   | 72                   |
| 0x82 | 2/3 of a half note               | 1240                   | 64                   |
| 0x83 | quarter note                     | 960                    | 48                   |
| 0x84 | dotted 8th note                  | 720                    | 36                   |
| 0x85 | 2/3 of a quarter note            | 600                    | 32                   |
| 0x86 | 8th note                         | 480                    | 24                   |
| 0x87 | dotted 16th note                 | 360                    | 18                   |
| 0x88 | 2/3 of a 8th note                | 320                    | 16                   |
| 0x89 | 16th note                        | 240                    | 12                   |
| 0x8A | dotted 32nd note                 | 180                    | 9                    |
| 0x8B | 2/3 of a 16th note               | 160                    | 8                    |
| 0x8C | 32nd note                        | 120                    | 6                    |
| 0x8D | dotted 64th note                 | 90                     | 4                    |
| 0x8E | 2/3 of a 32nd note               | 80                     | 3                    |
| 0x8F | 64th note                        | 60                     | 2                    |
|======|==================================|========================|======================|

I can't remember if truepikachu came to the same conclusions. But at least we're sure of their timing now!

Every time values for all events also uses PPQN ticks to specify it duration.

Also, Here's where the updated song chunk header structure:

------------------
   SONG:
------------------
Chunk holding data that applies to the entire song.

NOTE:
   Looking at sample SMDL files from 3 different games, all from Chunsoft, the only thing that
   changes is the nb of tracks and nb of channels. So its hard to determine what the rest is for
   or in what format it is..

Offset:            Length:            Type:               Description:
-------            -------            -------               -----------------------------------
0x00 label         4                  char[4]               Song chunk marker "song" {0x73,0x6F,0x6E,0x67}
0x04 unk1          4                  uint32                So far 0x0000 0001 (Little endian)
0x08 unk2          4                  uint32                So far 0x10FF 0000 (Little endian)
0x0C unk3          4                  uint32                So far 0xB0FF FFFF (Little endian)
0x10 unk4          2                  uint16                So far 0x0100 (Little endian)
0x12 ppqn          2                  uint16                Pulse Per Quarter Note. Usually 0x30. (See how PPQN works with MIDI)
0x14 unk5          2                  uint16                So far 0x01FF (Little endian)
0x16 nbtrks        1                  uint8                 Number of track chunks.
0x17 nbchans       1                  uint8                 Possibly number of midi/output tracks.
0x18 unk6          4                  uint32                So far 0x0000 000F (Little endian)
0x1C unk7          4                  uint32                So far 0xFFFF FFFF 
0x20 unk8          4                  uint32                So far 0x0000 0040 (Little endian)
0x24 unk9          4                  uint32                So far 0x0040 4000 (Little endian)
0x28 unk10         2                  uint16                So far 0x0002 (Little endian)
0x2A unk11         2                  uint16                So far 0x0008 (Little endian)
0x2C unk12         4                  uint32                So far 0x00FF FFFF (Little endian)
0x30 unkpad        16                 -                     0xFF Padding, unknown reasons..
-------            -------            -------               -----------------------------------
Total:             64 (0x40)

There is still a very long way to go though. But at least, most of those values are the same between every single PMD2 tracks.

Here the link to my full notes btw : https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/PMD2_MusicAndSoundFormats.txt

On the side of map tiles, I keep chickening out on having to go through trial and error to figure out the format.. If I had a good understanding of the scripts this might be easier though, seeing as each maps are held together by scripts.. But I don't know much about them.. ^^;

And well, I really could use feedback on ppmd_gfxcrunch, and on the pre-release ppmd_statsutil.

I'm looking to improve those before setting things in stone for the next few releases, and I need to know if the utilities are suitable for what they were made for basically ^^;

I've also been working on designing a automated and convenient way of sharing PMD2 mods. Possibly other romhacks as well.

Most other romhacks use a single differential patch for the whole rom, but that's problematic, because it doesn't support different revision and languages of the game, or anything that modifies the CRC of the ROM. And you cannot combine romhacks together, even if they could really be combined (think of a sprite pack and another hack that changes only, say, pokemon names for example ).

So, I've considered making a set of 2 utilities. One for easily and automatically assembling a mod package, and the other would be a redistributable utility that would handle extracting and patching selectively the ROM.

A mod package is just a zip file, that would contain:

- The patch installer

- ndstool (might just merge the source code into the installer)

- xdelta (might just merge the source code into the installer)

- A xml file with the details for patching the ROM, how to handle other languages, what CRCs are supported, etc..

- A directory containing a replica of the ROM's file structure, filled with differential binary patches of original ROM files(for "legality" reasons), and full copies of any new files added.

The directory with the files would contain sub-folders, and would be named according to the game ID string they contain files for. So that means there would be a single package for a romhack that was localized in multiple languages for instance!

The nice thing, is that people wouldn't need to download patching tools, and do things themselves if they're total noobs and etc.. The patcher would unpack the rom, patch/add the files, rebuild the rom, and that's it.

Conflict handling could be done via CRC checks, so the application could ask the user what files they want to keep.

The concept could be pushed further even! Any opinions ?

I've finally gotten around to mostly doing that, sorry for being weeks late. https://www.dropbox.com/s/w8p07cl76zu21sm/Tag%20codes.rtf

I found all(?) of the game's tag codes at 0x9D9AC/near the first result for searching for "tag code" in the rom. Some of those codes are never used, yet work. A few others crash the game, and probably aren't real.

I compared your notes to mine, and I removed tags that either we mutually had no idea about or that you already knew, if I had nothing to add to those.

Nice ! Don't worry about being late. I haven't been as productive as I wanted to as well..

Is 0x9D9AC near all the other game strings, or is it within the ARM binaries/overlays ?

And all the tags I have in my notes are straight from the game's dialog, so they're most likely used.

Some of them are context sensitive, and rely on some variables being set. I'd assume that you can fill those variables or call some functions from the script to set those. It might explain why some crash the game, considering strings are referred to via pointers, and dereferencing a null pointer is an instant segfault.

Link to comment
Share on other sites

The address is found inside of arm9.bin, 0x999AC relative to the start of the file.

Also, 0xA6910 relative to the start of the same file has lots of strings in full caps. These strings can return a name and species when using c_name and c_kind. Just something I thought was noteworthy.

Link to comment
Share on other sites

I..

do..

think..

we're..

getting..

there..

:D

I still have a couple of bug, like notes getting out of sync at the end on some tracks, the issues with the GM track 10 being forced to be a drum track, bgm0026 is pretty much the only track that most players I tried couldn't play, and I'm ignoring a lot of events here and there, and I haven't setup a conversion table yet to convert the PMD2 preset number to General Midi..

But its beginning to sound pretty decent ! :)

EDIT: It also works with Shiren 2 ! :D

The address is found inside of arm9.bin, 0x999AC relative to the start of the file.

Also, 0xA6910 relative to the start of the same file has lots of strings in full caps. These strings can return a name and species when using c_name and c_kind. Just something I thought was noteworthy.

What do you mean by "These strings can return a name and species when using c_name and c_kind." ? I mean c_name and c_kind are supposed to return the name and specie of the speaker in a dialog, but what's the link with those strings in all cap ?

That sounds interesting.

Link to comment
Share on other sites

I've also been working on designing a automated and convenient way of sharing PMD2 mods. Possibly other romhacks as well.

Most other romhacks use a single differential patch for the whole rom, but that's problematic, because it doesn't support different revision and languages of the game, or anything that modifies the CRC of the ROM. And you cannot combine romhacks together, even if they could really be combined (think of a sprite pack and another hack that changes only, say, pokemon names for example ).

So, I've considered making a set of 2 utilities. One for easily and automatically assembling a mod package, and the other would be a redistributable utility that would handle extracting and patching selectively the ROM.

A mod package is just a zip file, that would contain:

- The patch installer

- ndstool (might just merge the source code into the installer)

- xdelta (might just merge the source code into the installer)

- A xml file with the details for patching the ROM, how to handle other languages, what CRCs are supported, etc..

- A directory containing a replica of the ROM's file structure, filled with differential binary patches of original ROM files(for "legality" reasons), and full copies of any new files added.

The directory with the files would contain sub-folders, and would be named according to the game ID string they contain files for. So that means there would be a single package for a romhack that was localized in multiple languages for instance!

The nice thing, is that people wouldn't need to download patching tools, and do things themselves if they're total noobs and etc.. The patcher would unpack the rom, patch/add the files, rebuild the rom, and that's it.

Conflict handling could be done via CRC checks, so the application could ask the user what files they want to keep.

The concept could be pushed further even! Any opinions ?

I've actually had a similar idea, although I think we should take it a step further and think of this like Minecraft Forge mod packs. Each mod would be a zip file with patches like you've described, but would be contained in the parent "mod pack" zip (and maybe renamed to .mod). For example, a hypothetical mod pack could contain two mods: New Starters and New Backgrounds. New Starters would modify overlay13 to change the personality test results and text_en.str to change the corresponding message ("And a calm type like you... will be a ____"), while New Backgrounds replaces some pictures in /BACK.

Each mod could contain multiple file types: some being binary patches, others being full copies of files, while others can patch certain parts of a file, like different mods changing different parts of the text_en.str. Maybe something like text_en.str.lines containing "line X=Some text; line2=Some other text".

When I was thinking about it, I was going to have Sky Editor know how to patch ROMs, but having a patcher inside the mod pack is a good idea. If ndstool and xdelta aren't merged into the installer, the could be tucked away in a directory called "Utilities", while a program called "Patcher" sits in the root of the mod pack, waiting for someone to open it.

Link to comment
Share on other sites

What do you mean by "These strings can return a name and species when using c_name and c_kind." ? I mean c_name and c_kind are supposed to return the name and specie of the speaker in a dialog, but what's the link with those strings in all cap ?

That sounds interesting.

If I throw...

[c_name:NPC_BIPPA] is a [c_kind:NPC_BIPPA].[R]I've never met [c_name:NPC_DEBUG] before.[R][c_name:NPC_PARTNER] is my partner!

...into the message log, the game will print it as this.

c_stuff.png

I didn't know that c_name and c_kind did that in dialog; now I know who conducts the personality quiz!

TheTruth.png

c_stuff.png.2732b4273a7b8ed84a8b925e647a

TheTruth.png.7635c296ef97c396c2faafa46e3

Link to comment
Share on other sites

I've actually had a similar idea, although I think we should take it a step further and think of this like Minecraft Forge mod packs. Each mod would be a zip file with patches like you've described, but would be contained in the parent "mod pack" zip (and maybe renamed to .mod). For example, a hypothetical mod pack could contain two mods: New Starters and New Backgrounds. New Starters would modify overlay13 to change the personality test results and text_en.str to change the corresponding message ("And a calm type like you... will be a ____"), while New Backgrounds replaces some pictures in /BACK.

I haven't had the chance to take a look at minecraft forge mod packs.

But yeah that could work. The utility could parse the XML data of each sub-mods and install them itself one after the other. Though, I don't know what advantage it would have over putting them all together into a modpack. Considering modpacks are meant to be distributed together anyways.

Each mod could contain multiple file types: some being binary patches, others being full copies of files, while others can patch certain parts of a file, like different mods changing different parts of the text_en.str. Maybe something like text_en.str.lines containing "line X=Some text; line2=Some other text".

That sounds good.

But, one possible problem with patching only some part of the file is that, its done assuming the file was in a certain default state. If some extra lines were added by another mod, its hard to know if the lines another mod would change would be the right ones.

However, most of the tools I've made extract things in a format that could be rebuilt with many changes properly and re-distributed.. It might be possible to basically rebuild the ROM piece by piece doing it that way ?

Though, it might be a little extreme.. Especially since we don't know if it will catch on. But, who knows, with the new PMD game coming out, it could possibly be applied to it as well ?

(I'm planning on reversing that game as soon as I can get a decrypted ROM of it ! I sold my old 3DS and well, it was already updated past the firmware that could decrypt games easily.. xD I've actually taken a look at gates to infinity and etrian mystery dungeon and it seems pretty familiar. They even use compiled lua scripts for nearly everything ! The 3d model format looks pretty straightforward as well. But they're using a compression format that I've seen in other chunsoft games, but never found out what it was.. Probably another LZ compression variant. )

When I was thinking about it, I was going to have Sky Editor know how to patch ROMs, but having a patcher inside the mod pack is a good idea. If ndstool and xdelta aren't merged into the installer, the could be tucked away in a directory called "Utilities", while a program called "Patcher" sits in the root of the mod pack, waiting for someone to open it.

Now that you mention it. It might be better not to put too much relatively unrelated features into sky editors, if I could say so. Because SkyEditor seems to be heading towards being more of a powerful dev tool.

And while devs would feel at home with it, the average users might freak out if they're offered too many options. And well, it would be easier to debug two smaller programs and codebases than a single larger one. Anyways, that's just my opinion, feel free to disregard ^^;

But, I digress. I also considered making a patching program, but its way easier to have the patcher distributed in with the rest.. And people wouldn't have to go looking for the right version of the program and etc..

Then again, some people might see it as a security risk.. And they'd be right, really.. given anyone could put whatever they want instead of that utility.. xD

But, the mods could also be applied using an external utility. There would be the light-weight redistributable patcher utility, and the full-size one would combine the patch maker and a patcher together.

So that people that don't want to trust the utility in a modpack can just use the full-size stand-alone program instead. The XML data is really what would contain all the important mod specific details.

And I grabbed the source code for xdelta and ndstools. It shouldn't be too hard to work with and combine them, as long as the dependencies can be dealt with.

EDIT: Scratch that, I took a deeper look and they're both true C-style references salads xD I think it would be much easier to just include them in the same package as separate executable..

If I throw...

[c_name:NPC_BIPPA] is a [c_kind:NPC_BIPPA].[R]I've never met [c_name:NPC_DEBUG] before.[R][c_name:NPC_PARTNER] is my partner!

...into the message log, the game will print it as this.

[ATTACH=CONFIG]12383[/ATTACH]

Those are the entity names used in the scripts ! BIPPA is the name of some Pokemon in romanized japanese.

I guess I was looking at the wrong place on my end.. ^^;

Its interesting that it can work that way too. I thought it could only be used on the speaker..

I didn't know that c_name and c_kind did that in dialog; now I know who conducts the personality quiz![ATTACH=CONFIG]12384[/ATTACH]

lol, nice one. I really wonder what shiftry is up to xD

Edited by psy_commando
Link to comment
Share on other sites

  • 2 weeks later...

So, I've been stuck on a pretty annoying issue..

After writing a library to write soundfont files from scratch (ugh..), I came back to figuring out the missing bits I still haven't figured out in SWDL files.

And I can't figure out how they assign samples to parts of the full note range..

They clearly have something that somehow contains keygroups:

kgrp.png

But I can't think of how this could define a group of key...

Modifying that chunk of data was also kinda inconclusive.. Filling with junk had less effects than expected.

Any ideas ?

EDIT: Figured out how samples are assigned to key ranges. But I still have no ideas what the kgrp chunk is for !

kgrp.png.0caa5fdb1ba139eb0ff61028108641f

Edited by psy_commando
Link to comment
Share on other sites

Finally, got the soundfont format right ! The documentation is so bad... And so vague.. I wonder why DLS didn't get more popular than sounfont ? :/

And I managed to export some midis and play them with the "correct" samples in the sounfont! :

https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/converted_with_audioutil/B_MAP_HOME_01.mp3

https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/converted_with_audioutil/B_DUN_MABOROSHI.mp3

The samples still have bad looping data, no pitch correction, and no envelope and etc.. So they sound pretty bad!

And I've been plagued with this issue where the note pitch keeps climbing and go out of range.. So I'm guessing something might not be right with the octave change events and note octave change parameters..

That, or some samples are automatically shifted down in octave maybe ?

Link to comment
Share on other sites

I managed to somewhat improve conversion a bit. But some things just sound weird, and I don't know what is to blame. The midi exporter, my soundfont library, or the sample parameters not being exported correctly..

Example:

https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/converted_with_audioutil/11_B_SYS_MONSTERHO.mp3

A lot of samples for higher notes are just completely warped and distorted for some reasons..

Link to comment
Share on other sites

Aaannnd I've hit another wall..

This one is much harder to break through though..

I can't get the samples to play at the right pitch.

And some of the synthy samples have some extra processing applied to them, and I can't figure out what is going on..

Also, I can't figure out at all how come an instrument may play a note in the tenth octave.. That's beyond MIDI range, and DSE is largely based around importing MIDI data..

I'm completely stuck again. >_<

And I'm not expecting to have much luck with map tiles either..

That's depressing.. :/

Link to comment
Share on other sites

I've been messing around some more, and apparently, I'm not setting the correct root key for each samples, which makes them play at a too high or too low pitch. Then, my soundfont lib seem to produce soundfonts that are at least partly defective.

I need to find a software that loads soundfonts and that we can get a detailed log, or that has its sources up for download and can be compiled and debugged on windows, with visual studio preferably. But this far, I wasn't too lucky with that. BASSMIDI and Polyphone both load the soundfont with little problems, but as soon as I try it in anything else, like FL studios and LMMS some pretty weird things happen, before it just crashes..

The soundfont format specification is way too cryptic to validate a sf2 using it.. I still can't be sure I truly understand how the format works.

Anyone has any ideas or suggestions ?

Also, does anyone here have any experience with audio signal processing at all ? I'd like to find a way to figure out the root key for each samples, if it turns out that data isn't stored in the SWDL.

Link to comment
Share on other sites

Musescore is able to load soundfonts with built-in Fluidsynth, and it has code on github, but it isn't compiled with Visual Studio in mind.

For your second question, I'm not sure if this is want you want, but throwing a sample into Audacity, then "Analyze"-->"Plot Spectrum...", ending with mousing over the tallest peak can tell the frequency and note being played. If the sample doesn't sound like a tubular bell or other very high pitch sound, then selecting the sample, then "Effect"-->"Change Pitch..." will accomplish the same goal with less finding.

Link to comment
Share on other sites

Musescore is able to load soundfonts with built-in Fluidsynth, and it has code on github, but it isn't compiled with Visual Studio in mind.

For your second question, I'm not sure if this is want you want, but throwing a sample into Audacity, then "Analyze"-->"Plot Spectrum...", ending with mousing over the tallest peak can tell the frequency and note being played. If the sample doesn't sound like a tubular bell or other very high pitch sound, then selecting the sample, then "Effect"-->"Change Pitch..." will accomplish the same goal with less finding.

Thanks. I didn't know audacity could do that, but I was looking for something to use in my utility.

Anyways, it doesn't matter, because I found out where they were storing the root key and pitch correction of each sample ! :D

https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/converted_with_audioutil/2_B_SYS_TRAINING_Pitchfixed.mp3

Now, I just need to fix sample looping, properly convert sample envelopes, and fix issues with some instruments and it should work pretty well !

And I guess I'll give a try to musescore.

Link to comment
Share on other sites

Yet again out of ideas as to how samples loop info is stored.

The two values that do affect the loop points while the game is running don't seem to indicate the actual starting loop point or the loop end point..

I tried multiplying them by 4, to convert the value from adpcm samples to pcm16 samples, the format in which I'm trying to loop them.

I also tried adding both together and using the sample length as end point.

I tried using the values as an offset from the end of the sample.

But nothing worked quite right..

And to top it of, the sf2 files generated by my utility refuses to make its samples loop, unless I load it in Polyphone. And I triple checked to make sure they're loop legal according to the SF2 format's confusing and dumb prerequisites.. :

- At least 8 data points before the begining of the loop

- Loops are at least 32 data points long.

- 8 data points between the loop end and the sample's end.

- And at least 46 zeros after the sample's end..

And, I'm trying to convert the volume envelope to the SF2 format, but the data is stored as 0-127 values, which is just a proportion..

I need to convert those into "timecents" (which I'm not even sure what its supposed to represent..)

Then I tried to convert the value myself using equations I found in the Polyphone's source code, and in GBA Music Ripper, but I didn't get a great result. And I can't just fallback to doing a rule of 3 between proportions, because the envelopes would be linear instead of logarithmic.. (Actually maybe they're linear.. I can't really think of a way to test that out reliably.. But considering the GBA's were apparently logarithmic, they likely are as well..)

So, today I've been trying to reverse the process that decodes the envelope from the ROM. With little success this far..

I tend to step by step through the code, and it takes me hours..

In the end, I ended up with only these notes as the program was handling a split from the flute program while Hidden Land was playing :

[font=Fixedsys]
//
// Ran until break on reading value UNK35 from Program 0x51's first Split.
//
02074E14 E5D41000 ldrb    r1,[r4]                                                   ;2  75 //This reads Unk35, which is 1
02074E18 E3510000 cmp     r1,0h                                                     ;1  76 //This check if Unk35 is 0
02074E1C 0A000025 beq     2074EB8h                                                  ;3  79 //If its 0 run the code at 2074EB8
02074E20 E5D41009 ldrb    r1,[r4,9h]                                                ;2  81 //Read the "attack" byte
02074E24 E3510000 cmp     r1,0h                                                     ;1  82 //Check if "attack" is 0
02074E28 0A000008 beq     2074E50h                                                  ;3  85 //If 0, jump to 2074E50
(JUMP)
   02074E50 E3A015FE mov     r1,3F800000h                                          ;1  101
   02074E54 E5841010 str     r1,[r4,10h]                                           ;1  102 //Put 0x3F800000 right after the envelope of Program 0x51's first Split.
   02074E58 E5D4200C ldrb    r2,[r4,0Ch]                                           ;2  104 //Read "Hold"'s value! (0)
   02074E5C E3520000 cmp     r2,0h                                                 ;1  105 //Check if Hold is 0
   02074E60 0A000004 beq     2074E78h                                              ;3  108 //If Hold is 0, jump to 2074E78
   (JUMP)
       02074E78 E5D4200A ldrb    r2,[r4,0Ah]                                       ;2  119 //Read "Decay"'s value (0)
       02074E7C E3520000 cmp     r2,0h                                             ;1  120 //Check if Decay == 0
       02074E80 0A000004 beq     2074E98h                                          ;3  123 //If its 0, then jump to 2074E98
       (JUMP)
           02074E98 E5D4200D ldrb    r2,[r4,0Dh]                                   ;2  135 //Read "Decay2"'s value (0x7F)
           02074E9C E3A01000 mov     r1,0h                                         ;1  136 //Zero R1
           02074EA0 EBFFFFAC bl      2074D58h                                      ;3  139 //Jump to 2074D58
           (JUMP)
               02074D58 E92D4038 push    r3-r5,r14                                 ;1  44 //Return address is 0x2074EA4
               02074D5C E1A05000 mov     r5,r0                                     ;1  45
               02074D60 E1A04001 mov     r4,r1                                     ;1  46
               02074D64 E352007F cmp     r2,7Fh                                    ;1  47 //Check if Decay2 == 0x7F
               02074D68 1A000004 bne     2074D80h                                  ;3  50 //If Decay2 != 0x7F Jump to 2074D80
               02074D6C E3A00000 mov     r0,0h                                     ;1  51 //
               02074D70 E5850014 str     r0,[r5,14h]                               ;1  52 //Zero out memory location 4 bytes after last envelope parameter
               02074D74 E2400106 sub     r0,r0,80000001h                           ;1  53 //R0 => 0x7FFFFFFF
               02074D78 E5850018 str     r0,[r5,18h]                               ;1  54 //Put 0x7FFFFFFF 8 bytes after last envelope parameter
               02074D7C E8BD8038 pop     r3-r5,r15                                 ;4  58 //Return to 2074EA4
           (RETURN)
           02074EA4 E3A00006 mov     r0,6h                                         ;1  199 //R0 => 6
           02074EA8 E5C4001C strb    r0,[r4,1Ch]                                   ;1  200 //Put 6 right after the 0x7FFFFFFF we just wrote
           02074EAC E3A00001 mov     r0,1h                                         ;1  201 //R0 => 1 
           02074EB0 E5C4001E strb    r0,[r4,1Eh]                                   ;1  202 //Put 1, 2 bytes from where we wrote 6
           02074EB4 E8BD8010 pop     r4,r15                                        ;4  206 //Return to 20747BC
       (RETURN)
020747BC E5940158 ldr     r0,[r4,158h]                                      ;2  82 //We loaded 0, which was stored at 0x22B7BC4 
020747C0 E1500005 cmp     r0,r5                                             ;1  83 //false
020747C4 08BD8038 popeq   r3-r5,r15                                         ;4  87 //Jump to 2074138 if r0 and r5 are equal
020747C8 E59500B4 ldr     r0,[r5,0B4h]                                      ;2  89 //Loaded 0, from 0x22950C
020747CC E5840154 str     r0,[r4,154h]                                      ;1  90 //wrote 0, at 0x22B7BC0
020747D0 E58540B4 str     r4,[r5,0B4h]                                      ;1  91 //
020747D4 E5845158 str     r5,[r4,158h]                                      ;1  92
020747D8 E8BD8038 pop     r3-r5,r15                                         ;4  96 //2074138
(RETURNED)
02074138 EAFFFF80 b       2073F40h                                          ;3  31
(JUMPED)
02073F40 E5D92002 ldrb    r2,[r9,2h]                                        ;2  132 //Overwrote Decay2 in R2, with 0x59
02073F44 E1D930D3 ldrsb   r3,[r9,3h]                                        ;2  134 //R3 => 0x7F
02073F48 E1A00006 mov     r0,r6                                             ;1  135 //
02073F4C E1A01005 mov     r1,r5                                             ;1  136 // 
02073F50 EBFFE59C bl      206D5C8h                                          ;3  139 //
(JUMPED)
   0206D5C8 E92D4008 push    r3,r14                                        ;1  54 //Pushed return address 2073F54 onto the stack
   0206D5CC E5D0E002 ldrb    r14,[r0,2h]                                   ;2  56 //Reads "nbsplits" for Program 0x51!
   0206D5D0 E35E0000 cmp     r14,0h                                        ;1  57 //Check if has no splits
   0206D5D4 03A00000 moveq   r0,0h                                         ;1  58 //If no splits, put 0 in R0
   0206D5D8 08BD8008 popeq   r3,r15                                        ;4  62 //If no splits, return
   0206D5DC E3510000 cmp     r1,0h                                         ;1  63 // 0x228137C != 0 (This is probably a pointer ? being checked for being null ?)
   0206D5E0 02801060 addeq   r1,r0,60h                                     ;1  64 //Add R1 to R0 if R0 was not null, and R1 is null !
   0206D5E4 0A000005 beq     206D600h                                      ;3  67 //
   0206D5E8 E5D1C001 ldrb    r12,[r1,1h]                                   ;2  69 // Reads the first Split's ID
   0206D5EC E24E0001 sub     r0,r14,1h                                     ;1  70 // R0 = 1 - 1
   0206D5F0 E15C0000 cmp     r12,r0                                        ;1  71 // 0 == 0
   0206D5F4 A3A00000 movge   r0,0h                                         ;1  72 // True
   0206D5F8 A8BD8008 popge   r3,r15                                        ;4  76 //Returns to 2073F54
   (RETURNED)
02073F54 E1B05000 movs    r5,r0                                             ;1  25 //
02073F58 028DD00C addeq   r13,r13,0Ch                                       ;1  26 // R13 => 229AAA8 + 0xC => 229AAB4
02073F5C 08BD8FF0 popeq   r4-r11,r15                                        ;4  145 //Return to 2071398
(RETURN 2071398)[/font]

If anyone wants to give it a go themselves, feel free to set a few breakpoints on some of these lines and see if you can't find anything.

Link to comment
Share on other sites

Alright !

So I made a bit more progress.

I found out that envelope parameters are actually used as indexes in 2 possible 128 slots lookup tables.

I noticed that, the values I labeled unk35 to unk38 are in fact multipliers for the envelope parameters. And whether the values are 0, or not decides what lookup table to use to get the duration of the envelope paramter.

If its 0, we get the value from the 32 bits lookup table:

   
   (Located at 0x20B1050)
[font=Fixedsys]
   const int32_t Duration_Lookup_Table_B1 [128] =
   {
       0x00000000, 0x00000004, 0x00000007, 0x0000000A, 
       0x0000000F, 0x00000015, 0x0000001C, 0x00000024, 
       0x0000002E, 0x0000003A, 0x00000048, 0x00000057, 
       0x00000068, 0x0000007B, 0x00000091, 0x000000A8, 
       0x00000185, 0x000001BE, 0x000001FC, 0x0000023F, 
       0x00000288, 0x000002D6, 0x0000032A, 0x00000385, 
       0x000003E5, 0x0000044C, 0x000004BA, 0x0000052E, 
       0x000005A9, 0x0000062C, 0x000006B5, 0x00000746, 
       0x00000BCF, 0x00000CC0, 0x00000DBD, 0x00000EC6, 
       0x00000FDC, 0x000010FF, 0x0000122F, 0x0000136C, 
       0x000014B6, 0x0000160F, 0x00001775, 0x000018EA, 
       0x00001A6D, 0x00001BFF, 0x00001DA0, 0x00001F51, 
       0x00002C16, 0x00002E80, 0x00003100, 0x00003395, 
       0x00003641, 0x00003902, 0x00003BDB, 0x00003ECA, 
       0x000041D0, 0x000044EE, 0x00004824, 0x00004B73, 
       0x00004ED9, 0x00005259, 0x000055F2, 0x000059A4, 
       0x000074CC, 0x000079AB, 0x00007EAC, 0x000083CE, 
       0x00008911, 0x00008E77, 0x000093FF, 0x000099AA, 
       0x00009F78, 0x0000A56A, 0x0000AB80, 0x0000B1BB, 
       0x0000B81A, 0x0000BE9E, 0x0000C547, 0x0000CC17, 
       0x0000FD42, 0x000105CB, 0x00010E82, 0x00011768, 
       0x0001207E, 0x000129C4, 0x0001333B, 0x00013CE2, 
       0x000146BB, 0x000150C5, 0x00015B02, 0x00016572, 
       0x00017015, 0x00017AEB, 0x000185F5, 0x00019133, 
       0x0001E16D, 0x0001EF07, 0x0001FCE0, 0x00020AF7, 
       0x0002194F, 0x000227E6, 0x000236BE, 0x000245D7, 
       0x00025532, 0x000264CF, 0x000274AE, 0x000284D0, 
       0x00029536, 0x0002A5E0, 0x0002B6CE, 0x0002C802, 
       0x000341B0, 0x000355F8, 0x00036A90, 0x00037F79, 
       0x000394B4, 0x0003AA41, 0x0003C021, 0x0003D654, 
       0x0003ECDA, 0x000403B5, 0x00041AE5, 0x0004326A, 
       0x00044A45, 0x00046277, 0x00047B00, 0x7FFFFFFF
   };[/font]

If its not 0, we get the value from the 16 bits lookup table:

   (Located at 0x20B0F50)
[font=Fixedsys]
   const int16_t Duration_Lookup_Table_A1 [128] = 
   {
       0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 
       0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 
       0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 
       0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 
       0x0020, 0x0023, 0x0028, 0x002D, 0x0033, 0x0039, 0x0040, 0x0048, 
       0x0050, 0x0058, 0x0062, 0x006D, 0x0078, 0x0083, 0x0090, 0x009E, 
       0x00AC, 0x00BC, 0x00CC, 0x00DE, 0x00F0, 0x0104, 0x0119, 0x012F, 
       0x0147, 0x0160, 0x017A, 0x0196, 0x01B3, 0x01D2, 0x01F2, 0x0214, 
       0x0238, 0x025E, 0x0285, 0x02AE, 0x02D9, 0x0307, 0x0336, 0x0367, 
       0x039B, 0x03D1, 0x0406, 0x0442, 0x047E, 0x04C4, 0x0500, 0x0546, 
       0x058C, 0x0622, 0x0672, 0x06CC, 0x071C, 0x0776, 0x07DA, 0x0834, 
       0x0898, 0x0906, 0x096A, 0x09D8, 0x0A50, 0x0ABE, 0x0B40, 0x0BB8, 
       0x0C3A, 0x0CBC, 0x0D48, 0x0DDE, 0x0E6A, 0x0F00, 0x0FA0, 0x1040, 
       0x10EA, 0x1194, 0x123E, 0x12F2, 0x13B0, 0x146E, 0x1536, 0x15FE, 
       0x16D0, 0x17A2, 0x187E, 0x195A, 0x1A40, 0x1B30, 0x1C20, 0x1D1A, 
       0x1E1E, 0x1F22, 0x2030, 0x2148, 0x2260, 0x2382, 0x2710, 0x7FFF
   };[/font]

The locations are static, so its at the same place at every restarts.

There's also another large static table that is used to both store and load sound related data from, its located at 0x22B7330.

However, after looking up the value in the 16 bits Duration_Lookup table, a lot of things are happening to the values and I can't really tell what exactly.. First it gets multiplied by the corresponding multiplier stored between Unk35 to Unk38(I think), then, its multiplied by 1,000.

(Jumped in from 0x02074D8C)
[font=Fixedsys]
02074DB0 E59F0050 ldr     r0,=20B0F50h  //Load the address of "Attack_Lookup_Table_A1"
02074DB4 E1A01082 mov     r1,r2,lsl 1h  //Shift "Attack" by one to the left, and put it in R1. R1 = 0x46. (Equivalent to multiplying by 2 !(Accessing the table by index wouldn't require multiplying))
02074DB8 E19020B1 ldrh    r2,[r0,r1]    //Load unsigned half-word, from lookup table #1 at 0x20B0F50 + 0x46( the "Attack" value multiplied by 2 ). R2 = 0x2D
02074DBC E59F1040 ldr     r1,=22B7330h  //Set R1 to the address of a static table in memory where we store our envelope stuff
02074DC0 E3A00FFA mov     r0,3E8h       //R0 = 0x3E8 (1,000)
02074DC4 E0020293 mul     r2,r3,r2      //R2 = 1    * 0x2D  ( Unk36 * ConvertedAttackValueFromLookupTable )
02074DC8 E0000092 mul     r0,r2,r0      //R0 = 0x2D(45) * 0x3E8(1,000) => 0xAFC8(45,000)
02074DCC E1D112F8 ldrsh   r1,[r1,28h]   //Load Signed Half-word from That static table at 0x22B7330. At byte 0x28. R1 = 0x2710 (10,000)
02074DD0 EB006C33 bl      208FEA4h      //Exec sub-routine at 208FEA4[/font]
..

..But then the function at 208FEA4 is executed, and I have no clue what it does..

The value inside R0 is stored to memory after the function has run its course. When I ran it, it returned me 4 through R0..

I have no clues what could return 4 from 45,000, and be this elaborate..

(Sorry for the messy notes btw.. I just tend to put lots of stuff to keep track of what is going on.)

[font=Fixedsys]
(BRANCH and LINK 0208FEA4 ( R14 = 2074E4C -> 2074DD4, R15 = 2074DD0 -> 0208FEA4 ) )
   0208FEA4 E020C001 eor     r12,r0,r1          //R12 = 0xAFC8(45,000) ^ 0x2710(10,000)  => 0x88D8(35,032)
   0208FEA8 E20CC102 and     r12,r12,80000000h  //R12 = 0x88D8 & 0x80000000 => 0
   0208FEAC E3500000 cmp     r0,0h              //Check if 0xAFC8 < 0
   if( R0 < 0 )
   {
       0208FEB0 B2600000 rsblt   r0,r0,0h       //RSB – Reverse Subtract, R0 = (0 - R0)
       0208FEB4 B28CC001 addlt   r12,r12,1h 
   }
   0208FEB8 E3510000 cmp     r1,0h              //Check if 0x2710 < 0
   if( R1 < 0 )
   {
       0208FEBC B2611000 rsblt   r1,r1,0h       //RSB – Reverse Subtract, R1 = (0 - R1)
       0208FEC0 0A000075 beq     209009Ch 
   }
   0208FEC4 E1500001 cmp     r0,r1
   if( ( (R0 - R1) < 0xFFFFFFFF ) (Actually more like !C) //Check if result of subtraction(cmp subtract its operands) was smaller than 32 bits(No carry)
   {
       0208FEC8 31A01000 movcc   r1,r0 
       0208FECC 33A00000 movcc   r0,0h 
       0208FED0 3A000071 bcc     209009Ch 
   }
   0208FED4 E3A0201C mov     r2,1Ch             //R2 = 0x1C.
   0208FED8 E1A03220 mov     r3,r0,lsr 4h       //R3 = 0xAFC8(45,000) >> 4   => 0xAFC(2,812) (Remove the lowest 4 bits)
   0208FEDC E1510623 cmp     r1,r3,lsr 0Ch      //0x2710 - ( 0xAFC(2,812) >> 0xC )(Keep only the highest 3 bits ) )
   if( ( 0x2710 - (0xAFC >> 12) ) == 0 || ( ( 0x2710 - (0xAFC >> 12) ) < 0) != (( 0x2710 - (0xAFC >> 12) ) < 0x7FFFFFFF) ) //Approximation
   {
       0208FEE0 D2422010 suble   r2,r2,10h      //
       0208FEE4 D1A03823 movle   r3,r3,lsr 10h  //
   }
   0208FEE8 E1510223 cmp     r1,r3,lsr 4h       // 0x2710 - ( 0xAFC >> 4 )
   if( ( 0x2710 - (0xAFC >> 4) ) == 0 || ( ( 0x2710 - (0xAFC >> 4) ) < 0) != (( 0x2710 - (0xAFC >> 4) ) <= 0x7FFFFFFF) )
   {
       0208FEEC D2422008 suble   r2,r2,8h
       0208FEF0 D1A03423 movle   r3,r3,lsr 8h
   }
   0208FEF4 E1510003 cmp     r1,r3              //(0x2710 - 0xAFC)
   if( (0x2710 - 0xAFC) == 0 || ( (0x2710 - 0xAFC) < 0 ) != ( (0x2710 - 0xAFC) <= 0x7FFFFFFF ) )
   {
       0208FEF8 D2422004 suble   r2,r2,4h
       0208FEFC D1A03223 movle   r3,r3,lsr 4h
   }
   0208FF00 E1A00210 mov     r0,r0,lsl r2      //R0  = 0xAFC8(45,000) << 0x1C   => 0x80000000
   0208FF04 E2611000 rsb     r1,r1,0h          //R1  = (0 - 0x2710)             => 0xFFFFD8F0
   0208FF08 E0900000 adds    r0,r0,r0          //R0  = 0x80000000 + 0x80000000  => 0
   0208FF0C E0822082 add     r2,r2,r2,lsl 1h   //R2  = 0x1C + (0x1C << 1)       => 0x54
   0208FF10 E08FF102 add     r15,r15,r2,lsl 2h //R15 = 0x0208FF10 + (0x54 << 2) => 0x2090060
   0208FF14 E1A00000 nop                       //Apparently the No-op here got R15 to +4, before it was incremented by +4, so we landed at 0x2090068 !!

   //The next part is a loop done 31 times at most. The operation that modifies R15 basically sets the amount of times to iterate through the loop
   {
       0208FF18 E0B13083 adcs    r3,r1,r3,lsl 1h               
       0208FF1C 30433001 subcc   r3,r3,r1
       0208FF20 E0B00000 adcs    r0,r0,r0        

       0208FF24 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF28 30433001 subcc   r3,r3,r1        
       0208FF2C E0B00000 adcs    r0,r0,r0     

       0208FF30 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF34 30433001 subcc   r3,r3,r1        
       0208FF38 E0B00000 adcs    r0,r0,r0     

       0208FF3C E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF40 30433001 subcc   r3,r3,r1        
       0208FF44 E0B00000 adcs    r0,r0,r0   

       0208FF48 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF4C 30433001 subcc   r3,r3,r1        
       0208FF50 E0B00000 adcs    r0,r0,r0       

       0208FF54 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF58 30433001 subcc   r3,r3,r1        
       0208FF5C E0B00000 adcs    r0,r0,r0    

       0208FF60 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF64 30433001 subcc   r3,r3,r1        
       0208FF68 E0B00000 adcs    r0,r0,r0      

       0208FF6C E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FF70 30433001 subcc   r3,r3,r1        
       0208FF74 E0B00000 adcs    r0,r0,r0       

       0208FF78 E0B13083 adcs    r3,r1,r3,lsl 1h
       0208FF7C 30433001 subcc   r3,r3,r1       
       0208FF80 E0B00000 adcs    r0,r0,r0      

       0208FF84 E0B13083 adcs    r3,r1,r3,lsl 1h
       0208FF88 30433001 subcc   r3,r3,r1       
       0208FF8C E0B00000 adcs    r0,r0,r0       

       0208FF90 E0B13083 adcs    r3,r1,r3,lsl 1h
       0208FF94 30433001 subcc   r3,r3,r1       
       0208FF98 E0B00000 adcs    r0,r0,r0     

       0208FF9C E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFA0 30433001 subcc   r3,r3,r1        
       0208FFA4 E0B00000 adcs    r0,r0,r0    

       0208FFA8 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFAC 30433001 subcc   r3,r3,r1        
       0208FFB0 E0B00000 adcs    r0,r0,r0      

       0208FFB4 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFB8 30433001 subcc   r3,r3,r1        
       0208FFBC E0B00000 adcs    r0,r0,r0        

       0208FFC0 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFC4 30433001 subcc   r3,r3,r1        
       0208FFC8 E0B00000 adcs    r0,r0,r0        

       0208FFCC E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFD0 30433001 subcc   r3,r3,r1        
       0208FFD4 E0B00000 adcs    r0,r0,r0        

       0208FFD8 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0208FFDC 30433001 subcc   r3,r3,r1        
       0208FFE0 E0B00000 adcs    r0,r0,r0        

       0208FFE4 E0B13083 adcs    r3,r1,r3,lsl 1h
       0208FFE8 30433001 subcc   r3,r3,r1       
       0208FFEC E0B00000 adcs    r0,r0,r0        

       0208FFF0 E0B13083 adcs    r3,r1,r3,lsl 1h
       0208FFF4 30433001 subcc   r3,r3,r1       
       0208FFF8 E0B00000 adcs    r0,r0,r0       

       0208FFFC E0B13083 adcs    r3,r1,r3,lsl 1h
       02090000 30433001 subcc   r3,r3,r1        
       02090004 E0B00000 adcs    r0,r0,r0        

       02090008 E0B13083 adcs    r3,r1,r3,lsl 1h
       0209000C 30433001 subcc   r3,r3,r1        
       02090010 E0B00000 adcs    r0,r0,r0        

       02090014 E0B13083 adcs    r3,r1,r3,lsl 1h
       02090018 30433001 subcc   r3,r3,r1        
       0209001C E0B00000 adcs    r0,r0,r0        

       02090020 E0B13083 adcs    r3,r1,r3,lsl 1h
       02090024 30433001 subcc   r3,r3,r1       
       02090028 E0B00000 adcs    r0,r0,r0       

       0209002C E0B13083 adcs    r3,r1,r3,lsl 1h 
       02090030 30433001 subcc   r3,r3,r1        
       02090034 E0B00000 adcs    r0,r0,r0        

       02090038 E0B13083 adcs    r3,r1,r3,lsl 1h 
       0209003C 30433001 subcc   r3,r3,r1        
       02090040 E0B00000 adcs    r0,r0,r0         

       02090044 E0B13083 adcs    r3,r1,r3,lsl 1h 
       02090048 30433001 subcc   r3,r3,r1       
       0209004C E0B00000 adcs    r0,r0,r0         

       02090050 E0B13083 adcs    r3,r1,r3,lsl 1h 
       02090054 30433001 subcc   r3,r3,r1        
       02090058 E0B00000 adcs    r0,r0,r0        

       0209005C E0B13083 adcs    r3,r1,r3,lsl 1h 
       02090060 30433001 subcc   r3,r3,r1       
       02090064 E0B00000 adcs    r0,r0,r0        

       /////////////////////////////////////////////// Entered loop here !! ////////////////////////////
       02090068 E0B13083 adcs    r3,r1,r3,lsl 1h //R3 = 0xFFFFD8F0 + ( 0xAFC << 1 ) + Carry(1)  => 0xFFFFEEE9 (N flag ticked)
       if( R3 < 0  ) (Actually more like !C)     // 0xFFFFEEE9 (-4,375) is smaller than 0 !!
           0209006C 30433001 subcc   r3,r3,r1    //R3 = 0xFFFFEEE9(-4,375) - 0xFFFFD8F0(-10,000) => 0x15F9(5,625)
       02090070 E0B00000 adcs    r0,r0,r0        //R0 = 0 + 0 + Carry(0) => 0

       02090074 E0B13083 adcs    r3,r1,r3,lsl 1h //R3 = 0xFFFFD8F0(-10,000) + ( 0x15F9(5,625) << 1 )(11,250) + Carry(0) => 0x4E2(1,250) (C flag ticked)
       if( R3 < 0 ) (Actually more like !C) 
           02090078 30433001 subcc   r3,r3,r1        
       0209007C E0B00000 adcs    r0,r0,r0        //R0 = 0 + 0 + Carry (1) => 1

       02090080 E0B13083 adcs    r3,r1,r3,lsl 1h //R3 = 0xFFFFD8F0(-10,000) + ( 0x4E2(5,625) << 1 )(2,500) + Carry(0) =>  0xFFFFE2B4(-7,500) (C=1)
       if( R3 < 0 ) (Actually more like !C)      // -7,500 is smaller than 0
           02090084 30433001 subcc   r3,r3,r1    //R3 = 0xFFFFE2B4(-7,500) - 0xFFFFD8F0(-10,000) => 0x9C4(2,500)
       02090088 E0B00000 adcs    r0,r0,r0        //R0 = 1 + 1 + Carry(0) => 2
   }

   0209008C E0B13083 adcs    r3,r1,r3,lsl 1h     //R3 = 0xFFFFD8F0(-10,000) + ( 0x9C4 << 1 )(5,000) + Carry(0) => 0xFFFFEC78(-5,000)
   if( R3 < 0 ) (Actually more like !C)          //-5,000 is smaller than 0
       02090090 30433001 subcc   r3,r3,r1        //R3 = 0xFFFFEC78(-5,000) - 0xFFFFD8F0(-10,000) => 0x1388(5,000)
   02090094 E0B00000 adcs    r0,r0,r0            //R0 = 2 + 2 + Carry(0) => 4
   02090098 E1A01003 mov     r1,r3               //R1 = 0x1388(5,000)
   0209009C E21C3102 ands    r3,r12,80000000h    //R3 = 0 & 0x80000000 => 0 (Z=1,C=1)
   if( !Z )
       020900A0 12600000 rsbne   r0,r0,0h        //
   020900A4 E21C3001 ands    r3,r12,1h           //R3 = 0 & 1 (Z=1,C=1)
   if( !Z )
       020900A8 12611000 rsbne   r1,r1,0h           
   020900AC E12FFF1E bx      r14                 //Return from subroutine to 0x2074DD4
(RETURN to 2074DD4, from 0208FEA4 )
[/font]

Link to comment
Share on other sites

I have been waiting for this for a long time. I once tried to figure out the smd files, but that didn't go well ^^'

A lot of samples for higher notes are just completely warped and distorted for some reasons..

The soundfont may have different samples for higher pitches. I remember seeing im Metroid Prime: Hunters' soundfont having C3, C4 and C5 notes for the choir sounds to prevent them from sounding terrible on higher pitches. I think the DS Castlevanias do that too. If there is no indication for it in the smd file, it may very well be that it's hard coded to the sound engine to change the sample depending on what octave the note is played on or something like that.

I have actually made few MIDIs out of PMD2 by separating the audio channels and listening to them individually. They may not be perfect, but they may help when trying to find flaws in the conversion. It was a little slow to make the MIDIs due to the DS playing the sounds on first free channel instead of using channel per instrument, which causes different instrument sounds "all over the place".

I attached all the MIDIs I have finished so far. Hope they are useful :)

Midi.zip

Midi.zip

Link to comment
Share on other sites

When you realize that elusive, very complex, function 0x0208FEA4 you've been trying to figure out was actually the NDS's implementation of a signed division.. xD

Alright, so after much messing around in the game's code, I managed to kind of understand how durations are parsed from the envelope.

The value of the envelope parameter for durations is used as index in one of two table containing a 16 bits value, and the other a 32 bits value.

If the value is obtained from the 16 bits table, its multiplied by 1,000, and divided by 10,000. The result is used in a counter for each phases of the envelope.

Though, now the challenge is figuring out, how to turn that value into seconds ? Those aren't DSE ticks, like the music track uses..

I also figured out the loop point data. I decided to take a look at the NDS's sound registers while it was playing a sample I knew about. It turns out the loop data from the SWD is just copied exactly as-is into the sound registers. And they're in int32, not in bytes or samples..

Link to comment
Share on other sites

I have been waiting for this for a long time. I once tried to figure out the smd files, but that didn't go well ^^'

The soundfont may have different samples for higher pitches. I remember seeing im Metroid Prime: Hunters' soundfont having C3, C4 and C5 notes for the choir sounds to prevent them from sounding terrible on higher pitches. I think the DS Castlevanias do that too. If there is no indication for it in the smd file, it may very well be that it's hard coded to the sound engine to change the sample depending on what octave the note is played on or something like that.

I have actually made few MIDIs out of PMD2 by separating the audio channels and listening to them individually. They may not be perfect, but they may help when trying to find flaws in the conversion. It was a little slow to make the MIDIs due to the DS playing the sounds on first free channel instead of using channel per instrument, which causes different instrument sounds "all over the place".

I attached all the MIDIs I have finished so far. Hope they are useful :)

[ATTACH]12432[/ATTACH]

Wow.. you ninja-ed me xD

Well, it wasn't easy to get this far, don't worry too much about it xD

I've been trying on and off to figure them out for over a year now. I had to do a lot of trial and error and modifying the files myself to play specific notes or events. And nobody apparently even bothered trying reversing the DSE format before, so you had to start from scratch xD

Here's the dropbox link to my current notes, if you're interested : https://dl.dropboxusercontent.com/u/13343993/my_pmd_research_files/PMD2_MusicAndSoundFormats.txt

Its always fairly messy and not always fully up to date though. And I constantly write and erase stuff as I go.

But anyways. I mostly fixed the issues with the notes, it was mainly the root note, and pitch correction of each samples that wasn't parsed correctly.

There is still a bit of an issue with the pitch correction, because the NDS can do 255 semitones pitch correction while soundfont supports like 2 per sample. So I have to use the per instrument pitch correction, but then its limited between -120 and 120.. I might shift the root note with the remaining semitones, but IDK if it wouldn't mess things up more..

And all the sample/preset/splits data is located in the matching SWD, and in the master SWD file. The SMD is strictly for the event tracks.

And yeah, those are called "Splits" by the DSE devs. And the Sounfont format has a very unintuitive way of handling those, dealing with bag and zone nonsense when things could have been so much simpler.. I wish DLS had caught on xD

I'm actually dealing with issues with the sounfont format itself now.. Samples won't loop at all for some reasons, in anything but polyphone. And loops are still creating audio artifacts even though I followed their sample looping requirements..

I couldn't find a single library to write those to disk. And the official documentation is terrible. :/

And thanks for the midis, but I'll probably be fine at this point. Several months ago someone linked me to an utility that could rip the PMD2 midis, and we got some pretty accurate midis from those. Though it didn't do anything about the samples at all.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...