Bundle libOpenMPT as a dynamic framework, which should be safe once again, now that there is only one version to bundle. Also, now it is using the versions of libvorbisfile and libmpg123 that are bundled with the player, instead of compiling minimp3 and stbvorbis. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
432 lines
12 KiB
C++
432 lines
12 KiB
C++
/*
|
|
* Load_itp.cpp
|
|
* ------------
|
|
* Purpose: Impulse Tracker Project (ITP) module loader
|
|
* Notes : Despite its name, ITP is not a format supported by Impulse Tracker.
|
|
* In fact, it's a format invented by the OpenMPT team to allow people to work
|
|
* with the IT format, but keeping the instrument files with big samples separate
|
|
* from the pattern data, to keep the work files small and handy.
|
|
* The design of the format is quite flawed, though, so it was superseded by
|
|
* extra functionality in the MPTM format in OpenMPT 1.24.
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "../common/version.h"
|
|
#include "Loaders.h"
|
|
#include "ITTools.h"
|
|
#ifdef MODPLUG_TRACKER
|
|
// For loading external instruments
|
|
#include "../mptrack/Moddoc.h"
|
|
#endif // MODPLUG_TRACKER
|
|
#ifdef MPT_EXTERNAL_SAMPLES
|
|
#include "../common/mptFileIO.h"
|
|
#endif // MPT_EXTERNAL_SAMPLES
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
// Version changelog:
|
|
// v1.03: - Relative unicode instrument paths instead of absolute ANSI paths
|
|
// - Per-path variable string length
|
|
// - Embedded samples are IT-compressed
|
|
// (rev. 3249)
|
|
// v1.02: Explicitly updated format to use new instrument flags representation (rev. 483)
|
|
// v1.01: Added option to embed instrument headers
|
|
|
|
|
|
struct ITPModCommand
|
|
{
|
|
uint8 note;
|
|
uint8 instr;
|
|
uint8 volcmd;
|
|
uint8 command;
|
|
uint8 vol;
|
|
uint8 param;
|
|
|
|
operator ModCommand() const
|
|
{
|
|
static constexpr VolumeCommand ITPVolCmds[] =
|
|
{
|
|
VOLCMD_NONE, VOLCMD_VOLUME, VOLCMD_PANNING, VOLCMD_VOLSLIDEUP,
|
|
VOLCMD_VOLSLIDEDOWN, VOLCMD_FINEVOLUP, VOLCMD_FINEVOLDOWN, VOLCMD_VIBRATOSPEED,
|
|
VOLCMD_VIBRATODEPTH, VOLCMD_PANSLIDELEFT, VOLCMD_PANSLIDERIGHT, VOLCMD_TONEPORTAMENTO,
|
|
VOLCMD_PORTAUP, VOLCMD_PORTADOWN, VOLCMD_PLAYCONTROL, VOLCMD_OFFSET,
|
|
};
|
|
static constexpr EffectCommand ITPCommands[] =
|
|
{
|
|
CMD_NONE, CMD_ARPEGGIO, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN,
|
|
CMD_TONEPORTAMENTO, CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL,
|
|
CMD_TREMOLO, CMD_PANNING8, CMD_OFFSET, CMD_VOLUMESLIDE,
|
|
CMD_POSITIONJUMP, CMD_VOLUME, CMD_PATTERNBREAK, CMD_RETRIG,
|
|
CMD_SPEED, CMD_TEMPO, CMD_TREMOR, CMD_MODCMDEX,
|
|
CMD_S3MCMDEX, CMD_CHANNELVOLUME, CMD_CHANNELVOLSLIDE, CMD_GLOBALVOLUME,
|
|
CMD_GLOBALVOLSLIDE, CMD_KEYOFF, CMD_FINEVIBRATO, CMD_PANBRELLO,
|
|
CMD_XFINEPORTAUPDOWN, CMD_PANNINGSLIDE, CMD_SETENVPOSITION, CMD_MIDI,
|
|
CMD_SMOOTHMIDI, CMD_DELAYCUT, CMD_XPARAM,
|
|
};
|
|
ModCommand result;
|
|
result.note = (ModCommand::IsNote(note) || ModCommand::IsSpecialNote(note)) ? static_cast<ModCommand::NOTE>(note) : static_cast<ModCommand::NOTE>(NOTE_NONE);
|
|
result.instr = instr;
|
|
result.volcmd = (volcmd < std::size(ITPVolCmds)) ? ITPVolCmds[volcmd] : VOLCMD_NONE;
|
|
result.command = (command < std::size(ITPCommands)) ? ITPCommands[command] : CMD_NONE;
|
|
result.vol = vol;
|
|
result.param = param;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(ITPModCommand, 6)
|
|
|
|
|
|
struct ITPHeader
|
|
{
|
|
uint32le magic;
|
|
uint32le version;
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(ITPHeader, 8)
|
|
|
|
|
|
static bool ValidateHeader(const ITPHeader &hdr)
|
|
{
|
|
if(hdr.magic != MagicBE(".itp"))
|
|
{
|
|
return false;
|
|
}
|
|
if(hdr.version < 0x00000100 || hdr.version > 0x00000103)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static uint64 GetHeaderMinimumAdditionalSize(const ITPHeader &hdr)
|
|
{
|
|
return 76 + (hdr.version <= 0x102 ? 4 : 0);
|
|
}
|
|
|
|
|
|
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderITP(MemoryFileReader file, const uint64 *pfilesize)
|
|
{
|
|
ITPHeader hdr;
|
|
if(!file.ReadStruct(hdr))
|
|
{
|
|
return ProbeWantMoreData;
|
|
}
|
|
if(!ValidateHeader(hdr))
|
|
{
|
|
return ProbeFailure;
|
|
}
|
|
return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(hdr));
|
|
}
|
|
|
|
|
|
bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags)
|
|
{
|
|
#if !defined(MPT_EXTERNAL_SAMPLES) && !defined(MPT_FUZZ_TRACKER)
|
|
// Doesn't really make sense to support this format when there's no support for external files...
|
|
MPT_UNREFERENCED_PARAMETER(file);
|
|
MPT_UNREFERENCED_PARAMETER(loadFlags);
|
|
return false;
|
|
#else // !MPT_EXTERNAL_SAMPLES && !MPT_FUZZ_TRACKER
|
|
|
|
enum ITPSongFlags
|
|
{
|
|
ITP_EMBEDMIDICFG = 0x00001, // Embed macros in file
|
|
ITP_ITOLDEFFECTS = 0x00004, // Old Impulse Tracker effect implementations
|
|
ITP_ITCOMPATGXX = 0x00008, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects)
|
|
ITP_LINEARSLIDES = 0x00010, // Linear slides vs. Amiga slides
|
|
ITP_EXFILTERRANGE = 0x08000, // Cutoff Filter has double frequency range (up to ~10Khz)
|
|
ITP_ITPROJECT = 0x20000, // Is a project file
|
|
ITP_ITPEMBEDIH = 0x40000, // Embed instrument headers in project file
|
|
};
|
|
|
|
file.Rewind();
|
|
|
|
ITPHeader hdr;
|
|
if(!file.ReadStruct(hdr))
|
|
{
|
|
return false;
|
|
}
|
|
if(!ValidateHeader(hdr))
|
|
{
|
|
return false;
|
|
}
|
|
if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(hdr))))
|
|
{
|
|
return false;
|
|
}
|
|
if(loadFlags == onlyVerifyHeader)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const uint32 version = hdr.version;
|
|
|
|
InitializeGlobals(MOD_TYPE_IT);
|
|
m_playBehaviour.reset();
|
|
file.ReadSizedString<uint32le, mpt::String::maybeNullTerminated>(m_songName);
|
|
|
|
// Song comments
|
|
m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR);
|
|
|
|
// Song global config
|
|
const uint32 songFlags = file.ReadUint32LE();
|
|
if(!(songFlags & ITP_ITPROJECT))
|
|
{
|
|
return false;
|
|
}
|
|
m_SongFlags.set(SONG_IMPORTED);
|
|
if(songFlags & ITP_ITOLDEFFECTS)
|
|
m_SongFlags.set(SONG_ITOLDEFFECTS);
|
|
if(songFlags & ITP_ITCOMPATGXX)
|
|
m_SongFlags.set(SONG_ITCOMPATGXX);
|
|
if(songFlags & ITP_LINEARSLIDES)
|
|
m_SongFlags.set(SONG_LINEARSLIDES);
|
|
if(songFlags & ITP_EXFILTERRANGE)
|
|
m_SongFlags.set(SONG_EXFILTERRANGE);
|
|
|
|
m_nDefaultGlobalVolume = file.ReadUint32LE();
|
|
m_nSamplePreAmp = file.ReadUint32LE();
|
|
m_nDefaultSpeed = std::max(uint32(1), file.ReadUint32LE());
|
|
m_nDefaultTempo.Set(std::max(uint32(32), file.ReadUint32LE()));
|
|
m_nChannels = static_cast<CHANNELINDEX>(file.ReadUint32LE());
|
|
if(m_nChannels == 0 || m_nChannels > MAX_BASECHANNELS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// channel name string length (=MAX_CHANNELNAME)
|
|
uint32 size = file.ReadUint32LE();
|
|
|
|
// Channels' data
|
|
for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
|
|
{
|
|
ChnSettings[chn].nPan = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(256));
|
|
ChnSettings[chn].dwFlags.reset();
|
|
uint32 flags = file.ReadUint32LE();
|
|
if(flags & 0x100) ChnSettings[chn].dwFlags.set(CHN_MUTE);
|
|
if(flags & 0x800) ChnSettings[chn].dwFlags.set(CHN_SURROUND);
|
|
ChnSettings[chn].nVolume = std::min(static_cast<uint16>(file.ReadUint32LE()), uint16(64));
|
|
file.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, size);
|
|
}
|
|
|
|
// Song mix plugins
|
|
{
|
|
FileReader plugChunk = file.ReadChunk(file.ReadUint32LE());
|
|
LoadMixPlugins(plugChunk);
|
|
}
|
|
|
|
// MIDI Macro config
|
|
file.ReadStructPartial<MIDIMacroConfigData>(m_MidiCfg, file.ReadUint32LE());
|
|
m_MidiCfg.Sanitize();
|
|
|
|
// Song Instruments
|
|
m_nInstruments = static_cast<INSTRUMENTINDEX>(file.ReadUint32LE());
|
|
if(m_nInstruments >= MAX_INSTRUMENTS)
|
|
{
|
|
m_nInstruments = 0;
|
|
return false;
|
|
}
|
|
|
|
// Instruments' paths
|
|
if(version <= 0x102)
|
|
{
|
|
size = file.ReadUint32LE(); // path string length
|
|
}
|
|
|
|
std::vector<mpt::PathString> instrPaths(GetNumInstruments());
|
|
for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
|
|
{
|
|
if(version > 0x102)
|
|
{
|
|
size = file.ReadUint32LE(); // path string length
|
|
}
|
|
std::string path;
|
|
file.ReadString<mpt::String::maybeNullTerminated>(path, size);
|
|
#ifdef MODPLUG_TRACKER
|
|
if(version <= 0x102)
|
|
{
|
|
instrPaths[ins] = mpt::PathString::FromLocaleSilent(path);
|
|
} else
|
|
#endif // MODPLUG_TRACKER
|
|
{
|
|
instrPaths[ins] = mpt::PathString::FromUTF8(path);
|
|
}
|
|
#ifdef MODPLUG_TRACKER
|
|
if(const auto fileName = file.GetOptionalFileName(); fileName.has_value())
|
|
{
|
|
instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(fileName->GetPath());
|
|
} else if(GetpModDoc() != nullptr)
|
|
{
|
|
instrPaths[ins] = instrPaths[ins].RelativePathToAbsolute(GetpModDoc()->GetPathNameMpt().GetPath());
|
|
}
|
|
#endif // MODPLUG_TRACKER
|
|
}
|
|
|
|
// Song Orders
|
|
size = file.ReadUint32LE();
|
|
ReadOrderFromFile<uint8>(Order(), file, size, 0xFF, 0xFE);
|
|
|
|
// Song Patterns
|
|
const PATTERNINDEX numPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
|
|
const PATTERNINDEX numNamedPats = static_cast<PATTERNINDEX>(file.ReadUint32LE());
|
|
size_t patNameLen = file.ReadUint32LE(); // Size of each pattern name
|
|
FileReader pattNames = file.ReadChunk(numNamedPats * patNameLen);
|
|
|
|
// modcommand data length
|
|
size = file.ReadUint32LE();
|
|
if(size != sizeof(ITPModCommand))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(loadFlags & loadPatternData)
|
|
Patterns.ResizeArray(numPats);
|
|
for(PATTERNINDEX pat = 0; pat < numPats; pat++)
|
|
{
|
|
const ROWINDEX numRows = file.ReadUint32LE();
|
|
FileReader patternChunk = file.ReadChunk(numRows * size * GetNumChannels());
|
|
|
|
// Allocate pattern
|
|
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows))
|
|
{
|
|
pattNames.Skip(patNameLen);
|
|
continue;
|
|
}
|
|
|
|
if(pat < numNamedPats)
|
|
{
|
|
char patName[32];
|
|
if(pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen))
|
|
Patterns[pat].SetName(patName);
|
|
}
|
|
|
|
// Pattern data
|
|
size_t numCommands = GetNumChannels() * numRows;
|
|
|
|
if(patternChunk.CanRead(sizeof(ITPModCommand) * numCommands))
|
|
{
|
|
ModCommand *target = Patterns[pat].GetpModCommand(0, 0);
|
|
while(numCommands-- != 0)
|
|
{
|
|
ITPModCommand data;
|
|
patternChunk.ReadStruct(data);
|
|
*(target++) = data;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load embedded samples
|
|
|
|
// Read original number of samples
|
|
m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint32LE());
|
|
LimitMax(m_nSamples, SAMPLEINDEX(MAX_SAMPLES - 1));
|
|
|
|
// Read number of embedded samples - at most as many as there are real samples in a valid file
|
|
uint32 embeddedSamples = file.ReadUint32LE();
|
|
if(embeddedSamples > m_nSamples)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Read samples
|
|
for(uint32 smp = 0; smp < embeddedSamples && file.CanRead(8 + sizeof(ITSample)); smp++)
|
|
{
|
|
uint32 realSample = file.ReadUint32LE();
|
|
ITSample sampleHeader;
|
|
file.ReadStruct(sampleHeader);
|
|
FileReader sampleData = file.ReadChunk(file.ReadUint32LE());
|
|
|
|
if((loadFlags & loadSampleData)
|
|
&& realSample >= 1 && realSample <= GetNumSamples()
|
|
&& Samples[realSample].pData.pSample == nullptr
|
|
&& !memcmp(sampleHeader.id, "IMPS", 4))
|
|
{
|
|
sampleHeader.ConvertToMPT(Samples[realSample]);
|
|
m_szNames[realSample] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name);
|
|
|
|
// Read sample data
|
|
sampleHeader.GetSampleFormat().ReadSample(Samples[realSample], sampleData);
|
|
}
|
|
}
|
|
|
|
// Load instruments
|
|
for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
|
|
{
|
|
if(instrPaths[ins].empty())
|
|
continue;
|
|
|
|
#ifdef MPT_EXTERNAL_SAMPLES
|
|
InputFile f(instrPaths[ins], SettingCacheCompleteFileBeforeLoading());
|
|
FileReader instrFile = GetFileReader(f);
|
|
if(!ReadInstrumentFromFile(ins + 1, instrFile, true))
|
|
{
|
|
AddToLog(LogWarning, U_("Unable to open instrument: ") + instrPaths[ins].ToUnicode());
|
|
}
|
|
#else
|
|
AddToLog(LogWarning, MPT_UFORMAT("Loading external instrument {} ('{}') failed: External instruments are not supported.")(ins + 1, instrPaths[ins].ToUnicode()));
|
|
#endif // MPT_EXTERNAL_SAMPLES
|
|
}
|
|
|
|
// Extra info data
|
|
uint32 code = file.ReadUint32LE();
|
|
|
|
// Embed instruments' header [v1.01]
|
|
if(version >= 0x101 && (songFlags & ITP_ITPEMBEDIH) && code == MagicBE("EBIH"))
|
|
{
|
|
code = file.ReadUint32LE();
|
|
|
|
INSTRUMENTINDEX ins = 1;
|
|
while(ins <= GetNumInstruments() && file.CanRead(4))
|
|
{
|
|
if(code == MagicBE("MPTS"))
|
|
{
|
|
break;
|
|
} else if(code == MagicBE("SEP@") || code == MagicBE("MPTX"))
|
|
{
|
|
// jump code - switch to next instrument
|
|
ins++;
|
|
} else
|
|
{
|
|
ReadExtendedInstrumentProperty(Instruments[ins], code, file);
|
|
}
|
|
|
|
code = file.ReadUint32LE();
|
|
}
|
|
}
|
|
|
|
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
|
|
{
|
|
Samples[smp].SetDefaultCuePoints();
|
|
}
|
|
|
|
// Song extensions
|
|
if(code == MagicBE("MPTS"))
|
|
{
|
|
file.SkipBack(4);
|
|
LoadExtendedSongProperties(file, true);
|
|
}
|
|
|
|
m_nMaxPeriod = 0xF000;
|
|
m_nMinPeriod = 8;
|
|
|
|
// Before OpenMPT 1.20.01.09, the MIDI macros were always read from the file, even if the "embed" flag was not set.
|
|
if(m_dwLastSavedWithVersion >= MPT_V("1.20.01.09") && !(songFlags & ITP_EMBEDMIDICFG))
|
|
{
|
|
m_MidiCfg.Reset();
|
|
}
|
|
|
|
m_modFormat.formatName = U_("Impulse Tracker Project");
|
|
m_modFormat.type = U_("itp");
|
|
m_modFormat.madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion);
|
|
m_modFormat.charset = mpt::Charset::Windows1252;
|
|
|
|
return true;
|
|
#endif // MPT_EXTERNAL_SAMPLES
|
|
}
|
|
|
|
OPENMPT_NAMESPACE_END
|