#include "stdafx.h"
#include "Loaders.h"
#include "ChunkReader.h"
#ifdef LIBOPENMPT_BUILD
#define MPT_PSM_USE_REAL_SUBSONGS
#endif
OPENMPT_NAMESPACE_BEGIN
struct PSMFileHeader
{
char formatID[4]; uint32le fileSize; char fileInfoID[4]; };
MPT_BINARY_STRUCT(PSMFileHeader, 12)
struct PSMChunk
{
enum ChunkIdentifiers
{
idTITL = MagicLE("TITL"),
idSDFT = MagicLE("SDFT"),
idPBOD = MagicLE("PBOD"),
idSONG = MagicLE("SONG"),
idDATE = MagicLE("DATE"),
idOPLH = MagicLE("OPLH"),
idPPAN = MagicLE("PPAN"),
idPATT = MagicLE("PATT"),
idDSAM = MagicLE("DSAM"),
idDSMP = MagicLE("DSMP"),
};
uint32le id;
uint32le length;
size_t GetLength() const
{
return length;
}
ChunkIdentifiers GetID() const
{
return static_cast<ChunkIdentifiers>(id.get());
}
};
MPT_BINARY_STRUCT(PSMChunk, 8)
struct PSMSongHeader
{
char songType[9]; uint8 compression; uint8 numChannels;
};
MPT_BINARY_STRUCT(PSMSongHeader, 11)
struct PSMSampleHeader
{
uint8le flags;
char fileName[8]; char sampleID[4]; char sampleName[33];
uint8le unknown1[6]; uint16le sampleNumber;
uint32le sampleLength;
uint32le loopStart;
uint32le loopEnd; uint8le unknown3;
uint8le finetune; uint8le defaultVolume;
uint32le unknown4;
uint32le c5Freq; char padding[19];
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mpt::String::Read<mpt::String::maybeNullTerminated>(mptSmp.filename, fileName);
mptSmp.nC5Speed = c5Freq;
mptSmp.nLength = sampleLength;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nVolume = (defaultVolume + 1) * 2;
mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0);
LimitMax(mptSmp.nLoopEnd, mptSmp.nLength);
LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd);
}
};
MPT_BINARY_STRUCT(PSMSampleHeader, 96)
struct PSMSinariaSampleHeader
{
uint8le flags;
char fileName[8]; char sampleID[8]; char sampleName[33];
uint8le unknown1[6]; uint16le sampleNumber;
uint32le sampleLength;
uint32le loopStart;
uint32le loopEnd;
uint16le unknown3;
uint8le finetune; uint8le defaultVolume;
uint32le unknown4;
uint16le c5Freq;
char padding[16];
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mpt::String::Read<mpt::String::maybeNullTerminated>(mptSmp.filename, fileName);
mptSmp.nC5Speed = c5Freq;
mptSmp.nLength = sampleLength;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nVolume = (defaultVolume + 1) * 2;
mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0);
LimitMax(mptSmp.nLoopEnd, mptSmp.nLength);
LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd);
}
};
MPT_BINARY_STRUCT(PSMSinariaSampleHeader, 96)
struct PSMSubSong {
std::vector<uint8> channelPanning, channelVolume;
std::vector<bool> channelSurround;
ORDERINDEX startOrder = ORDERINDEX_INVALID, endOrder = ORDERINDEX_INVALID, restartPos = 0;
uint8 defaultTempo = 125, defaultSpeed = 6;
char songName[10];
PSMSubSong()
{
channelPanning.assign(MAX_BASECHANNELS, 128);
channelVolume.assign(MAX_BASECHANNELS, 64);
channelSurround.assign(MAX_BASECHANNELS, false);
MemsetZero(songName);
}
};
static uint8 ConvertPSMPorta(uint8 param, bool sinariaFormat)
{
if(sinariaFormat)
return param;
if(param < 4)
return (param | 0xF0);
else
return (param >> 2);
}
static PATTERNINDEX ReadPSMPatternIndex(FileReader &file, bool &sinariaFormat)
{
char patternID[5];
uint8 offset = 1;
file.ReadString<mpt::String::spacePadded>(patternID, 4);
if(!memcmp(patternID, "PATT", 4))
{
file.ReadString<mpt::String::spacePadded>(patternID, 4);
sinariaFormat = true;
offset = 0;
}
return ConvertStrTo<uint16>(&patternID[offset]);
}
static bool ValidateHeader(const PSMFileHeader &fileHeader)
{
if(!std::memcmp(fileHeader.formatID, "PSM ", 4)
&& !std::memcmp(fileHeader.fileInfoID, "FILE", 4))
{
return true;
}
#ifdef MPT_PSM_DECRYPT
if(!std::memcmp(fileHeader.formatID, "QUP$", 4)
&& !std::memcmp(fileHeader.fileInfoID, "OSWQ", 4))
{
return true;
}
#endif
return false;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPSM(MemoryFileReader file, const uint64 *pfilesize)
{
PSMFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
PSMChunk chunkHeader;
if(!file.ReadStruct(chunkHeader))
{
return ProbeWantMoreData;
}
if(chunkHeader.length == 0)
{
return ProbeFailure;
}
if((chunkHeader.id & 0x7f7f7f7fu) != chunkHeader.id) {
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
PSMFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
#ifdef MPT_PSM_DECRYPT
std::vector<mpt::byte> decrypted;
if(!memcmp(fileHeader.formatID, "QUP$", 4)
&& !memcmp(fileHeader.fileInfoID, "OSWQ", 4))
{
if(loadFlags == onlyVerifyHeader)
return true;
file.Rewind();
decrypted.resize(file.GetLength());
file.ReadRaw(decrypted.data(), decrypted.size());
uint8 i = 0;
for(auto &c : decrypted)
{
c -= ++i;
}
file = FileReader(mpt::as_span(decrypted));
file.ReadStruct(fileHeader);
}
#endif
if(!ValidateHeader(fileHeader))
{
return false;
}
ChunkReader chunkFile(file);
ChunkReader::ChunkList<PSMChunk> chunks;
if(loadFlags == onlyVerifyHeader)
chunks = chunkFile.ReadChunksUntil<PSMChunk>(1, PSMChunk::idSDFT);
else
chunks = chunkFile.ReadChunks<PSMChunk>(1);
if(!chunks.GetChunk(PSMChunk::idSDFT).ReadMagic("MAINSONG"))
return false;
else if(loadFlags == onlyVerifyHeader)
return true;
InitializeGlobals(MOD_TYPE_PSM);
m_SongFlags = SONG_ITOLDEFFECTS | SONG_ITCOMPATGXX;
FileReader titleChunk = chunks.GetChunk(PSMChunk::idTITL);
titleChunk.ReadString<mpt::String::spacePadded>(m_songName, titleChunk.GetLength());
Order().clear();
std::vector<PSMSubSong> subsongs;
bool subsongPanningDiffers = false; bool sinariaFormat = false;
auto songChunks = chunks.GetAllChunks(PSMChunk::idSONG);
for(ChunkReader chunk : songChunks)
{
PSMSongHeader songHeader;
if(!chunk.ReadStruct(songHeader)
|| songHeader.compression != 0x01) {
return false;
}
m_nChannels = Clamp(static_cast<CHANNELINDEX>(songHeader.numChannels), m_nChannels, MAX_BASECHANNELS);
PSMSubSong subsong;
mpt::String::Read<mpt::String::nullTerminated>(subsong.songName, songHeader.songType);
#ifdef MPT_PSM_USE_REAL_SUBSONGS
if(!Order().empty())
{
if(Order.AddSequence(false) == SEQUENCEINDEX_INVALID)
break;
}
Order().SetName(subsong.songName);
#endif
auto subChunks = chunk.ReadChunks<PSMChunk>(1);
for(const auto &subChunkIter : subChunks)
{
FileReader subChunk(subChunkIter.GetData());
PSMChunk subChunkHead = subChunkIter.GetHeader();
switch(subChunkHead.GetID())
{
#if 0#endif
case PSMChunk::idOPLH: if(subChunkHead.GetLength() >= 9)
{
subChunk.Skip(2);
uint16 chunkCount = 0, firstOrderChunk = uint16_max;
while(subChunk.CanRead(1))
{
uint8 opcode = subChunk.ReadUint8();
if(!opcode)
{
break;
}
switch(opcode)
{
case 0x01: {
if(subsong.startOrder == ORDERINDEX_INVALID)
subsong.startOrder = Order().GetLength();
subsong.endOrder = Order().GetLength();
PATTERNINDEX pat = ReadPSMPatternIndex(subChunk, sinariaFormat);
if(pat == 0xFF)
pat = Order.GetInvalidPatIndex();
else if(pat == 0xFE)
pat = Order.GetIgnoreIndex();
Order().push_back(pat);
if(firstOrderChunk == uint16_max)
firstOrderChunk = chunkCount;
}
break;
case 0x04: {
uint16 restartChunk = subChunk.ReadUint16LE();
if(restartChunk >= firstOrderChunk)
subsong.restartPos = static_cast<ORDERINDEX>(restartChunk - firstOrderChunk); Order().SetRestartPos(subsong.restartPos);
}
break;
case 0x07: subsong.defaultSpeed = subChunk.ReadUint8();
break;
case 0x08: subsong.defaultTempo = subChunk.ReadUint8();
break;
case 0x0C: {
uint8 mapTable[6];
if(!subChunk.ReadArray(mapTable)
|| mapTable[0] != 0x00 || mapTable[1] != 0xFF || mapTable[2] != 0x00 || mapTable[3] != 0x00 || mapTable[4] != 0x01 || mapTable[5] != 0x00) {
return false;
}
}
break;
case 0x0D: {
uint8 chn = subChunk.ReadUint8();
uint8 pan = subChunk.ReadUint8();
uint8 type = subChunk.ReadUint8();
if(chn < subsong.channelPanning.size())
{
switch(type)
{
case 0: subsong.channelPanning[chn] = pan ^ 128;
subsong.channelSurround[chn] = false;
break;
case 2: subsong.channelPanning[chn] = 128;
subsong.channelSurround[chn] = true;
break;
case 4: subsong.channelPanning[chn] = 128;
subsong.channelSurround[chn] = false;
break;
}
if(subsongPanningDiffers == false && subsongs.size() > 0)
{
if(subsongs.back().channelPanning[chn] != subsong.channelPanning[chn]
|| subsongs.back().channelSurround[chn] != subsong.channelSurround[chn])
subsongPanningDiffers = true;
}
}
}
break;
case 0x0E: {
uint8 chn = subChunk.ReadUint8();
uint8 vol = subChunk.ReadUint8();
if(chn < subsong.channelVolume.size())
{
subsong.channelVolume[chn] = (vol / 4u) + 1;
}
}
break;
default:
return false;
}
chunkCount++;
}
}
break;
case PSMChunk::idPPAN: MPT_ASSERT(subChunkHead.GetLength() >= m_nChannels * 2u);
for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
{
if(!subChunk.CanRead(2))
break;
uint8 type = subChunk.ReadUint8();
uint8 pan = subChunk.ReadUint8();
switch(type)
{
case 0: subsong.channelPanning[chn] = pan ^ 128;
subsong.channelSurround[chn] = false;
break;
case 2: subsong.channelPanning[chn] = 128;
subsong.channelSurround[chn] = true;
break;
case 4: subsong.channelPanning[chn] = 128;
subsong.channelSurround[chn] = false;
break;
default:
break;
}
}
break;
case PSMChunk::idPATT: break;
case PSMChunk::idDSAM: break;
default:
break;
}
}
if(subsong.startOrder != ORDERINDEX_INVALID && subsong.endOrder != ORDERINDEX_INVALID)
{
Order().push_back();
subsongs.push_back(subsong);
}
}
#ifdef MPT_PSM_USE_REAL_SUBSONGS
Order.SetSequence(0);
#endif
if(subsongs.empty())
return false;
if(loadFlags & loadSampleData)
{
auto sampleChunks = chunks.GetAllChunks(PSMChunk::idDSMP);
for(auto &chunk : sampleChunks)
{
SAMPLEINDEX smp;
if(!sinariaFormat)
{
PSMSampleHeader sampleHeader;
if(!chunk.ReadStruct(sampleHeader))
{
continue;
}
smp = static_cast<SAMPLEINDEX>(sampleHeader.sampleNumber + 1);
if(smp > 0 && smp < MAX_SAMPLES)
{
m_nSamples = std::max(m_nSamples, smp);
mpt::String::Read<mpt::String::nullTerminated>(m_szNames[smp], sampleHeader.sampleName);
sampleHeader.ConvertToMPT(Samples[smp]);
}
} else
{
PSMSinariaSampleHeader sampleHeader;
if(!chunk.ReadStruct(sampleHeader))
{
continue;
}
smp = static_cast<SAMPLEINDEX>(sampleHeader.sampleNumber + 1);
if(smp > 0 && smp < MAX_SAMPLES)
{
m_nSamples = std::max(m_nSamples, smp);
mpt::String::Read<mpt::String::nullTerminated>(m_szNames[smp], sampleHeader.sampleName);
sampleHeader.ConvertToMPT(Samples[smp]);
}
}
if(smp > 0 && smp < MAX_SAMPLES)
{
SampleIO(
SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::deltaPCM).ReadSample(Samples[smp], chunk);
}
}
}
m_nDefaultSpeed = subsongs[0].defaultSpeed;
m_nDefaultTempo.Set(subsongs[0].defaultTempo);
Order().SetRestartPos(subsongs[0].restartPos);
for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
{
ChnSettings[chn].Reset();
ChnSettings[chn].nVolume = subsongs[0].channelVolume[chn];
ChnSettings[chn].nPan = subsongs[0].channelPanning[chn];
ChnSettings[chn].dwFlags.set(CHN_SURROUND, subsongs[0].channelSurround[chn]);
}
m_modFormat.formatName = sinariaFormat ? U_("Epic MegaGames MASI (New Version / Sinaria)") : U_("Epic MegaGames MASI (New Version)");
m_modFormat.type = U_("psm");
m_modFormat.charset = mpt::CharsetCP437;
if(!(loadFlags & loadPatternData) || m_nChannels == 0)
{
return true;
}
auto pattChunks = chunks.GetAllChunks(PSMChunk::idPBOD);
Patterns.ResizeArray(static_cast<PATTERNINDEX>(pattChunks.size()));
for(auto &chunk : pattChunks)
{
if(chunk.GetLength() != chunk.ReadUint32LE() || !chunk.LengthIsAtLeast(8))
{
continue;
}
PATTERNINDEX pat = ReadPSMPatternIndex(chunk, sinariaFormat);
uint16 numRows = chunk.ReadUint16LE();
if(!Patterns.Insert(pat, numRows))
{
continue;
}
enum
{
noteFlag = 0x80,
instrFlag = 0x40,
volFlag = 0x20,
effectFlag = 0x10,
};
for(ROWINDEX row = 0; row < numRows; row++)
{
PatternRow rowBase = Patterns[pat].GetRow(row);
uint16 rowSize = chunk.ReadUint16LE();
if(rowSize <= 2)
{
continue;
}
FileReader rowChunk = chunk.ReadChunk(rowSize - 2);
while(rowChunk.CanRead(3))
{
uint8 flags = rowChunk.ReadUint8();
uint8 channel = rowChunk.ReadUint8();
ModCommand &m = rowBase[std::min<CHANNELINDEX>(m_nChannels - 1, channel)];
if(flags & noteFlag)
{
uint8 note = rowChunk.ReadUint8();
if(!sinariaFormat)
{
if(note == 0xFF) note = NOTE_NOTECUT;
else
if(note < 129) note = (note & 0x0F) + 12 * (note >> 4) + 13;
} else
{
if(note < 85) note += 36;
}
m.note = note;
}
if(flags & instrFlag)
{
m.instr = rowChunk.ReadUint8() + 1;
}
if(flags & volFlag)
{
uint8 vol = rowChunk.ReadUint8();
m.volcmd = VOLCMD_VOLUME;
m.vol = (MIN(vol, 127) + 1) / 2;
}
if(flags & effectFlag)
{
m.command = rowChunk.ReadUint8();
m.param = rowChunk.ReadUint8();
switch(m.command)
{
case 0x01: m.command = CMD_VOLUMESLIDE;
if (sinariaFormat) m.param = (m.param << 4) | 0x0F;
else m.param = ((m.param & 0x1E) << 3) | 0x0F;
break;
case 0x02: m.command = CMD_VOLUMESLIDE;
if (sinariaFormat) m.param = 0xF0 & (m.param << 4);
else m.param = 0xF0 & (m.param << 3);
break;
case 0x03: m.command = CMD_VOLUMESLIDE;
if (sinariaFormat) m.param |= 0xF0;
else m.param = 0xF0 | (m.param >> 1);
break;
case 0x04: m.command = CMD_VOLUMESLIDE;
if (sinariaFormat) m.param &= 0x0F;
else if(m.param < 2) m.param |= 0xF0; else m.param = (m.param >> 1) & 0x0F;
break;
case 0x0B: m.command = CMD_PORTAMENTOUP;
m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat);
break;
case 0x0C: m.command = CMD_PORTAMENTOUP;
m.param = ConvertPSMPorta(m.param, sinariaFormat);
break;
case 0x0D: m.command = CMD_PORTAMENTODOWN;
m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat);
break;
case 0x0E: m.command = CMD_PORTAMENTODOWN;
m.param = ConvertPSMPorta(m.param, sinariaFormat);
break;
case 0x0F: m.command = CMD_TONEPORTAMENTO;
if(!sinariaFormat) m.param >>= 2;
break;
case 0x11: m.command = CMD_S3MCMDEX;
m.param = 0x10 | (m.param & 0x01);
break;
case 0x10: m.command = CMD_TONEPORTAVOL;
m.param = m.param & 0xF0;
break;
case 0x12: m.command = CMD_TONEPORTAVOL;
m.param = (m.param >> 4) & 0x0F;
break;
case 0x13: m.command = CMD_S3MCMDEX;
break;
case 0x15: m.command = CMD_VIBRATO;
break;
case 0x16: m.command = CMD_S3MCMDEX;
m.param = 0x30 | (m.param & 0x0F);
break;
case 0x17: m.command = CMD_VIBRATOVOL;
m.param = 0xF0 | m.param;
break;
case 0x18: m.command = CMD_VIBRATOVOL;
break;
case 0x1F: m.command = CMD_TREMOLO;
break;
case 0x20: m.command = CMD_S3MCMDEX;
m.param = 0x40 | (m.param & 0x0F);
break;
case 0x29: m.command = CMD_OFFSET;
m.param = rowChunk.ReadUint8();
rowChunk.Skip(1);
break;
case 0x2A: m.command = CMD_RETRIG;
break;
case 0x2B: m.command = CMD_S3MCMDEX;
m.param = 0xC0 | (m.param & 0x0F);
break;
case 0x2C: m.command = CMD_S3MCMDEX;
m.param = 0xD0 | (m.param & 0x0F);
break;
case 0x33: m.command = CMD_POSITIONJUMP;
m.param /= 2u; rowChunk.Skip(1);
break;
case 0x34: m.command = CMD_PATTERNBREAK;
m.param = 0;
break;
case 0x35: m.command = CMD_S3MCMDEX;
m.param = 0xB0 | (m.param & 0x0F);
break;
case 0x36: m.command = CMD_S3MCMDEX;
m.param = 0xE0 | (m.param & 0x0F);
break;
case 0x3D: m.command = CMD_SPEED;
break;
case 0x3E: m.command = CMD_TEMPO;
break;
case 0x47: m.command = CMD_ARPEGGIO;
break;
case 0x48: m.command = CMD_S3MCMDEX;
m.param = 0x20 | (m.param & 0x0F);
break;
case 0x49: m.command = CMD_S3MCMDEX;
m.param = 0x80 | (m.param & 0x0F);
break;
default:
m.command = CMD_NONE;
break;
}
}
}
}
}
if(subsongs.size() > 1)
{
for(size_t i = 0; i < subsongs.size(); i++)
{
#ifdef MPT_PSM_USE_REAL_SUBSONGS
ModSequence &order = Order(static_cast<SEQUENCEINDEX>(i));
#else
ModSequence &order = Order();
#endif const PSMSubSong &subsong = subsongs[i];
PATTERNINDEX startPattern = order[subsong.startOrder];
if(Patterns.IsValidPat(startPattern))
{
startPattern = order.EnsureUnique(subsong.startOrder);
if(subsongPanningDiffers)
{
for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
{
if(subsong.channelSurround[chn])
Patterns[startPattern].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x91).Row(0).Channel(chn).RetryNextRow());
else
Patterns[startPattern].WriteEffect(EffectWriter(CMD_PANNING8, subsong.channelPanning[chn]).Row(0).Channel(chn).RetryNextRow());
}
}
Patterns[startPattern].WriteEffect(EffectWriter(CMD_SPEED, subsong.defaultSpeed).Row(0).RetryNextRow());
Patterns[startPattern].WriteEffect(EffectWriter(CMD_TEMPO, subsong.defaultTempo).Row(0).RetryNextRow());
}
#ifndef MPT_PSM_USE_REAL_SUBSONGS
PATTERNINDEX endPattern = order[subsong.endOrder];
if(Patterns.IsValidPat(endPattern))
{
endPattern = order.EnsureUnique(subsong.endOrder);
ROWINDEX lastRow = Patterns[endPattern].GetNumRows() - 1;
auto m = Patterns[endPattern].cbegin();
for(uint32 cell = 0; cell < m_nChannels * Patterns[endPattern].GetNumRows(); cell++, m++)
{
if(m->command == CMD_PATTERNBREAK || m->command == CMD_POSITIONJUMP)
{
lastRow = cell / m_nChannels;
break;
}
}
Patterns[endPattern].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast<ModCommand::PARAM>(subsong.startOrder + subsong.restartPos)).Row(lastRow).RetryPreviousRow());
}
for(ORDERINDEX ord = subsong.startOrder; ord <= subsong.endOrder; ord++)
{
if(Patterns.IsValidIndex(order[ord]))
Patterns[order[ord]].SetName(subsong.songName);
}
#endif }
}
return true;
}
struct PSM16FileHeader
{
char formatID[4]; char songName[59]; uint8le lineEnd; uint8le songType; uint8le formatVersion; uint8le patternVersion; uint8le songSpeed; uint8le songTempo; uint8le masterVolume; uint16le songLength; uint16le songOrders; uint16le numPatterns; uint16le numSamples; uint16le numChannelsPlay; uint16le numChannelsReal; uint32le orderOffset; uint32le panOffset; uint32le patOffset; uint32le smpOffset; uint32le commentsOffset; uint32le patSize; char filler[40];
};
MPT_BINARY_STRUCT(PSM16FileHeader, 146)
struct PSM16SampleHeader
{
enum SampleFlags
{
smpMask = 0x7F,
smp16Bit = 0x04,
smpUnsigned = 0x08,
smpDelta = 0x10,
smpPingPong = 0x20,
smpLoop = 0x80,
};
char filename[13]; char name[24]; uint32le offset; uint32le memoffset; uint16le sampleNumber; uint8le flags; uint32le length; uint32le loopStart; uint32le loopEnd; uint8le finetune; uint8le volume; uint16le c2freq;
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mpt::String::Read<mpt::String::nullTerminated>(mptSmp.filename, filename);
mptSmp.nLength = length;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
mptSmp.nC5Speed = c2freq;
mptSmp.Transpose(((finetune ^ 0x08) - 0x78) / (12.0 * 16.0));
mptSmp.nVolume = std::min<uint8>(volume, 64u) * 4u;
mptSmp.uFlags.reset();
if(flags & PSM16SampleHeader::smp16Bit)
{
mptSmp.uFlags.set(CHN_16BIT);
mptSmp.nLength /= 2u;
}
if(flags & PSM16SampleHeader::smpPingPong)
{
mptSmp.uFlags.set(CHN_PINGPONGLOOP);
}
if(flags & PSM16SampleHeader::smpLoop)
{
mptSmp.uFlags.set(CHN_LOOP);
}
}
SampleIO GetSampleFormat() const
{
SampleIO sampleIO(
(flags & PSM16SampleHeader::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::signedPCM);
if(flags & PSM16SampleHeader::smpUnsigned)
{
sampleIO |= SampleIO::unsignedPCM;
} else if((flags & PSM16SampleHeader::smpDelta) || (flags & PSM16SampleHeader::smpMask) == 0)
{
sampleIO |= SampleIO::deltaPCM;
}
return sampleIO;
}
};
MPT_BINARY_STRUCT(PSM16SampleHeader, 64)
struct PSM16PatternHeader
{
uint16le size; uint8le numRows; uint8le numChans; };
MPT_BINARY_STRUCT(PSM16PatternHeader, 4)
static bool ValidateHeader(const PSM16FileHeader &fileHeader)
{
if(std::memcmp(fileHeader.formatID, "PSM\xFE", 4)
|| fileHeader.lineEnd != 0x1A
|| (fileHeader.formatVersion != 0x10 && fileHeader.formatVersion != 0x01) || fileHeader.patternVersion != 0 || (fileHeader.songType & 3) != 0
|| fileHeader.numChannelsPlay > MAX_BASECHANNELS
|| fileHeader.numChannelsReal > MAX_BASECHANNELS
|| std::max(fileHeader.numChannelsPlay, fileHeader.numChannelsReal) == 0)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPSM16(MemoryFileReader file, const uint64 *pfilesize)
{
PSM16FileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
PSM16FileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
InitializeGlobals(MOD_TYPE_PSM);
m_modFormat.formatName = U_("Epic MegaGames MASI (Old Version)");
m_modFormat.type = U_("psm");
m_modFormat.charset = mpt::CharsetCP437;
m_nChannels = Clamp(CHANNELINDEX(fileHeader.numChannelsPlay), CHANNELINDEX(fileHeader.numChannelsReal), MAX_BASECHANNELS);
m_nSamplePreAmp = fileHeader.masterVolume;
if(m_nSamplePreAmp == 255)
{
m_nSamplePreAmp = 48;
}
m_nDefaultSpeed = fileHeader.songSpeed;
m_nDefaultTempo.Set(fileHeader.songTempo);
mpt::String::Read<mpt::String::spacePadded>(m_songName, fileHeader.songName);
if(fileHeader.orderOffset > 4 && file.Seek(fileHeader.orderOffset - 4) && file.ReadMagic("PORD"))
{
ReadOrderFromFile<uint8>(Order(), file, fileHeader.songOrders);
}
if(fileHeader.panOffset > 4 && file.Seek(fileHeader.panOffset - 4) && file.ReadMagic("PPAN"))
{
for(CHANNELINDEX i = 0; i < 32; i++)
{
ChnSettings[i].Reset();
ChnSettings[i].nPan = ((15 - (file.ReadUint8() & 0x0F)) * 256 + 8) / 15; }
}
if(fileHeader.smpOffset > 4 && file.Seek(fileHeader.smpOffset - 4) && file.ReadMagic("PSAH"))
{
FileReader sampleChunk = file.ReadChunk(fileHeader.numSamples * sizeof(PSM16SampleHeader));
for(SAMPLEINDEX fileSample = 0; fileSample < fileHeader.numSamples; fileSample++)
{
PSM16SampleHeader sampleHeader;
if(!sampleChunk.ReadStruct(sampleHeader))
{
break;
}
SAMPLEINDEX smp = sampleHeader.sampleNumber;
if(smp > 0 && smp < MAX_SAMPLES)
{
m_nSamples = std::max(m_nSamples, smp);
sampleHeader.ConvertToMPT(Samples[smp]);
mpt::String::Read<mpt::String::nullTerminated>(m_szNames[smp], sampleHeader.name);
if(loadFlags & loadSampleData)
{
file.Seek(sampleHeader.offset);
sampleHeader.GetSampleFormat().ReadSample(Samples[smp], file);
}
}
}
}
if(!(loadFlags & loadPatternData))
{
return true;
}
if(fileHeader.patOffset > 4 && file.Seek(fileHeader.patOffset - 4) && file.ReadMagic("PPAT"))
{
Patterns.ResizeArray(fileHeader.numPatterns);
for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
{
PSM16PatternHeader patternHeader;
if(!file.ReadStruct(patternHeader))
{
break;
}
if(patternHeader.size < sizeof(PSM16PatternHeader))
{
continue;
}
FileReader patternChunk = file.ReadChunk(((patternHeader.size + 15) & ~15) - sizeof(PSM16PatternHeader));
if(!Patterns.Insert(pat, patternHeader.numRows))
{
continue;
}
enum
{
channelMask = 0x1F,
noteFlag = 0x80,
volFlag = 0x40,
effectFlag = 0x20,
};
ROWINDEX curRow = 0;
while(patternChunk.CanRead(1) && curRow < patternHeader.numRows)
{
uint8 chnFlag = patternChunk.ReadUint8();
if(chnFlag == 0)
{
curRow++;
continue;
}
ModCommand &m = *Patterns[pat].GetpModCommand(curRow, std::min<CHANNELINDEX>(chnFlag & channelMask, m_nChannels - 1));
if(chnFlag & noteFlag)
{
m.note = patternChunk.ReadUint8() + 36;
m.instr = patternChunk.ReadUint8();
}
if(chnFlag & volFlag)
{
m.volcmd = VOLCMD_VOLUME;
m.vol = std::min(patternChunk.ReadUint8(), uint8(64));
}
if(chnFlag & effectFlag)
{
m.command = patternChunk.ReadUint8();
m.param = patternChunk.ReadUint8();
switch(m.command)
{
case 0x01: m.command = CMD_VOLUMESLIDE;
m.param = (m.param << 4) | 0x0F;
break;
case 0x02: m.command = CMD_VOLUMESLIDE;
m.param = (m.param << 4) & 0xF0;
break;
case 0x03: m.command = CMD_VOLUMESLIDE;
m.param = 0xF0 | m.param;
break;
case 0x04: m.command = CMD_VOLUMESLIDE;
m.param = m.param & 0x0F;
break;
case 0x0A: m.command = CMD_PORTAMENTOUP;
m.param |= 0xF0;
break;
case 0x0B: m.command = CMD_PORTAMENTOUP;
break;
case 0x0C: m.command = CMD_PORTAMENTODOWN;
m.param |= 0xF0;
break;
case 0x0D: m.command = CMD_PORTAMENTODOWN;
break;
case 0x0E: m.command = CMD_TONEPORTAMENTO;
break;
case 0x0F: m.command = CMD_S3MCMDEX;
m.param |= 0x10;
break;
case 0x10: m.command = CMD_TONEPORTAVOL;
m.param <<= 4;
break;
case 0x11: m.command = CMD_TONEPORTAVOL;
m.param &= 0x0F;
break;
case 0x14: m.command = CMD_VIBRATO;
break;
case 0x15: m.command = CMD_S3MCMDEX;
m.param |= 0x30;
break;
case 0x16: m.command = CMD_VIBRATOVOL;
m.param <<= 4;
break;
case 0x17: m.command = CMD_VIBRATOVOL;
m.param &= 0x0F;
break;
case 0x1E: m.command = CMD_TREMOLO;
break;
case 0x1F: m.command = CMD_S3MCMDEX;
m.param |= 0x40;
break;
case 0x28: m.command = CMD_OFFSET;
m.param = patternChunk.ReadUint8();
patternChunk.Skip(1);
break;
case 0x29: m.command = CMD_RETRIG;
m.param &= 0x0F;
break;
case 0x2A: m.command = CMD_S3MCMDEX;
#ifdef MODPLUG_TRACKER
if(m.param == 0) {
if(m.note == NOTE_NONE)
{
m.note = NOTE_NOTECUT;
m.command = CMD_NONE;
} else
{
m.param = 1;
}
}
#endif m.param |= 0xC0;
break;
case 0x2B: m.command = CMD_S3MCMDEX;
m.param |= 0xD0;
break;
case 0x32: m.command = CMD_POSITIONJUMP;
break;
case 0x33: m.command = CMD_PATTERNBREAK;
break;
case 0x34: m.command = CMD_S3MCMDEX;
m.param |= 0xB0;
break;
case 0x35: m.command = CMD_S3MCMDEX;
m.param |= 0xE0;
break;
case 0x3C: m.command = CMD_SPEED;
break;
case 0x3D: m.command = CMD_TEMPO;
break;
case 0x46: m.command = CMD_ARPEGGIO;
break;
case 0x47: m.command = CMD_S3MCMDEX;
m.param = 0x20 | (m.param & 0x0F);
break;
case 0x48: m.command = CMD_S3MCMDEX;
m.param = 0x80 | (m.param & 0x0F);
break;
default:
m.command = CMD_NONE;
break;
}
}
}
if(patternHeader.numRows != 64)
{
Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(patternHeader.numRows - 1).RetryNextRow());
}
}
}
if(fileHeader.commentsOffset != 0)
{
file.Seek(fileHeader.commentsOffset);
m_songMessage.Read(file, file.ReadUint16LE(), SongMessage::leAutodetect);
}
return true;
}
OPENMPT_NAMESPACE_END