
/**
 * PlayStation XA (ADPCM) source support for MultiVoc
 * Adapted and remixed from superxa2wav
 */

#include "compat.h"
#include "pitch.h"
#include "multivoc.h"
#include "_multivc.h"

//#define NO_XA_HEADER

#define USE_FXD 1

#define kNumOfSamples   224
#define kNumOfSGs       18
#define TTYWidth        80

/* ADPCM */
#define XA_DATA_START   (0x44-48)

#define FXD_FxdToPCM(dt)        (max(min((int16_t)((dt)>>16), 32767), -32768))
#define DblToPCM(dt)            (int16_t)(max(min((dt), 32767), -32768))

#if USE_FXD
#define FXD_FxdToPcm16(dt)      (max(min((dt)/2, 32767), -32768))
#define FXD_Pcm16ToFxd(dt)      ((int32_t)dt*2)
#endif

typedef struct {
   void * ptr;
   size_t length;
   size_t pos;

#if USE_FXD
   int32_t t1, t2;
   int32_t t1_x, t2_x;
#else
   double t1, t2;
   double t1_x, t2_x;
#endif

   int8_t *block;
   size_t blocksize;

   VoiceNode *owner;
} xa_data;

typedef int8_t SoundGroup[128];

typedef struct XASector {
    int8_t     sectorFiller[48];
    SoundGroup SoundGroups[18];
} XASector;

#if USE_FXD
static int32_t K0[4] = {
    0x00000000,
    0x0000F000,
    0x0001CC00,
    0x00018800,
};
static int32_t K1[4] = {
    0x00000000,
    0x00000000,
    (int32_t)0xFFFF3000u,
    (int32_t)0xFFFF2400u,
};
#else
static double K0[4] = {
    0.0,
    0.9375,
    1.796875,
    1.53125
};
static double K1[4] = {
    0.0,
    0.0,
    -0.8125,
    -0.859375
};
#endif


#if USE_FXD
static int32_t FXD_FixMul(int32_t a, int32_t b)
{
    int32_t  high_a, low_a, high_b, low_b;
    int32_t  hahb, halb, lahb;
    uint32_t lalb;
    int32_t  ret;

    high_a = a >> 16;
    low_a  = a & 0x0000FFFF;
    high_b = b >> 16;
    low_b  = b & 0x0000FFFF;

    hahb = (high_a * high_b) << 16;
    halb = high_a * low_b;
    lahb = low_a * high_b;
    lalb = (uint32_t)(low_a * low_b) >> 16;

    ret = hahb + halb + lahb + lalb;

    return ret;
}
#endif



static int8_t getSoundData(int8_t *buf, int32_t unit, int32_t sample)
{
    int8_t ret;
    int8_t *p;
    int32_t offset, shift;

    p = buf;
    shift = (unit%2) * 4;

    offset = 16 + (unit / 2) + (sample * 4);
    p += offset;

    ret = (*p >> shift) & 0x0F;

    if (ret > 7) {
        ret -= 16;
    }
    return ret;
}

static int8_t getFilter(int8_t *buf, int32_t unit)
{
    return (*(buf + 4 + unit) >> 4) & 0x03;
}


static int8_t getRange(int8_t *buf, int32_t unit)
{
    return *(buf + 4 + unit) & 0x0F;
}


static void decodeSoundSectMono(XASector *ssct, xa_data * xad)
{
    size_t count = 0;
    int8_t snddat, filt, range;
    int16_t decoded;
    int32_t unit, sample;
    int32_t sndgrp;
#if USE_FXD
    int32_t tmp2, tmp3, tmp4, tmp5;
#else
    double tmp2, tmp3, tmp4, tmp5;
#endif
    int8_t decodeBuf[kNumOfSGs*kNumOfSamples*2];

    for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
    {
        for (unit = 0; unit < 8; unit++)
        {
            range = getRange(ssct->SoundGroups[sndgrp], unit);
            filt = getFilter(ssct->SoundGroups[sndgrp], unit);
            for (sample = 0; sample < 28; sample++)
            {
                snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
#if USE_FXD
                tmp2 = (int32_t)(snddat) << (12 - range);
                tmp3 = FXD_Pcm16ToFxd(tmp2);
                tmp4 = FXD_FixMul(K0[filt], xad->t1);
                tmp5 = FXD_FixMul(K1[filt], xad->t2);
                xad->t2 = xad->t1;
                xad->t1 = tmp3 + tmp4 + tmp5;
                decoded = FXD_FxdToPcm16(xad->t1);
#else
                tmp2 = (double)(1 << (12 - range));
                tmp3 = (double)snddat * tmp2;
                tmp4 = xad->t1 * K0[filt];
                tmp5 = xad->t2 * K1[filt];
                xad->t2 = xad->t1;
                xad->t1 = tmp3 + tmp4 + tmp5;
                decoded = DblToPCM(xad->t1);
#endif
                decodeBuf[count++] = (int8_t) ((uint16_t)decoded & 0x00FF);
                decodeBuf[count++] = (int8_t)(((uint16_t)decoded & 0xFF00) >> 8);
            }
        }
    }

    if (count > xad->blocksize)
        xad->block = (int8_t *)realloc(xad->block, count);

    memcpy(xad->block, decodeBuf, count);
    xad->blocksize = count;
}

static void decodeSoundSectStereo(XASector *ssct, xa_data * xad)
{
    size_t count = 0;
    int8_t snddat, filt, range;
    int8_t filt1, range1;
    int16_t decoded;
    int32_t unit, sample;
    int32_t sndgrp;
#if USE_FXD
    int32_t tmp2, tmp3, tmp4, tmp5;
#else
    double tmp2, tmp3, tmp4, tmp5;
#endif
    int8_t decodeBuf[kNumOfSGs*kNumOfSamples*2];

    for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
    {
        for (unit = 0; unit < 8; unit+= 2)
        {
            range = getRange(ssct->SoundGroups[sndgrp], unit);
            filt = getFilter(ssct->SoundGroups[sndgrp], unit);
            range1 = getRange(ssct->SoundGroups[sndgrp], unit+1);
            filt1 = getFilter(ssct->SoundGroups[sndgrp], unit+1);

            for (sample = 0; sample < 28; sample++)
            {
                // Channel 1
                snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
#if USE_FXD
                tmp2 = (int32_t)(snddat) << (12 - range);
                tmp3 = FXD_Pcm16ToFxd(tmp2);
                tmp4 = FXD_FixMul(K0[filt], xad->t1);
                tmp5 = FXD_FixMul(K1[filt], xad->t2);
                xad->t2 = xad->t1;
                xad->t1 = tmp3 + tmp4 + tmp5;
                decoded = FXD_FxdToPcm16(xad->t1);
#else
                tmp2 = (double)(1 << (12 - range));
                tmp3 = (double)snddat * tmp2;
                tmp4 = xad->t1 * K0[filt];
                tmp5 = xad->t2 * K1[filt];
                xad->t2 = xad->t1;
                xad->t1 = tmp3 + tmp4 + tmp5;
                decoded = DblToPCM(xad->t1);
#endif
                decodeBuf[count++] = (int8_t) ((uint16_t)decoded & 0x00FF);
                decodeBuf[count++] = (int8_t)(((uint16_t)decoded & 0xFF00) >> 8);

                // Channel 2
                snddat = getSoundData(ssct->SoundGroups[sndgrp], unit+1, sample);
#if USE_FXD
                tmp2 = (int32_t)(snddat) << (12 - range1);
                tmp3 = FXD_Pcm16ToFxd(tmp2);
                tmp4 = FXD_FixMul(K0[filt1], xad->t1_x);
                tmp5 = FXD_FixMul(K1[filt1], xad->t2_x);
                xad->t2_x = xad->t1_x;
                xad->t1_x = tmp3 + tmp4 + tmp5;
                decoded = FXD_FxdToPcm16(xad->t1_x);
#else
                tmp2 = (double)(1 << (12 - range1));
                tmp3 = (double)snddat * tmp2;
                tmp4 = xad->t1_x * K0[filt1];
                tmp5 = xad->t2_x * K1[filt1];
                xad->t2_x = xad->t1_x;
                xad->t1_x = tmp3 + tmp4 + tmp5;
                decoded = DblToPCM(xad->t1_x);
#endif
                decodeBuf[count++] = (int8_t) ((uint16_t)decoded & 0x00FF);
                decodeBuf[count++] = (int8_t)(((uint16_t)decoded & 0xFF00) >> 8);
            }
        }
    }

    if (count > xad->blocksize)
        xad->block = (int8_t *)realloc(xad->block, count);

    memcpy(xad->block, decodeBuf, count);
    xad->blocksize = count;
}

int32_t MV_GetXAPosition(VoiceNode *voice)
{
    xa_data * xad = (xa_data *) voice->rawdataptr;
    return xad->pos;
}

void MV_SetXAPosition(VoiceNode *voice, int32_t position)
{
    xa_data * xad = (xa_data *) voice->rawdataptr;

    if (position < XA_DATA_START || (size_t)position >= xad->length)
    {
        position = XA_DATA_START;
        xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
    }

    xad->pos = position;
}

/*---------------------------------------------------------------------
Function: MV_GetNextXABlock

Controls playback of XA data
---------------------------------------------------------------------*/

static playbackstatus MV_GetNextXABlock
(
 VoiceNode *voice
 )
{
    xa_data * xad = (xa_data *) voice->rawdataptr;
    XASector ssct;
    int coding;

    voice->Playing = TRUE;

    do
    {
        size_t bytes = xad->length - xad->pos;

        if (sizeof(XASector) < bytes)
            bytes = sizeof(XASector);

        memcpy(&ssct, (int8_t *)xad->ptr + xad->pos, bytes);
        xad->pos += bytes;
    }
#define SUBMODE_REAL_TIME_SECTOR (1 << 6)
#define SUBMODE_FORM             (1 << 5)
#define SUBMODE_AUDIO_DATA       (1 << 2)
    while (ssct.sectorFiller[46] != (SUBMODE_REAL_TIME_SECTOR | SUBMODE_FORM | SUBMODE_AUDIO_DATA));

    coding = ssct.sectorFiller[47];

    voice->channels = (coding & 3) + 1;
    voice->SamplingRate = (((coding >> 2) & 3) == 1) ? 18900 : 37800;

    // CODEDUP multivoc.c MV_SetVoicePitch
    voice->RateScale    = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate;
    voice->FixedPointBufferSize = ( voice->RateScale * MV_MIXBUFFERSIZE ) - voice->RateScale;
    MV_SetVoiceMixMode( voice );

    if (voice->channels == 2)
        decodeSoundSectStereo(&ssct, xad);
    else
        decodeSoundSectMono(&ssct, xad);

    voice->sound        = (char *)xad->block;
    voice->length       = (((xad->blocksize * (voice->bits/8))/2)<<voice->bits) / 4;
    voice->position     = 0;
    voice->BlockLength  = 0;

    if (xad->length == xad->pos)
    {
        if (voice->LoopSize > 0)
        {
            xad->pos = XA_DATA_START;
            xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
        }
        else
        {
            voice->Playing = FALSE;
            return NoMoreData;
        }
    }

    return KeepPlaying;
}


/*---------------------------------------------------------------------
Function: MV_PlayXA3D

Begin playback of sound data at specified angle and distance
from listener.
---------------------------------------------------------------------*/

int32_t MV_PlayXA3D
(
 char *ptr,
 uint32_t ptrlength,
 int32_t loophow,
 int32_t  pitchoffset,
 int32_t  angle,
 int32_t  distance,
 int32_t  priority,
 uint32_t callbackval
 )
{
   int32_t left;
   int32_t right;
   int32_t mid;
   int32_t volume;
   int32_t status;

   if ( !MV_Installed )
   {
      MV_SetErrorCode( MV_NotInstalled );
      return MV_Error;
   }

   if ( distance < 0 )
   {
      distance  = -distance;
      angle    += MV_NUMPANPOSITIONS / 2;
   }

   volume = MIX_VOLUME( distance );

   // Ensure angle is within 0 - 127
   angle &= MV_MAXPANPOSITION;

   left  = MV_PanTable[ angle ][ volume ].left;
   right = MV_PanTable[ angle ][ volume ].right;
   mid   = max( 0, 255 - distance );

   status = MV_PlayXA(ptr, ptrlength, loophow, -1, pitchoffset, mid, left, right, priority, callbackval);

   return status;
}


/*---------------------------------------------------------------------
Function: MV_PlayXA

Begin playback of sound data with the given sound levels and
priority.
---------------------------------------------------------------------*/

int32_t MV_PlayXA
(
 char *ptr,
 uint32_t ptrlength,
 int32_t   loopstart,
 int32_t   loopend,
 int32_t   pitchoffset,
 int32_t   vol,
 int32_t   left,
 int32_t   right,
 int32_t   priority,
 uint32_t callbackval
 )

{
   VoiceNode   *voice;
   xa_data * xad = 0;

   UNREFERENCED_PARAMETER(loopend);

   if ( !MV_Installed )
   {
      MV_SetErrorCode( MV_NotInstalled );
      return MV_Error;
   }

   xad = (xa_data *) calloc( 1, sizeof(xa_data) );
   if (!xad) {
      MV_SetErrorCode( MV_InvalidFile );
      return MV_Error;
   }

   xad->ptr = ptr;
   xad->pos = XA_DATA_START;
   xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
   xad->blocksize = 0;
   xad->length = ptrlength;

   xad->block = NULL;

   // Request a voice from the voice pool
   voice = MV_AllocVoice( priority );
   if ( voice == NULL )
   {

      free(xad);
      MV_SetErrorCode( MV_NoVoices );
      return MV_Error;
   }

   xad->owner = voice;

   voice->wavetype    = FMT_XA;
   voice->rawdataptr  = (void*)xad;
   voice->GetSound    = MV_GetNextXABlock;
   voice->NextBlock   = (char *)xad->block;
   voice->LoopCount   = 0;
   voice->BlockLength = 0;
   voice->PitchScale  = PITCH_GetScale( pitchoffset );
   voice->next        = NULL;
   voice->prev        = NULL;
   voice->priority    = priority;
   voice->callbackval = callbackval;

   voice->bits        = 16;

   voice->Playing     = TRUE;
   voice->Paused      = FALSE;

   voice->LoopStart   = 0;
   voice->LoopEnd     = 0;
   voice->LoopSize    = (loopstart >= 0 ? 1 : 0);

   MV_SetVoiceVolume( voice, vol, left, right );
   MV_PlayVoice( voice );

   return voice->handle;
}


void MV_ReleaseXAVoice( VoiceNode * voice )
{
    xa_data * xad = (xa_data *) voice->rawdataptr;

    if (voice->wavetype != FMT_XA) {
        return;
    }

    free(xad->block);
    free(xad);

    voice->rawdataptr = 0;
}
