#include "stdafx.h"
#include "Loaders.h"
#include "mod_specifications.h"
OPENMPT_NAMESPACE_BEGIN
struct GDMFileHeader
{
char magic[4]; char songTitle[32]; char songMusician[32]; char dosEOF[3]; char magic2[4]; uint8le formatMajorVer; uint8le formatMinorVer; uint16le trackerID; uint8le trackerMajorVer; uint8le trackerMinorVer; uint8le panMap[32]; uint8le masterVol; uint8le tempo; uint8le bpm; uint16le originalFormat;
uint32le orderOffset;
uint8le lastOrder; uint32le patternOffset;
uint8le lastPattern; uint32le sampleHeaderOffset;
uint32le sampleDataOffset;
uint8le lastSample; uint32le messageTextOffset; uint32le messageTextLength;
uint32le scrollyScriptOffset; uint16le scrollyScriptLength;
uint32le textGraphicOffset; uint16le textGraphicLength;
};
MPT_BINARY_STRUCT(GDMFileHeader, 157)
struct GDMSampleHeader
{
enum SampleFlags
{
smpLoop = 0x01,
smp16Bit = 0x02, smpVolume = 0x04,
smpPanning = 0x08,
smpLZW = 0x10, smpStereo = 0x20, };
char name[32]; char fileName[12]; uint8le emsHandle; uint32le length; uint32le loopBegin; uint32le loopEnd; uint8le flags; uint16le c4Hertz; uint8le volume; uint8le panning; };
MPT_BINARY_STRUCT(GDMSampleHeader, 62)
static const MODTYPE gdmFormatOrigin[] =
{
MOD_TYPE_NONE, MOD_TYPE_MOD, MOD_TYPE_MTM, MOD_TYPE_S3M, MOD_TYPE_669, MOD_TYPE_FAR, MOD_TYPE_ULT, MOD_TYPE_STM, MOD_TYPE_MED, MOD_TYPE_PSM
};
static const MPT_UCHAR_TYPE gdmFormatOriginType[][4] =
{
UL_(""), UL_("mod"), UL_("mtm"), UL_("s3m"), UL_("669"), UL_("far"), UL_("ult"), UL_("stm"), UL_("med"), UL_("psm")
};
static const MPT_UCHAR_TYPE * const gdmFormatOriginFormat[] =
{
UL_(""),
UL_("Generic MOD"),
UL_("MultiTracker"),
UL_("ScreamTracker 3"),
UL_("Composer 669 / UNIS 669"),
UL_("Farandole Composer"),
UL_("UltraTracker"),
UL_("ScreamTracker 2"),
UL_("OctaMED"),
UL_("Epic Megagames MASI")
};
static bool ValidateHeader(const GDMFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.magic, "GDM\xFE", 4)
|| fileHeader.dosEOF[0] != 13 || fileHeader.dosEOF[1] != 10 || fileHeader.dosEOF[2] != 26
|| std::memcmp(fileHeader.magic2, "GMFS", 4)
|| fileHeader.formatMajorVer != 1 || fileHeader.formatMinorVer != 0
|| fileHeader.originalFormat >= mpt::size(gdmFormatOrigin)
|| fileHeader.originalFormat == 0)
{
return false;
}
return true;
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderGDM(MemoryFileReader file, const uint64 *pfilesize)
{
GDMFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return ProbeWantMoreData;
}
if(!ValidateHeader(fileHeader))
{
return ProbeFailure;
}
MPT_UNREFERENCED_PARAMETER(pfilesize);
return ProbeSuccess;
}
bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
GDMFileHeader fileHeader;
if(!file.ReadStruct(fileHeader))
{
return false;
}
if(!ValidateHeader(fileHeader))
{
return false;
}
if(loadFlags == onlyVerifyHeader)
{
return true;
}
InitializeGlobals(gdmFormatOrigin[fileHeader.originalFormat]);
m_modFormat.formatName = U_("General Digital Music");
m_modFormat.type = U_("gdm");
m_modFormat.madeWithTracker = mpt::format(U_("BWSB 2GDM %1.%2"))(fileHeader.trackerMajorVer, fileHeader.formatMinorVer);
m_modFormat.originalType = gdmFormatOriginType[fileHeader.originalFormat];
m_modFormat.originalFormatName = gdmFormatOriginFormat[fileHeader.originalFormat];
m_modFormat.charset = mpt::CharsetCP437;
mpt::String::Read<mpt::String::maybeNullTerminated>(m_songName, fileHeader.songTitle);
{
std::string artist;
mpt::String::Read<mpt::String::maybeNullTerminated>(artist, fileHeader.songMusician);
if(artist != "Unknown")
{
m_songArtist = mpt::ToUnicode(mpt::CharsetCP437, artist);
}
}
m_nChannels = 32;
for(CHANNELINDEX i = 0; i < 32; i++)
{
ChnSettings[i].Reset();
if(fileHeader.panMap[i] < 16)
{
ChnSettings[i].nPan = static_cast<uint16>(std::min((fileHeader.panMap[i] * 16) + 8, 256));
} else if(fileHeader.panMap[i] == 16)
{
ChnSettings[i].nPan = 128;
ChnSettings[i].dwFlags = CHN_SURROUND;
} else if(fileHeader.panMap[i] == 0xFF)
{
m_nChannels = i;
break;
}
}
if(m_nChannels < 1)
{
return false;
}
m_nDefaultGlobalVolume = std::min(fileHeader.masterVol * 4u, 256u);
m_nDefaultSpeed = fileHeader.tempo;
m_nDefaultTempo.Set(fileHeader.bpm);
if(file.Seek(fileHeader.orderOffset))
{
ReadOrderFromFile<uint8>(Order(), file, fileHeader.lastOrder + 1, 0xFF, 0xFE);
}
if(!file.Seek(fileHeader.sampleHeaderOffset))
{
return false;
}
m_nSamples = fileHeader.lastSample + 1;
for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
{
GDMSampleHeader gdmSample;
if(!file.ReadStruct(gdmSample))
{
break;
}
ModSample &sample = Samples[smp];
sample.Initialize();
mpt::String::Read<mpt::String::maybeNullTerminated>(m_szNames[smp], gdmSample.name);
mpt::String::Read<mpt::String::maybeNullTerminated>(sample.filename, gdmSample.fileName);
sample.nC5Speed = gdmSample.c4Hertz;
sample.nGlobalVol = 64;
sample.nLength = gdmSample.length;
if(gdmSample.flags & GDMSampleHeader::smp16Bit)
{
sample.uFlags.set(CHN_16BIT);
sample.nLength /= 2;
}
sample.nLoopStart = gdmSample.loopBegin;
sample.nLoopEnd = gdmSample.loopEnd - 1;
if(UseFinetuneAndTranspose())
{
static const uint16 rate2finetune[] = { 8363, 8424, 8485, 8547, 8608, 8671, 8734, 8797, 7894, 7951, 8009, 8067, 8125, 8184, 8244, 8303 };
for(uint8 i = 0; i < 16; i++)
{
if(sample.nC5Speed == rate2finetune[i])
{
sample.nFineTune = MOD2XMFineTune(i);
}
}
}
if(gdmSample.flags & GDMSampleHeader::smpLoop) sample.uFlags.set(CHN_LOOP);
if(gdmSample.flags & GDMSampleHeader::smpVolume)
{
sample.nVolume = std::min<uint8>(gdmSample.volume, 64) * 4;
} else
{
sample.uFlags.set(SMP_NODEFAULTVOLUME);
}
if(gdmSample.flags & GDMSampleHeader::smpPanning)
{
sample.uFlags.set(CHN_PANNING);
sample.nPan = static_cast<uint16>((gdmSample.panning > 15) ? 128 : std::min((gdmSample.panning * 16) + 8, 256));
sample.uFlags.set(CHN_SURROUND, gdmSample.panning == 16);
} else
{
sample.nPan = 128;
}
}
if((loadFlags & loadSampleData) && file.Seek(fileHeader.sampleDataOffset))
{
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
SampleIO(
Samples[smp].uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
SampleIO::mono,
SampleIO::littleEndian,
SampleIO::unsignedPCM)
.ReadSample(Samples[smp], file);
}
}
Patterns.ResizeArray(fileHeader.lastPattern + 1);
const CModSpecifications &modSpecs = GetModSpecifications(GetBestSaveFormat());
bool onlyAmigaNotes = true;
file.Seek(fileHeader.patternOffset);
for(PATTERNINDEX pat = 0; pat <= fileHeader.lastPattern; pat++)
{
uint16 patternLength = file.ReadUint16LE();
if(patternLength <= 2)
{
continue;
}
FileReader chunk = file.ReadChunk(patternLength - 2);
if(!(loadFlags & loadPatternData) || !chunk.IsValid() || !Patterns.Insert(pat, 64))
{
continue;
}
enum
{
rowDone = 0, channelMask = 0x1F, noteFlag = 0x20, effectFlag = 0x40, effectMask = 0x1F, effectDone = 0x20, };
for(ROWINDEX row = 0; row < 64; row++)
{
PatternRow rowBase = Patterns[pat].GetRow(row);
uint8 channelByte;
while((channelByte = chunk.ReadUint8()) != rowDone)
{
CHANNELINDEX channel = channelByte & channelMask;
if(channel >= m_nChannels) break;
ModCommand &m = rowBase[channel];
if(channelByte & noteFlag)
{
uint8 noteByte = chunk.ReadUint8();
uint8 noteSample = chunk.ReadUint8();
if(noteByte)
{
noteByte = (noteByte & 0x7F) - 1; if(noteByte < 0xF0) noteByte = (noteByte & 0x0F) + 12 * (noteByte >> 4) + 12 + NOTE_MIN;
m.note = noteByte;
if(!m.IsAmigaNote())
{
onlyAmigaNotes = false;
}
}
m.instr = noteSample;
}
if(channelByte & effectFlag)
{
m.command = CMD_NONE;
m.volcmd = VOLCMD_NONE;
while(chunk.CanRead(2))
{
const ModCommand oldCmd = m;
uint8 effByte = chunk.ReadUint8();
m.param = chunk.ReadUint8();
static const EffectCommand gdmEffTrans[] =
{
CMD_NONE, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_TONEPORTAMENTO,
CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL, CMD_TREMOLO,
CMD_TREMOR, CMD_OFFSET, CMD_VOLUMESLIDE, CMD_POSITIONJUMP,
CMD_VOLUME, CMD_PATTERNBREAK, CMD_MODCMDEX, CMD_SPEED,
CMD_ARPEGGIO, CMD_NONE , CMD_RETRIG, CMD_GLOBALVOLUME,
CMD_FINEVIBRATO, CMD_NONE, CMD_NONE, CMD_NONE,
CMD_NONE, CMD_NONE, CMD_NONE, CMD_NONE,
CMD_NONE, CMD_NONE, CMD_S3MCMDEX, CMD_TEMPO,
};
uint8 command = effByte & effectMask;
if(command < CountOf(gdmEffTrans))
m.command = gdmEffTrans[command];
else
m.command = CMD_NONE;
switch(m.command)
{
case CMD_PORTAMENTOUP:
if(m.param >= 0xE0)
m.param = 0xDF;
break;
case CMD_PORTAMENTODOWN:
if(m.param >= 0xE0)
m.param = 0xDF;
break;
case CMD_TONEPORTAVOL:
if(m.param & 0xF0)
m.param &= 0xF0;
break;
case CMD_VIBRATOVOL:
if(m.param & 0xF0)
m.param &= 0xF0;
break;
case CMD_VOLUME:
m.param = MIN(m.param, 64);
if(modSpecs.HasVolCommand(VOLCMD_VOLUME))
{
m.volcmd = VOLCMD_VOLUME;
m.vol = m.param;
m.command = oldCmd.command;
m.param = oldCmd.param;
}
break;
case CMD_MODCMDEX:
if(!modSpecs.HasCommand(CMD_MODCMDEX))
{
m.ExtendedMODtoS3MEffect();
}
break;
case CMD_RETRIG:
if(!modSpecs.HasCommand(CMD_RETRIG) && modSpecs.HasCommand(CMD_MODCMDEX))
{
m.command = CMD_MODCMDEX;
m.param = 0x90 | (m.param & 0x0F);
}
break;
case CMD_S3MCMDEX:
switch(m.param >> 4)
{
case 0x0:
switch(m.param & 0x0F)
{
case 0x0: case 0x1: m.param += 0x90;
break;
case 0x2: case 0x3: m.command = CMD_NONE;
break;
case 0x4: m.command = CMD_S3MCMDEX;
m.param = 0x9E;
break;
case 0x5: m.command = CMD_S3MCMDEX;
m.param = 0x9F;
break;
case 0x6: case 0x7: case 0x8: case 0x9: default:
m.command = CMD_NONE;
break;
}
break;
case 0x8: if(!modSpecs.HasCommand(CMD_S3MCMDEX))
{
m.command = CMD_MODCMDEX;
}
break;
case 0xD: default:
m.command = CMD_NONE;
break;
}
break;
case 0x1F:
m.command = CMD_TEMPO;
break;
}
if(m.command == CMD_S3MCMDEX && ((m.param >> 4) == 0x8) && m.volcmd == VOLCMD_NONE)
{
m.volcmd = VOLCMD_PANNING;
m.vol = ((m.param & 0x0F) * 64 + 8) / 15;
m.command = oldCmd.command;
m.param = oldCmd.param;
}
if(!(effByte & effectDone)) break; }
}
}
}
}
m_SongFlags.set(SONG_AMIGALIMITS | SONG_ISAMIGA, GetType() == MOD_TYPE_MOD && GetNumChannels() == 4 && onlyAmigaNotes);
if(fileHeader.messageTextLength > 0 && file.Seek(fileHeader.messageTextOffset))
{
m_songMessage.Read(file, fileHeader.messageTextLength, SongMessage::leAutodetect);
}
return true;
}
OPENMPT_NAMESPACE_END