A-Law compressor.c

/*
    File:       A-Law compressor.c
 
    Contains:   Sample sound compression component to convert
                16-bit linear samples to ALaw-encoded data 
 
    Written by: Mark Cookson    
 
    Copyright:  Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/13/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
#include "ComponentDispatch.h"
#include <Memory.h>
#include <Errors.h>
#include <SoundInput.h>
#include <Components.h>
#include <Gestalt.h>
 
#include <Sound.h>
#include <SoundComponents.h>
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Standard Compression Component Methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
/*  ==============================================================================
    SetSource
 
    This routine sets the component we should call to get more data. We must remember
    this component and also tell it the data format of our component requires.
    ============================================================================== */
 
pascal ComponentResult __SoundComponentSetSource(SoundComponentGlobalsPtr globals, SoundSource sourceID, ComponentInstance source)
{
#pragma unused (sourceID)
 
    globals->sourceComponent = source;                      // remember our source component
    globals->sourceDataPtr = nil;                           // nothing read from source yet
 
    return (noErr);
}
 
/*  ==============================================================================
    SetOutput
 
    This routine sets the data format our component should output. If we can't output
    the requested format, we should return a pointer to the format we do support,
    and return an error, and the Sound Manager will attempt the conversion for us.
    ============================================================================== */
 
pascal ComponentResult __SoundComponentSetOutput(SoundComponentGlobalsPtr globals, SoundComponentDataPtr requested, SoundComponentDataPtr *actual)
{
    globals->outputSamples = requested->sampleCount;        // no. samples to output
    if (globals->outputSamples > kMaxOutputSamples) {       // too much for our buffer
        globals->outputSamples = kMaxOutputSamples;         // only output what we can
    }
 
    // make sure data format and sample sizes match
    if (requested->format == kOutputSampleFormat) {         // formats match
        globals->thisComponent = *requested;
        return (noErr);                                     // no problem outputting this format
    } else {
        // If we can't output the requested format, the Sound Manager will make an attempt to convert our
        // format into something that can be used. In order for the Sound Manager to do this, we need to
        // tell it here the format we will be outputting, so it can setup the proper conversion. This is really
        // handy if your algorithm only outputs 16-bit data, but the Sound Manager is requesting 8-bit. In this
        // case, the Sound Manager will automatically convert your 16-bit data to 8-bit. 
    
        *actual = &globals->thisComponent;                  // tell the Sound Manager what we will output
        return (paramErr);                                  // force the Sound Manager to convert for us
    }
}
 
/*  ==============================================================================
    GetSourceData
 
    This routine is called when the Sound Manager wants your component to compress
    some more data. It should first make sure it has some source data, then
    compress into an internal buffer and return that buffer to the Sound Manager.
    If the Sound Manager is requesting the data in reverse, you must compress
    the data starting from the end of the source buffer, and the Sound Manager will
    reverse the samples for you.
 
    NOTE: This will most often be called at interrupt time.
    ============================================================================== */
 
pascal ComponentResult __SoundComponentGetSourceData(SoundComponentGlobalsPtr globals, SoundComponentDataPtr *resultDataPtr)
{
    SoundComponentDataPtr   sourceDataPtr;
    unsigned long           samplesConverted, framesToConvert, bytesToSkip;
    ComponentResult         result;
    short                   *inputBuffer;
 
    result = noErr;
    sourceDataPtr = globals->sourceDataPtr;                 // get pointer to source sound component
 
    if (sourceDataPtr == nil)                               // source buffer was flushed
    {
        result = PrimeSource(globals);                      // start with all new source data
    }
    else if (sourceDataPtr->sampleCount == 0)               // source buffer is empty
    {
        result = SoundComponentGetSourceData(globals->sourceComponent, &globals->sourceDataPtr); // fill it up                  // continue where we left off
    }
 
    if (result != noErr)                                    // bail if error
        return (result);
 
    sourceDataPtr = globals->sourceDataPtr;                 // get pointer to source sound component
 
    if ((sourceDataPtr->format == globals->thisComponent.format) || // input and output are same
        (sourceDataPtr->buffer == nil))                     // or no source buffer
    {
        globals->sourceDataPtr = nil;                       // get new source next time
        *resultDataPtr = sourceDataPtr;                     // pass source on down
    }
    else
    {
        // convert the source samples into frames
        framesToConvert = sourceDataPtr->sampleCount / globals->compInfo.samplesPerPacket;
 
        if (framesToConvert)                                // source has some data for us
        {
            if (framesToConvert > globals->outputFrames)
                framesToConvert = globals->outputFrames;    // limited to size of output
 
            // convert frames back into samples to quantize the source correctly
            samplesConverted = framesToConvert * globals->compInfo.samplesPerPacket;
 
            inputBuffer = (short *)sourceDataPtr->buffer;           // point at input buffer
 
            bytesToSkip = (samplesConverted / globals->compInfo.samplesPerPacket) * globals->compInfo.bytesPerFrame;
            sourceDataPtr->buffer += bytesToSkip;       // skip over the source consumed
 
            sourceDataPtr->sampleCount -= samplesConverted; // this many samples will be compressed
 
            // Do the compression
            CompressALaw(inputBuffer, globals->buffer, framesToConvert, sourceDataPtr->numChannels);
        }
        else
            samplesConverted = 0;                           // no samples were converted
 
        globals->thisComponent.buffer = (Byte *) globals->buffer;   // data in this buffer
        globals->thisComponent.sampleCount = samplesConverted;      // return num. samples converted
 
        *resultDataPtr = &globals->thisComponent;           // return description of compressed data
    }
 
    return (result);
}
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This routine is used to prime the source. It gets the first load of data
// from the source and intializes the output format and compression factors.
 
ComponentResult PrimeSource(SoundComponentGlobalsPtr globals)
{
    ComponentResult         result;
    SoundComponentDataPtr   sourceDataPtr;
 
    // get data from source
    result = SoundComponentGetSourceData(globals->sourceComponent, &globals->sourceDataPtr);
    if (result != noErr)
        return (result);
    if (globals->sourceDataPtr == nil)
        return (paramErr);
 
    sourceDataPtr = globals->sourceDataPtr;
    globals->thisComponent.flags = sourceDataPtr->flags;                // copy flags unchanged
    globals->thisComponent.sampleRate = sourceDataPtr->sampleRate;      // copy sample rate unchanged
    globals->thisComponent.numChannels = sourceDataPtr->numChannels;    // copy numchannels unchanged
 
    globals->compInfo.recordSize = sizeof(CompressionInfo);
    result = GetCompressionInfo(fixedCompression, sourceDataPtr->format,
                                sourceDataPtr->numChannels, sourceDataPtr->sampleSize,
                                &globals->compInfo);
 
    globals->destCompInfo.recordSize = sizeof(CompressionInfo);
    globals->destCompInfo.format = globals->thisComponent.format;
    GetCompressorInfo(&globals->destCompInfo);                  // get compression info
    globals->destCompInfo.bytesPerFrame = globals->destCompInfo.bytesPerPacket * sourceDataPtr->numChannels;
 
    globals->outputFrames = globals->outputSamples / globals->compInfo.samplesPerPacket;
 
    return (result);
}
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Compressor-specific Methods
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This routine initializes the compressor.
 
void InitializeCompressor(SoundComponentGlobalsPtr globals)             // initialize our compressor state
{
#pragma unused (globals)
 
    // Here you should initialize any state used by your compression alorithm (such as predictors)
    // to default values. This routine will be called whenever a new sound is started so you can set
    // up correctly. In our example, we do not have any state, so we have nothing to set up.
}
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This routine returns information about the compression ratios.
 
void GetCompressorInfo(CompressionInfoPtr cp)
{
    if (cp->recordSize > sizeof(CompressionInfo))           // limit amount we return
        cp->recordSize = sizeof(CompressionInfo);
 
    cp->compressionID = fixedCompression;                   // must set this to fixedCompression
    cp->samplesPerPacket = 1;                               // no. samples in one compressed packet
    cp->bytesPerPacket = 1;                                 // no. bytes in a packet
    cp->bytesPerSample = 2;                                 // no. bytes in a compressed sample
}
 
//Code taken from "IMA Recommended Practices for Enhancing Digital Audio Compatibility in Multimedia Systems"
inline short Normalize (short *value) {
    int numShiftBits, msb, nextmsb;
 
    numShiftBits = 0;
    msb = (*value & 0x8000) >> 15;
    nextmsb = (*value & 0x4000) >> 14;
    while (msb == nextmsb) {
        *value <<= 1;
        numShiftBits++;
        msb = (*value & 0x8000) >> 15;
        nextmsb = (*value & 0x4000) >> 14;
    }
    return (numShiftBits);
}
 
//Code taken from "IMA Recommended Practices for Enhancing Digital Audio Compatibility in Multimedia Systems"
void CompressALaw(short *inbuf, Byte *outbuf, unsigned long framesToConvert,
                    unsigned long numChannels)
{
    int             i;
    short           origSample, X, YYY, ZZZZ, numberOfShiftBits;
    unsigned char   newSample;
 
    for (i = 0; i < framesToConvert*numChannels; i++) {     // loop over all source frames
        origSample = *inbuf;                                // get ALaw sample
        inbuf ++;
 
        if (origSample == -32768) {                         //check boundary condition
            newSample = 0x2A;
        } else {
            X = (origSample & 0x8000) >> 8;                 //create the sign bit
            if (origSample & 0x8000)                        //abs(origSample)
                origSample = -origSample;
            if (origSample < 0x0100) {                      //check for zero segment
                ZZZZ = (origSample >> 4) & 0x000F;  //if zero segment, shift down 4 to get position
                YYY = 0x0000;                               //create segment
            } else {                                        //not zero segment
                numberOfShiftBits = Normalize (&origSample);
                ZZZZ = (origSample & 0x3C00) >> 10;         //create position
                YYY = (7 - numberOfShiftBits) << 4;         //create segment
            }
            newSample = X | YYY | ZZZZ;                 //combine sign bit, segment, and position
            newSample ^= 0xD5;                              //invert necessary bits
        }
 
        *outbuf = newSample;                                // output 16-bit sample
        outbuf ++;
    }
}