ADPCM codecs: Difference between revisions
(Created page with "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, Goldwa...") |
mNo edit summary |
||
Line 211: | Line 211: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
[[Category:Audio system]] |
Revision as of 13:49, 3 May 2012
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];
if ((acc & ~0x7ff) != 0) // acc is > 2047
acc |= ~0xfff;
else acc &= 0xfff;
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;
}
}
}