Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Platinum Lucario

#1
Programming / Re: BrawlBox v0.78
April 15, 2017, 12:58:01 PM
I've just noticed something quite interesting about CSTMs. I've found that Pokémon Mystery Dungeon: Gates to Infinity's CSTMs use a different encoding than Pokémon Super Mystery Dungeon's CSTMs.

Pokémon Super Mystery Dungeon's CSTMs use ADPCM, where as Gates to Infinity's CSTMs use an original NW4C encoding. Probably because the developers of that said game were using a much earlier version of NW4C Soundmaker at the time of developing Gates to Infinity.

Here's two CSTMs of Ragged Mountain in which I can show you the difference and compare them with the Super Mystery Dungeon's version.

BGM_DUN_01 (Original NW4C/Gates to Infinity version):

43 53 54 4D FF FE 40 00 00 00 01 02 40 B2 1D 00
03 00 00 00 00 40 00 00 40 00 00 00 20 01 00 00
01 40 00 00 60 01 00 00 C0 03 00 00 02 40 00 00
20 05 00 00 20 AD 1D 00 00 00 00 00 00 00 00 00
49 4E 46 4F 20 01 00 00 00 41 00 00 18 00 00 00
01 01 00 00 5C 00 00 00 01 01 00 00 68 00 00 00
02 01 02 00 C0 5D 00 00 00 48 03 00 B4 F6 19 00
77 00 00 00 00 20 00 00 00 38 00 00 67 16 00 00
B4 26 00 00 80 16 00 00 04 00 00 00 00 38 00 00
00 1F 00 00 18 00 00 00 00 01 00 00 00 00 00 00
FF FF FF FF 01 00 00 00 01 41 00 00 20 00 00 00
02 00 00 00 02 41 00 00 28 00 00 00 02 41 00 00
30 00 00 00 7F 40 00 00 00 01 00 00 0C 00 00 00
02 00 00 00 00 01 00 00 00 03 00 00 10 00 00 00
00 03 00 00 36 00 00 00 E3 01 EB 00 2C 0A EC FA
00 07 CA FE F0 0C 31 FA F5 03 48 02 AE 0B C3 FA
2D 08 2B FF 33 0D 77 FA 00 00 00 00 00 00 28 00
19 EA 38 E9 00 00 0E 02 B1 00 FD 09 F4 FA 47 07
7F FE FE 0C 20 FA 47 04 AF 01 94 0B D6 FA 4D 08
02 FF 4C 0D 5D FA 00 00 00 00 00 00 78 00 8A D3
19 D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
53 45 45 4B


BGM_DUN_01_OLD (ADPCM/Super Mystery Dungeon version):
43 53 54 4D FF FE 40 00 00 00 03 02 20 B2 1D 00
03 00 00 00 00 40 00 00 40 00 00 00 00 01 00 00
01 40 00 00 40 01 00 00 C0 03 00 00 02 40 00 00
00 05 00 00 20 AD 1D 00 00 00 00 00 00 00 00 00
49 4E 46 4F 00 01 00 00 00 41 00 00 18 00 00 00
00 00 00 00 FF FF FF FF 01 01 00 00 64 00 00 00
02 01 02 00 C0 5D 00 00 00 48 03 00 B4 F6 19 00
77 00 00 00 00 20 00 00 00 38 00 00 67 16 00 00
B4 26 00 00 80 16 00 00 04 00 00 00 00 38 00 00
00 1F 00 00 18 00 00 00 00 01 00 00 00 00 00 00
FF FF FF FF 3D 31 03 00 F1 DF 19 00 02 00 00 00
02 41 00 00 14 00 00 00 02 41 00 00 1C 00 00 00
00 03 00 00 10 00 00 00 00 03 00 00 36 00 00 00
E3 01 EB 00 2C 0A EC FA 00 07 CA FE F0 0C 31 FA
F5 03 48 02 AE 0B C3 FA 2D 08 2B FF 33 0D 77 FA
00 00 00 00 00 00 28 00 19 EA 38 E9 00 00 0E 02
B1 00 FD 09 F4 FA 47 07 7F FE FE 0C 20 FA 47 04
AF 01 94 0B D6 FA 4D 08 02 FF 4C 0D 5D FA 00 00
00 00 00 00 78 00 8A D3 19 D0 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
53 45 45 4B


The differences between the two are at 0x0A, 0x0C, 0x1C, 0x24, 0x30, 0x50-51, 0x54-57, 0x5C, 0xA4-A6, 0xA8-AA, 0xAC, 0xB1, 0xB4-B5, 0xB8-B9, 0xBC-BD, 0xC0-C1, 0xC4-C5, 0xC9, 0xCC, 0xD0-D2, 0xD4-D7, 0xD9-E0, 0xE2-EE, 0xF1-11C, 0x11E-135, 0x13C, 0x13E-145, 0x150, 0x152, 0x154, 0x156, 0x158-15C and 0x15E-163.

SEEK begins at 0x160 on the non-ADPCM one (Gates to Infinity), while SEEK begins at 0x140 in ADPCM encoded CSTMs (Super Mystery Dungeon. INFO is located at 0x40 in both versions. It seems the INFO section in the non-ADPCM ones are literally 30 bytes larger than ADPCM ones. And somehow, the program mistakes the non-ADPCM file for having more than one stream, which it clearly doesn't, therefore showing this text:
System.Exception: BCSTM files with more than one track data section are not supported.

at BrawlLib.SSBBTypes.CSTMINFOHeader.get_ChannelInfoRefTable()
at BrawlLib.SSBBTypes.CSTMINFOHeader.get_ChannelInfoRefTableEnd()
at BrawlLib.SSBBTypes.CSTMINFOHeader.get_ChannelInfoEntries()
at BrawlLib.SSBBTypes.CSTMINFOHeader.Get_ChannelInfo(Int32 index)
at BrawlLib.Wii.Audio.CSTMConverter.ToRSTM(CSTMHeader* cstm)
at BrawlLib.SSBB.ResourceNodes.RSTMNode.OnInitialize()
at BrawlLib.SSBB.ResourceNodes.ResourceNode.Initialize(ResourceNode parent, DataSource origSource, DataSource uncompSource)
at BrawlLib.SSBB.ResourceNodes.NodeFactory.FromSource(ResourceNode parent, DataSource source)
at BrawlLib.SSBB.ResourceNodes.NodeFactory.FromFile(ResourceNode parent, String path, FileOptions options)
at BrawlBox.Program.Open(String path)


It's probably bringing up this error, because multistream is supported, but it just can't find the extra stream that it the program thinks that it has to initialise and convert to RSTM.

Perhaps one day in the future, the file responsible for encoding for RSTM can also have CSTM and FSTM variants.
#2
Programming / Re: BrawlBox v0.78
March 14, 2017, 02:35:15 PM
Quote from: libertyernie on March 13, 2017, 02:09:27 AM
Also, I'm surprised that CSTM and FSTM would be the same, since I added a field at the beginning of a struct that wasn't there before. Someone might need to take a look at the code and see if they can get it working.

I'm pretty sure the same thing that you did to RSTM is identical to the problem that FSTM has. But as for CSTM, it doesn't have any clicking noise, so it doesn't seem to have any issue in it's encoding.

I just need to find where the code is for encoding CSTMs and FSTMs, then I'll experiment around with them and see if I can fix the issue.
#3
Programming / Re: BrawlBox v0.78
March 12, 2017, 11:56:03 AM
Okay, testing RSTM files... They seem to be out of position in their music offsets, causing the clicking noise.

Testing CSTMs and FSTMs... It seems there's been no changes between 0.77 and 0.78 in regards to FSTM and CSTM playback. CSTMs with more than one track data are still not supported yet (single track data CSTMs however work without any problems so far), FSTMs still have the clicking noise (since their offsets are still in the same position as in 0.77, no changes have been made).

Comparing the difference in the changes between 0.77 and 0.78 in regards to RSTM playback, it seems some code has been altered in that area that determines where the waveform offset starts at. I'm glad the bug has been successfully replicated, so now it will be easier to fix the issue with the FSTMs and revert the changes with the RSTMs.

Also, just to note. The code for FSTMs and CSTMs still has "NW4R Node" in the info, instead of it saying "NW4C Node" or "NW4F Node".
#4
Quote from: libertyernie on March 06, 2017, 02:01:04 PM
Do you have a link to some sort of specification for the header formats?

I assume vgmstream doesn't have this problem, right?

I'm pretty sure vgmstream doesn't have this issue. And neither does Uwizard.

As for specifications, I can't actually find any, but the closest I can get to, in regards to this format is here: https://gitlab.kode54.net/kode54/vgmstream/blob/master/src/meta/bcstm.c and here: https://gitlab.kode54.net/kode54/vgmstream/blob/master/src/meta/bfstm.c

I would assume there might be some documentation in the NW4C and NW4F official SoundMaker documents. I'll check them out and post it here when I've found it.

Edit: Found it!

Cafe DSP-ADPCM Stream (FSTM):
Sorry but you are not allowed to view spoiler contents.


CTR DSP-ADPCM Stream (CSTM):
[spoiler]
QuoteOverview
WaveCodecCtr.dll is a Win32 run-time dynamic link library (DLL). This library provides an API for encoding and decoding between 16-bit signed (little-endian) sampling data and the DSP ADPCM compressed format.
The DSP ADPCM format is a compressed format that can be played by a CTR system's DSP. It can result in data sizes that are 1/3.5 of 16-bit PCM. This format can be used through the nn::snd::Voice class and the nn::snd::WaveBuffer structure.
This DLL is for developers who want to develop their own tools for creating and previewing DSP ADPCM sampling data. Note that this DLL is single-threaded.
ctr_WaveConverter uses the features of this DLL to create BCWAV files in ADPCM format.
Exported Functions
getBytesForAdpcmBuffer
getBytesForAdpcmSamples
getBytesForPcmBuffer
getBytesForPcmSamples
getSampleForAdpcmNibble
getNibbleAddress
getLoopContext
encode
decode
getBytesForAdpcmBuffer
Syntax
u32 getBytesForAdpcmBuffer(u32 samples);
Arguments

NameDescription
insamplesNumber of (16-bit PCM) samples to encode.
Return Values
The number of bytes required to store the DSP ADPCM-encoded samples.
Description
getBytesForAdpcmBuffer calculates and returns the number of bytes required to store the sampling data that are being DSP ADPCM-encoded.
Note that the number of bytes will be a multiple of the 8-byte length of DSP ADPCM frames.
The actual length (in bytes) of the sampling data being encoded will probably be smaller than the value returned by this function. (For more information, see the section getBytesForAdpcmSamples.)
Use this function before encoding to allocate storage for the result.
getBytesForAdpcmSamples
Syntax
u32 getBytesForAdpcmSamples(u32 samples);
Arguments

NameDescription
insamplesNumber of (16-bit PCM) samples to encode.
Return Values
The actual length (in bytes) of the encoded sampling data.
Description
getBytesForAdpcmSamples returns the actual number of bytes occupied by the sampling data that are being DSP ADPCM-encoded.
getBytesForPcmBuffer
Syntax
u32 getBytesForPcmBuffer(u32 samples);
Arguments

NameDescription
insamplesThe number of samples being decoded.
Return Values
The predicted length (in bytes) of the DSP ADPCM sampling data being decoded.
Description
getBytesForPcmBuffer calculates and returns the number of bytes of data that must be stored after the number of samples passed in the argument have been decoded from the DSP ADPCM format.
getBytesForPcmSamples
Syntax
u32 getBytesForPcmSamples(u32 samples);
Arguments

NameDescription
insamplesNumber of samples.
Return Values
The length (in bytes) of the value specified in the sampling data.
Description
getBytesForPcmSamples returns the number of bytes for copying the specified user application value in the sampling data.
getSampleForAdpcmNibble
Syntax
u32 getSampleForAdpcmNibble(u32 nibble);
Arguments

NameDescription
innibbleADPCM nibble offset, including frame headers.
Return Values
The zero-based sample index for the corresponding ADPCM sampling data.
Description
getSampleForAdpcmNibble returns the number of zero-based samples for the corresponding ADPCM nibble.
getNibbleAddress
Syntax
u32 getNibbleAddress(u32 samples);
Arguments

NameDescription
insamples16-bit PCM address offset. 0 is the first sample. 100 is the 101st sample.
Return Values
Corresponding nibble address.
Description
getNibbleAddress calculates and returns the nibble corresponding to the sampling data, as based on the offset passed to the argument.
For example, the 100th sample (where the count starts at 0) corresponds to the 116th nibble (where the count also starts at 0). Note that the calculated nibble address incorporates the 2-nibble frame header that is already in DSP ADPCM compressed format.
One more example: The 0th sample corresponds to a nibble offset of 2, which is the 3rd nibble (counting from 0).
getLoopContext
Syntax
typedef struct
{
   // start context
   s16 coef[16];
   u16 gain;
   u16 pred_scale;
   s16 yn1;
   s16 yn2;

   // loop context
   u16 loop_pred_scale;
   s16 loop_yn1;
   s16 loop_yn2;
} ADPCMINFO;  

void getLoopContext(
   u8          *src,       // location of ADPCM buffer in RAM
   ADPCMINFO   *cxt,       // location of adpcminfo
   u32         samples     // samples to desired context
);

Arguments

NameDescription
insrcPointer to the buffer containing the DSP ADPCM encoded sample.
outcxtThe pointer to the ADPCMINFO structure. The getLoopContext function places the loop context information inside this structure.
insamplesThe offset from the starting position of the loop in the sampling data. This loop starting position is not a nibble address, but rather the raw sample number where the loop starts (in other words, the first sample played in the loop).
If the loop begins at the very first sample, the offset is zero. If the loop begins at the 101st sample, the offset is 100.
Return Values
None.
Description
getLoopContext returns the DSP ADPCM loop context, based on the offset in the sample data given to the argument.
encode
Syntax
typedef struct
{
   // start context
   s16 coef[16];
   u16 gain;
   u16 pred_scale;
   s16 yn1;
   s16 yn2;

   // loop context
   u16 loop_pred_scale;
   s16 loop_yn1;
   s16 loop_yn2;
} ADPCMINFO;  

void encode(
   s16         *src,       // location of source samples (16bit PCM signed little endian)
   u8          *dst,       // location of destination buffer
   ADPCMINFO   *cxt,       // location of adpcm info
   u32         samples     // number of samples to encode
);

Arguments

NameDescription
insrcPointer to the buffer for signed little-endian 16-bit PCM sampling data.
outdstThe pointer to the buffer where the DSP ADPCM encoded data are output.
The application must allocate memory for this buffer. You must calculate the buffer size using getBytesForAdpcmBuffer.
incxtPointer to the ADPCMINFO structure. The encode function stores sampling data coefficients and context information in this structure.
Note that a number of parameters (gain, yn1, and yn2) are always 0.
Before decoding commences, the corresponding registers in the DSP decoder hardware must be cleared. These parameters are included in the structure to force this to happen.
After encode has been called, the loop context parameters always become 0. To set these values, you must call getLoopContext after the sampling data have been looped.
insamplesNumber of samples to encode.
Return Values
None.
Description
encode compresses 16-bit PCM data into DSP ADPCM format.
decode
Syntax
typedef struct
{
   // start context
   s16 coef[16];
   u16 gain;
   u16 pred_scale;
   s16 yn1;
   s16 yn2;

   // loop context
   u16 loop_pred_scale;
   s16 loop_yn1;
   s16 loop_yn2;
} ADPCMINFO;

void decode(
   u8          *src,   // location of encoded source samples
   s16         *dst,   // location of destination buffer (16 bits / sample)
   ADPCMINFO   *cxt,   // location of adpcm info
   u32         samples // number of samples to decode
);

Arguments

NameDescription
insrcPointer to the buffer containing the DSP ADPCM data.
outdstPointer to a buffer that has been allocated for storing the uncompressed PCM data.
The size of this buffer is calculated by calling getBytesForPcmBuffer.
incxtPointer to the ADPCMINFO structure.
This structure must contain the coefficient and initial state data that corresponds to the sample being decoded.
insamplesNumber of samples to decode.
Return Values
None.
Description
decode is used for decoding DSP ADPCM sample data into signed, little-endian, 16-bit PCM data.
Sample Code
WaveCodecCtr.dll has been created for use with Win32 applications. This section provides some samples.
Initialization
Encoding
Decoding
Initialization
Applications that call DLLs must comply with Win32 rules when calling WaveCodecCtr.dll.
typedef signed short   s16;
typedef unsigned char  u8;
typedef unsigned short u16;
typedef unsigned long  u32;

/* ---------------------------------------------------------------

 Import the WaveCodecCtr.dll API.
 
---------------------------------------------------------------- */
typedef struct
{
   // start context
   s16 coef[16];
   u16 gain;
   u16 pred_scale;
   s16 yn1;
   s16 yn2;

   // loop context
   u16 loop_pred_scale;
   s16 loop_yn1;
   s16 loop_yn2;

} ADPCMINFO;

static HINSTANCE hDll;
typedef u32 (*lpFunc1)(u32);
typedef u32 (*lpFunc2)(void);
typedef void (*lpFunc3)(s16*, u8*, ADPCMINFO*, u32);
typedef void (*lpFunc4)(u8*, s16*, ADPCMINFO*, u32);
typedef void (*lpFunc5)(u8*, ADPCMINFO*, u32);
lpFunc1 getBytesForAdpcmBuffer;
lpFunc1 getBytesForAdpcmSamples;
lpFunc1 getBytesForPcmBuffer;
lpFunc1 getBytesForPcmSamples;
lpFunc1 getSampleForAdpcmNibble;
lpFunc1 getNibbleAddress;
lpFunc2 getBytesForAdpcmInfo;
lpFunc3 encode;
lpFunc4 decode;
lpFunc5 getLoopContext;

/*--------------------------------------------------------------------------*/
void clean_up(void)
{
   if (hDll)
       FreeLibrary(hDll);
}

/*--------------------------------------------------------------------------*/
int getDll(void)
{
   hDll = LoadLibrary("WaveCodecCtr.dll");
   if (hDll)
   {
       if (!(getBytesForAdpcmBuffer =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getBytesForAdpcmBuffer"
                            ))) return 1;
       if (!(getBytesForAdpcmSamples =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getBytesForAdpcmSamples"
                            ))) return 1;
       if (!(getBytesForPcmBuffer =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getBytesForPcmBuffer"
                            ))) return 1;
       if (!(getBytesForPcmSamples =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getBytesForPcmSamples"
                            ))) return 1;
       if (!(getNibbleAddress =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getNibbleAddress"
                            ))) return 1;
       if (!(getSampleForAdpcmNibble =
               (lpFunc1)GetProcAddress(
                            hDll,
                            "getSampleForAdpcmNibble"
                            ))) return 1;
       if (!(getBytesForAdpcmInfo =
               (lpFunc2)GetProcAddress(
                            hDll,
                            "getBytesForAdpcmInfo"
                            ))) return 1;
       if (!(encode =
               (lpFunc3)GetProcAddress(
                           hDll,
                           "encode"
                           ))) return 1;
       if (!(decode =
               (lpFunc4)GetProcAddress(
                           hDll,
                           "decode"
                           ))) return 1;
       if (!(getLoopContext =
               (lpFunc5)GetProcAddress(
                            hDll,
                            "getLoopContext"
                            ))) return 1;
       return(0);
   }
   return(1);
}


/*--------------------------------------------------------------------------*/
int _tmain(int argc, _TCHAR* argv[])
{
   if (getDll())
   {
       clean_up();
       exit(1);
   }

   // do stuff here

   clean_up();
}

Encoding
The encoding function assumes that the data are 16-bit, little endian, PCM data (as used for Windows .wav files). If you want the application to encode big-endian data, you must flip the endian before encoding.
//... loaded WaveCodecCtr.dll

//... put some PCM buffer in memory, reverse the endian if you have to
u8 *adpcm = (u8*)malloc(getBytesForAdpcmBuffer(samplesToEncode));

if (adpcm)
{
   ADPCMINFO adpcminfo;

   // ok.. lets encode it!
   encode(source, adpcm, &adpcminfo, samplesToEncode);

   // get ADPCM loop context if sample is looped
   if (samplesToLoopStart)
           getLoopContext(adpcm, &adpcminfo, samplesToLoopStart);

   // see how many bytes to store the encoded data stream
   u32 nBytesToStore = getBytesForAdpcmSamples(samplesToEncode);

   ... store encoded ADPCM data stream to file

   ... store ADPCM context to file

   u32 nibbleStartOffset   = getNibbleAddress(0);
   u32 nibbleLoopStartOffset = getNibbleAddress(samplesToLoopStart);
   u32 nibbleEndAddress  = getNibbleAddress(samplesToEncode);

   ... store nibble addressing to file

   // don't need the ADPCM buffer anymore
   free(adpcm);
}

... continue

   
Decoding
The decoding result is output as 16-bit, little-endian, PCM data.
... loaded WaveCodecCtr.dll

... put some ADPCM buffer and corresponding ADPCMINFO in memory,
... ADPCM is byte ordered.. not endian sensitive.

s16 *pcm = (u8*)malloc(getBytesForPcmBuffer(samplesToDecode));

if (pcm)
{
   // Decode
   decode(source, pcm, adpcminfo, samplesToDecode);

   ... store decoded PCM buffer to file

   // Free the PCM buffer
   free(pcm);
}

... continue

Cautions for Encoding and Playing Looped Wave Files
Note the following cautions when encoding looped waveforms and when playing them on the actual hardware.
The information relating to encoding also pertains to the use of the nn::snd::EncodeAdpcmData function for encoding data into DSP ADPCM on the actual hardware.
How to Play Regular Looped Waveforms
Align the Loop Start Position to an 8-byte Boundary
Noise When the Loop Start Equals the Start of the Waveform
Noise When the Loop End Equals the End of the Waveform
How to Play Regular Looped Waveforms
When looping a waveform like the following on the actual hardware:
   WS      LS                     LE
   |-------|<-------------------->|
 
     WS: Start of waveform
     LS: Loop start position
     LE: Loop end position

[list=1]
  • WaveBuffer for playing WS to LE
  • WaveBuffer for playing LS to LE
Prepare two WaveBuffer instances (as shown) and set the order as 1. → 2. in nn::snd::Voice.
[/list]    WS                             LE
1. |------------------------------|    (When the loopFlag of WaveBuffer is false.)
 
           LS                     LE
2.         |<-------------------->|    (When the loopFlag of WaveBuffer is true.)

Align the Loop Start Position to an 8-byte Boundary
In the DSP ADPCM format, 8 bytes (corresponding to 14 samples) are handled as one set of information. For this reason, the bufferAddress argument of the nn::snd::WaveBuffer function must specify an address with an 8-byte boundary.
On the other hand, when creating WaveBuffer as described in 2, LS is not necessarily at an 8-byte (14-sample) boundary.
If the boundary is not 8 bytes (14 samples), you must displace the LS position to an 8-byte boundary so this restriction is satisfied.
For example, if LS is at the 16th sample, you could slide it to the next 14-sample boundary (28 samples) and top off the waveforms after LE with the moved part before encoding.
    LS                  LE
    |<----------------->|
     ^^^^
               ↓
        LS'                   LE'
    ----|<------------------->|
                          ^^^^

Noise When the Loop Start Equals the Start of the Waveform
Because of encoder limitations, when the loop start position equals the start of a waveform, you cannot play a clean loop without noise.
If the loop start position equals the start of the waveform, use the technique described in Align the loop start position to an 8-byte boundary to slide LS' to 14, so you can play the loop cleanly without noise.
Noise When the Loop End Equals the End of the Waveform
Because of encoder limitations, when the loop start position equals the end of a waveform, encoding is not stable and noise occurs.
When waveforms continue beyond LE, for the samples argument of the encode function, pass WS to end of waveform rather than WS to LE.
   WS      LS                     LE   WE
   |-------|<-------------------->|----|
 
     WS:  Start of waveform
     WE : End of waveform
     LS: Loop start position
     LE: Loop end position

   encode( WS address, dst, cxt, WE-WS );

When waveforms do not continue beyond LE, apply the technique introduced in Align the loop start position to an 8-byte boundary to use copied waveforms for about 100 samples from LS, and position them after LE.
   WS      LS                     LE=WE
   |-------|<-------------------->|
            ^^^^^
                     ↓
   WS      LS                     LE   WE'
   |-------|<-------------------->|----|
                                   ^^^^^

   encode( WS address, dst, cxt, WE'-WS );

I hope some of this info from the official SDK docs and from vgmstream's code will help in how the header and the coding of the DSP-ADPCM binaries are encoded. ^^
#5
I have bug to report on BrawlBox. It has not been fixed in 0.78 RC1 either.

The BFSTM framework for BrawlBox is not developed properly. Whenever you play a BFSTM in BrawlBox, there is always a clicking noise in the stream. Do you want to know what causes it? It's because it's using an incorrect hex value to indicate on moving to the next sample. So what happens is the next sample that is played is using an extra byte in playing back, but at the same time, missing the end of the sample that is supposed to be played as a sound sample.

It may sound like there's a clicking noise every 0.5 seconds, but that's because it plays too quickly for it to be heard until 0.5 seconds later. But what really is happening, is it's playing a clicking noise on every single sample in the stream.

Next up, multiple streams in BCSTM are not supported in BrawlBox yet, therefore bringing up an error saying that multi-stream files are not supported. But in reality, it's actually because BrawlBox got it's header values in the wrong position when reading the file format.

BCSTMs and BFSTMs are completely different format, you can't just expect them to be identical to BRSTMs. Their coding may be similar in that it's made using NintendoWare, but there are huge differences in their header positions in NW4C and NW4F formats.

Please fix them. Otherwise I may have to do some trial and error in trying to fix those bugs myself. ^^'