123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- /*
- * Load_amf.cpp
- * ------------
- * Purpose: AMF module loader
- * Notes : There are two types of AMF files, the ASYLUM Music Format (used in Crusader: No Remorse and Crusader: No Regret)
- * and Advanced Music Format (DSMI / Digital Sound And Music Interface, used in various games such as Pinball World).
- * Both module types are handled here.
- * Authors: Olivier Lapicque
- * OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Loaders.h"
- #include <algorithm>
- OPENMPT_NAMESPACE_BEGIN
- // ASYLUM AMF File Header
- struct AsylumFileHeader
- {
- char signature[32];
- uint8 defaultSpeed;
- uint8 defaultTempo;
- uint8 numSamples;
- uint8 numPatterns;
- uint8 numOrders;
- uint8 restartPos;
- };
- MPT_BINARY_STRUCT(AsylumFileHeader, 38)
- // ASYLUM AMF Sample Header
- struct AsylumSampleHeader
- {
- char name[22];
- uint8le finetune;
- uint8le defaultVolume;
- int8le transpose;
- uint32le length;
- uint32le loopStart;
- uint32le loopLength;
- // Convert an AMF sample header to OpenMPT's internal sample header.
- void ConvertToMPT(ModSample &mptSmp) const
- {
- mptSmp.Initialize();
- mptSmp.nFineTune = MOD2XMFineTune(finetune);
- mptSmp.nVolume = std::min(defaultVolume.get(), uint8(64)) * 4u;
- mptSmp.RelativeTone = transpose;
- mptSmp.nLength = length;
- if(loopLength > 2 && loopStart + loopLength <= length)
- {
- mptSmp.uFlags.set(CHN_LOOP);
- mptSmp.nLoopStart = loopStart;
- mptSmp.nLoopEnd = loopStart + loopLength;
- }
- }
- };
- MPT_BINARY_STRUCT(AsylumSampleHeader, 37)
- static bool ValidateHeader(const AsylumFileHeader &fileHeader)
- {
- if(std::memcmp(fileHeader.signature, "ASYLUM Music Format V1.0\0", 25)
- || fileHeader.numSamples > 64
- )
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const AsylumFileHeader &fileHeader)
- {
- return 256 + 64 * sizeof(AsylumSampleHeader) + 64 * 4 * 8 * fileHeader.numPatterns;
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderAMF_Asylum(MemoryFileReader file, const uint64 *pfilesize)
- {
- AsylumFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- AsylumFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(!ValidateHeader(fileHeader))
- {
- return false;
- }
- if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
- {
- return false;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
- InitializeGlobals(MOD_TYPE_AMF0);
- InitializeChannels();
- SetupMODPanning(true);
- m_nChannels = 8;
- m_nDefaultSpeed = fileHeader.defaultSpeed;
- m_nDefaultTempo.Set(fileHeader.defaultTempo);
- m_nSamples = fileHeader.numSamples;
- if(fileHeader.restartPos < fileHeader.numOrders)
- {
- Order().SetRestartPos(fileHeader.restartPos);
- }
- m_modFormat.formatName = U_("ASYLUM Music Format");
- m_modFormat.type = U_("amf");
- m_modFormat.charset = mpt::Charset::CP437;
- uint8 orders[256];
- file.ReadArray(orders);
- ReadOrderFromArray(Order(), orders, fileHeader.numOrders);
- // Read Sample Headers
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- AsylumSampleHeader sampleHeader;
- file.ReadStruct(sampleHeader);
- sampleHeader.ConvertToMPT(Samples[smp]);
- m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name);
- }
- file.Skip((64 - fileHeader.numSamples) * sizeof(AsylumSampleHeader));
- // Read Patterns
- Patterns.ResizeArray(fileHeader.numPatterns);
- for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
- {
- if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
- {
- file.Skip(64 * 4 * 8);
- continue;
- }
- for(auto &m : Patterns[pat])
- {
- const auto [note, instr, command, param] = file.ReadArray<uint8, 4>();
- if(note && note + 12 + NOTE_MIN <= NOTE_MAX)
- {
- m.note = note + 12 + NOTE_MIN;
- }
- m.instr = instr;
- m.command = command;
- m.param = param;
- ConvertModCommand(m);
- #ifdef MODPLUG_TRACKER
- if(m.command == CMD_PANNING8)
- {
- // Convert 7-bit panning to 8-bit
- m.param = mpt::saturate_cast<ModCommand::PARAM>(m.param * 2u);
- }
- #endif
- }
- }
- if(loadFlags & loadSampleData)
- {
- // Read Sample Data
- const SampleIO sampleIO(
- SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::signedPCM);
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- sampleIO.ReadSample(Samples[smp], file);
- }
- }
- return true;
- }
- // DSMI AMF File Header
- struct AMFFileHeader
- {
- char amf[3];
- uint8le version;
- char title[32];
- uint8le numSamples;
- uint8le numOrders;
- uint16le numTracks;
- uint8le numChannels;
- };
- MPT_BINARY_STRUCT(AMFFileHeader, 41)
- // DSMI AMF Sample Header (v1-v9)
- struct AMFSampleHeaderOld
- {
- uint8le type;
- char name[32];
- char filename[13];
- uint32le index;
- uint16le length;
- uint16le sampleRate;
- uint8le volume;
- uint16le loopStart;
- uint16le loopEnd;
- void ConvertToMPT(ModSample &mptSmp) const
- {
- mptSmp.Initialize();
- mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
- mptSmp.nLength = length;
- mptSmp.nC5Speed = sampleRate;
- mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
- mptSmp.nLoopStart = loopStart;
- mptSmp.nLoopEnd = loopEnd;
- if(mptSmp.nLoopEnd == uint16_max)
- mptSmp.nLoopStart = mptSmp.nLoopEnd = 0;
- else if(type != 0 && mptSmp.nLoopEnd > mptSmp.nLoopStart + 2 && mptSmp.nLoopEnd <= mptSmp.nLength)
- mptSmp.uFlags.set(CHN_LOOP);
- }
- };
- MPT_BINARY_STRUCT(AMFSampleHeaderOld, 59)
- // DSMI AMF Sample Header (v10+)
- struct AMFSampleHeaderNew
- {
- uint8le type;
- char name[32];
- char filename[13];
- uint32le index;
- uint32le length;
- uint16le sampleRate;
- uint8le volume;
- uint32le loopStart;
- uint32le loopEnd;
- void ConvertToMPT(ModSample &mptSmp, bool truncated) const
- {
- mptSmp.Initialize();
- mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
- mptSmp.nLength = length;
- mptSmp.nC5Speed = sampleRate;
- mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
- mptSmp.nLoopStart = loopStart;
- mptSmp.nLoopEnd = loopEnd;
- if(truncated && mptSmp.nLoopStart > 0)
- mptSmp.nLoopEnd = mptSmp.nLength;
- if(type != 0 && mptSmp.nLoopEnd > mptSmp.nLoopStart + 2 && mptSmp.nLoopEnd <= mptSmp.nLength)
- mptSmp.uFlags.set(CHN_LOOP);
- }
- // Check if sample headers might be truncated
- bool IsValid(uint8 numSamples) const
- {
- return type <= 1 && index <= numSamples && length <= 0x100000 && volume <= 64 && loopStart <= length && loopEnd <= length;
- }
- };
- MPT_BINARY_STRUCT(AMFSampleHeaderNew, 65)
- // Read a single AMF track (channel) into a pattern.
- static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &fileChunk)
- {
- fileChunk.Rewind();
- while(fileChunk.CanRead(3))
- {
- const auto [row, command, value] = fileChunk.ReadArray<uint8, 3>();
- if(row >= pattern.GetNumRows())
- {
- break;
- }
- ModCommand &m = *pattern.GetpModCommand(row, chn);
- if(command < 0x7F)
- {
- // Note + Volume
- if(command == 0 && value == 0)
- {
- m.note = NOTE_NOTECUT;
- } else
- {
- m.note = command + NOTE_MIN;
- if(value != 0xFF)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = value;
- }
- }
- } else if(command == 0x7F)
- {
- // Instrument without note retrigger in MOD (no need to do anything here, should be preceded by 0x80 command)
- } else if(command == 0x80)
- {
- // Instrument
- m.instr = value + 1;
- } else
- {
- // Effect
- static constexpr ModCommand::COMMAND effTrans[] =
- {
- CMD_NONE, CMD_SPEED, CMD_VOLUMESLIDE, CMD_VOLUME,
- CMD_PORTAMENTOUP, CMD_NONE, CMD_TONEPORTAMENTO, CMD_TREMOR,
- CMD_ARPEGGIO, CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL,
- CMD_PATTERNBREAK, CMD_POSITIONJUMP, CMD_NONE, CMD_RETRIG,
- CMD_OFFSET, CMD_VOLUMESLIDE, CMD_PORTAMENTOUP, CMD_S3MCMDEX,
- CMD_S3MCMDEX, CMD_TEMPO, CMD_PORTAMENTOUP, CMD_PANNING8,
- };
- uint8 cmd = (command & 0x7F);
- uint8 param = value;
- if(cmd < std::size(effTrans))
- cmd = effTrans[cmd];
- else
- cmd = CMD_NONE;
- // Fix some commands...
- switch(command & 0x7F)
- {
- // 02: Volume Slide
- // 0A: Tone Porta + Vol Slide
- // 0B: Vibrato + Vol Slide
- case 0x02:
- case 0x0A:
- case 0x0B:
- if(param & 0x80)
- param = (-static_cast<int8>(param)) & 0x0F;
- else
- param = (param & 0x0F) << 4;
- break;
- // 03: Volume
- case 0x03:
- param = std::min(param, uint8(64));
- if(m.volcmd == VOLCMD_NONE || m.volcmd == VOLCMD_VOLUME)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = param;
- cmd = CMD_NONE;
- }
- break;
- // 04: Porta Up/Down
- case 0x04:
- if(param & 0x80)
- param = (-static_cast<int8>(param)) & 0x7F;
- else
- cmd = CMD_PORTAMENTODOWN;
- break;
- // 11: Fine Volume Slide
- case 0x11:
- if(param)
- {
- if(param & 0x80)
- param = 0xF0 | ((-static_cast<int8>(param)) & 0x0F);
- else
- param = 0x0F | ((param & 0x0F) << 4);
- } else
- {
- cmd = CMD_NONE;
- }
- break;
- // 12: Fine Portamento
- // 16: Extra Fine Portamento
- case 0x12:
- case 0x16:
- if(param)
- {
- cmd = static_cast<uint8>((param & 0x80) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN);
- if(param & 0x80)
- {
- param = ((-static_cast<int8>(param)) & 0x0F);
- }
- param |= (command == 0x16) ? 0xE0 : 0xF0;
- } else
- {
- cmd = CMD_NONE;
- }
- break;
- // 13: Note Delay
- case 0x13:
- param = 0xD0 | (param & 0x0F);
- break;
- // 14: Note Cut
- case 0x14:
- param = 0xC0 | (param & 0x0F);
- break;
- // 17: Panning
- case 0x17:
- if(param == 100)
- {
- // History lesson intermission: According to Otto Chrons, he remembers that he added support
- // for 8A4 / XA4 "surround" panning in DMP for MOD and S3M files before any other trackers did,
- // So DSMI / DMP are most likely the original source of these 7-bit panning + surround commands!
- param = 0xA4;
- } else
- {
- param = static_cast<uint8>(std::clamp(static_cast<int8>(param) + 64, 0, 128));
- if(m.command != CMD_NONE)
- {
- // Move to volume column if required
- if(m.volcmd == VOLCMD_NONE || m.volcmd == VOLCMD_PANNING)
- {
- m.volcmd = VOLCMD_PANNING;
- m.vol = param / 2;
- }
- cmd = CMD_NONE;
- }
- }
- break;
- }
- if(cmd != CMD_NONE)
- {
- m.command = cmd;
- m.param = param;
- }
- }
- }
- }
- static bool ValidateHeader(const AMFFileHeader &fileHeader)
- {
- if(std::memcmp(fileHeader.amf, "AMF", 3)
- || (fileHeader.version < 8 && fileHeader.version != 1) || fileHeader.version > 14
- || ((fileHeader.numChannels < 1 || fileHeader.numChannels > 32) && fileHeader.version >= 9))
- {
- return false;
- }
- return true;
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderAMF_DSMI(MemoryFileReader file, const uint64 *pfilesize)
- {
- AMFFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- MPT_UNREFERENCED_PARAMETER(pfilesize);
- return ProbeSuccess;
- }
- bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- AMFFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(!ValidateHeader(fileHeader))
- {
- return false;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
- InitializeGlobals(MOD_TYPE_AMF);
- InitializeChannels();
- m_modFormat.formatName = MPT_UFORMAT("DSMI v{}")(fileHeader.version);
- m_modFormat.type = U_("amf");
- m_modFormat.charset = mpt::Charset::CP437;
- m_nChannels = fileHeader.numChannels;
- m_nSamples = fileHeader.numSamples;
- m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.title);
- if(fileHeader.version < 9)
- {
- // Old format revisions are fixed to 4 channels
- m_nChannels = 4;
- file.SkipBack(1);
- SetupMODPanning(true);
- }
- // Setup Channel Pan Positions
- if(fileHeader.version >= 11)
- {
- const CHANNELINDEX readChannels = fileHeader.version >= 12 ? 32 : 16;
- for(CHANNELINDEX chn = 0; chn < readChannels; chn++)
- {
- int8 pan = file.ReadInt8();
- if(pan == 100)
- ChnSettings[chn].dwFlags = CHN_SURROUND;
- else
- ChnSettings[chn].nPan = static_cast<uint16>(std::clamp((pan + 64) * 2, 0, 256));
- }
- } else if(fileHeader.version >= 9)
- {
- uint8 panPos[16];
- file.ReadArray(panPos);
- for(CHANNELINDEX chn = 0; chn < 16; chn++)
- {
- ChnSettings[chn].nPan = (panPos[chn] & 1) ? 0x40 : 0xC0;
- }
- }
- // Get Tempo/Speed
- if(fileHeader.version >= 13)
- {
- auto [tempo, speed] = file.ReadArray<uint8, 2>();
- if(tempo < 32)
- tempo = 125;
- m_nDefaultTempo.Set(tempo);
- m_nDefaultSpeed = speed;
- } else
- {
- m_nDefaultTempo.Set(125);
- m_nDefaultSpeed = 6;
- }
- // Setup Order List
- Order().resize(fileHeader.numOrders);
- std::vector<uint16> patternLength;
- const FileReader::off_t trackStartPos = file.GetPosition() + (fileHeader.version >= 14 ? 2 : 0);
- if(fileHeader.version >= 14)
- {
- patternLength.resize(fileHeader.numOrders);
- }
- for(ORDERINDEX ord = 0; ord < fileHeader.numOrders; ord++)
- {
- Order()[ord] = ord;
- if(fileHeader.version >= 14)
- {
- patternLength[ord] = file.ReadUint16LE();
- }
- // Track positions will be read as needed.
- file.Skip(m_nChannels * 2);
- }
- // Read Sample Headers
- bool truncatedSampleHeaders = false;
- if(fileHeader.version == 10)
- {
- // M2AMF 1.3 included with DMP 2.32 wrote new (v10+) sample headers, but using the old struct length.
- const auto startPos = file.GetPosition();
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- AMFSampleHeaderNew sample;
- if(file.ReadStruct(sample) && !sample.IsValid(fileHeader.numSamples))
- {
- truncatedSampleHeaders = true;
- break;
- }
- }
- file.Seek(startPos);
- }
- std::vector<uint32> sampleMap(GetNumSamples(), 0);
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- if(fileHeader.version < 10)
- {
- AMFSampleHeaderOld sample;
- file.ReadStruct(sample);
- sample.ConvertToMPT(Samples[smp]);
- m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sample.name);
- sampleMap[smp - 1] = sample.index;
- } else
- {
- AMFSampleHeaderNew sample;
- file.ReadStructPartial(sample, truncatedSampleHeaders ? sizeof(AMFSampleHeaderOld) : sizeof(AMFSampleHeaderNew));
- sample.ConvertToMPT(Samples[smp], truncatedSampleHeaders);
- m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sample.name);
- sampleMap[smp - 1] = sample.index;
- }
- }
-
- // Read Track Mapping Table
- std::vector<uint16le> trackMap;
- if(!file.ReadVector(trackMap, fileHeader.numTracks))
- {
- return false;
- }
- uint16 trackCount = 0;
- if(!trackMap.empty())
- trackCount = *std::max_element(trackMap.cbegin(), trackMap.cend());
- // Read pattern tracks
- std::vector<FileReader> trackData(trackCount);
- for(uint16 i = 0; i < trackCount; i++)
- {
- // Track size is a 16-Bit value describing the number of byte triplets in this track, followed by a track type byte.
- uint16 numEvents = file.ReadUint16LE();
- file.Skip(1);
- if(numEvents)
- trackData[i] = file.ReadChunk(numEvents * 3 + (fileHeader.version == 1 ? 3 : 0));
- }
- if(loadFlags & loadSampleData)
- {
- // Read Sample Data
- const SampleIO sampleIO(
- SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::unsignedPCM);
- // Note: in theory a sample can be reused by several instruments and appear in a different order in the file
- // However, M2AMF doesn't take advantage of this and just writes instruments in the order they appear,
- // without de-duplicating identical sample data.
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples() && file.CanRead(1); smp++)
- {
- auto startPos = file.GetPosition();
- for(SAMPLEINDEX target = 0; target < GetNumSamples(); target++)
- {
- if(sampleMap[target] != smp)
- continue;
- file.Seek(startPos);
- sampleIO.ReadSample(Samples[target + 1], file);
- }
- }
- }
- if(!(loadFlags & loadPatternData))
- {
- return true;
- }
- // Create the patterns from the list of tracks
- Patterns.ResizeArray(fileHeader.numOrders);
- for(PATTERNINDEX pat = 0; pat < fileHeader.numOrders; pat++)
- {
- uint16 patLength = pat < patternLength.size() ? patternLength[pat] : 64;
- if(!Patterns.Insert(pat, patLength))
- {
- continue;
- }
- // Get table with per-channel track assignments
- file.Seek(trackStartPos + pat * (GetNumChannels() * 2 + (fileHeader.version >= 14 ? 2 : 0)));
- std::vector<uint16le> tracks;
- if(!file.ReadVector(tracks, GetNumChannels()))
- {
- continue;
- }
- for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
- {
- if(tracks[chn] > 0 && tracks[chn] <= fileHeader.numTracks)
- {
- uint16 realTrack = trackMap[tracks[chn] - 1];
- if(realTrack > 0 && realTrack <= trackCount)
- {
- realTrack--;
- AMFReadPattern(Patterns[pat], chn, trackData[realTrack]);
- }
- }
- }
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|