Page 1 of 1
BRR decoding/encoding (again)
Posted: Fri Nov 13, 2009 1:27 pm
by Bregalad
OK there is already a few programms that allows you to convert to BRR, but most of them aren't widely spread and are glitchy / lack options I'd like them to have.
So I decided to start to code a java BRR->WAV converter (in the scope to add a WAV->BRR converter to it once it'll function).
It seems to work fine for "normal" samples, but my issue is that the samples that are supposed to be noisy aren't noisy, even tough I use the supposely accurate anomie's algorithms. I tried looking into Snes9x's sourcecode, but it was a huge mess and I doubt it's accuracy as well.
I've uploaded the
sources and
binaries. In addition to normal decoding it also allows you to decode samples while looping and test their stability on loop point (which isn't complete yet).
For people who just feel too lazy to download here is the "hot" part of decoding :
Code: Select all
static short[] DecodeBRR (byte[] Data) { //Decode a string of BRR bytes
int Filter = (Data[0] & 0x0c)>>2;
int ShiftAmount = Data[0]>>4; //Read filter & shift amount
if(ShiftAmount<0) ShiftAmount+=0x10; //This is needed because of the stupid way Java handles neg numbers
short[] out = new short[(Data.length-1)<<1]; //Output string of 16-bit samples
for(int i=0; i<Data.length-1; i++) { //Loop for each byte
DecodeSample((short)(Data[i+1]>>4), ShiftAmount, Filter); //Decode high nybble
out[i<<1]=p1;
DecodeSample((short)(Data[i+1]&0x0f), ShiftAmount, Filter); //Decode low nybble
out[(i<<1)+1]=p1;
}
return out;
}
static void DecodeSample (short s, int ShiftAmount, int Filter) {
if (s>=8) s-=0x10; //Fix numbers that should be negative
s =(short)(s << ShiftAmount);
int a;
switch(Filter) {
case 0 :
a = s;
break;
case 1 :
a = s + p1 + (-p1>>4);
break;
case 2 :
a = s + (p1<<1) + ((-((p1<<1)+p1))>>5) - p2 + (p2>>4);
break;
default :
a = s + (p1<<1) + ((-(p1+(p1<<2)+(p1<<3)))>>6) - p2 + (((p2<<1) + p2)>>4);
}
if(a>Short.MAX_VALUE) a=Short.MAX_VALUE;
if(a<Short.MIN_VALUE) a=Short.MIN_VALUE; //Clamp to 16-bit
a &= 0xFFFE; //Clip to 15-bit
p2 = p1;
p1 = (short)a;
}
Posted: Fri Nov 13, 2009 7:48 pm
by tepples
//This is needed because of the stupid way Java handles neg numbers
Is it true even if you use the >>> operator? Also make sure you're taking the compressed data 9 bytes (1 header byte and 8 nibble pairs) at a time.
Posted: Sat Nov 14, 2009 1:58 pm
by Bregalad
Well in java when you shift right a negative number, it's still negative. I guess it's called "arithmetic shift right", which correspond to "cmp #$80, ror A" in 6502 code. In fact it's me who is stupid to call it stupid when it perfectly make sense, the only "stupid" stuff is that you can't choose between logical and arithmetic shift rights.
Left shifts however CAN change the sign. I don't know if this is because of that that I'm having issues, nor if C/C++ handles shifts the same as java does (shifts have always been widely undocumented in all my cs courses for some reason I had to figure everything out by myself, teachers prefer using multiplications/divisions and don't care about performance).
Altough my loop can decode BRR blocks of theorically any size, the main loop always use it to decode 9 bytes at a time. Note that my programm apparently WORKS for most samples, but not for the noisy ones used in Square and Capcom games for sound effects.
Posted: Sat Nov 14, 2009 11:47 pm
by Near
That's why tepples said to use >>>.
In C++, << always moves things left with a zero added onto the right. >> is "undefined" in the official spec, but on virtually every platform, it copies the leftmost bit prior to shifting for signed types, and uses a zero for unsigned types.
In Java, << acts the same, but >> always acts like the C++ signed type. If you want the C++ unsigned type, use >>>. A stupid workaround for a stupid language that lacks native unsigned types.
Recap:
Code: Select all
00001111 << 1 = 00011110 in C++ (signed or unsigned types) and Java
11110000 >> 1 = 11111000 in C++ (signed types) and Java
11110000 >> 1 = 01111000 in C++ (unsigned types)
11110000 >>> 1 = 01111000 in Java
Now if only either language had a native rotate operator.
Re: BRR decoding/encoding (again)
Posted: Sun Nov 15, 2009 1:21 am
by kode54
Change it like so to fix those Square / Capcom samples:
Code: Select all
if(a>Short.MAX_VALUE*2) a=Short.MAX_VALUE*2;
if(a<Short.MIN_VALUE*2) a=Short.MIN_VALUE*2; //Clamp to 17-bit
Actually, in reality, the nibble should be shifted up by scale - 1, then the clamp should be correct as-is (16-bit), then the sample is doubled. For reference, see bsnes src/dsp/sdsp/brr.cpp.
I think my
encoder sample takes this into account, but I could be wrong.
Posted: Sun Nov 15, 2009 2:25 am
by Bregalad
Thank you VERY MUCH kode54 now it works perfectly !! Thanks !
Now I'll add a stability tester for looping samples so you can know how long a noise sample takes until it repeats.
I also plan to code an encoder, kode54 can I get inspiration from yours ? (you'll be credited of course)
As for shifts in java thanks for clarifying sorry it's me who is stupid not have figured that. Anyways I've found workarrounds everytime I had to deal with signed/unsighed conversions so it's allright.
Posted: Sun Nov 15, 2009 2:33 am
by kode54
Feel free to use mine for an example, and know that it was based off the SoX MS ADPCM encoder, which introduced me to brute force ADPCM encoding, as well as quantizing the sample to a nibble and calculating the resulting sample again for the sample history. A valuable process for ADPCM encoding that I didn't really think about when I originally started on a BRR decoder years ago.
Posted: Sun Nov 15, 2009 11:18 am
by Bregalad
Err... thanks but I still have a little trouble...
it seems that while I get a noisy sample when I'm supposed to get a noisy sample, I get a noisy sample, but different sounding that the supposely accurate SPCamp. This is paricularly notable with Seiken Densetsu 3 samples, which have 3 "noisy" samples : 2, 3 and 7.
Mines sounds like metallic noise repeating more often than supposed. Since I wrote a code to be able to detect the lenght of repeating noise it's important I get it right.
Posted: Mon Nov 16, 2009 1:26 pm
by Bregalad
Okay Kode54, sorry but can I ask you several questions on your encoder ?
First this :
Code: Select all
dmin = 0.;
kmin = 0;
smin = 0;
for (s = 0; s < 13; s++)
{
for (k = 0; k < 4; k++)
{
double d;
d =
AdpcmMashS (c, ch, &v[c << 1], vl ? &vl[c << 1] : 0, k,
ip, s, NULL);
/*if ( display )
{
fprintf( stderr, "%02u, %u: %f\n", s, k, (float)d );
}*/
if ((!s && !k) || d < dmin)
{
kmin = k;
dmin = d;
smin = s;
}
}
}
It seems to try all 12 possible shift values and 4 possible filters, to find the less worse.
The main beef I have is with the if(!s && !k) statement. You'd want to remember the filter and shift amount used if it's better than the current best (d < dmin), but why should you force remembering the k,s combination if they are zero ?
Because dmin is initialised to 0.0 so you'll normally never find anything smaller than it (assming the d returned is supposed to be positive). But I guess the above trick is to force initialisation on dmin when both k and s are zero. Isn't there some better way to handle this (like initialising dmin to positive infinity ?).
My third beef is at the end of APDCMMash method :
return sqrt (d2);
As far I understand, d2 is the sum of the square of errors on all samples in the filter. What i don't understand is why you bother using square root, considering the mathematical fat squrt(a) < squrt(b) if and only if a < b, you could return d2 directly and it'd just make the programm faster without any side effect.
Third beef I have is there :
Code: Select all
if (vl)
{
d = v0 - vl[0];
d2 += (double)d * d;
d = v1 - vl[1];
d2 += (double)d * d;
d2 /= 18.;
}
else
d2 /= 16.; /* be sure it's non-negative */
I'm not sure what this code does, but it seems to take accound in the square-delta the last 2 values used as a feedback for filters. Why would you want to do that ? And again why the division by 16 or 18, since we only use d for compare purpose, and a/16 < b/16 if and only if a < b (same with 18), so again it sounds like an useless computation to me.
Thanks in advance for clarifying those points.
Posted: Sat Nov 28, 2009 4:38 am
by Bregalad
OK now I've made my BRR encoder / decoder fully working (as far I know), I want to add the feature of it to resample waves before encoding them.
Resampling with no interpolation sounds extremely simple, something like :
Code: Select all
out[i] = sample[(int)(i*resample)]
Linear interpolation sounds extremely simple as well :
Code: Select all
int a = (int)(i*resample); //whole part
double b = i*resample-a; //fractional part
out[i]= (1-b)*sample[a] + b*sample[a+1];
I'd like to have it do some kind of better interpolation, ideally guassian in the exact same way as the SNES does it. Unfortunately I don't understand exactly what butcha's docs says about it, especially that part :
Code: Select all
// 4-point gaussian interpolation
i = voice[x].interpolation_index >> 12; // 0 <= i <= 4
d = (voice[x].interpolation_index >> 4) & 0xff; // 0 <= d <= 255
outx = ((gauss[255-d] * voice[x].BRRdata[i+0]) >> 11);
outx += ((gauss[511-d] * voice[x].BRRdata[i+1]) >> 11);
outx += ((gauss[256+d] * voice[x].BRRdata[i+2]) >> 11);
// The above 3 wrap at 15 bits signed. The last is added to that, and is
// clamped rather than wrapped.
outx = ((outx & 0x7FFF) ^ 0x4000) - 0x4000;
outx += ((gauss[ 0+d] * voice[x].BRRdata[i+3]) >> 11);
CLAMP15(outx);
If someone could explain that'd be nice, else I guess I'd deal with the simpler interpolations.
Posted: Sat Nov 28, 2009 3:13 pm
by Bregalad
BRRTools is now released to the public so enjoy :
http://jonathan.microclub.ch/BRRTools/
(I still can't get the gaussian thing

)
Posted: Mon Nov 30, 2009 10:53 am
by mic_
Good to have some encoding examples, since I'll have to write an encoder myself if/when I start adding SNES-support for XPMCK.