qzdoom/src/sound/musicformats/music_xa.cpp

367 lines
8.5 KiB
C++
Raw Normal View History

2019-09-25 17:38:48 +00:00
#include "i_musicinterns.h"
/**
* PlayStation XA (ADPCM) source support for MultiVoc
* Adapted and remixed from superxa2wav (taken from EDuke32)
*/
//#define NO_XA_HEADER
#define kNumOfSamples 224
#define kNumOfSGs 18
#define TTYWidth 80
#define kBufSize (kNumOfSGs*kNumOfSamples)
#define kSamplesMono (kNumOfSGs*kNumOfSamples)
#define kSamplesStereo (kNumOfSGs*kNumOfSamples/2)
/* ADPCM */
#define XA_DATA_START (0x44-48)
#define DblToPCMF(dt) ((dt) / 32768.f)
typedef struct {
FileReader reader;
size_t committed;
bool blockIsMono;
bool blockIs18K;
bool finished;
double t1, t2;
double t1_x, t2_x;
float block[kBufSize];
} xa_data;
typedef int8_t SoundGroup[128];
typedef struct XASector {
int8_t sectorFiller[48];
SoundGroup SoundGroups[18];
} XASector;
static double K0[4] = {
0.0,
0.9375,
1.796875,
1.53125
};
static double K1[4] = {
0.0,
0.0,
-0.8125,
-0.859375
};
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(const int8_t *buf, int32_t unit)
{
return (*(buf + 4 + unit) >> 4) & 0x03;
}
static int8_t getRange(const 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;
int32_t unit, sample;
int32_t sndgrp;
double tmp2, tmp3, tmp4, tmp5;
int8_t &decodeBuf = xad->block;
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);
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;
decodeBuf[count++] = DblToPCM(xad->t1);
}
}
}
}
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;
double tmp2, tmp3, tmp4, tmp5;
int8_t &decodeBuf = xad->block;
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);
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;
decodeBuf[count++] = DblToPCMF(xad->t1);
// Channel 2
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit+1, sample);
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;
decodeBuf[count++] = DblToPCMF(xad->t1_x);
}
}
}
}
//==========================================================================
//
// Get one decoded block of data
//
//==========================================================================
static bool getNextXABlock(xa_data *xad, bool looping )
{
XASector ssct;
int coding;
const int SUBMODE_REAL_TIME_SECTOR = (1 << 6);
const int SUBMODE_FORM = (1 << 5);
const int SUBMODE_AUDIO_DATA = (1 << 2);
do
{
size_t bytes = xad->reader.GetLength() - xad->reader.Tell();
if (sizeof(XASector) < bytes)
bytes = sizeof(XASector);
xad->reader.Read(&ssct, bytes);
}
while (ssct.sectorFiller[46] != (SUBMODE_REAL_TIME_SECTOR | SUBMODE_FORM | SUBMODE_AUDIO_DATA));
coding = ssct.sectorFiller[47];
xa->blockIsMono = (coding & 3) == 0;
xa->blockIs18K = (((coding >> 2) & 3) == 1);
uint32_t samples;
if (!xa->blockIsMono)
{
decodeSoundSectStereo(&ssct, xad);
samples = kSamplesStereo;
}
else
{
decodeSoundSectMono(&ssct, xad);
samples = kSamplesMono;
}
if (xad->GetLength() == xad->Tell())
{
if (looping)
{
xad->pos = XA_DATA_START;
xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
}
else
xa->finished = true;
}
xa->finished = false;
}
//==========================================================================
//
// XASong
//
//==========================================================================
class XASong : public StreamSong
{
public:
GMESong(FileReader & readr);
~GMESong();
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
protected:
xa_data xad;
static bool Read(SoundStream *stream, void *buff, int len, void *userdata);
};
//==========================================================================
//
// XASong - Constructor
//
//==========================================================================
XASong::XASong(FileReader &reader)
{
ChannelConfig iChannels;
SampleType Type;
xad.ptr = std::move(reader);
xad.pos = XA_DATA_START;
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
getNextXABlock(&xad, false);
auto SampleRate = xad.blockIs18K? 18900 : 37800;
const uint32_t sampleLength = (uint32_t)decoder->getSampleLength();
Reader = std::move(reader);
Decoder = decoder;
Channels = 2; // Since the format can theoretically switch between mono and stereo we need to output everything as stereo.
m_Stream = GSnd->CreateStream(Read, 64 * 1024, 0, SampleRate, this);
}
//==========================================================================
//
// XASong - Destructor
//
//==========================================================================
XASong::~XASong()
{
Stop();
if (m_Stream != nullptr)
{
delete m_Stream;
m_Stream = nullptr;
}
}
//==========================================================================
//
// XASong :: Play
//
//==========================================================================
void XASong::Play(bool looping, int track)
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (xad.finished && looping)
{
xad.pos = XA_DATA_START;
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
xad.reader.Seek(0, FileReader::SeekSet);
xad.finished = false;
}
if (m_Stream->Play(looping, 1))
{
m_Status = STATE_Playing;
}
}
//==========================================================================
//
// XASong :: SetSubsong
//
//==========================================================================
bool XASong::SetSubsong(int track)
{
return false;
}
//==========================================================================
//
// XASong :: Read STATIC
//
//==========================================================================
bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
{
auto self = (XASong*)userdata;
while (ilen > 0)
{
if (self->xad.committed < kBufSize)
{
// commit the data
if (self->xad.blockIsMono)
{
}
else
{
}
}
if (self->xad.finished)
{
// fill the rest with 0's.
}
if (ilen > 0)
{
// we ran out of data and need more
getNextXABlock(&self->xad, m_Looping);
// repeat until done.
}
else break;
}
return !self->xad.finished;
}
//==========================================================================
//
// XA_OpenSong
//
//==========================================================================
MusInfo *XA_OpenSong(FileReader &reader)
{
return new XASong(reader);
}