#include #include "streamsource.h" #include "fileio.h" /** * PlayStation XA (ADPCM) source support for MultiVoc * Adapted and remixed from superxa2wav * * taken from EDuke32 and adapted for GZDoom by Christoph Oelckers */ //#define NO_XA_HEADER enum { kNumOfSamples = 224, kNumOfSGs = 18, TTYWidth = 80, kBufSize = (kNumOfSGs*kNumOfSamples), kSamplesMono = (kNumOfSGs*kNumOfSamples), kSamplesStereo = (kNumOfSGs*kNumOfSamples/2), /* ADPCM */ XA_DATA_START = (0x44-48) }; inline float constexpr DblToPCMF(double dt) { return float(dt) * (1.f/32768.f); } typedef struct { MusicIO::FileInterface *reader; size_t committed; size_t length; 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; auto &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++] = DblToPCMF(xad->t1); } } } } static void decodeSoundSectStereo(XASector *ssct, xa_data * xad) { size_t count = 0; int8_t snddat, filt, range; int8_t filt1, range1; int32_t unit, sample; int32_t sndgrp; double tmp2, tmp3, tmp4, tmp5; auto &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 void 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->length - xad->reader->tell(); if (sizeof(XASector) < bytes) bytes = sizeof(XASector); xad->reader->read(&ssct, (int)bytes); } while (ssct.sectorFiller[46] != (SUBMODE_REAL_TIME_SECTOR | SUBMODE_FORM | SUBMODE_AUDIO_DATA)); coding = ssct.sectorFiller[47]; xad->committed = 0; xad->blockIsMono = (coding & 3) == 0; xad->blockIs18K = (((coding >> 2) & 3) == 1); if (!xad->blockIsMono) { decodeSoundSectStereo(&ssct, xad); } else { decodeSoundSectMono(&ssct, xad); } if (xad->length == xad->reader->tell()) { if (looping) { xad->reader->seek(XA_DATA_START, SEEK_SET); xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0; } else xad->finished = true; } xad->finished = false; } //========================================================================== // // XASong // //========================================================================== class XASong : public StreamSource { public: XASong(MusicIO::FileInterface *readr); SoundStreamInfoEx GetFormatEx() override; bool Start() override; bool GetData(void *buffer, size_t len) override; protected: xa_data xad; }; //========================================================================== // // XASong - Constructor // //========================================================================== XASong::XASong(MusicIO::FileInterface * reader) { reader->seek(0, SEEK_END); xad.length = reader->tell(); reader->seek(XA_DATA_START, SEEK_SET); xad.reader = reader; xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0; getNextXABlock(&xad, false); } SoundStreamInfoEx XASong::GetFormatEx() { auto SampleRate = xad.blockIs18K? 18900 : 37800; return { 64*1024, SampleRate, SampleType_Float32, ChannelConfig_Stereo }; } //========================================================================== // // XASong :: Play // //========================================================================== bool XASong::Start() { if (xad.finished && m_Looping) { xad.reader->seek(XA_DATA_START, SEEK_SET); xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0; xad.finished = false; } return true; } //========================================================================== // // XASong :: Read // //========================================================================== bool XASong::GetData(void *vbuff, size_t len) { float *dest = (float*)vbuff; while (len > 0) { auto ptr = xad.committed; auto block = xad.block + ptr; if (ptr < kBufSize) { // commit the data if (xad.blockIsMono) { size_t numsamples = len / 8; size_t availdata = kBufSize - ptr; for(size_t tocopy = std::min(numsamples, availdata); tocopy > 0; tocopy--) { float f = *block++; *dest++ = f; *dest++ = f; len -= 8; ptr++; } xad.committed = ptr; } else { size_t availdata = (kBufSize - ptr) * 4; size_t tocopy = std::min(availdata, len); memcpy(dest, block, tocopy); dest += tocopy / 4; len -= tocopy; xad.committed += tocopy / 4; } } if (xad.finished) { memset(dest, 0, len); return true; } if (len > 0) { // we ran out of data and need more getNextXABlock(&xad, m_Looping); // repeat until done. } else break; } return !xad.finished; } //========================================================================== // // XA_OpenSong // //========================================================================== StreamSource *XA_OpenSong(MusicIO::FileInterface *reader) { return new XASong(reader); }