/* -----------------------------------------------------------------------------
Software License for The Fraunhofer FDK AAC Codec Library for Android

© Copyright  1995 - 2018 Fraunhofer-Gesellschaft zur Förderung der angewandten
Forschung e.V. All rights reserved.

 1.    INTRODUCTION
The Fraunhofer FDK AAC Codec Library for Android ("FDK AAC Codec") is software
that implements the MPEG Advanced Audio Coding ("AAC") encoding and decoding
scheme for digital audio. This FDK AAC Codec software is intended to be used on
a wide variety of Android devices.

AAC's HE-AAC and HE-AAC v2 versions are regarded as today's most efficient
general perceptual audio codecs. AAC-ELD is considered the best-performing
full-bandwidth communications codec by independent studies and is widely
deployed. AAC has been standardized by ISO and IEC as part of the MPEG
specifications.

Patent licenses for necessary patent claims for the FDK AAC Codec (including
those of Fraunhofer) may be obtained through Via Licensing
(www.vialicensing.com) or through the respective patent owners individually for
the purpose of encoding or decoding bit streams in products that are compliant
with the ISO/IEC MPEG audio standards. Please note that most manufacturers of
Android devices already license these patent claims through Via Licensing or
directly from the patent owners, and therefore FDK AAC Codec software may
already be covered under those patent licenses when it is used for those
licensed purposes only.

Commercially-licensed AAC software libraries, including floating-point versions
with enhanced sound quality, are also available from Fraunhofer. Users are
encouraged to check the Fraunhofer website for additional applications
information and documentation.

2.    COPYRIGHT LICENSE

Redistribution and use in source and binary forms, with or without modification,
are permitted without payment of copyright license fees provided that you
satisfy the following conditions:

You must retain the complete text of this software license in redistributions of
the FDK AAC Codec or your modifications thereto in source code form.

You must retain the complete text of this software license in the documentation
and/or other materials provided with redistributions of the FDK AAC Codec or
your modifications thereto in binary form. You must make available free of
charge copies of the complete source code of the FDK AAC Codec and your
modifications thereto to recipients of copies in binary form.

The name of Fraunhofer may not be used to endorse or promote products derived
from this library without prior written permission.

You may not charge copyright license fees for anyone to use, copy or distribute
the FDK AAC Codec software or your modifications thereto.

Your modified versions of the FDK AAC Codec must carry prominent notices stating
that you changed the software and the date of any change. For modified versions
of the FDK AAC Codec, the term "Fraunhofer FDK AAC Codec Library for Android"
must be replaced by the term "Third-Party Modified Version of the Fraunhofer FDK
AAC Codec Library for Android."

3.    NO PATENT LICENSE

NO EXPRESS OR IMPLIED LICENSES TO ANY PATENT CLAIMS, including without
limitation the patents of Fraunhofer, ARE GRANTED BY THIS SOFTWARE LICENSE.
Fraunhofer provides no warranty of patent non-infringement with respect to this
software.

You may use this FDK AAC Codec software or modifications thereto only for
purposes that are authorized by appropriate patent licenses.

4.    DISCLAIMER

This FDK AAC Codec software is provided by Fraunhofer on behalf of the copyright
holders and contributors "AS IS" and WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
including but not limited to the implied warranties of merchantability and
fitness for a particular purpose. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE for any direct, indirect, incidental, special, exemplary,
or consequential damages, including but not limited to procurement of substitute
goods or services; loss of use, data, or profits, or business interruption,
however caused and on any theory of liability, whether in contract, strict
liability, or tort (including negligence), arising in any way out of the use of
this software, even if advised of the possibility of such damage.

5.    CONTACT INFORMATION

Fraunhofer Institute for Integrated Circuits IIS
Attention: Audio and Multimedia Departments - FDK AAC LL
Am Wolfsmantel 33
91058 Erlangen, Germany

www.iis.fraunhofer.de/amm
amm-info@iis.fraunhofer.de
----------------------------------------------------------------------------- */

/*********************** MPEG surround encoder library *************************

   Author(s):   Max Neuendorf

   Description: Encoder Library Interface
                Interface to Spacial Audio Coding Encoder lib

*******************************************************************************/

/****************************************************************************
\file
Description of file contents
******************************************************************************/

/* Includes ******************************************************************/
#include "sacenc_lib.h"
#include "sacenc_const.h"
#include "genericStds.h"
#include "FDK_core.h"
#include "sacenc_tree.h"
#include "sacenc_bitstream.h"
#include "sacenc_onsetdetect.h"
#include "sacenc_framewindowing.h"
#include "sacenc_filter.h"
#include "sacenc_paramextract.h"
#include "sacenc_staticgain.h"
#include "sacenc_delay.h"
#include "sacenc_dmx_tdom_enh.h"
#include "sacenc_vectorfunctions.h"
#include "qmf.h"

/* Defines *******************************************************************/

/* Encoder library info */
#define SACENC_LIB_VL0 2
#define SACENC_LIB_VL1 0
#define SACENC_LIB_VL2 0
#define SACENC_LIB_TITLE "MPEG Surround Encoder"
#ifdef __ANDROID__
#define SACENC_LIB_BUILD_DATE ""
#define SACENC_LIB_BUILD_TIME ""
#else
#define SACENC_LIB_BUILD_DATE __DATE__
#define SACENC_LIB_BUILD_TIME __TIME__
#endif

#define MAX_MPEGS_BYTES (1 << 14)
#define MAX_SSC_BYTES (1 << 6)

#define MAX_SPACE_TREE_CHANNELS 2
#define NUM_KEEP_WINDOWS 3

/* Data Types ****************************************************************/
typedef struct {
  MP4SPACEENC_MODE encMode;
  MP4SPACEENC_BANDS_CONFIG nParamBands;
  MP4SPACEENC_QUANTMODE quantMode;
  UCHAR bUseCoarseQuant;
  UCHAR bLdMode;
  UCHAR bTimeDomainDmx;
  UINT sampleRate;
  UINT frameTimeSlots;     /* e.g. 32 when used with HE-AAC */
  UINT independencyFactor; /* how often should we set the independency flag */
  INT timeAlignment;       /* additional delay for downmix */

} MP4SPACEENC_SETUP, *HANDLE_MP4SPACEENC_SETUP;

struct ENC_CONFIG_SETUP {
  UCHAR bEncMode_212;
  UCHAR maxHybridInStaticSlots;
  LONG maxSamplingrate;
  INT maxAnalysisLengthTimeSlots;
  INT maxHybridBands;
  INT maxQmfBands;
  INT maxChIn;
  INT maxFrameTimeSlots;
  INT maxFrameLength;
  INT maxChOut;
  INT maxChTotOut;
};

struct MP4SPACE_ENCODER {
  MP4SPACEENC_SETUP user;

  ENC_CONFIG_SETUP setup; /* describe allocated instance */

  HANDLE_FRAMEWINDOW
  hFrameWindow;      /* Windowing, only created+updated, but not used */
  INT nSamplesValid; /* Input Buffer Handling */

  /* Routing Sensible Switches/Variables */
  MP4SPACEENC_BANDS_CONFIG nParamBands;
  UCHAR useTimeDomDownmix;

  /* not Routing Sensible Switches/Varibles - must be contained in Check */
  MP4SPACEENC_MODE encMode;
  UCHAR bEncMode_212_only;

  /* not Routing Sensible Switches/Varibles + lower Classes */
  UCHAR useFrameKeep;
  UINT independencyFactor;
  UINT nSampleRate;
  UCHAR nInputChannels;
  UCHAR nOutputChannels;
  UCHAR nFrameTimeSlots; /* e.g. 32 when used with HE-AAC */
  UCHAR nQmfBands;
  UCHAR nHybridBands;
  UINT nFrameLength; /* number of output waveform samples/channel/frame */

  /* not Routing Sensible Switches/Varibles + lower Classes, secondary computed
   */
  INT nSamplesNext;
  INT nAnalysisLengthTimeSlots;
  INT nAnalysisLookaheadTimeSlots;
  INT nUpdateHybridPositionTimeSlots;
  INT *pnOutputBits;
  INT nInputDelay;
  INT nOutputBufferDelay;
  INT nSurroundAnalysisBufferDelay;
  INT nBitstreamDelayBuffer;
  INT nBitstreamBufferRead;
  INT nBitstreamBufferWrite;
  INT nDiscardOutFrames;
  INT avoid_keep;

  /* not Routing Sensible Switches/Varibles -> moved to lower Classes */
  UCHAR useCoarseQuantCld;    /* Only Used in SpaceTreeSetup */
  UCHAR useCoarseQuantIcc;    /* Only Used in SpaceTreeSetup */
  UCHAR useCoarseQuantCpc;    /* Only Used in SpaceTreeSetup */
  UCHAR useCoarseQuantArbDmx; /* ArbitraryDmx,... not available yet */
  MP4SPACEENC_QUANTMODE
  quantMode;          /* Used for quanitzation and in bitstream writer */
  INT coreCoderDelay; /* Used in delay compensation */
  INT timeAlignment;  /* Used in delay compensation */

  /* Local Processing Variables */
  INT independencyCount;
  INT independencyFlag;
  INT **ppTrCurrPos;                /* belongs somehow to Onset Detection */
  INT trPrevPos[2 * MAX_NUM_TRANS]; /* belongs somehow to Onset Detection */

  FRAMEWIN_LIST frameWinList;
  SPATIALFRAME saveFrame;

  /* Module-Handles */
  SPACE_TREE_SETUP spaceTreeSetup;
  MPEG4SPACEENC_SSCBUF sscBuf;
  FIXP_WIN *pFrameWindowAna__FDK[MAX_NUM_PARAMS];
  HANDLE_QMF_FILTER_BANK *phQmfFiltIn__FDK;
  HANDLE_DC_FILTER phDCFilterSigIn[SACENC_MAX_INPUT_CHANNELS];
  HANDLE_ONSET_DETECT phOnset[SACENC_MAX_INPUT_CHANNELS];
  HANDLE_SPACE_TREE hSpaceTree;
  HANDLE_BSF_INSTANCE hBitstreamFormatter;
  HANDLE_STATIC_GAIN_CONFIG hStaticGainConfig;
  HANDLE_STATIC_GAIN hStaticGain;
  HANDLE_DELAY hDelay;

  /* enhanced time domain downmix (for stereo input) */
  HANDLE_ENHANCED_TIME_DOMAIN_DMX hEnhancedTimeDmx;

  /* Data Buffers */
  INT_PCM **ppTimeSigIn__FDK;
  INT_PCM **ppTimeSigDelayIn__FDK;
  INT_PCM **ppTimeSigOut__FDK;
  FIXP_DPK ***pppHybridIn__FDK;
  FIXP_DPK ***pppHybridInStatic__FDK;
  FIXP_DPK ***pppProcDataIn__FDK;
  INT_PCM *pOutputDelayBuffer__FDK;

  UCHAR **ppBitstreamDelayBuffer;

  UCHAR *pParameterBand2HybridBandOffset;
  INT staticGainScale;

  INT *pEncoderInputChScale;
  INT *staticTimeDomainDmxInScale;
};

/* Constants *****************************************************************/
static const UCHAR pValidBands_Ld[8] = {4, 5, 7, 9, 12, 15, 23, 40};

static const UCHAR qmf2qmf[] = /* Bypass the HybridAnylyis/Synthesis*/
    {0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
     15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
     30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,
     45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
     60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,
     75,  76,  77,  78,  79,  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,
     90,  91,  92,  93,  94,  95,  96,  97,  98,  99,  100, 101, 102, 103, 104,
     105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
     120, 121, 122, 123, 124, 125, 126, 127};

/* Function / Class Declarations *********************************************/
static FDK_SACENC_ERROR mp4SpaceEnc_create(
    HANDLE_MP4SPACE_ENCODER *phMp4SpaceEnc);

static FDK_SACENC_ERROR FillSpatialSpecificConfig(
    const HANDLE_MP4SPACE_ENCODER hEnc, SPATIALSPECIFICCONFIG *const hSsc);

static FDK_SACENC_ERROR mp4SpaceEnc_FillSpaceTreeSetup(
    const HANDLE_MP4SPACE_ENCODER hEnc,
    SPACE_TREE_SETUP *const hSpaceTreeSetup);

static FDK_SACENC_ERROR mp4SpaceEnc_InitDelayCompensation(
    HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc, const INT coreCoderDelay);

static FDK_SACENC_ERROR mp4SpaceEnc_InitDefault(
    HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc);

static DECORRCONFIG mp4SpaceEnc_GetDecorrConfig(const MP4SPACEENC_MODE encMode);

static FDK_SACENC_ERROR mp4SpaceEnc_InitNumParamBands(
    HANDLE_MP4SPACE_ENCODER hEnc, const MP4SPACEENC_BANDS_CONFIG nParamBands);

/* Function / Class Definition ***********************************************/
static UINT mp4SpaceEnc_GetNumQmfBands(const UINT nSampleRate) {
  UINT nQmfBands = 0;

  if (nSampleRate < 27713)
    nQmfBands = 32;
  else if (nSampleRate < 55426)
    nQmfBands = 64;

  return nQmfBands;
}

static UINT updateQmfFlags(const UINT flags, const INT keepStates) {
  UINT qmfFlags = flags;

  qmfFlags = (qmfFlags & (~(UINT)QMF_FLAG_LP));
  qmfFlags = (qmfFlags | QMF_FLAG_MPSLDFB);
  qmfFlags = (keepStates) ? (qmfFlags | QMF_FLAG_KEEP_STATES)
                          : (qmfFlags & (~(UINT)QMF_FLAG_KEEP_STATES));

  return qmfFlags;
}

static INT freq2HybridBand(const UINT nFrequency, const UINT nSampleRate,
                           const UINT nQmfBands) {
  /*
    nQmfSlotWidth = (nSampleRate/2) / nQmfBands;
    nQmfBand      = nFrequency / nQmfSlotWidth;
  */
  int nHybridBand = -1;
  int scale = 0;
  const FIXP_DBL temp = fDivNorm((FIXP_DBL)(2 * nFrequency * nQmfBands),
                                 (FIXP_DBL)nSampleRate, &scale);
  const int nQmfBand = scaleValue(temp, scale - (DFRACT_BITS - 1));

  if ((nQmfBand > -1) && (nQmfBand < (int)nQmfBands)) {
    nHybridBand = qmf2qmf[nQmfBand];
  }

  return nHybridBand;
}

/*
 * Examine buffer descriptor regarding choosen type.
 *
 * \param pBufDesc              Pointer to buffer descriptor
 * \param type                  Buffer type to look for.

 * \return - Buffer descriptor index.
 *         -1, if there is no entry available.
 */
static INT getBufDescIdx(const FDK_bufDescr *pBufDesc, const UINT type) {
  INT i, idx = -1;

  for (i = 0; i < (int)pBufDesc->numBufs; i++) {
    if (pBufDesc->pBufType[i] == type) {
      idx = i;
      break;
    }
  }
  return idx;
}

FDK_SACENC_ERROR FDK_sacenc_open(HANDLE_MP4SPACE_ENCODER *phMp4SpaceEnc) {
  return mp4SpaceEnc_create(phMp4SpaceEnc);
}

static FDK_SACENC_ERROR mp4SpaceEnc_create(
    HANDLE_MP4SPACE_ENCODER *phMp4SpaceEnc) {
  FDK_SACENC_ERROR error = SACENC_OK;
  HANDLE_MP4SPACE_ENCODER hEnc = NULL;
  ENC_CONFIG_SETUP setup;

  if (NULL == phMp4SpaceEnc) {
    error = SACENC_INVALID_HANDLE;
  } else {
    int i, ch;
    FDKmemclear(&setup, sizeof(ENC_CONFIG_SETUP));

    /* Allocate Encoder Instance */
    FDK_ALLOCATE_MEMORY_1D(hEnc, 1, struct MP4SPACE_ENCODER);

    /* Clear everything, also pointers. */
    if (NULL != hEnc) {
      FDKmemclear(hEnc, sizeof(struct MP4SPACE_ENCODER));
    }

    setup.maxSamplingrate = 48000;
    setup.maxFrameTimeSlots = 16;

    setup.maxAnalysisLengthTimeSlots = 3 * setup.maxFrameTimeSlots;
    setup.maxQmfBands = mp4SpaceEnc_GetNumQmfBands(setup.maxSamplingrate);
    ;
    setup.maxHybridBands = setup.maxQmfBands;
    setup.maxFrameLength = setup.maxQmfBands * setup.maxFrameTimeSlots;

    setup.maxChIn = 2;
    setup.maxChOut = 1;
    setup.maxChTotOut = setup.maxChOut;
    setup.bEncMode_212 = 1;
    setup.maxHybridInStaticSlots = 24;

    /* Open Static Gain*/
    if (SACENC_OK !=
        (error = fdk_sacenc_staticGain_OpenConfig(&hEnc->hStaticGainConfig))) {
      goto bail;
    }

    /* enhanced time domain downmix (for stereo input) */
    if (SACENC_OK != (error = fdk_sacenc_open_enhancedTimeDomainDmx(
                          &hEnc->hEnhancedTimeDmx, setup.maxFrameLength))) {
      goto bail;
    }

    FDK_ALLOCATE_MEMORY_1D(hEnc->pParameterBand2HybridBandOffset,
                           MAX_NUM_PARAM_BANDS, UCHAR);

    /* Create Space Tree first, to get number of in-/output channels */
    if (SACENC_OK != (error = fdk_sacenc_spaceTree_Open(&hEnc->hSpaceTree))) {
      goto bail;
    }

    FDK_ALLOCATE_MEMORY_1D(hEnc->pEncoderInputChScale, setup.maxChIn, INT);
    FDK_ALLOCATE_MEMORY_1D(hEnc->staticTimeDomainDmxInScale, setup.maxChIn,
                           INT);

    FDK_ALLOCATE_MEMORY_1D(hEnc->phQmfFiltIn__FDK, setup.maxChIn,
                           HANDLE_QMF_FILTER_BANK);

    /* Allocate Analysis Filterbank Structs */
    for (ch = 0; ch < setup.maxChIn; ch++) {
      FDK_ALLOCATE_MEMORY_1D_INT(hEnc->phQmfFiltIn__FDK[ch], 1,
                                 struct QMF_FILTER_BANK, SECT_DATA_L2)
      FDK_ALLOCATE_MEMORY_1D_INT(hEnc->phQmfFiltIn__FDK[ch]->FilterStates,
                                 2 * 5 * setup.maxQmfBands, FIXP_QAS,
                                 SECT_DATA_L2)
    }

    /* Allocate Synthesis Filterbank Structs for arbitrary downmix */

    /* Allocate DC Filter Struct for normal signal input */
    for (ch = 0; ch < setup.maxChIn; ch++) {
      if (SACENC_OK !=
          (error = fdk_sacenc_createDCFilter(&hEnc->phDCFilterSigIn[ch]))) {
        goto bail;
      }
    }

    /* Open Onset Detection */
    for (ch = 0; ch < setup.maxChIn; ch++) {
      if (SACENC_OK != (error = fdk_sacenc_onsetDetect_Open(
                            &hEnc->phOnset[ch], setup.maxFrameTimeSlots))) {
        goto bail;
      }
    }

    FDK_ALLOCATE_MEMORY_2D(hEnc->ppTrCurrPos, setup.maxChIn, MAX_NUM_TRANS,
                           INT);

    /* Create Windowing */
    if (SACENC_OK !=
        (error = fdk_sacenc_frameWindow_Create(&hEnc->hFrameWindow))) {
      goto bail;
    }

    /* Open static gain */
    if (SACENC_OK != (error = fdk_sacenc_staticGain_Open(&hEnc->hStaticGain))) {
      goto bail;
    }

    /* create bitstream encoder */
    if (SACENC_OK != (error = fdk_sacenc_createSpatialBitstreamEncoder(
                          &hEnc->hBitstreamFormatter))) {
      goto bail;
    }

    FDK_ALLOCATE_MEMORY_1D(hEnc->sscBuf.pSsc, MAX_SSC_BYTES, UCHAR);

    {
      FDK_ALLOCATE_MEMORY_2D(hEnc->ppTimeSigIn__FDK, setup.maxChIn,
                             setup.maxFrameLength + MAX_DELAY_SURROUND_ANALYSIS,
                             INT_PCM);
    }
    FDK_ALLOCATE_MEMORY_2D(hEnc->ppTimeSigDelayIn__FDK, setup.maxChIn,
                           MAX_DELAY_SURROUND_ANALYSIS, INT_PCM);

    /* Create new buffers for several signals (including arbitrary downmix) */
    if (setup.bEncMode_212 == 0) {
      /* pOutputDelayBuffer__FDK buffer is not needed for SACENC_212 mode */
      FDK_ALLOCATE_MEMORY_1D(
          hEnc->pOutputDelayBuffer__FDK,
          (setup.maxFrameLength + MAX_DELAY_OUTPUT) * setup.maxChOut, INT_PCM);
    }

    /* allocate buffers */
    if (setup.bEncMode_212 == 0) {
      /* ppTimeSigOut__FDK buffer is not needed for SACENC_212 mode */
      FDK_ALLOCATE_MEMORY_2D(hEnc->ppTimeSigOut__FDK, setup.maxChTotOut,
                             setup.maxFrameLength, INT_PCM);
    }

    if (setup.bEncMode_212 == 1) {
      /* pppHybridIn__FDK buffer can be reduced by maxFrameTimeSlots/2 slots for
       * SACENC_212 mode */
      FDK_ALLOCATE_MEMORY_3D(
          hEnc->pppHybridIn__FDK, setup.maxChIn,
          setup.maxAnalysisLengthTimeSlots - (setup.maxFrameTimeSlots >> 1),
          setup.maxHybridBands, FIXP_DPK);
      FDK_ALLOCATE_MEMORY_3D(hEnc->pppHybridInStatic__FDK, setup.maxChIn,
                             setup.maxHybridInStaticSlots, setup.maxHybridBands,
                             FIXP_DPK);
    } else {
      FDK_ALLOCATE_MEMORY_3D(hEnc->pppHybridIn__FDK, setup.maxChIn,
                             setup.maxAnalysisLengthTimeSlots,
                             setup.maxHybridBands, FIXP_DPK);
    }

    if (setup.bEncMode_212 == 0) {
      /* pppProcDataIn__FDK buffer is not needed for SACENC_212 mode */
      FDK_ALLOCATE_MEMORY_3D(hEnc->pppProcDataIn__FDK, MAX_SPACE_TREE_CHANNELS,
                             setup.maxAnalysisLengthTimeSlots,
                             setup.maxHybridBands, FIXP_DPK);
    }
    for (i = 0; i < MAX_NUM_PARAMS; i++) {
      FDK_ALLOCATE_MEMORY_1D(hEnc->pFrameWindowAna__FDK[i],
                             setup.maxAnalysisLengthTimeSlots, FIXP_WIN);
    } /* for i */

    if (SACENC_OK != (error = fdk_sacenc_delay_Open(&hEnc->hDelay))) {
      goto bail;
    }

    if (setup.bEncMode_212 == 0) {
      /* ppBitstreamDelayBuffer buffer is not needed for SACENC_212 mode */
      FDK_ALLOCATE_MEMORY_2D(hEnc->ppBitstreamDelayBuffer, MAX_BITSTREAM_DELAY,
                             MAX_MPEGS_BYTES, UCHAR);
    }
    FDK_ALLOCATE_MEMORY_1D(hEnc->pnOutputBits, MAX_BITSTREAM_DELAY, INT);

    hEnc->setup = setup; /* save configuration used while encoder allocation. */
    mp4SpaceEnc_InitDefault(hEnc);

    if (NULL != phMp4SpaceEnc) {
      *phMp4SpaceEnc = hEnc; /* return encoder handle */
    }

  } /* valid handle */

  return error;

bail:
  if (NULL != hEnc) {
    hEnc->setup = setup;
    FDK_sacenc_close(&hEnc);
  }
  return ((SACENC_OK == error) ? SACENC_MEMORY_ERROR : error);
}

static FDK_SACENC_ERROR mp4SpaceEnc_InitDefault(HANDLE_MP4SPACE_ENCODER hEnc) {
  FDK_SACENC_ERROR err = SACENC_OK;

  /* Get default static gain configuration. */
  if (SACENC_OK != (err = fdk_sacenc_staticGain_InitDefaultConfig(
                        hEnc->hStaticGainConfig))) {
    goto bail;
  }

bail:
  return err;
}

static FDK_SACENC_ERROR FDK_sacenc_configure(
    HANDLE_MP4SPACE_ENCODER hEnc, const HANDLE_MP4SPACEENC_SETUP hSetup) {
  FDK_SACENC_ERROR error = SACENC_OK;

  hEnc->nSampleRate = hSetup->sampleRate;
  hEnc->encMode = hSetup->encMode;
  hEnc->nQmfBands = mp4SpaceEnc_GetNumQmfBands(hEnc->nSampleRate);

  /* Make sure that we have set time domain downmix for 212 */
  if (hSetup->encMode == SACENC_212 && hSetup->bTimeDomainDmx == 0) {
    error = SACENC_INVALID_CONFIG;
  } else {
    hEnc->useTimeDomDownmix = hSetup->bTimeDomainDmx;
  }

  hEnc->timeAlignment = hSetup->timeAlignment;
  hEnc->quantMode = hSetup->quantMode;

  hEnc->useCoarseQuantCld = hSetup->bUseCoarseQuant;
  hEnc->useCoarseQuantCpc = hSetup->bUseCoarseQuant;
  hEnc->useFrameKeep = (hSetup->bLdMode == 2);
  hEnc->useCoarseQuantIcc = 0;    /* not available */
  hEnc->useCoarseQuantArbDmx = 0; /* not available for user right now */
  hEnc->independencyFactor = hSetup->independencyFactor;
  hEnc->independencyCount = 0;
  hEnc->independencyFlag = 1;

  /* set number of Hybrid bands */
  hEnc->nHybridBands = hEnc->nQmfBands;
  hEnc->nFrameTimeSlots = hSetup->frameTimeSlots;
  mp4SpaceEnc_InitNumParamBands(hEnc, hSetup->nParamBands);

  return error;
}

FDK_SACENC_ERROR FDK_sacenc_init(HANDLE_MP4SPACE_ENCODER hEnc,
                                 const INT dmxDelay) {
  FDK_SACENC_ERROR error = SACENC_OK;

  /* Sanity Checks */
  if (NULL == hEnc) {
    error = SACENC_INVALID_HANDLE;
  } else {
    const int initStatesFlag = 1;

    int ch; /* loop counter */
    int nChInArbDmx;

    if (SACENC_OK != (error = FDK_sacenc_configure(hEnc, &hEnc->user))) {
      goto bail;
    }

    hEnc->bEncMode_212_only = hEnc->setup.bEncMode_212;

    /* Slots per Frame and Frame Length */
    if (hEnc->nFrameTimeSlots < 1) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }
    hEnc->nFrameLength = hEnc->nQmfBands * hEnc->nFrameTimeSlots;

    if (hEnc->useFrameKeep == 1) {
      hEnc->nAnalysisLengthTimeSlots = 3 * hEnc->nFrameTimeSlots;
      hEnc->nUpdateHybridPositionTimeSlots = hEnc->nFrameTimeSlots;
    } else {
      hEnc->nAnalysisLengthTimeSlots = 2 * hEnc->nFrameTimeSlots;
      hEnc->nUpdateHybridPositionTimeSlots = 0;
    }

    {
      hEnc->nAnalysisLookaheadTimeSlots =
          hEnc->nAnalysisLengthTimeSlots - 3 * hEnc->nFrameTimeSlots / 2;
    }

    /* init parameterBand2hybridBandOffset table */
    fdk_sacenc_calcParameterBand2HybridBandOffset(
        (BOX_SUBBAND_CONFIG)hEnc->nParamBands, hEnc->nHybridBands,
        hEnc->pParameterBand2HybridBandOffset);

    /* Fill Setup structure for Space Tree */
    if (SACENC_OK !=
        (error = mp4SpaceEnc_FillSpaceTreeSetup(hEnc, &hEnc->spaceTreeSetup))) {
      goto bail;
    }

    /* Init space tree configuration */
    if (SACENC_OK !=
        (error = fdk_sacenc_spaceTree_Init(
             hEnc->hSpaceTree, &hEnc->spaceTreeSetup,
             hEnc->pParameterBand2HybridBandOffset, hEnc->useFrameKeep))) {
      goto bail;
    }

    /* Get space tree description and resulting number of input/output channels
     */
    {
      SPACE_TREE_DESCRIPTION spaceTreeDescription;

      if (SACENC_OK != (error = fdk_sacenc_spaceTree_GetDescription(
                            hEnc->hSpaceTree, &spaceTreeDescription))) {
        goto bail;
      }

      hEnc->nInputChannels =
          spaceTreeDescription.nOutChannels; /* space tree description
                                                describes decoder
                                                configuration */
      hEnc->nOutputChannels =
          spaceTreeDescription.nInChannels; /* space tree description
                                               describes decoder
                                               configuration */
    }

    nChInArbDmx = 0;

    /* INITIALIZATION */
    for (ch = 0; ch < hEnc->nInputChannels; ch++) {
      /* scaling in analysis qmf filterbank (7) */
      hEnc->pEncoderInputChScale[ch] = 7;

      {
        /* additional scaling in qmf prototype filter for low delay */
        hEnc->pEncoderInputChScale[ch] += 1;
      }

      { hEnc->pEncoderInputChScale[ch] += DC_FILTER_SF; }
    } /* nInputChannels */

    /* Init analysis filterbank */
    for (ch = 0; ch < hEnc->nInputChannels; ch++) {
      hEnc->phQmfFiltIn__FDK[ch]->flags =
          updateQmfFlags(hEnc->phQmfFiltIn__FDK[ch]->flags, !initStatesFlag);

      if (0 != qmfInitAnalysisFilterBank(
                   hEnc->phQmfFiltIn__FDK[ch],
                   (FIXP_QAS *)hEnc->phQmfFiltIn__FDK[ch]->FilterStates, 1,
                   hEnc->nQmfBands, hEnc->nQmfBands, hEnc->nQmfBands,
                   hEnc->phQmfFiltIn__FDK[ch]->flags)) {
        error = SACENC_INIT_ERROR;
        goto bail;
      }
    }

    /* Initialize DC Filter. */
    {
      for (ch = 0; ch < hEnc->nInputChannels; ch++) {
        if (SACENC_OK != (error = fdk_sacenc_initDCFilter(
                              hEnc->phDCFilterSigIn[ch], hEnc->nSampleRate))) {
          goto bail;
        }
      }
    }

    /* Init onset detect. */
    {
      /* init onset detect configuration struct */
      ONSET_DETECT_CONFIG onsetDetectConfig;
      onsetDetectConfig.maxTimeSlots = hEnc->nFrameTimeSlots;
      onsetDetectConfig.lowerBoundOnsetDetection =
          freq2HybridBand(1725, hEnc->nSampleRate, hEnc->nQmfBands);
      onsetDetectConfig.upperBoundOnsetDetection = hEnc->nHybridBands;

      for (ch = 0; ch < hEnc->nInputChannels; ch++) {
        if (SACENC_OK != (error = fdk_sacenc_onsetDetect_Init(
                              hEnc->phOnset[ch], &onsetDetectConfig, 1))) {
          goto bail;
        }
      }
    }

    {
      /* init windowing */
      FRAMEWINDOW_CONFIG framewindowConfig;
      framewindowConfig.nTimeSlotsMax = hEnc->nFrameTimeSlots;
      framewindowConfig.bFrameKeep = hEnc->useFrameKeep;

      if (SACENC_OK != (error = fdk_sacenc_frameWindow_Init(
                            hEnc->hFrameWindow, &framewindowConfig))) {
        goto bail;
      }
    }

    /* Set encoder mode for static gain initialization. */
    if (SACENC_OK != (error = fdk_sacenc_staticGain_SetEncMode(
                          hEnc->hStaticGainConfig, hEnc->encMode))) {
      goto bail;
    }

    /* Init static gain. */
    if (SACENC_OK != (error = fdk_sacenc_staticGain_Init(
                          hEnc->hStaticGain, hEnc->hStaticGainConfig,
                          &(hEnc->staticGainScale)))) {
      goto bail;
    }

    for (ch = 0; ch < hEnc->nInputChannels; ch++) {
      hEnc->pEncoderInputChScale[ch] += hEnc->staticGainScale;
    }

    /* enhanced downmix for stereo input*/
    if (hEnc->useTimeDomDownmix != 0) {
      if (SACENC_OK != (error = fdk_sacenc_init_enhancedTimeDomainDmx(
                            hEnc->hEnhancedTimeDmx,
                            fdk_sacenc_getPreGainPtrFDK(hEnc->hStaticGain),
                            hEnc->staticGainScale,
                            fdk_sacenc_getPostGainFDK(hEnc->hStaticGain),
                            hEnc->staticGainScale, hEnc->nFrameLength))) {
        goto bail;
      }
    }

    /* Create config structure for bitstream formatter including arbitrary
     * downmix residual */
    if (SACENC_OK != (error = fdk_sacenc_initSpatialBitstreamEncoder(
                          hEnc->hBitstreamFormatter))) {
      goto bail;
    }

    if (SACENC_OK != (error = FillSpatialSpecificConfig(
                          hEnc, fdk_sacenc_getSpatialSpecificConfig(
                                    hEnc->hBitstreamFormatter)))) {
      goto bail;
    }

    if (SACENC_OK !=
        (error = fdk_sacenc_writeSpatialSpecificConfig(
             fdk_sacenc_getSpatialSpecificConfig(hEnc->hBitstreamFormatter),
             hEnc->sscBuf.pSsc, MAX_SSC_BYTES, &hEnc->sscBuf.nSscSizeBits))) {
      goto bail;
    }

    /* init delay compensation with dmx core coder delay; if no core coder is
     * used, many other buffers are initialized nevertheless */
    if (SACENC_OK !=
        (error = mp4SpaceEnc_InitDelayCompensation(hEnc, dmxDelay))) {
      goto bail;
    }

    /* How much input do we need? */
    hEnc->nSamplesNext =
        hEnc->nFrameLength * (hEnc->nInputChannels + nChInArbDmx);
    hEnc->nSamplesValid = 0;
  } /* valid handle */

bail:
  return error;
}

static INT getAnalysisLengthTimeSlots(FIXP_WIN *pFrameWindowAna,
                                      INT nTimeSlots) {
  int i;
  for (i = nTimeSlots - 1; i >= 0; i--) {
    if (pFrameWindowAna[i] != (FIXP_WIN)0) {
      break;
    }
  }
  nTimeSlots = i + 1;
  return nTimeSlots;
}

static INT getAnalysisStartTimeSlot(FIXP_WIN *pFrameWindowAna, INT nTimeSlots) {
  int startTimeSlot = 0;
  int i;
  for (i = 0; i < nTimeSlots; i++) {
    if (pFrameWindowAna[i] != (FIXP_WIN)0) {
      break;
    }
  }
  startTimeSlot = i;
  return startTimeSlot;
}

static FDK_SACENC_ERROR __FeedDeinterPreScale(
    HANDLE_MP4SPACE_ENCODER hEnc, INT_PCM const *const pSamples,
    INT_PCM *const pOutputSamples, INT const nSamples,
    UINT const isInputInterleaved, UINT const inputBufferSizePerChannel,
    UINT *const pnSamplesFed) {
  FDK_SACENC_ERROR error = SACENC_OK;

  if ((hEnc == NULL) || (pSamples == NULL) || (pnSamplesFed == NULL)) {
    error = SACENC_INVALID_HANDLE;
  } else if (nSamples == 0) {
    error = SACENC_INVALID_CONFIG; /* Flushing not implemented */
  } else {
    int ch;
    const INT nChIn = hEnc->nInputChannels;
    const INT nChInWithDmx = nChIn;
    const INT samplesToFeed =
        FDKmin(nSamples, hEnc->nSamplesNext - hEnc->nSamplesValid);
    const INT nSamplesPerChannel = samplesToFeed / nChInWithDmx;

    if ((samplesToFeed < 0) || (samplesToFeed % nChInWithDmx != 0) ||
        (samplesToFeed > nChInWithDmx * (INT)hEnc->nFrameLength)) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }
    int i;

    const INT_PCM *pInput__FDK;
    const INT_PCM *pInput2__FDK;

    { /* no dmx align = default*/
      pInput__FDK = pSamples;
      pInput2__FDK = pSamples + (hEnc->nInputDelay * nChInWithDmx);
    }

    for (i = 0; i < hEnc->nInputChannels; i++) {
      hEnc->staticTimeDomainDmxInScale[i] = hEnc->staticGainScale;
    }

    /*****        N-channel-input     *****/
    for (ch = 0; ch < nChIn; ch++) {
      /* Write delayed time signal into time signal buffer */
      FDKmemcpy(&(hEnc->ppTimeSigIn__FDK[ch][0]),
                &(hEnc->ppTimeSigDelayIn__FDK[ch][0]),
                hEnc->nSurroundAnalysisBufferDelay * sizeof(INT_PCM));

      if (isInputInterleaved) {
        /* Add the new frame de-interleaved. Apply nSurroundAnalysisBufferDelay.
         */
        FDKmemcpy_flex(
            &(hEnc->ppTimeSigIn__FDK[ch][hEnc->nSurroundAnalysisBufferDelay]),
            1, pInput__FDK + ch, nChInWithDmx, hEnc->nInputDelay);
        FDKmemcpy_flex(
            &(hEnc->ppTimeSigIn__FDK[ch][hEnc->nSurroundAnalysisBufferDelay +
                                         hEnc->nInputDelay]),
            1, pInput2__FDK + ch, nChInWithDmx,
            nSamplesPerChannel - hEnc->nInputDelay);
      } else {
        /* Input is already deinterleaved, just copy */
        FDKmemcpy(
            &(hEnc->ppTimeSigIn__FDK[ch][hEnc->nSurroundAnalysisBufferDelay]),
            pInput__FDK + ch * inputBufferSizePerChannel,
            hEnc->nInputDelay * sizeof(INT_PCM));
        FDKmemcpy(
            &(hEnc->ppTimeSigIn__FDK[ch][hEnc->nSurroundAnalysisBufferDelay +
                                         hEnc->nInputDelay]),
            pInput2__FDK + ch * inputBufferSizePerChannel,
            (nSamplesPerChannel - hEnc->nInputDelay) * sizeof(INT_PCM));
      }

      /* Update time signal delay buffer */
      FDKmemcpy(&(hEnc->ppTimeSigDelayIn__FDK[ch][0]),
                &(hEnc->ppTimeSigIn__FDK[ch][hEnc->nFrameLength]),
                hEnc->nSurroundAnalysisBufferDelay * sizeof(INT_PCM));
    } /* for ch */

    /*****      No Arbitrary Downmix      *****/
    /* "Crude TD Dmx": Time DomainDownmix + NO Arbitrary Downmix, Delay Added at
     * pOutputBuffer */
    if ((hEnc->useTimeDomDownmix > 0)) {
      if ((hEnc->useTimeDomDownmix == 1) || (hEnc->nInputChannels != 2)) {
        error = SACENC_INVALID_CONFIG;
        goto bail;
      } else {
        /* enhanced time domain downmix (for stereo input) */
        if (hEnc->encMode == SACENC_212) {
          if (pOutputSamples == NULL) {
            error = SACENC_INVALID_HANDLE;
            goto bail;
          }

          fdk_sacenc_apply_enhancedTimeDomainDmx(
              hEnc->hEnhancedTimeDmx, hEnc->ppTimeSigIn__FDK, pOutputSamples,
              hEnc->nSurroundAnalysisBufferDelay);
        } else {
          if (&hEnc->ppTimeSigOut__FDK[0][0] == NULL) {
            error = SACENC_INVALID_HANDLE;
            goto bail;
          }

          fdk_sacenc_apply_enhancedTimeDomainDmx(
              hEnc->hEnhancedTimeDmx, hEnc->ppTimeSigIn__FDK,
              &hEnc->ppTimeSigOut__FDK[0][0],
              hEnc->nSurroundAnalysisBufferDelay);
        }
      }
    }

    /* update number of samples still to process */
    hEnc->nSamplesValid += samplesToFeed;

    /*return number of fed samples */
    *pnSamplesFed = samplesToFeed;
  }
bail:
  return error;
}

FDK_SACENC_ERROR FDK_sacenc_encode(const HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc,
                                   const FDK_bufDescr *inBufDesc,
                                   const FDK_bufDescr *outBufDesc,
                                   const SACENC_InArgs *inargs,
                                   SACENC_OutArgs *outargs) {
  FDK_SACENC_ERROR error = SACENC_OK;

  const INT_PCM *pInputSamples =
      (const INT_PCM *)inBufDesc->ppBase[getBufDescIdx(
          inBufDesc, (FDK_BUF_TYPE_INPUT | FDK_BUF_TYPE_PCM_DATA))];

  INT_PCM *const pOutputSamples = (INT_PCM *)outBufDesc->ppBase[getBufDescIdx(
      outBufDesc, (FDK_BUF_TYPE_OUTPUT | FDK_BUF_TYPE_PCM_DATA))];

  const int nOutputSamplesBufferSize =
      outBufDesc->pBufSize[getBufDescIdx(
          outBufDesc, (FDK_BUF_TYPE_OUTPUT | FDK_BUF_TYPE_PCM_DATA))] /
      outBufDesc->pEleSize[getBufDescIdx(
          outBufDesc, (FDK_BUF_TYPE_OUTPUT | FDK_BUF_TYPE_PCM_DATA))];

  if ((hMp4SpaceEnc == NULL) || (pInputSamples == NULL)) {
    error = SACENC_INVALID_HANDLE;
  } else {
    int nOutputSamples;
    int i, ch, ps, winCnt, ts, slot;
    INT currTransPos = -1;
    SPATIALFRAME *pFrameData = NULL;

    /* Improve Code Readability */
    const int nChIn = hMp4SpaceEnc->nInputChannels;
    const int nChInWithDmx = nChIn;
    const int nChOut = hMp4SpaceEnc->nOutputChannels;
    const int nSamplesPerChannel = inargs->nInputSamples / nChInWithDmx;
    const int nOutputSamplesMax = nSamplesPerChannel * nChOut;
    const int nFrameTimeSlots = hMp4SpaceEnc->nFrameTimeSlots;

    INT encoderInputChScale[SACENC_MAX_INPUT_CHANNELS];
    INT nFrameTimeSlotsReduction = 0;

    if (hMp4SpaceEnc->encMode == SACENC_212) {
      nFrameTimeSlotsReduction = hMp4SpaceEnc->nFrameTimeSlots >> 1;
    }

    for (i = 0; i < nChIn; i++)
      encoderInputChScale[i] = hMp4SpaceEnc->pEncoderInputChScale[i];

    /* Sanity Check */
    if ((0 != inargs->nInputSamples % nChInWithDmx)) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }

    /*
     * Get Frame Data Handle.
     */

    /* get bitstream handle (for storage of cld's, icc's and so on)
     * get spatialframe 2 frames in the future; NOTE: this is necessary to
     * synchronise spatial data and audio data */
    if (NULL == (pFrameData = fdk_sacenc_getSpatialFrame(
                     hMp4SpaceEnc->hBitstreamFormatter, WRITE_SPATIALFRAME))) {
      error = SACENC_INVALID_HANDLE;
      goto bail;
    }

    /* Independent Frames Counters*/
    if (hMp4SpaceEnc->nDiscardOutFrames >
        0) { /* Independent Frames if they should be discarded, Reset Counter*/
      hMp4SpaceEnc->independencyCount =
          0; /* Reset the counter, first valid frame is an independent one*/
      hMp4SpaceEnc->independencyFlag = 1;
    } else { /*hMp4SpaceEnc->nDiscardOutFrames == 0*/
      hMp4SpaceEnc->independencyFlag =
          (hMp4SpaceEnc->independencyCount == 0) ? 1 : 0;
      if (hMp4SpaceEnc->independencyFactor > 0) {
        hMp4SpaceEnc->independencyCount++;
        hMp4SpaceEnc->independencyCount =
            hMp4SpaceEnc->independencyCount %
            ((int)hMp4SpaceEnc->independencyFactor);
      } else { /* independencyFactor == 0 */
        hMp4SpaceEnc->independencyCount = -1;
      }
    }

    /*
     * Time signal preprocessing:
     * - Feed input buffer
     * - Prescale time signal
     * - Apply DC filter on input signal
     */

    /* Feed, Deinterleave, Pre-Scale the input time signals */
    if (SACENC_OK !=
        (error = __FeedDeinterPreScale(
             hMp4SpaceEnc, pInputSamples, pOutputSamples, inargs->nInputSamples,
             inargs->isInputInterleaved, inargs->inputBufferSizePerChannel,
             &outargs->nSamplesConsumed))) {
      goto bail;
    }

    if (hMp4SpaceEnc->nSamplesNext != hMp4SpaceEnc->nSamplesValid) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }

    if (hMp4SpaceEnc->encMode == SACENC_212 &&
        hMp4SpaceEnc->bEncMode_212_only) {
      for (ch = 0; ch < nChIn; ch++) {
        for (slot = 0; slot < nFrameTimeSlots; slot++) {
          setCplxVec(
              hMp4SpaceEnc->pppHybridIn__FDK
                  [ch][hMp4SpaceEnc->nUpdateHybridPositionTimeSlots +
                       nFrameTimeSlots - nFrameTimeSlotsReduction + slot],
              (FIXP_DBL)0, hMp4SpaceEnc->nHybridBands);
        }
      }
    }

    /*
     * Time / Frequency:
     * - T/F audio input channels
     * - T/F arbitrary downmix input channels
     */
    for (ch = 0; ch < nChIn; ch++) {
      C_AALLOC_SCRATCH_START(pQmfInReal, FIXP_DBL, MAX_QMF_BANDS)
      C_AALLOC_SCRATCH_START(pQmfInImag, FIXP_DBL, MAX_QMF_BANDS)
      FIXP_GAIN *pPreGain =
          fdk_sacenc_getPreGainPtrFDK(hMp4SpaceEnc->hStaticGain);

      for (ts = 0; ts < nFrameTimeSlots; ts++) {
        FIXP_DBL *pSpecReal;
        FIXP_DBL *pSpecImag;

        INT_PCM *pTimeIn =
            &hMp4SpaceEnc->ppTimeSigIn__FDK[ch][(ts * hMp4SpaceEnc->nQmfBands)];

        {
          /* Apply DC filter on input channels */
          if (SACENC_OK != (error = fdk_sacenc_applyDCFilter(
                                hMp4SpaceEnc->phDCFilterSigIn[ch], pTimeIn,
                                pTimeIn, hMp4SpaceEnc->nQmfBands))) {
            goto bail;
          }
        }

        /* QMF filterbank */
        C_ALLOC_SCRATCH_START(pWorkBuffer, FIXP_DBL, (MAX_QMF_BANDS << 1));

        qmfAnalysisFilteringSlot(hMp4SpaceEnc->phQmfFiltIn__FDK[ch], pQmfInReal,
                                 pQmfInImag, pTimeIn, 1, pWorkBuffer);

        C_ALLOC_SCRATCH_END(pWorkBuffer, FIXP_DBL, (MAX_QMF_BANDS << 1));

        pSpecReal = pQmfInReal;
        pSpecImag = pQmfInImag;

        /* Apply pre-scale after filterbank */
        if (MAXVAL_GAIN != pPreGain[ch]) {
          for (i = 0; i < hMp4SpaceEnc->nHybridBands; i++) {
            hMp4SpaceEnc
                ->pppHybridIn__FDK[ch]
                                  [hMp4SpaceEnc->nAnalysisLookaheadTimeSlots +
                                   ts][i]
                .v.re = fMult(pSpecReal[i], pPreGain[ch]);
            hMp4SpaceEnc
                ->pppHybridIn__FDK[ch]
                                  [hMp4SpaceEnc->nAnalysisLookaheadTimeSlots +
                                   ts][i]
                .v.im = fMult(pSpecImag[i], pPreGain[ch]);
          }
        } else {
          for (i = 0; i < hMp4SpaceEnc->nHybridBands; i++) {
            hMp4SpaceEnc
                ->pppHybridIn__FDK[ch]
                                  [hMp4SpaceEnc->nAnalysisLookaheadTimeSlots +
                                   ts][i]
                .v.re = pSpecReal[i];
            hMp4SpaceEnc
                ->pppHybridIn__FDK[ch]
                                  [hMp4SpaceEnc->nAnalysisLookaheadTimeSlots +
                                   ts][i]
                .v.im = pSpecImag[i];
          }
        }
      } /* ts */
      C_AALLOC_SCRATCH_END(pQmfInImag, FIXP_DBL, MAX_QMF_BANDS)
      C_AALLOC_SCRATCH_END(pQmfInReal, FIXP_DBL, MAX_QMF_BANDS)

      if (SACENC_OK != error) {
        goto bail;
      }
    } /* ch */

    if (hMp4SpaceEnc->encMode == SACENC_212 &&
        hMp4SpaceEnc->bEncMode_212_only) {
      for (ch = 0; ch < nChIn; ch++) {
        for (slot = 0;
             slot < (int)(hMp4SpaceEnc->nUpdateHybridPositionTimeSlots +
                          nFrameTimeSlots - nFrameTimeSlotsReduction);
             slot++) {
          copyCplxVec(hMp4SpaceEnc->pppHybridIn__FDK[ch][slot],
                      hMp4SpaceEnc->pppHybridInStatic__FDK[ch][slot],
                      hMp4SpaceEnc->nHybridBands);
        }
      }
      for (ch = 0; ch < nChIn; ch++) {
        for (slot = 0;
             slot < (int)(hMp4SpaceEnc->nUpdateHybridPositionTimeSlots +
                          nFrameTimeSlots - nFrameTimeSlotsReduction);
             slot++) {
          copyCplxVec(
              hMp4SpaceEnc->pppHybridInStatic__FDK[ch][slot],
              hMp4SpaceEnc->pppHybridIn__FDK[ch][nFrameTimeSlots + slot],
              hMp4SpaceEnc->nHybridBands);
        }
      }
    }

    /*
     * Onset Detection:
     * - detection of transients
     * - build framing
     */
    for (ch = 0; ch < nChIn; ch++) {
      if (ch != 3) { /* !LFE */
        if (SACENC_OK !=
            (error = fdk_sacenc_onsetDetect_Apply(
                 hMp4SpaceEnc->phOnset[ch], nFrameTimeSlots,
                 hMp4SpaceEnc->nHybridBands,
                 &hMp4SpaceEnc->pppHybridIn__FDK
                      [ch][hMp4SpaceEnc->nAnalysisLookaheadTimeSlots],
                 encoderInputChScale[ch],
                 hMp4SpaceEnc->trPrevPos[1], /* contains previous Transient */
                 hMp4SpaceEnc->ppTrCurrPos[ch]))) {
          goto bail;
        }

        if ((1) && (hMp4SpaceEnc->useFrameKeep == 0)) {
          hMp4SpaceEnc->ppTrCurrPos[ch][0] = -1;
        }

        /* Find first Transient Position */
        if ((hMp4SpaceEnc->ppTrCurrPos[ch][0] >= 0) &&
            ((currTransPos < 0) ||
             (hMp4SpaceEnc->ppTrCurrPos[ch][0] < currTransPos))) {
          currTransPos = hMp4SpaceEnc->ppTrCurrPos[ch][0];
        }
      } /* !LFE */
    }   /* ch */

    if (hMp4SpaceEnc->useFrameKeep == 1) {
      if ((currTransPos != -1) || (hMp4SpaceEnc->independencyFlag == 1)) {
        hMp4SpaceEnc->avoid_keep = NUM_KEEP_WINDOWS;
        currTransPos = -1;
      }
    }

    /* Save previous Transient Position */
    hMp4SpaceEnc->trPrevPos[0] =
        FDKmax(-1, hMp4SpaceEnc->trPrevPos[1] - (INT)nFrameTimeSlots);
    hMp4SpaceEnc->trPrevPos[1] = currTransPos;

    /* Update Onset Detection Energy Buffer */
    for (ch = 0; ch < nChIn; ch++) {
      if (SACENC_OK != (error = fdk_sacenc_onsetDetect_Update(
                            hMp4SpaceEnc->phOnset[ch], nFrameTimeSlots))) {
        goto bail;
      }
    }

    /* Framing */
    if (SACENC_OK !=
        (error = fdk_sacenc_frameWindow_GetWindow(
             hMp4SpaceEnc->hFrameWindow, hMp4SpaceEnc->trPrevPos,
             nFrameTimeSlots, &pFrameData->framingInfo,
             hMp4SpaceEnc->pFrameWindowAna__FDK, &hMp4SpaceEnc->frameWinList,
             hMp4SpaceEnc->avoid_keep))) {
      goto bail;
    }

    /*
     * MPS Processing:
     */
    for (ps = 0, winCnt = 0; ps < hMp4SpaceEnc->frameWinList.n; ++ps) {
      /* Analysis Windowing */
      if (hMp4SpaceEnc->frameWinList.dat[ps].hold == FW_HOLD) {
        /* ************************************** */
        /* ONLY COPY AND HOLD PREVIOUS PARAMETERS */
        if (SACENC_OK != (error = fdk_sacenc_duplicateParameterSet(
                              &hMp4SpaceEnc->saveFrame, 0, pFrameData, ps))) {
          goto bail;
        }

      } else { /* !FW_HOLD */
        /* ************************************** */
        /* NEW WINDOW */

        INT nAnalysisLengthTimeSlots, analysisStartTimeSlot;

        nAnalysisLengthTimeSlots = getAnalysisLengthTimeSlots(
            hMp4SpaceEnc->pFrameWindowAna__FDK[winCnt],
            hMp4SpaceEnc->nAnalysisLengthTimeSlots);

        analysisStartTimeSlot =
            getAnalysisStartTimeSlot(hMp4SpaceEnc->pFrameWindowAna__FDK[winCnt],
                                     hMp4SpaceEnc->nAnalysisLengthTimeSlots);

        /* perform main signal analysis windowing in
         * fdk_sacenc_spaceTree_Apply() */
        FIXP_WIN *pFrameWindowAna__FDK =
            hMp4SpaceEnc->pFrameWindowAna__FDK[winCnt];
        FIXP_DPK ***pppHybridIn__FDK = hMp4SpaceEnc->pppHybridIn__FDK;
        FIXP_DPK ***pppProcDataIn__FDK = hMp4SpaceEnc->pppProcDataIn__FDK;

        if (hMp4SpaceEnc->encMode == SACENC_212 &&
            hMp4SpaceEnc->bEncMode_212_only) {
          pppProcDataIn__FDK = pppHybridIn__FDK;
        }

        if (SACENC_OK !=
            (error = fdk_sacenc_spaceTree_Apply(
                 hMp4SpaceEnc->hSpaceTree, ps, nChIn, nAnalysisLengthTimeSlots,
                 analysisStartTimeSlot, hMp4SpaceEnc->nHybridBands,
                 pFrameWindowAna__FDK, pppHybridIn__FDK,
                 pppProcDataIn__FDK, /* multi-channel input */
                 pFrameData, hMp4SpaceEnc->avoid_keep, encoderInputChScale))) {
          goto bail;
        }

        /* Save spatial frame for potential hold parameter set */
        if (SACENC_OK != (error = fdk_sacenc_duplicateParameterSet(
                              pFrameData, ps, &hMp4SpaceEnc->saveFrame, 0))) {
          goto bail;
        }

        ++winCnt;
      }
      if (hMp4SpaceEnc->avoid_keep > 0) {
        hMp4SpaceEnc->avoid_keep--;
      }
    } /* Loop over Parameter Sets */
    /* ---- End of Processing Loop ---- */

    /*
     * Update hybridInReal/Imag buffer and do the same for arbDmx
     * this means to move the hybrid data of the current frame to the beginning
     * of the 2*nFrameLength-long buffer
     */
    if (!(hMp4SpaceEnc->encMode == SACENC_212 &&
          hMp4SpaceEnc->bEncMode_212_only)) {
      for (ch = 0; ch < nChIn; ch++) { /* for automatic downmix */
        for (slot = 0;
             slot < (int)(hMp4SpaceEnc->nUpdateHybridPositionTimeSlots +
                          nFrameTimeSlots - nFrameTimeSlotsReduction);
             slot++) {
          copyCplxVec(
              hMp4SpaceEnc->pppHybridIn__FDK[ch][slot],
              hMp4SpaceEnc->pppHybridIn__FDK[ch][nFrameTimeSlots + slot],
              hMp4SpaceEnc->nHybridBands);
        }
        for (slot = 0; slot < nFrameTimeSlots; slot++) {
          setCplxVec(
              hMp4SpaceEnc->pppHybridIn__FDK
                  [ch][hMp4SpaceEnc->nUpdateHybridPositionTimeSlots +
                       nFrameTimeSlots - nFrameTimeSlotsReduction + slot],
              (FIXP_DBL)0, hMp4SpaceEnc->nHybridBands);
        }
      }
    }
    /*
     * Spatial Tonality:
     */
    {
      /* Smooth config off. */
      FDKmemclear(&pFrameData->smgData, sizeof(pFrameData->smgData));
    }

    /*
     * Create bitstream
     * - control independecy flag
     * - write spatial frame
     * - return bitstream
     */
    UCHAR *pBitstreamDelayBuffer;

    if (hMp4SpaceEnc->encMode == SACENC_212) {
      /* no bitstream delay buffer for SACENC_212 mode, write bitstream directly
       * into the sacOutBuffer buffer which is provided by the core routine */
      pBitstreamDelayBuffer = (UCHAR *)outBufDesc->ppBase[1];
    } else {
      /* bitstream delay is handled in ppBitstreamDelayBuffer buffer */
      pBitstreamDelayBuffer =
          hMp4SpaceEnc
              ->ppBitstreamDelayBuffer[hMp4SpaceEnc->nBitstreamBufferWrite];
    }
    if (pBitstreamDelayBuffer == NULL) {
      error = SACENC_INVALID_HANDLE;
      goto bail;
    }

    pFrameData->bsIndependencyFlag = hMp4SpaceEnc->independencyFlag;

    if (SACENC_OK !=
        (error = fdk_sacenc_writeSpatialFrame(
             pBitstreamDelayBuffer, MAX_MPEGS_BYTES,
             &hMp4SpaceEnc->pnOutputBits[hMp4SpaceEnc->nBitstreamBufferWrite],
             hMp4SpaceEnc->hBitstreamFormatter))) {
      goto bail;
    }

    /* return bitstream info */
    if ((hMp4SpaceEnc->nDiscardOutFrames == 0) &&
        (getBufDescIdx(outBufDesc,
                       (FDK_BUF_TYPE_OUTPUT | FDK_BUF_TYPE_BS_DATA)) != -1)) {
      const INT idx = getBufDescIdx(
          outBufDesc, (FDK_BUF_TYPE_OUTPUT | FDK_BUF_TYPE_BS_DATA));
      const INT outBits =
          hMp4SpaceEnc->pnOutputBits[hMp4SpaceEnc->nBitstreamBufferRead];

      if (((outBits + 7) / 8) >
          (INT)(outBufDesc->pBufSize[idx] / outBufDesc->pEleSize[idx])) {
        outargs->nOutputBits = 0;
        error = SACENC_ENCODE_ERROR;
        goto bail;
      }

      /* return bitstream buffer, copy delayed bitstream for all configurations
       * except for the SACENC_212 mode */
      if (hMp4SpaceEnc->encMode != SACENC_212) {
        FDKmemcpy(
            outBufDesc->ppBase[idx],
            hMp4SpaceEnc
                ->ppBitstreamDelayBuffer[hMp4SpaceEnc->nBitstreamBufferRead],
            (outBits + 7) / 8);
      }

      /* return number of valid bits */
      outargs->nOutputBits = outBits;
    } else { /* No spatial data should be returned if the current frame is to be
                discarded. */
      outargs->nOutputBits = 0;
    }

    /* update pointers */
    hMp4SpaceEnc->nBitstreamBufferRead =
        (hMp4SpaceEnc->nBitstreamBufferRead + 1) %
        hMp4SpaceEnc->nBitstreamDelayBuffer;
    hMp4SpaceEnc->nBitstreamBufferWrite =
        (hMp4SpaceEnc->nBitstreamBufferWrite + 1) %
        hMp4SpaceEnc->nBitstreamDelayBuffer;

    /* Set Output Parameters */
    nOutputSamples =
        (hMp4SpaceEnc->nDiscardOutFrames == 0)
            ? (nOutputSamplesMax)
            : 0; /* don't output samples in case frames to be discarded */
    if (nOutputSamples > nOutputSamplesBufferSize) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }
    outargs->nOutputSamples = nOutputSamples;

    { /* !bQmfOutput */

      if (hMp4SpaceEnc->encMode != SACENC_212) {
        /* delay output samples and interleave them */
        /* note: in case of arbitrary downmix this will always be processed,
         * because nOutputSamples != 0, even if bDMXAlign is switched on */
        /* always run copy-func, so nOutputSamplesMax instead of nOutputSamples
         */
        for (ch = 0; ch < nChOut; ch++) {
          FDKmemcpy_flex(
              &hMp4SpaceEnc->pOutputDelayBuffer__FDK
                   [ch + (hMp4SpaceEnc->nOutputBufferDelay) * nChOut],
              nChOut, hMp4SpaceEnc->ppTimeSigOut__FDK[ch], 1,
              nOutputSamplesMax / nChOut);
        }

        /* write delayed data in output pcm stream */
        /* always calculate, limiter must have a lookahead!!! */
        FDKmemcpy(pOutputSamples, hMp4SpaceEnc->pOutputDelayBuffer__FDK,
                  nOutputSamplesMax * sizeof(INT_PCM));

        /* update delay buffer (move back end to the beginning of the buffer) */
        FDKmemmove(
            hMp4SpaceEnc->pOutputDelayBuffer__FDK,
            &hMp4SpaceEnc->pOutputDelayBuffer__FDK[nOutputSamplesMax],
            nChOut * (hMp4SpaceEnc->nOutputBufferDelay) * sizeof(INT_PCM));
      }

      if (hMp4SpaceEnc->useTimeDomDownmix <= 0) {
        if (SACENC_OK != (error = fdk_sacenc_staticPostGain_ApplyFDK(
                              hMp4SpaceEnc->hStaticGain, pOutputSamples,
                              nOutputSamplesMax, 0))) {
          goto bail;
        }
      }

    } /* !bQmfOutput */

    if (hMp4SpaceEnc->nDiscardOutFrames > 0) {
      hMp4SpaceEnc->nDiscardOutFrames--;
    }

    /* Invalidate Input Buffer */
    hMp4SpaceEnc->nSamplesValid = 0;

  } /* valid handle */
bail:
  return error;
}

FDK_SACENC_ERROR FDK_sacenc_close(HANDLE_MP4SPACE_ENCODER *phMp4SpaceEnc) {
  FDK_SACENC_ERROR error = SACENC_OK;

  if (NULL != phMp4SpaceEnc) {
    if (NULL != *phMp4SpaceEnc) {
      int ch, i;
      HANDLE_MP4SPACE_ENCODER const hEnc = *phMp4SpaceEnc;

      if (hEnc->pParameterBand2HybridBandOffset != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->pParameterBand2HybridBandOffset);
      }
      /* Free Analysis Filterbank Structs */
      if (hEnc->pEncoderInputChScale != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->pEncoderInputChScale);
      }
      if (hEnc->staticTimeDomainDmxInScale != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->staticTimeDomainDmxInScale);
      }
      if (hEnc->phQmfFiltIn__FDK != NULL) {
        for (ch = 0; ch < hEnc->setup.maxChIn; ch++) {
          if (hEnc->phQmfFiltIn__FDK[ch] != NULL) {
            if (hEnc->phQmfFiltIn__FDK[ch]->FilterStates != NULL) {
              FDK_FREE_MEMORY_1D(hEnc->phQmfFiltIn__FDK[ch]->FilterStates);
            }
            FDK_FREE_MEMORY_1D(hEnc->phQmfFiltIn__FDK[ch]);
          }
        }
        FDK_FREE_MEMORY_1D(hEnc->phQmfFiltIn__FDK);
      }
      for (ch = 0; ch < hEnc->setup.maxChIn; ch++) {
        if (NULL != hEnc->phDCFilterSigIn[ch]) {
          fdk_sacenc_destroyDCFilter(&hEnc->phDCFilterSigIn[ch]);
        }
      }
      /* Close Onset Detection */
      for (ch = 0; ch < hEnc->setup.maxChIn; ch++) {
        if (NULL != hEnc->phOnset[ch]) {
          fdk_sacenc_onsetDetect_Close(&hEnc->phOnset[ch]);
        }
      }
      if (hEnc->ppTrCurrPos) {
        FDK_FREE_MEMORY_2D(hEnc->ppTrCurrPos);
      }
      if (hEnc->hFrameWindow) {
        fdk_sacenc_frameWindow_Destroy(&hEnc->hFrameWindow);
      }
      /* Close Space Tree */
      if (NULL != hEnc->hSpaceTree) {
        fdk_sacenc_spaceTree_Close(&hEnc->hSpaceTree);
      }
      if (NULL != hEnc->hEnhancedTimeDmx) {
        fdk_sacenc_close_enhancedTimeDomainDmx(&hEnc->hEnhancedTimeDmx);
      }
      /* Close Static Gain */
      if (NULL != hEnc->hStaticGain) {
        fdk_sacenc_staticGain_Close(&hEnc->hStaticGain);
      }
      if (NULL != hEnc->hStaticGainConfig) {
        fdk_sacenc_staticGain_CloseConfig(&hEnc->hStaticGainConfig);
      }
      /* Close Delay*/
      if (NULL != hEnc->hDelay) {
        fdk_sacenc_delay_Close(&hEnc->hDelay);
      }
      /* Delete Bitstream Stuff */
      if (NULL != hEnc->hBitstreamFormatter) {
        fdk_sacenc_destroySpatialBitstreamEncoder(&(hEnc->hBitstreamFormatter));
      }
      if (hEnc->pppHybridIn__FDK != NULL) {
        if (hEnc->setup.bEncMode_212 == 1) {
          FDK_FREE_MEMORY_3D(hEnc->pppHybridIn__FDK);
          FDK_FREE_MEMORY_3D(hEnc->pppHybridInStatic__FDK);
        } else {
          FDK_FREE_MEMORY_3D(hEnc->pppHybridIn__FDK);
        }
      }
      if (hEnc->pppProcDataIn__FDK != NULL) {
        FDK_FREE_MEMORY_3D(hEnc->pppProcDataIn__FDK);
      }
      if (hEnc->pOutputDelayBuffer__FDK != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->pOutputDelayBuffer__FDK);
      }
      if (hEnc->ppTimeSigIn__FDK != NULL) {
        { FDK_FREE_MEMORY_2D(hEnc->ppTimeSigIn__FDK); }
      }
      if (hEnc->ppTimeSigDelayIn__FDK != NULL) {
        FDK_FREE_MEMORY_2D(hEnc->ppTimeSigDelayIn__FDK);
      }
      if (hEnc->ppTimeSigOut__FDK != NULL) {
        FDK_FREE_MEMORY_2D(hEnc->ppTimeSigOut__FDK);
      }
      for (i = 0; i < MAX_NUM_PARAMS; i++) {
        if (hEnc->pFrameWindowAna__FDK[i] != NULL) {
          FDK_FREE_MEMORY_1D(hEnc->pFrameWindowAna__FDK[i]);
        }
      }
      if (hEnc->pnOutputBits != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->pnOutputBits);
      }
      if (hEnc->ppBitstreamDelayBuffer != NULL) {
        FDK_FREE_MEMORY_2D(hEnc->ppBitstreamDelayBuffer);
      }
      if (hEnc->sscBuf.pSsc != NULL) {
        FDK_FREE_MEMORY_1D(hEnc->sscBuf.pSsc);
      }
      FDK_FREE_MEMORY_1D(*phMp4SpaceEnc);
    }
  }

  return error;
}

/*-----------------------------------------------------------------------------
  functionname: mp4SpaceEnc_InitDelayCompensation()
  description:  initialzes delay compensation
  returns:      noError on success, an apropriate error code else
  -----------------------------------------------------------------------------*/
static FDK_SACENC_ERROR mp4SpaceEnc_InitDelayCompensation(
    HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc, const INT coreCoderDelay) {
  FDK_SACENC_ERROR error = SACENC_OK;

  /* Sanity Check */
  if (hMp4SpaceEnc == NULL) {
    error = SACENC_INVALID_HANDLE;
  } else {
    hMp4SpaceEnc->coreCoderDelay = coreCoderDelay;

    if (SACENC_OK != (error = fdk_sacenc_delay_Init(
                          hMp4SpaceEnc->hDelay, hMp4SpaceEnc->nQmfBands,
                          hMp4SpaceEnc->nFrameLength, coreCoderDelay,
                          hMp4SpaceEnc->timeAlignment))) {
      goto bail;
    }

    fdk_sacenc_delay_SetDmxAlign(hMp4SpaceEnc->hDelay, 0);
    fdk_sacenc_delay_SetTimeDomDmx(
        hMp4SpaceEnc->hDelay, (hMp4SpaceEnc->useTimeDomDownmix >= 1) ? 1 : 0);
    fdk_sacenc_delay_SetMinimizeDelay(hMp4SpaceEnc->hDelay, 1);

    if (SACENC_OK != (error = fdk_sacenc_delay_SubCalulateBufferDelays(
                          hMp4SpaceEnc->hDelay))) {
      goto bail;
    }

    /* init output delay compensation */
    hMp4SpaceEnc->nBitstreamDelayBuffer =
        fdk_sacenc_delay_GetBitstreamFrameBufferSize(hMp4SpaceEnc->hDelay);
    hMp4SpaceEnc->nOutputBufferDelay =
        fdk_sacenc_delay_GetOutputAudioBufferDelay(hMp4SpaceEnc->hDelay);
    hMp4SpaceEnc->nSurroundAnalysisBufferDelay =
        fdk_sacenc_delay_GetSurroundAnalysisBufferDelay(hMp4SpaceEnc->hDelay);
    hMp4SpaceEnc->nBitstreamBufferRead = 0;
    hMp4SpaceEnc->nBitstreamBufferWrite =
        hMp4SpaceEnc->nBitstreamDelayBuffer - 1;

    if (hMp4SpaceEnc->encMode == SACENC_212) {
      /* mode 212 expects no bitstream delay */
      if (hMp4SpaceEnc->nBitstreamBufferWrite !=
          hMp4SpaceEnc->nBitstreamBufferRead) {
        error = SACENC_PARAM_ERROR;
        goto bail;
      }

      /* mode 212 expects no output buffer delay */
      if (hMp4SpaceEnc->nOutputBufferDelay != 0) {
        error = SACENC_PARAM_ERROR;
        goto bail;
      }
    }

    /*** Input delay to obtain a net encoder delay that is a multiple
    of the used framelength to ensure synchronization of framing
    in artistic down-mix with the corresponding spatial data.      ***/
    hMp4SpaceEnc->nDiscardOutFrames =
        fdk_sacenc_delay_GetDiscardOutFrames(hMp4SpaceEnc->hDelay);
    hMp4SpaceEnc->nInputDelay =
        fdk_sacenc_delay_GetDmxAlignBufferDelay(hMp4SpaceEnc->hDelay);

    /* reset independency Flag counter */
    hMp4SpaceEnc->independencyCount = 0;
    hMp4SpaceEnc->independencyFlag = 1;

    int i;

    /* write some parameters to bitstream */
    for (i = 0; i < hMp4SpaceEnc->nBitstreamDelayBuffer - 1; i++) {
      SPATIALFRAME *pFrameData = NULL;

      if (NULL == (pFrameData = fdk_sacenc_getSpatialFrame(
                       hMp4SpaceEnc->hBitstreamFormatter, READ_SPATIALFRAME))) {
        error = SACENC_INVALID_HANDLE;
        goto bail;
      }

      pFrameData->bsIndependencyFlag = 1;
      pFrameData->framingInfo.numParamSets = 1;
      pFrameData->framingInfo.bsFramingType = 0;

      fdk_sacenc_writeSpatialFrame(
          hMp4SpaceEnc->ppBitstreamDelayBuffer[i], MAX_MPEGS_BYTES,
          &hMp4SpaceEnc->pnOutputBits[i], hMp4SpaceEnc->hBitstreamFormatter);
    }

    if ((hMp4SpaceEnc->nInputDelay > MAX_DELAY_INPUT) ||
        (hMp4SpaceEnc->nOutputBufferDelay > MAX_DELAY_OUTPUT) ||
        (hMp4SpaceEnc->nSurroundAnalysisBufferDelay >
         MAX_DELAY_SURROUND_ANALYSIS) ||
        (hMp4SpaceEnc->nBitstreamDelayBuffer > MAX_BITSTREAM_DELAY)) {
      error = SACENC_INIT_ERROR;
      goto bail;
    }
  }

bail:

  return error;
}

static QUANTMODE __mapQuantMode(const MP4SPACEENC_QUANTMODE quantMode) {
  QUANTMODE bsQuantMode = QUANTMODE_INVALID;

  switch (quantMode) {
    case SACENC_QUANTMODE_FINE:
      bsQuantMode = QUANTMODE_FINE;
      break;
    case SACENC_QUANTMODE_EBQ1:
      bsQuantMode = QUANTMODE_EBQ1;
      break;
    case SACENC_QUANTMODE_EBQ2:
      bsQuantMode = QUANTMODE_EBQ2;
      break;
    case SACENC_QUANTMODE_RSVD3:
    case SACENC_QUANTMODE_INVALID:
    default:
      bsQuantMode = QUANTMODE_INVALID;
  } /* switch hEnc->quantMode */

  return bsQuantMode;
}

static FDK_SACENC_ERROR FillSpatialSpecificConfig(
    const HANDLE_MP4SPACE_ENCODER hEnc, SPATIALSPECIFICCONFIG *const hSsc) {
  FDK_SACENC_ERROR error = SACENC_OK;

  if ((NULL == hEnc) || (NULL == hSsc)) {
    error = SACENC_INVALID_HANDLE;
  } else {
    SPACE_TREE_DESCRIPTION spaceTreeDescription;
    int i;

    /* Get tree description */
    if (SACENC_OK != (error = fdk_sacenc_spaceTree_GetDescription(
                          hEnc->hSpaceTree, &spaceTreeDescription))) {
      goto bail;
    }

    /* Fill SSC */
    FDKmemclear(hSsc, sizeof(SPATIALSPECIFICCONFIG)); /* reset */

    hSsc->numBands = hEnc->spaceTreeSetup.nParamBands; /* for bsFreqRes */

    /* Fill tree configuration */
    hSsc->treeDescription.numOttBoxes = spaceTreeDescription.nOttBoxes;
    hSsc->treeDescription.numInChan = spaceTreeDescription.nInChannels;
    hSsc->treeDescription.numOutChan = spaceTreeDescription.nOutChannels;

    for (i = 0; i < SACENC_MAX_NUM_BOXES; i++) {
      hSsc->ottConfig[i].bsOttBands = hSsc->numBands;
    }

    switch (hEnc->encMode) {
      case SACENC_212:
        hSsc->bsTreeConfig = TREE_212;
        break;
      case SACENC_INVALID_MODE:
      default:
        error = SACENC_INVALID_CONFIG;
        goto bail;
    }

    hSsc->bsSamplingFrequency =
        hEnc->nSampleRate; /* for bsSamplingFrequencyIndex */
    hSsc->bsFrameLength = hEnc->nFrameTimeSlots - 1;

    /* map decorr type */
    if (DECORR_INVALID ==
        (hSsc->bsDecorrConfig = mp4SpaceEnc_GetDecorrConfig(hEnc->encMode))) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }

    /* map quantMode */
    if (QUANTMODE_INVALID ==
        (hSsc->bsQuantMode = __mapQuantMode(hEnc->quantMode))) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }

    /* Configure Gains*/
    hSsc->bsFixedGainDMX = fdk_sacenc_staticGain_GetDmxGain(hEnc->hStaticGain);
    hSsc->bsEnvQuantMode = 0;

  } /* valid handle */

bail:
  return error;
}

static FDK_SACENC_ERROR mp4SpaceEnc_FillSpaceTreeSetup(
    const HANDLE_MP4SPACE_ENCODER hEnc,
    SPACE_TREE_SETUP *const hSpaceTreeSetup) {
  FDK_SACENC_ERROR error = SACENC_OK;

  /* Sanity Check */
  if (NULL == hEnc || NULL == hSpaceTreeSetup) {
    error = SACENC_INVALID_HANDLE;
  } else {
    QUANTMODE tmpQuantmode = QUANTMODE_INVALID;

    /* map quantMode */
    if (QUANTMODE_INVALID == (tmpQuantmode = __mapQuantMode(hEnc->quantMode))) {
      error = SACENC_INVALID_CONFIG;
      goto bail;
    }

    hSpaceTreeSetup->nParamBands = hEnc->nParamBands;
    hSpaceTreeSetup->bUseCoarseQuantTtoCld = hEnc->useCoarseQuantCld;
    hSpaceTreeSetup->bUseCoarseQuantTtoIcc = hEnc->useCoarseQuantIcc;
    hSpaceTreeSetup->quantMode = tmpQuantmode;
    hSpaceTreeSetup->nHybridBandsMax = hEnc->nHybridBands;

    switch (hEnc->encMode) {
      case SACENC_212:
        hSpaceTreeSetup->mode = SPACETREE_212;
        hSpaceTreeSetup->nChannelsInMax = 2;
        break;
      case SACENC_INVALID_MODE:
      default:
        error = SACENC_INVALID_CONFIG;
        goto bail;
    } /* switch hEnc->encMode */

  } /* valid handle */
bail:
  return error;
}

FDK_SACENC_ERROR FDK_sacenc_getInfo(const HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc,
                                    MP4SPACEENC_INFO *const pInfo) {
  FDK_SACENC_ERROR error = SACENC_OK;

  if ((NULL == hMp4SpaceEnc) || (NULL == pInfo)) {
    error = SACENC_INVALID_HANDLE;
  } else {
    pInfo->nSampleRate = hMp4SpaceEnc->nSampleRate;
    pInfo->nSamplesFrame = hMp4SpaceEnc->nFrameLength;
    pInfo->nTotalInputChannels = hMp4SpaceEnc->nInputChannels;
    pInfo->nDmxDelay = fdk_sacenc_delay_GetInfoDmxDelay(hMp4SpaceEnc->hDelay);
    pInfo->nCodecDelay =
        fdk_sacenc_delay_GetInfoCodecDelay(hMp4SpaceEnc->hDelay);
    pInfo->nDecoderDelay =
        fdk_sacenc_delay_GetInfoDecoderDelay(hMp4SpaceEnc->hDelay);
    pInfo->nPayloadDelay =
        fdk_sacenc_delay_GetBitstreamFrameBufferSize(hMp4SpaceEnc->hDelay) - 1;
    pInfo->nDiscardOutFrames = hMp4SpaceEnc->nDiscardOutFrames;

    pInfo->pSscBuf = &hMp4SpaceEnc->sscBuf;
  }
  return error;
}

FDK_SACENC_ERROR FDK_sacenc_setParam(HANDLE_MP4SPACE_ENCODER hMp4SpaceEnc,
                                     const SPACEENC_PARAM param,
                                     const UINT value) {
  FDK_SACENC_ERROR error = SACENC_OK;

  /* check encoder handle */
  if (hMp4SpaceEnc == NULL) {
    error = SACENC_INVALID_HANDLE;
    goto bail;
  }

  /* apply param value */
  switch (param) {
    case SACENC_LOWDELAY:
      if (!((value == 0) || (value == 1) || (value == 2))) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.bLdMode = value;
      break;

    case SACENC_ENC_MODE:
      switch ((MP4SPACEENC_MODE)value) {
        case SACENC_212:
          hMp4SpaceEnc->user.encMode = (MP4SPACEENC_MODE)value;
          break;
        default:
          error = SACENC_INVALID_CONFIG;
      }
      break;

    case SACENC_SAMPLERATE:
      if (((int)value < 0) ||
          ((int)value > hMp4SpaceEnc->setup.maxSamplingrate)) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.sampleRate = value;
      break;

    case SACENC_FRAME_TIME_SLOTS:
      if (((int)value < 0) ||
          ((int)value > hMp4SpaceEnc->setup.maxFrameTimeSlots)) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.frameTimeSlots = value;
      break;

    case SACENC_PARAM_BANDS:
      switch ((MP4SPACEENC_BANDS_CONFIG)value) {
        case SACENC_BANDS_4:
        case SACENC_BANDS_5:
        case SACENC_BANDS_7:
        case SACENC_BANDS_9:
        case SACENC_BANDS_12:
        case SACENC_BANDS_15:
        case SACENC_BANDS_23:
          hMp4SpaceEnc->user.nParamBands = (MP4SPACEENC_BANDS_CONFIG)value;
          break;
        default:
          error = SACENC_INVALID_CONFIG;
      }
      break;

    case SACENC_TIME_DOM_DMX:
      if (!((value == 0) || (value == 2))) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.bTimeDomainDmx = value;
      break;

    case SACENC_DMX_GAIN:
      if (!((value == 0) || (value == 1) || (value == 2) || (value == 3) ||
            (value == 4) || (value == 5) || (value == 6) || (value == 7))) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      error = fdk_sacenc_staticGain_SetDmxGain(hMp4SpaceEnc->hStaticGainConfig,
                                               (MP4SPACEENC_DMX_GAIN)value);
      break;

    case SACENC_COARSE_QUANT:
      if (!((value == 0) || (value == 1))) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.bUseCoarseQuant = value;
      break;

    case SACENC_QUANT_MODE:
      switch ((MP4SPACEENC_QUANTMODE)value) {
        case SACENC_QUANTMODE_FINE:
        case SACENC_QUANTMODE_EBQ1:
        case SACENC_QUANTMODE_EBQ2:
          hMp4SpaceEnc->user.quantMode = (MP4SPACEENC_QUANTMODE)value;
          break;
        default:
          error = SACENC_INVALID_CONFIG;
      }
      break;

    case SACENC_TIME_ALIGNMENT:
      if ((INT)value < -32768 || (INT)value > 32767) {
        error = SACENC_INVALID_CONFIG;
        break;
      }
      hMp4SpaceEnc->user.timeAlignment = value;
      break;

    case SACENC_INDEPENDENCY_COUNT:
      hMp4SpaceEnc->independencyCount = value;
      break;

    case SACENC_INDEPENDENCY_FACTOR:
      hMp4SpaceEnc->user.independencyFactor = value;
      break;

    default:
      error = SACENC_UNSUPPORTED_PARAMETER;
      break;
  } /* switch(param) */
bail:
  return error;
}

FDK_SACENC_ERROR FDK_sacenc_getLibInfo(LIB_INFO *info) {
  int i = 0;

  if (info == NULL) {
    return SACENC_INVALID_HANDLE;
  }

  FDK_toolsGetLibInfo(info);

  /* search for next free tab */
  for (i = 0; i < FDK_MODULE_LAST; i++) {
    if (info[i].module_id == FDK_NONE) break;
  }
  if (i == FDK_MODULE_LAST) {
    return SACENC_INIT_ERROR;
  }

  info[i].module_id = FDK_MPSENC;
  info[i].build_date = SACENC_LIB_BUILD_DATE;
  info[i].build_time = SACENC_LIB_BUILD_TIME;
  info[i].title = SACENC_LIB_TITLE;
  info[i].version = LIB_VERSION(SACENC_LIB_VL0, SACENC_LIB_VL1, SACENC_LIB_VL2);
  LIB_VERSION_STRING(&info[i]);

  /* Capability flags */
  info[i].flags = 0;
  /* End of flags */

  return SACENC_OK;
}

static DECORRCONFIG mp4SpaceEnc_GetDecorrConfig(
    const MP4SPACEENC_MODE encMode) {
  DECORRCONFIG decorrConfig = DECORR_INVALID;

  /* set decorrConfig dependent on tree mode */
  switch (encMode) {
    case SACENC_212:
      decorrConfig = DECORR_QMFSPLIT0;
      break;
    case SACENC_INVALID_MODE:
    default:
      decorrConfig = DECORR_INVALID;
  }
  return decorrConfig;
}

static FDK_SACENC_ERROR mp4SpaceEnc_InitNumParamBands(
    HANDLE_MP4SPACE_ENCODER hEnc, const MP4SPACEENC_BANDS_CONFIG nParamBands) {
  FDK_SACENC_ERROR error = SACENC_OK;

  /* Set/Check nParamBands */
  int k = 0;
  const int n = sizeof(pValidBands_Ld) / sizeof(UCHAR);
  const UCHAR *pBands = pValidBands_Ld;

  while (k < n && pBands[k] != (UCHAR)nParamBands) ++k;
  if (k == n) {
    hEnc->nParamBands = SACENC_BANDS_INVALID;
  } else {
    hEnc->nParamBands = nParamBands;
  }
  return error;
}
