/*
 * Copyright (c) 2011 Apple Inc. All rights reserved.
 *
 * @APPLE_APACHE_LICENSE_HEADER_START@
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @APPLE_APACHE_LICENSE_HEADER_END@
 */

/*
        File:		ALACDecoder.cpp
*/

#include <stdlib.h>
#include <string.h>

#include "ALACDecoder.h"

#include "dplib.h"
#include "aglib.h"
#include "matrixlib.h"

#include "ALACBitUtilities.h"
#include "EndianPortable.h"

// constants/data
// const uint32_t kMaxBitDepth = 32; // max allowed bit depth is 32

// prototypes
static void Zero16(int16_t *buffer, uint32_t numItems, uint32_t stride);
static void Zero24(uint8_t *buffer, uint32_t numItems, uint32_t stride);
static void Zero32(int32_t *buffer, uint32_t numItems, uint32_t stride);

/*
        Constructor
*/
ALACDecoder::ALACDecoder() :
    mMixBufferU(nil),
    mMixBufferV(nil),
    mPredictor(nil),
    mShiftBuffer(nil)
{
    memset(&mConfig, 0, sizeof(mConfig));
}

/*
        Destructor
*/
ALACDecoder::~ALACDecoder()
{
    // delete the matrix mixing buffers
    if (mMixBufferU) {
        free(mMixBufferU);
        mMixBufferU = NULL;
    }
    if (mMixBufferV) {
        free(mMixBufferV);
        mMixBufferV = NULL;
    }

    // delete the dynamic predictor's "corrector" buffer
    // - note: mShiftBuffer shares memory with this buffer
    if (mPredictor) {
        free(mPredictor);
        mPredictor = NULL;
    }
}

/*
        Init()
        - initialize the decoder with the given configuration
*/
int32_t ALACDecoder::Init(void *inMagicCookie, uint32_t inMagicCookieSize)
{
    int32_t            status = ALAC_noErr;
    ALACSpecificConfig theConfig;
    uint8_t           *theActualCookie         = (uint8_t *)inMagicCookie;
    uint32_t           theCookieBytesRemaining = inMagicCookieSize;

    // For historical reasons the decoder needs to be resilient to magic cookies vended by older encoders.
    // As specified in the ALACMagicCookieDescription.txt document, there may be additional data encapsulating
    // the ALACSpecificConfig. This would consist of format ('frma') and 'alac' atoms which precede the
    // ALACSpecificConfig.
    // See ALACMagicCookieDescription.txt for additional documentation concerning the 'magic cookie'

    // skip format ('frma') atom if present
    if (theActualCookie[4] == 'f' && theActualCookie[5] == 'r' && theActualCookie[6] == 'm' && theActualCookie[7] == 'a') {
        theActualCookie += 12;
        theCookieBytesRemaining -= 12;
    }

    // skip 'alac' atom header if present
    if (theActualCookie[4] == 'a' && theActualCookie[5] == 'l' && theActualCookie[6] == 'a' && theActualCookie[7] == 'c') {
        theActualCookie += 12;
        theCookieBytesRemaining -= 12;
    }

    // read the ALACSpecificConfig
    if (theCookieBytesRemaining >= sizeof(ALACSpecificConfig)) {
        theConfig.frameLength       = Swap32BtoN(((ALACSpecificConfig *)theActualCookie)->frameLength);
        theConfig.compatibleVersion = ((ALACSpecificConfig *)theActualCookie)->compatibleVersion;
        theConfig.bitDepth          = ((ALACSpecificConfig *)theActualCookie)->bitDepth;
        theConfig.pb                = ((ALACSpecificConfig *)theActualCookie)->pb;
        theConfig.mb                = ((ALACSpecificConfig *)theActualCookie)->mb;
        theConfig.kb                = ((ALACSpecificConfig *)theActualCookie)->kb;
        theConfig.numChannels       = ((ALACSpecificConfig *)theActualCookie)->numChannels;
        theConfig.maxRun            = Swap16BtoN(((ALACSpecificConfig *)theActualCookie)->maxRun);
        theConfig.maxFrameBytes     = Swap32BtoN(((ALACSpecificConfig *)theActualCookie)->maxFrameBytes);
        theConfig.avgBitRate        = Swap32BtoN(((ALACSpecificConfig *)theActualCookie)->avgBitRate);
        theConfig.sampleRate        = Swap32BtoN(((ALACSpecificConfig *)theActualCookie)->sampleRate);

        mConfig = theConfig;

        RequireAction(mConfig.compatibleVersion <= kALACVersion, return kALAC_ParamError;);

        // allocate mix buffers
        mMixBufferU = (int32_t *)calloc(mConfig.frameLength * sizeof(int32_t), 1);
        mMixBufferV = (int32_t *)calloc(mConfig.frameLength * sizeof(int32_t), 1);

        // allocate dynamic predictor buffer
        mPredictor = (int32_t *)calloc(mConfig.frameLength * sizeof(int32_t), 1);

        // "shift off" buffer shares memory with predictor buffer
        mShiftBuffer = (uint16_t *)mPredictor;

        RequireAction((mMixBufferU != nil) && (mMixBufferV != nil) && (mPredictor != nil),
                      status = kALAC_MemFullError;
                      goto Exit;);
    }
    else {
        status = kALAC_ParamError;
    }

    // skip to Channel Layout Info
    // theActualCookie += sizeof(ALACSpecificConfig);

    // Currently, the Channel Layout Info portion of the magic cookie (as defined in the
    // ALACMagicCookieDescription.txt document) is unused by the decoder.

Exit:
    return status;
}

/*
        Decode()
        - the decoded samples are interleaved into the output buffer in the order they arrive in
          the bitstream
*/
int32_t ALACDecoder::Decode(BitBuffer *bits, uint8_t *sampleBuffer, uint32_t numSamples, uint32_t numChannels, uint32_t *outNumSamples)
{
    BitBuffer  shiftBits;
    uint32_t   bits1, bits2;
    uint8_t    tag;
    uint8_t    elementInstanceTag;
    AGParamRec agParams;
    uint32_t   channelIndex;
    int16_t    coefsU[32]; // max possible size is 32 although NUMCOEPAIRS is the current limit
    int16_t    coefsV[32];
    uint8_t    numU, numV;
    uint8_t    mixBits;
    int8_t     mixRes;
    uint16_t   unusedHeader;
    uint8_t    escapeFlag;
    uint32_t   chanBits;
    uint8_t    bytesShifted;
    uint32_t   shift;
    uint8_t    modeU, modeV;
    uint32_t   denShiftU, denShiftV;
    uint16_t   pbFactorU, pbFactorV;
    uint16_t   pb;
    // int16_t   *samples;
    int16_t *out16;
    uint8_t *out20;
    uint8_t *out24;
    int32_t *out32;
    uint8_t  headerByte;
    uint8_t  partialFrame;
    uint32_t extraBits;
    int32_t  val;
    uint32_t i, j;
    int32_t  status;

    RequireAction((bits != nil) && (sampleBuffer != nil) && (outNumSamples != nil), return kALAC_ParamError;);
    RequireAction(numChannels > 0, return kALAC_ParamError;);

    mActiveElements = 0;
    channelIndex    = 0;

    // samples = (int16_t *)sampleBuffer;

    status         = ALAC_noErr;
    *outNumSamples = numSamples;

    while (status == ALAC_noErr) {
        // bail if we ran off the end of the buffer
        RequireAction(bits->cur < bits->end, status = kALAC_ParamError; goto Exit;);

        // copy global decode params for this element
        pb = mConfig.pb;

        // read element tag
        tag = BitBufferReadSmall(bits, 3);
        switch (tag) {
            case ID_SCE:
            case ID_LFE: {
                // mono/LFE channel
                elementInstanceTag = BitBufferReadSmall(bits, 4);
                mActiveElements |= (1u << elementInstanceTag);

                // read the 12 unused header bits
                unusedHeader = (uint16_t)BitBufferRead(bits, 12);
                RequireAction(unusedHeader == 0, status = kALAC_ParamError; goto Exit;);

                // read the 1-bit "partial frame" flag, 2-bit "shift-off" flag & 1-bit "escape" flag
                headerByte = (uint8_t)BitBufferRead(bits, 4);

                partialFrame = headerByte >> 3;

                bytesShifted = (headerByte >> 1) & 0x3u;
                RequireAction(bytesShifted != 3, status = kALAC_ParamError; goto Exit;);

                shift = bytesShifted * 8;

                escapeFlag = headerByte & 0x1;

                chanBits = mConfig.bitDepth - (bytesShifted * 8);

                // check for partial frame to override requested numSamples
                if (partialFrame != 0) {
                    numSamples = BitBufferRead(bits, 16) << 16;
                    numSamples |= BitBufferRead(bits, 16);
                }

                if (escapeFlag == 0) {
                    // compressed frame, read rest of parameters
                    mixBits = (uint8_t)BitBufferRead(bits, 8);
                    mixRes  = (int8_t)BitBufferRead(bits, 8);
                    // Assert( (mixBits == 0) && (mixRes == 0) );		// no mixing for mono

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    modeU      = headerByte >> 4;
                    denShiftU  = headerByte & 0xfu;

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    pbFactorU  = headerByte >> 5;
                    numU       = headerByte & 0x1fu;

                    for (i = 0; i < numU; i++)
                        coefsU[i] = (int16_t)BitBufferRead(bits, 16);

                    // if shift active, skip the the shift buffer but remember where it starts
                    if (bytesShifted != 0) {
                        shiftBits = *bits;
                        BitBufferAdvance(bits, (bytesShifted * 8) * numSamples);
                    }

                    // decompress
                    set_ag_params(&agParams, mConfig.mb, (pb * pbFactorU) / 4, mConfig.kb, numSamples, numSamples, mConfig.maxRun);
                    status = dyn_decomp(&agParams, bits, mPredictor, numSamples, chanBits, &bits1);
                    RequireNoErr(status, goto Exit;);

                    if (modeU == 0) {
                        unpc_block(mPredictor, mMixBufferU, numSamples, &coefsU[0], numU, chanBits, denShiftU);
                    }
                    else {
                        // the special "numActive == 31" mode can be done in-place
                        unpc_block(mPredictor, mPredictor, numSamples, nil, 31, chanBits, 0);
                        unpc_block(mPredictor, mMixBufferU, numSamples, &coefsU[0], numU, chanBits, denShiftU);
                    }
                }
                else {
                    // Assert( bytesShifted == 0 );

                    // uncompressed frame, copy data into the mix buffer to use common output code
                    shift = 32 - chanBits;
                    if (chanBits <= 16) {
                        for (i = 0; i < numSamples; i++) {
                            val            = (int32_t)BitBufferRead(bits, (uint8_t)chanBits);
                            val            = (val << shift) >> shift;
                            mMixBufferU[i] = val;
                        }
                    }
                    else {
                        // BitBufferRead() can't read more than 16 bits at a time so break up the reads
                        extraBits = chanBits - 16;
                        for (i = 0; i < numSamples; i++) {
                            val            = (int32_t)BitBufferRead(bits, 16);
                            val            = (val << 16) >> shift;
                            mMixBufferU[i] = val | BitBufferRead(bits, (uint8_t)extraBits);
                        }
                    }

                    mixBits = mixRes = 0;
                    bits1            = chanBits * numSamples;
                    bytesShifted     = 0;
                }

                // now read the shifted values into the shift buffer
                if (bytesShifted != 0) {
                    shift = bytesShifted * 8;
                    // Assert( shift <= 16 );

                    for (i = 0; i < numSamples; i++)
                        mShiftBuffer[i] = (uint16_t)BitBufferRead(&shiftBits, (uint8_t)shift);
                }

                // convert 32-bit integers into output buffer
                switch (mConfig.bitDepth) {
                    case 16:
                        out16 = &((int16_t *)sampleBuffer)[channelIndex];
                        for (i = 0, j = 0; i < numSamples; i++, j += numChannels)
                            out16[j] = (int16_t)mMixBufferU[i];
                        break;
                    case 20:
                        out20 = (uint8_t *)sampleBuffer + (channelIndex * 3);
                        copyPredictorTo20(mMixBufferU, out20, numChannels, numSamples);
                        break;
                    case 24:
                        out24 = (uint8_t *)sampleBuffer + (channelIndex * 3);
                        if (bytesShifted != 0)
                            copyPredictorTo24Shift(mMixBufferU, mShiftBuffer, out24, numChannels, numSamples, bytesShifted);
                        else
                            copyPredictorTo24(mMixBufferU, out24, numChannels, numSamples);
                        break;
                    case 32:
                        out32 = &((int32_t *)sampleBuffer)[channelIndex];
                        if (bytesShifted != 0)
                            copyPredictorTo32Shift(mMixBufferU, mShiftBuffer, out32, numChannels, numSamples, bytesShifted);
                        else
                            copyPredictorTo32(mMixBufferU, out32, numChannels, numSamples);
                        break;
                }

                channelIndex += 1;
                *outNumSamples = numSamples;
                break;
            }

            case ID_CPE: {
                // if decoding this pair would take us over the max channels limit, bail
                if ((channelIndex + 2) > numChannels)
                    goto NoMoreChannels;

                // stereo channel pair
                elementInstanceTag = BitBufferReadSmall(bits, 4);
                mActiveElements |= (1u << elementInstanceTag);

                // read the 12 unused header bits
                unusedHeader = (uint16_t)BitBufferRead(bits, 12);
                RequireAction(unusedHeader == 0, status = kALAC_ParamError; goto Exit;);

                // read the 1-bit "partial frame" flag, 2-bit "shift-off" flag & 1-bit "escape" flag
                headerByte = (uint8_t)BitBufferRead(bits, 4);

                partialFrame = headerByte >> 3;

                bytesShifted = (headerByte >> 1) & 0x3u;
                RequireAction(bytesShifted != 3, status = kALAC_ParamError; goto Exit;);

                shift = bytesShifted * 8;

                escapeFlag = headerByte & 0x1;

                chanBits = mConfig.bitDepth - (bytesShifted * 8) + 1;

                // check for partial frame length to override requested numSamples
                if (partialFrame != 0) {
                    numSamples = BitBufferRead(bits, 16) << 16;
                    numSamples |= BitBufferRead(bits, 16);
                }

                if (escapeFlag == 0) {
                    // compressed frame, read rest of parameters
                    mixBits = (uint8_t)BitBufferRead(bits, 8);
                    mixRes  = (int8_t)BitBufferRead(bits, 8);

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    modeU      = headerByte >> 4;
                    denShiftU  = headerByte & 0xfu;

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    pbFactorU  = headerByte >> 5;
                    numU       = headerByte & 0x1fu;
                    for (i = 0; i < numU; i++)
                        coefsU[i] = (int16_t)BitBufferRead(bits, 16);

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    modeV      = headerByte >> 4;
                    denShiftV  = headerByte & 0xfu;

                    headerByte = (uint8_t)BitBufferRead(bits, 8);
                    pbFactorV  = headerByte >> 5;
                    numV       = headerByte & 0x1fu;
                    for (i = 0; i < numV; i++)
                        coefsV[i] = (int16_t)BitBufferRead(bits, 16);

                    // if shift active, skip the interleaved shifted values but remember where they start
                    if (bytesShifted != 0) {
                        shiftBits = *bits;
                        BitBufferAdvance(bits, (bytesShifted * 8) * 2 * numSamples);
                    }

                    // decompress and run predictor for "left" channel
                    set_ag_params(&agParams, mConfig.mb, (pb * pbFactorU) / 4, mConfig.kb, numSamples, numSamples, mConfig.maxRun);
                    status = dyn_decomp(&agParams, bits, mPredictor, numSamples, chanBits, &bits1);
                    RequireNoErr(status, goto Exit;);

                    if (modeU == 0) {
                        unpc_block(mPredictor, mMixBufferU, numSamples, &coefsU[0], numU, chanBits, denShiftU);
                    }
                    else {
                        // the special "numActive == 31" mode can be done in-place
                        unpc_block(mPredictor, mPredictor, numSamples, nil, 31, chanBits, 0);
                        unpc_block(mPredictor, mMixBufferU, numSamples, &coefsU[0], numU, chanBits, denShiftU);
                    }

                    // decompress and run predictor for "right" channel
                    set_ag_params(&agParams, mConfig.mb, (pb * pbFactorV) / 4, mConfig.kb, numSamples, numSamples, mConfig.maxRun);
                    status = dyn_decomp(&agParams, bits, mPredictor, numSamples, chanBits, &bits2);
                    RequireNoErr(status, goto Exit;);

                    if (modeV == 0) {
                        unpc_block(mPredictor, mMixBufferV, numSamples, &coefsV[0], numV, chanBits, denShiftV);
                    }
                    else {
                        // the special "numActive == 31" mode can be done in-place
                        unpc_block(mPredictor, mPredictor, numSamples, nil, 31, chanBits, 0);
                        unpc_block(mPredictor, mMixBufferV, numSamples, &coefsV[0], numV, chanBits, denShiftV);
                    }
                }
                else {
                    // Assert( bytesShifted == 0 );

                    // uncompressed frame, copy data into the mix buffers to use common output code
                    chanBits = mConfig.bitDepth;
                    shift    = 32 - chanBits;
                    if (chanBits <= 16) {
                        for (i = 0; i < numSamples; i++) {
                            val            = (int32_t)BitBufferRead(bits, (uint8_t)chanBits);
                            val            = (val << shift) >> shift;
                            mMixBufferU[i] = val;

                            val            = (int32_t)BitBufferRead(bits, (uint8_t)chanBits);
                            val            = (val << shift) >> shift;
                            mMixBufferV[i] = val;
                        }
                    }
                    else {
                        // BitBufferRead() can't read more than 16 bits at a time so break up the reads
                        extraBits = chanBits - 16;
                        for (i = 0; i < numSamples; i++) {
                            val            = (int32_t)BitBufferRead(bits, 16);
                            val            = (val << 16) >> shift;
                            mMixBufferU[i] = val | BitBufferRead(bits, (uint8_t)extraBits);

                            val            = (int32_t)BitBufferRead(bits, 16);
                            val            = (val << 16) >> shift;
                            mMixBufferV[i] = val | BitBufferRead(bits, (uint8_t)extraBits);
                        }
                    }

                    bits1   = chanBits * numSamples;
                    bits2   = chanBits * numSamples;
                    mixBits = mixRes = 0;
                    bytesShifted     = 0;
                }

                // now read the shifted values into the shift buffer
                if (bytesShifted != 0) {
                    shift = bytesShifted * 8;
                    // Assert( shift <= 16 );

                    for (i = 0; i < (numSamples * 2); i += 2) {
                        mShiftBuffer[i + 0] = (uint16_t)BitBufferRead(&shiftBits, (uint8_t)shift);
                        mShiftBuffer[i + 1] = (uint16_t)BitBufferRead(&shiftBits, (uint8_t)shift);
                    }
                }

                // un-mix the data and convert to output format
                // - note that mixRes = 0 means just interleave so we use that path for uncompressed frames
                switch (mConfig.bitDepth) {
                    case 16:
                        out16 = &((int16_t *)sampleBuffer)[channelIndex];
                        unmix16(mMixBufferU, mMixBufferV, out16, numChannels, numSamples, mixBits, mixRes);
                        break;
                    case 20:
                        out20 = (uint8_t *)sampleBuffer + (channelIndex * 3);
                        unmix20(mMixBufferU, mMixBufferV, out20, numChannels, numSamples, mixBits, mixRes);
                        break;
                    case 24:
                        out24 = (uint8_t *)sampleBuffer + (channelIndex * 3);
                        unmix24(mMixBufferU, mMixBufferV, out24, numChannels, numSamples,
                                mixBits, mixRes, mShiftBuffer, bytesShifted);
                        break;
                    case 32:
                        out32 = &((int32_t *)sampleBuffer)[channelIndex];
                        unmix32(mMixBufferU, mMixBufferV, out32, numChannels, numSamples,
                                mixBits, mixRes, mShiftBuffer, bytesShifted);
                        break;
                }

                channelIndex += 2;
                *outNumSamples = numSamples;
                break;
            }

            case ID_CCE:
            case ID_PCE: {
                // unsupported element, bail
                // AssertNoErr( tag );
                status = kALAC_ParamError;
                break;
            }

            case ID_DSE: {
                // data stream element -- parse but ignore
                status = this->DataStreamElement(bits);
                break;
            }

            case ID_FIL: {
                // fill element -- parse but ignore
                status = this->FillElement(bits);
                break;
            }

            case ID_END: {
                // frame end, all done so byte align the frame and check for overruns
                BitBufferByteAlign(bits, false);
                // Assert( bits->cur == bits->end );
                goto Exit;
            }
        }

#if !DEBUG
        // if we've decoded all of our channels, bail (but not in debug b/c we want to know if we're seeing bad bits)
        // - this also protects us if the config does not match the bitstream or crap data bits follow the audio bits
        if (channelIndex >= numChannels)
            break;
#endif
    }

NoMoreChannels:

    // if we get here and haven't decoded all of the requested channels, fill the remaining channels with zeros
    for (; channelIndex < numChannels; channelIndex++) {
        switch (mConfig.bitDepth) {
            case 16: {
                int16_t *fill16 = &((int16_t *)sampleBuffer)[channelIndex];
                Zero16(fill16, numSamples, numChannels);
                break;
            }
            case 24: {
                uint8_t *fill24 = (uint8_t *)sampleBuffer + (channelIndex * 3);
                Zero24(fill24, numSamples, numChannels);
                break;
            }
            case 32: {
                int32_t *fill32 = &((int32_t *)sampleBuffer)[channelIndex];
                Zero32(fill32, numSamples, numChannels);
                break;
            }
        }
    }

Exit:
    return status;
}

#if PRAGMA_MARK
#pragma mark -
#endif

/*
        FillElement()
        - they're just filler so we don't need 'em
*/
int32_t ALACDecoder::FillElement(BitBuffer *bits)
{
    int16_t count;

    // 4-bit count or (4-bit + 8-bit count) if 4-bit count == 15
    // - plus this weird -1 thing I still don't fully understand
    count = BitBufferReadSmall(bits, 4);
    if (count == 15)
        count += (int16_t)BitBufferReadSmall(bits, 8) - 1;

    BitBufferAdvance(bits, count * 8);

    RequireAction(bits->cur <= bits->end, return kALAC_ParamError;);

    return ALAC_noErr;
}

/*
        DataStreamElement()
        - we don't care about data stream elements so just skip them
*/
int32_t ALACDecoder::DataStreamElement(BitBuffer *bits)
{
    // uint8_t  element_instance_tag;
    int32_t  data_byte_align_flag;
    uint16_t count;

    // the tag associates this data stream element with a given audio element
    // element_instance_tag = BitBufferReadSmall(bits, 4);

    data_byte_align_flag = BitBufferReadOne(bits);

    // 8-bit count or (8-bit + 8-bit count) if 8-bit count == 255
    count = BitBufferReadSmall(bits, 8);
    if (count == 255)
        count += BitBufferReadSmall(bits, 8);

    // the align flag means the bitstream should be byte-aligned before reading the following data bytes
    if (data_byte_align_flag)
        BitBufferByteAlign(bits, false);

    // skip the data bytes
    BitBufferAdvance(bits, count * 8);

    RequireAction(bits->cur <= bits->end, return kALAC_ParamError;);

    return ALAC_noErr;
}

/*
        ZeroN()
        - helper routines to clear out output channel buffers when decoding fewer channels than requested
*/
static void Zero16(int16_t *buffer, uint32_t numItems, uint32_t stride)
{
    if (stride == 1) {
        memset(buffer, 0, numItems * sizeof(int16_t));
    }
    else {
        for (uint32_t index = 0; index < (numItems * stride); index += stride)
            buffer[index] = 0;
    }
}

static void Zero24(uint8_t *buffer, uint32_t numItems, uint32_t stride)
{
    if (stride == 1) {
        memset(buffer, 0, numItems * 3);
    }
    else {
        for (uint32_t index = 0; index < (numItems * stride * 3); index += (stride * 3)) {
            buffer[index + 0] = 0;
            buffer[index + 1] = 0;
            buffer[index + 2] = 0;
        }
    }
}

static void Zero32(int32_t *buffer, uint32_t numItems, uint32_t stride)
{
    if (stride == 1) {
        memset(buffer, 0, numItems * sizeof(int32_t));
    }
    else {
        for (uint32_t index = 0; index < (numItems * stride); index += stride)
            buffer[index] = 0;
    }
}
