BRR decoding/encoding (again)

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
BRR decoding/encoding (again)
by on (#52579)
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:
   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;
   }

by on (#52585)
Quote:
//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.

by on (#52607)
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.

by on (#52630)
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:
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)
by on (#52632)
Change it like so to fix those Square / Capcom samples:
Code:
      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.

by on (#52633)
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.

by on (#52634)
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.

by on (#52649)
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.

by on (#52688)
Okay Kode54, sorry but can I ask you several questions on your encoder ?
First this :
Code:
      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:
   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.

by on (#53175)
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:
out[i] = sample[(int)(i*resample)]


Linear interpolation sounds extremely simple as well :
Code:
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:
    // 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.

by on (#53215)
BRRTools is now released to the public so enjoy : http://jonathan.microclub.ch/BRRTools/

(I still can't get the gaussian thing :cry: )

by on (#53279)
Good to have some encoding examples, since I'll have to write an encoder myself if/when I start adding SNES-support for XPMCK.