- adapted the PSX XA decoder from EDuke32 as a music format in GZDoom.

Libsndfile cannot decode this format but tries to play these files as regular WAVs and turns them into noise (Both are in a RIFF container.)
This commit is contained in:
Christoph Oelckers 2019-09-25 21:11:00 +02:00
parent 4ba8da290c
commit b8824b572f
4 changed files with 61 additions and 42 deletions

View file

@ -1212,6 +1212,7 @@ set (PCH_SOURCES
sound/musicformats/music_midistream.cpp sound/musicformats/music_midistream.cpp
sound/musicformats/music_opl.cpp sound/musicformats/music_opl.cpp
sound/musicformats/music_stream.cpp sound/musicformats/music_stream.cpp
sound/musicformats/music_xa.cpp
sound/oplsynth/fmopl.cpp sound/oplsynth/fmopl.cpp
sound/oplsynth/musicblock.cpp sound/oplsynth/musicblock.cpp
sound/oplsynth/oplio.cpp sound/oplsynth/oplio.cpp

View file

@ -451,6 +451,10 @@ MusInfo *I_RegisterSong (FileReader &reader, MidiDeviceSetting *device)
{ {
info = new OPLMUSSong (reader, device != nullptr? device->args.GetChars() : ""); info = new OPLMUSSong (reader, device != nullptr? device->args.GetChars() : "");
} }
else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
{
info = XA_OpenSong(reader);
}
// Check for game music // Check for game music
else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0') else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0')
{ {

View file

@ -380,6 +380,7 @@ MusInfo *MOD_OpenSong(FileReader &reader);
const char *GME_CheckFormat(uint32_t header); const char *GME_CheckFormat(uint32_t header);
MusInfo *GME_OpenSong(FileReader &reader, const char *fmt); MusInfo *GME_OpenSong(FileReader &reader, const char *fmt);
MusInfo *SndFile_OpenSong(FileReader &fr); MusInfo *SndFile_OpenSong(FileReader &fr);
MusInfo* XA_OpenSong(FileReader& reader);
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------

View file

@ -1,24 +1,29 @@
#include "i_musicinterns.h" #include "i_musicinterns.h"
/** /**
* PlayStation XA (ADPCM) source support for MultiVoc * PlayStation XA (ADPCM) source support for MultiVoc
* Adapted and remixed from superxa2wav (taken from EDuke32) * Adapted and remixed from superxa2wav
*
* taken from EDuke32 and adapted for GZDoom by Christoph Oelckers
*/ */
//#define NO_XA_HEADER //#define NO_XA_HEADER
#define kNumOfSamples 224 enum
#define kNumOfSGs 18 {
#define TTYWidth 80 kNumOfSamples = 224,
kNumOfSGs = 18,
TTYWidth = 80,
#define kBufSize (kNumOfSGs*kNumOfSamples) kBufSize = (kNumOfSGs*kNumOfSamples),
#define kSamplesMono (kNumOfSGs*kNumOfSamples) kSamplesMono = (kNumOfSGs*kNumOfSamples),
#define kSamplesStereo (kNumOfSGs*kNumOfSamples/2) kSamplesStereo = (kNumOfSGs*kNumOfSamples/2),
/* ADPCM */ /* ADPCM */
#define XA_DATA_START (0x44-48) XA_DATA_START = (0x44-48)
};
#define DblToPCMF(dt) ((dt) / 32768.f) inline float constexpr DblToPCMF(double dt) { return float(dt) * (1.f/32768.f); }
typedef struct { typedef struct {
FileReader reader; FileReader reader;
@ -94,7 +99,7 @@ static void decodeSoundSectMono(XASector *ssct, xa_data * xad)
int32_t unit, sample; int32_t unit, sample;
int32_t sndgrp; int32_t sndgrp;
double tmp2, tmp3, tmp4, tmp5; double tmp2, tmp3, tmp4, tmp5;
int8_t &decodeBuf = xad->block; auto &decodeBuf = xad->block;
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++) for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
{ {
@ -111,7 +116,7 @@ static void decodeSoundSectMono(XASector *ssct, xa_data * xad)
tmp5 = xad->t2 * K1[filt]; tmp5 = xad->t2 * K1[filt];
xad->t2 = xad->t1; xad->t2 = xad->t1;
xad->t1 = tmp3 + tmp4 + tmp5; xad->t1 = tmp3 + tmp4 + tmp5;
decodeBuf[count++] = DblToPCM(xad->t1); decodeBuf[count++] = DblToPCMF(xad->t1);
} }
} }
} }
@ -122,11 +127,10 @@ static void decodeSoundSectStereo(XASector *ssct, xa_data * xad)
size_t count = 0; size_t count = 0;
int8_t snddat, filt, range; int8_t snddat, filt, range;
int8_t filt1, range1; int8_t filt1, range1;
int16_t decoded;
int32_t unit, sample; int32_t unit, sample;
int32_t sndgrp; int32_t sndgrp;
double tmp2, tmp3, tmp4, tmp5; double tmp2, tmp3, tmp4, tmp5;
int8_t &decodeBuf = xad->block; auto &decodeBuf = xad->block;
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++) for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
{ {
@ -169,7 +173,7 @@ static void decodeSoundSectStereo(XASector *ssct, xa_data * xad)
// //
//========================================================================== //==========================================================================
static bool getNextXABlock(xa_data *xad, bool looping ) static void getNextXABlock(xa_data *xad, bool looping )
{ {
XASector ssct; XASector ssct;
int coding; int coding;
@ -190,34 +194,31 @@ static bool getNextXABlock(xa_data *xad, bool looping )
coding = ssct.sectorFiller[47]; coding = ssct.sectorFiller[47];
xa->blockIsMono = (coding & 3) == 0; xad->committed = 0;
xa->blockIs18K = (((coding >> 2) & 3) == 1); xad->blockIsMono = (coding & 3) == 0;
xad->blockIs18K = (((coding >> 2) & 3) == 1);
uint32_t samples; if (!xad->blockIsMono)
if (!xa->blockIsMono)
{ {
decodeSoundSectStereo(&ssct, xad); decodeSoundSectStereo(&ssct, xad);
samples = kSamplesStereo;
} }
else else
{ {
decodeSoundSectMono(&ssct, xad); decodeSoundSectMono(&ssct, xad);
samples = kSamplesMono;
} }
if (xad->GetLength() == xad->Tell()) if (xad->reader.GetLength() == xad->reader.Tell())
{ {
if (looping) if (looping)
{ {
xad->pos = XA_DATA_START; xad->reader.Seek(XA_DATA_START, FileReader::SeekSet);
xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0; xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
} }
else else
xa->finished = true; xad->finished = true;
} }
xa->finished = false; xad->finished = false;
} }
//========================================================================== //==========================================================================
@ -229,8 +230,8 @@ static bool getNextXABlock(xa_data *xad, bool looping )
class XASong : public StreamSong class XASong : public StreamSong
{ {
public: public:
GMESong(FileReader & readr); XASong(FileReader & readr);
~GMESong(); ~XASong();
bool SetSubsong(int subsong); bool SetSubsong(int subsong);
void Play(bool looping, int subsong); void Play(bool looping, int subsong);
@ -248,21 +249,13 @@ protected:
XASong::XASong(FileReader &reader) XASong::XASong(FileReader &reader)
{ {
ChannelConfig iChannels; reader.Seek(XA_DATA_START, FileReader::SeekSet);
SampleType Type; xad.reader = std::move(reader);
xad.ptr = std::move(reader);
xad.pos = XA_DATA_START;
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0; xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
getNextXABlock(&xad, false); getNextXABlock(&xad, false);
auto SampleRate = xad.blockIs18K? 18900 : 37800; auto SampleRate = xad.blockIs18K? 18900 : 37800;
const uint32_t sampleLength = (uint32_t)decoder->getSampleLength(); m_Stream = GSnd->CreateStream(Read, 64 * 1024, SoundStream::Float, SampleRate, this); // create a floating point stereo stream.
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);
} }
//========================================================================== //==========================================================================
@ -294,9 +287,8 @@ void XASong::Play(bool looping, int track)
m_Looping = looping; m_Looping = looping;
if (xad.finished && looping) if (xad.finished && looping)
{ {
xad.pos = XA_DATA_START; xad.reader.Seek(XA_DATA_START, FileReader::SeekSet);
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0; xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
xad.reader.Seek(0, FileReader::SeekSet);
xad.finished = false; xad.finished = false;
} }
if (m_Stream->Play(looping, 1)) if (m_Stream->Play(looping, 1))
@ -325,16 +317,37 @@ bool XASong::SetSubsong(int track)
bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata) bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
{ {
auto self = (XASong*)userdata; auto self = (XASong*)userdata;
float *dest = (float*)vbuff;
while (ilen > 0) while (ilen > 0)
{ {
if (self->xad.committed < kBufSize) auto ptr = self->xad.committed;
auto block = self->xad.block + ptr;
if (ptr < kBufSize)
{ {
// commit the data // commit the data
if (self->xad.blockIsMono) if (self->xad.blockIsMono)
{ {
size_t numsamples = ilen / 8;
size_t availdata = kBufSize - ptr;
for(size_t tocopy = std::min(numsamples, availdata); tocopy > 0; tocopy--)
{
float f = *block++;
*dest++ = f;
*dest++ = f;
ilen -= 8;
ptr++;
}
self->xad.committed = ptr;
} }
else else
{ {
size_t availdata = (kBufSize - ptr) * 4;
size_t tocopy = std::min(availdata, (size_t)ilen);
memcpy(dest, block, tocopy);
dest += tocopy / 4;
ilen -= (int)tocopy;
self->xad.committed += tocopy / 4;
} }
} }
if (self->xad.finished) if (self->xad.finished)
@ -344,7 +357,7 @@ bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
if (ilen > 0) if (ilen > 0)
{ {
// we ran out of data and need more // we ran out of data and need more
getNextXABlock(&self->xad, m_Looping); getNextXABlock(&self->xad, self->m_Looping);
// repeat until done. // repeat until done.
} }
else break; else break;