Alesis Sample Compression

Introduction

The Alesis HR-16 drum machine appears to use a simple compression scheme to get 16-bit performance from its 8-bit sample ROMs. During the initial attack of the drum hit, the eight bits of data are sent to the most significant bits of the converter. Once the signal decays to half the initial volume, a zero value is inserted into the ROM data which tells the sample playback chip to shift the sample data one place to the right. The data in the ROM still uses the full eight-bit scale but in conversion is reduced by 6dB. Thus with an 8-bit sample ROM the HR-16 can achieve a greater bit depth than would otherwise be possible. The lower resolution at the "loud" end is not nearly as important as the resolution at the quiet end - notice how certain eight-bit samples "fizz" during the fade out. For a full-scale output of 770mV (0dB into 600 ohms, standard line level) one bit of conversion is 3mV or so, still quite loud!

Previously in replacing the ROMs they have just been loaded with 8-bit samples without applying the compression. However, it may be possible to encode and decode these samples using simple bits of software. I'm going to present a couple of ideas I have about how this may be done. The code samples will be in a C-like pseudocode. If a practical version were to be written, I would recommend either C or Python.

Method of Decoding

Decoding the sample should be fairly simple. Let's assume we have a couple of helper functions that load the raw ROM image into an array, and write an array of floats out as a standard sound file (for instance, .WAV).

We need to read through the sample, and divide the output by two every time we hit a zero (and skip the zero so it doesn't get output). To do this, we might use something like this:

// output multiplier, start off at unity gain
mult = 1;
// output pointer, lets us skip zeroes
outptr = 0;
// loop through each byte
for (i = 0; i < SAMPLE_SIZE; i++) {
  // get the sample
  s = input[i];
  if (s == 0) {
    mult = mult / 2;  // drop by 6dB
  } else {
    // put it in the output array, and bump the pointer
    output[outptr] = (1-(s/128)) * mult;
    outptr++;
  }
}

Note that this does not attempt to find the start and end of samples, or deal with any "odd bytes" that some samples seem to have. If this decoder produces correct results for the "factory" Alesis samples, it may be used to confirm that the encoder software is doing the right thing.

Method of Encoding

Given the above method of decoding, it seems reasonable to expect that if we can find the point where the average signal dips below -6dB, we can insert a zero and double the byte value. It's not just as simple as seeing if the output goes below 0.5f, because at some point during every cycle it will be there. So we need to take an average over a few samples, ideally a whole cycle's worth. Another thing to bear in mind is that a sample with a loud initial attack, a loud ending and a quiet bit in the middle will be unsuitable for this technique.

mult = 1;
outptr = 0;
for (i = 0; i < SAMPLE_SIZE; i++) {
  // loop round and get the average of this bit
  av = 0;
  for (j = i; j < i + WINDOW_SIZE; j++) {
    av = av + abs(input[j]; // full-wave rectified
  }
  av = av / WINDOW_SIZE; // average in window
  // less than -6dB?
  if (av < 0.5) {
    // put a zero in the output, bump the pointer
    output[outptr]=0;
    outptr++;
    mult = mult * 2;
  }
  // and output the byte
  output[outptr]=128+(127*input[i]*mult); // doesn't really work, ignores possible overscale errors
  outptr++;
}

For different samples you may need to adjust the window size for best results. A proper solution would attempt to determine the period of the waveform being sampled and base WINDOW_SIZE on that.