Cog/Frameworks/OpenMPT/OpenMPT/soundlib/Load_itp.cpp
Christopher Snowhill da1973bcd9 Build libOpenMPT from source once again
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>
2022-06-30 22:56:52 -07:00

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