ADPCM codecs: Difference between revisions
mNo edit summary |
(fix accumulator wrapping for ADPCM_A decoding to match YM2610 hardware. Thanks MrMadbrain for info, and darrylrev for hardware tests.) |
||
(4 intermediate revisions by 2 users not shown) | |||
Line 10: | Line 10: | ||
Inspired by Mame and MVStracker. This codec implementation was done with some trial & error, might not be 100% correct. It however fixes the issue with long samples degenerating. | Inspired by Mame and MVStracker. This codec implementation was done with some trial & error, might not be 100% correct. It however fixes the issue with long samples degenerating. | ||
<syntaxhighlight | <syntaxhighlight lang="csharp"> | ||
static short[] step_size = { | static short[] step_size = { | ||
16, 17, 19, 21, 23, 25, 28, 31, 34, 37, | 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, | ||
Line 58: | Line 58: | ||
{ | { | ||
acc += jedi_table[decstep + code]; | acc += jedi_table[decstep + code]; | ||
acc &= 0xfff; // accumulator wraps | |||
if (acc & 0x800) acc |= ~0xfff; // sign extend if negative | |||
decstep += step_adj[code & 7] * 16; | decstep += step_adj[code & 7] * 16; | ||
if (decstep < 0) decstep = 0; | if (decstep < 0) decstep = 0; | ||
Line 157: | Line 156: | ||
Official codec from the [[YM2610]] datasheeet, works fine. | Official codec from the [[YM2610]] datasheeet, works fine. | ||
<syntaxhighlight | <syntaxhighlight lang="csharp"> | ||
static long[] stepsizeTable = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 }; | static long[] stepsizeTable = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 }; | ||
private byte[] buffer; //our input buffer, load your sample file into this before encoding | private byte[] buffer; //our input buffer, load your sample file into this before encoding | ||
Line 211: | Line 210: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Decoding tool== | |||
[[File:decoder_shot.png|right|thumb|DAT interface]] | |||
Decoding ROM files / samples can be done using the YM2610 ADPCM decoder tool. | |||
It decodes A and B type samples to a WAV file. | |||
Works under modern windows OS, unlike previous tools that are deprecated. | |||
[[File:YM2610_Decoder.zip]] | |||
==ABOUT NEO GEO CD== | |||
[[File:Pcmvader.png|right|thumb|Actual events leading to this discovery.]] | |||
Fun fact: Neo Geo CD does not support ADPCM-B. | |||
[[Category:Audio system]] | [[Category:Audio system]] |
Latest revision as of 17:11, 22 April 2019
ADPCM codecs are used to encode samples to be used by the YM2610.
All the code in this page process 16bit monoral, little endian raw sound data data (".snd" files, Goldwave can output this), make sure to have your samples at the proper frequency. C# implementation.
Remember to size/pad your sample so it's a 256 bytes multiple, this is not done in the following code.
ADPCM-A
This codec accepts 18.5kHz samples, playback rate is fixed.
Inspired by Mame and MVStracker. This codec implementation was done with some trial & error, might not be 100% correct. It however fixes the issue with long samples degenerating.
static short[] step_size = {
16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
}; //49 items
static int[] step_adj = { -1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9 };
//buffers
private byte[] buffer; //input buffer, load your sound file into this
private short[] inBuffer; //temp work buffer, used correct byte order and downsample
private byte[] outBuffer; //output buffer, this is your PCM file, save it
//decode stuff
private int[] jedi_table;
int acc = 0; //ADPCM accumulator, initial condition must be 0
int decstep = 0; //ADPCM decoding step, initial condition must be 0
//encode stuff
int diff;
int step;
int predsample;
int index;
int prevsample = 0; // previous sample, initial condition must be 0
int previndex = 0; //previous index, initial condition must be 0
//jedi table is used speed up decoding, run this to init the table before encoding. Mame copy-pasta.
private void jedi_table_init()
{
int step, nib;
jedi_table = new int[16 * 49];
for (step = 0; step < 49; step++)
{
for (nib = 0; nib < 16; nib++)
{
int value = (2 * (nib & 0x07) + 1) * step_size[step] / 8;
jedi_table[step * 16 + nib] = ((nib & 0x08) != 0) ? -value : value;
}
}
}
//decode sub, returns decoded 12bit data
private short YM2610_ADPCM_A_Decode(byte code)
{
acc += jedi_table[decstep + code];
acc &= 0xfff; // accumulator wraps
if (acc & 0x800) acc |= ~0xfff; // sign extend if negative
decstep += step_adj[code & 7] * 16;
if (decstep < 0) decstep = 0;
if (decstep > 48 * 16) decstep = 48 * 16;
return (short)acc;
}
// our encoding sub, returns ADPCM nibble
private byte YM2610_ADPCM_A_Encode(short sample)
{
int tempstep;
byte code;
predsample = prevsample;
index = previndex;
step = step_size[index];
diff = sample - predsample;
if (diff >= 0)
code = 0;
else
{
code = 8;
diff = -diff;
}
tempstep = step;
if (diff >= tempstep)
{
code |= 4;
diff -= tempstep;
}
tempstep >>= 1;
if (diff >= tempstep)
{
code |= 2;
diff -= tempstep;
}
tempstep >>= 1;
if (diff >= tempstep) code |= 1;
predsample = YM2610_ADPCM_A_Decode(code);
index += step_adj[code];
if (index < 0) index = 0;
if (index > 48) index = 48;
prevsample = predsample;
previndex = index;
return code;
}
//our main sub, init buffers and runs the encode process
//enter this with your sound file loaded into buffer
private void YM_encode()
{
int i;
//reset to initial conditions
acc = 0;
decstep = 0;
prevsample = 0;
previndex = 0;
//watch out for odd data count & allocate buffers
if ((buffer.Length / 2) % 2 != 0)
{
inBuffer = new short[(buffer.Length / 2) + 1];
inBuffer[inBuffer.Length - 1] = 0x00;
}
else inBuffer = new short[buffer.Length / 2];
outBuffer = new byte[inBuffer.Length / 2];
//fix byte order and downscale data to 12 bits
for (i = 0; i < buffer.Length; i += 2)
{
inBuffer[i / 2] = (short)((buffer[i]) | (buffer[i + 1] << 8));
inBuffer[i / 2] >>= 4;
}
//actual encoding
for (i = 0; i < inBuffer.Length; i += 2)
{
outBuffer[i / 2] = (byte)((YM2610_ADPCM_A_Encode(inBuffer[i]) << 4) | YM2610_ADPCM_A_Encode(inBuffer[i + 1]));
}
}
Encoding example: File:Gold Rush 16b.snd.zip
ADPCM-B
This codec accepts 1.8kHz ~ 55.5kHz samples, playback rate has to be specified at runtime.
Official codec from the YM2610 datasheeet, works fine.
static long[] stepsizeTable = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 };
private byte[] buffer; //our input buffer, load your sample file into this before encoding
private byte[] outBuffer; //our output buffer, this is your PCM file, save it after encoding. Its size has to be allocated to buffer.length / 4 (16 bits per sample to 4 bits per sample)
private int outBufferIndex = 0; //reset to 0 before each encoding
private void YM2610_ADPCM_B_Encode()
{
int lpc, flag;
long i, dn, xn, stepSize;
byte adpcm;
byte adpcmPack = 0;
short src;
xn = 0;
stepSize = 127;
flag = 0;
for (lpc = 0; lpc < (buffer.Length / 2); lpc++)
{
src = (short)((buffer[lpc * 2]) | (buffer[lpc * 2 + 1] << 8)); //16 bit samples, + fixing byte order
dn = src - xn;
i = (Math.Abs(dn) << 16) / (stepSize << 14);
if (i > 7) i = 7;
adpcm = (byte)i;
i = (adpcm * 2 + 1) * stepSize / 8;
if (dn < 0)
{
adpcm |= 0x8;
xn -= i;
}
else
{
xn += i;
}
stepSize = (stepsizeTable[adpcm] * stepSize) / 64;
if (stepSize < 127)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
if (flag == 0)
{
adpcmPack = (byte)(adpcm << 4);
flag = 1;
}
else
{
adpcmPack |= adpcm;
outBuffer[outBufferIndex++] = adpcmPack;
flag = 0;
}
}
}
Decoding tool
Decoding ROM files / samples can be done using the YM2610 ADPCM decoder tool. It decodes A and B type samples to a WAV file.
Works under modern windows OS, unlike previous tools that are deprecated.
ABOUT NEO GEO CD
Fun fact: Neo Geo CD does not support ADPCM-B.