123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- /*
- * 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
|