From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- .../openmpt-trunk/soundlib/ITTools.cpp | 685 +++++++++++++++++++++ 1 file changed, 685 insertions(+) create mode 100644 Src/external_dependencies/openmpt-trunk/soundlib/ITTools.cpp (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/ITTools.cpp') diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/ITTools.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/ITTools.cpp new file mode 100644 index 00000000..e72184f9 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/ITTools.cpp @@ -0,0 +1,685 @@ +/* + * ITTools.cpp + * ----------- + * Purpose: Definition of IT file structures and helper functions + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "ITTools.h" +#include "Tables.h" +#include "../common/mptStringBuffer.h" +#include "../common/version.h" + + +OPENMPT_NAMESPACE_BEGIN + + +// Convert OpenMPT's internal envelope format into an IT/MPTM envelope. +void ITEnvelope::ConvertToIT(const InstrumentEnvelope &mptEnv, uint8 envOffset, uint8 envDefault) +{ + // Envelope Flags + if(mptEnv.dwFlags[ENV_ENABLED]) flags |= ITEnvelope::envEnabled; + if(mptEnv.dwFlags[ENV_LOOP]) flags |= ITEnvelope::envLoop; + if(mptEnv.dwFlags[ENV_SUSTAIN]) flags |= ITEnvelope::envSustain; + if(mptEnv.dwFlags[ENV_CARRY]) flags |= ITEnvelope::envCarry; + + // Nodes and Loops + num = (uint8)std::min(mptEnv.size(), uint32(25)); + lpb = (uint8)mptEnv.nLoopStart; + lpe = (uint8)mptEnv.nLoopEnd; + slb = (uint8)mptEnv.nSustainStart; + sle = (uint8)mptEnv.nSustainEnd; + + // Envelope Data + MemsetZero(data); + if(!mptEnv.empty()) + { + // Attention: Full MPTM envelope is stored in extended instrument properties + for(uint32 ev = 0; ev < num; ev++) + { + data[ev].value = static_cast(mptEnv[ev].value) - envOffset; + data[ev].tick = mptEnv[ev].tick; + } + } else + { + // Fix non-existing envelopes so that they can still be edited in Impulse Tracker. + num = 2; + data[0].value = data[1].value = envDefault - envOffset; + data[1].tick = 10; + } +} + + +// Convert IT/MPTM envelope data into OpenMPT's internal envelope format - To be used by ITInstrToMPT() +void ITEnvelope::ConvertToMPT(InstrumentEnvelope &mptEnv, uint8 envOffset, uint8 maxNodes) const +{ + // Envelope Flags + mptEnv.dwFlags.set(ENV_ENABLED, (flags & ITEnvelope::envEnabled) != 0); + mptEnv.dwFlags.set(ENV_LOOP, (flags & ITEnvelope::envLoop) != 0); + mptEnv.dwFlags.set(ENV_SUSTAIN, (flags & ITEnvelope::envSustain) != 0); + mptEnv.dwFlags.set(ENV_CARRY, (flags & ITEnvelope::envCarry) != 0); + + // Nodes and Loops + mptEnv.resize(std::min(num, maxNodes)); + mptEnv.nLoopStart = std::min(lpb, maxNodes); + mptEnv.nLoopEnd = Clamp(lpe, mptEnv.nLoopStart, maxNodes); + mptEnv.nSustainStart = std::min(slb, maxNodes); + mptEnv.nSustainEnd = Clamp(sle, mptEnv.nSustainStart, maxNodes); + + // Envelope Data + // Attention: Full MPTM envelope is stored in extended instrument properties + for(uint32 ev = 0; ev < std::min(uint8(25), num); ev++) + { + mptEnv[ev].value = Clamp(data[ev].value + envOffset, 0, 64); + mptEnv[ev].tick = data[ev].tick; + if(ev > 0 && mptEnv[ev].tick < mptEnv[ev - 1].tick && !(mptEnv[ev].tick & 0xFF00)) + { + // Fix broken envelopes... Instruments 2 and 3 in NoGap.it by Werewolf have envelope points where the high byte of envelope nodes is missing. + // NoGap.it was saved with MPT 1.07 - 1.09, which *normally* doesn't do this in IT files. + // However... It turns out that MPT 1.07 omitted the high byte of envelope nodes when saving an XI instrument file, and it looks like + // Instrument 2 and 3 in NoGap.it were loaded from XI files. + mptEnv[ev].tick |= mptEnv[ev - 1].tick & 0xFF00; + if(mptEnv[ev].tick < mptEnv[ev - 1].tick) + mptEnv[ev].tick += 0x100; + } + } +} + + +// Convert an ITOldInstrument to OpenMPT's internal instrument representation. +void ITOldInstrument::ConvertToMPT(ModInstrument &mptIns) const +{ + // Header + if(memcmp(id, "IMPI", 4)) + { + return; + } + + mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name); + mptIns.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename); + + // Volume / Panning + mptIns.nFadeOut = fadeout << 6; + mptIns.nGlobalVol = 64; + mptIns.nPan = 128; + + // NNA Stuff + mptIns.nNNA = static_cast(nna.get()); + mptIns.nDCT = static_cast(dnc.get()); + + // Sample Map + for(size_t i = 0; i < 120; i++) + { + uint8 note = keyboard[i * 2]; + SAMPLEINDEX ins = keyboard[i * 2 + 1]; + if(ins < MAX_SAMPLES) + { + mptIns.Keyboard[i] = ins; + } + if(note < 120) + { + mptIns.NoteMap[i] = note + 1u; + } else + { + mptIns.NoteMap[i] = static_cast(i + 1); + } + } + + // Volume Envelope Flags + mptIns.VolEnv.dwFlags.set(ENV_ENABLED, (flags & ITOldInstrument::envEnabled) != 0); + mptIns.VolEnv.dwFlags.set(ENV_LOOP, (flags & ITOldInstrument::envLoop) != 0); + mptIns.VolEnv.dwFlags.set(ENV_SUSTAIN, (flags & ITOldInstrument::envSustain) != 0); + + // Volume Envelope Loops + mptIns.VolEnv.nLoopStart = vls; + mptIns.VolEnv.nLoopEnd = vle; + mptIns.VolEnv.nSustainStart = sls; + mptIns.VolEnv.nSustainEnd = sle; + mptIns.VolEnv.resize(25); + + // Volume Envelope Data + for(uint32 i = 0; i < 25; i++) + { + if((mptIns.VolEnv[i].tick = nodes[i * 2]) == 0xFF) + { + mptIns.VolEnv.resize(i); + break; + } + mptIns.VolEnv[i].value = nodes[i * 2 + 1]; + } + + if(std::max(mptIns.VolEnv.nLoopStart, mptIns.VolEnv.nLoopEnd) >= mptIns.VolEnv.size()) mptIns.VolEnv.dwFlags.reset(ENV_LOOP); + if(std::max(mptIns.VolEnv.nSustainStart, mptIns.VolEnv.nSustainEnd) >= mptIns.VolEnv.size()) mptIns.VolEnv.dwFlags.reset(ENV_SUSTAIN); +} + + +// Convert OpenMPT's internal instrument representation to an ITInstrument. +uint32 ITInstrument::ConvertToIT(const ModInstrument &mptIns, bool compatExport, const CSoundFile &sndFile) +{ + MemsetZero(*this); + + // Header + memcpy(id, "IMPI", 4); + trkvers = 0x5000 | static_cast(Version::Current().GetRawVersion() >> 16); + + mpt::String::WriteBuf(mpt::String::nullTerminated, filename) = mptIns.filename; + mpt::String::WriteBuf(mpt::String::nullTerminated, name) = mptIns.name; + + // Volume / Panning + fadeout = static_cast(std::min(mptIns.nFadeOut >> 5, uint32(256))); + gbv = static_cast(std::min(mptIns.nGlobalVol * 2u, uint32(128))); + dfp = static_cast(std::min(mptIns.nPan / 4u, uint32(64))); + if(!mptIns.dwFlags[INS_SETPANNING]) dfp |= ITInstrument::ignorePanning; + + // Random Variation + rv = std::min(mptIns.nVolSwing, uint8(100)); + rp = std::min(mptIns.nPanSwing, uint8(64)); + + // NNA Stuff + nna = static_cast(mptIns.nNNA); + dct = static_cast((mptIns.nDCT < DuplicateCheckType::Plugin || !compatExport) ? mptIns.nDCT : DuplicateCheckType::None); + dca = static_cast(mptIns.nDNA); + + // Pitch / Pan Separation + pps = mptIns.nPPS; + ppc = mptIns.nPPC; + + // Filter Stuff + ifc = mptIns.GetCutoff() | (mptIns.IsCutoffEnabled() ? ITInstrument::enableCutoff : 0x00); + ifr = mptIns.GetResonance() | (mptIns.IsResonanceEnabled() ? ITInstrument::enableResonance : 0x00); + + // MIDI Setup + if(mptIns.nMidiProgram > 0) + mpr = mptIns.nMidiProgram - 1u; + else + mpr = 0xFF; + if(mptIns.wMidiBank > 0) + { + mbank[0] = static_cast((mptIns.wMidiBank - 1) & 0x7F); + mbank[1] = static_cast((mptIns.wMidiBank - 1) >> 7); + } else + { + mbank[0] = 0xFF; + mbank[1] = 0xFF; + } + if(mptIns.nMidiChannel != MidiNoChannel || mptIns.nMixPlug == 0 || mptIns.nMixPlug > 127 || compatExport) + { + // Default. Prefer MIDI channel over mixplug to keep the semantics intact. + mch = mptIns.nMidiChannel; + } else + { + // Keep compatibility with MPT 1.16's instrument format if possible, as XMPlay / BASS also uses this. + mch = mptIns.nMixPlug + 128; + } + + // Sample Map + nos = 0; // Only really relevant for ITI files + std::vector smpCount(sndFile.GetNumSamples(), false); + for(int i = 0; i < 120; i++) + { + keyboard[i * 2] = (mptIns.NoteMap[i] >= NOTE_MIN && mptIns.NoteMap[i] <= NOTE_MAX) ? (mptIns.NoteMap[i] - NOTE_MIN) : static_cast(i); + + const SAMPLEINDEX smp = mptIns.Keyboard[i]; + if(smp < MAX_SAMPLES && smp < 256) + { + keyboard[i * 2 + 1] = static_cast(smp); + + if(smp && smp <= sndFile.GetNumSamples() && !smpCount[smp - 1]) + { + // We haven't considered this sample yet. Update number of samples. + smpCount[smp - 1] = true; + nos++; + } + } + } + + // Writing Volume envelope + volenv.ConvertToIT(mptIns.VolEnv, 0, 64); + // Writing Panning envelope + panenv.ConvertToIT(mptIns.PanEnv, 32, 32); + // Writing Pitch Envelope + pitchenv.ConvertToIT(mptIns.PitchEnv, 32, 32); + if(mptIns.PitchEnv.dwFlags[ENV_FILTER]) pitchenv.flags |= ITEnvelope::envFilter; + + return sizeof(ITInstrument); +} + + +// Convert an ITInstrument to OpenMPT's internal instrument representation. Returns size of the instrument data that has been read. +uint32 ITInstrument::ConvertToMPT(ModInstrument &mptIns, MODTYPE modFormat) const +{ + if(memcmp(id, "IMPI", 4)) + { + return 0; + } + + mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name); + mptIns.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename); + + // Volume / Panning + mptIns.nFadeOut = fadeout << 5; + mptIns.nGlobalVol = gbv / 2; + LimitMax(mptIns.nGlobalVol, 64u); + mptIns.nPan = (dfp & 0x7F) * 4; + if(mptIns.nPan > 256) mptIns.nPan = 128; + mptIns.dwFlags.set(INS_SETPANNING, !(dfp & ITInstrument::ignorePanning)); + + // Random Variation + mptIns.nVolSwing = std::min(static_cast(rv), uint8(100)); + mptIns.nPanSwing = std::min(static_cast(rp), uint8(64)); + + // NNA Stuff + mptIns.nNNA = static_cast(nna.get()); + mptIns.nDCT = static_cast(dct.get()); + mptIns.nDNA = static_cast(dca.get()); + + // Pitch / Pan Separation + mptIns.nPPS = pps; + mptIns.nPPC = ppc; + + // Filter Stuff + mptIns.SetCutoff(ifc & 0x7F, (ifc & ITInstrument::enableCutoff) != 0); + mptIns.SetResonance(ifr & 0x7F, (ifr & ITInstrument::enableResonance) != 0); + + // MIDI Setup + + // MPT used to have a slightly different encoding of MIDI program and banks which we are trying to fix here. + // Impulse Tracker / Schism Tracker will set trkvers to 0 in IT files, + // and we won't care about correctly importing MIDI programs and banks in ITI files. + // Chibi Tracker sets trkvers to 0x214, but always writes mpr=mbank=0 anyway. + // Old BeRoTracker versions set trkvers to 0x214 or 0x217. + // <= MPT 1.07 <= MPT 1.16 OpenMPT 1.17-? <= OpenMPT 1.26 definitely not MPT + if((trkvers == 0x0202 || trkvers == 0x0211 || trkvers == 0x0220 || trkvers == 0x0214) && mpr != 0xFF) + { + if(mpr <= 128) + { + mptIns.nMidiProgram = mpr; + } + uint16 bank = mbank[0] | (mbank[1] << 8); + // These versions also ignored the high bank nibble (was only handled correctly in OpenMPT instrument extensions) + if(bank <= 128) + { + mptIns.wMidiBank = bank; + } + } else + { + if(mpr < 128) + { + mptIns.nMidiProgram = mpr + 1; + } + uint16 bank = 0; + if(mbank[0] < 128) + bank = mbank[0] + 1; + if(mbank[1] < 128) + bank += (mbank[1] << 7); + mptIns.wMidiBank = bank; + } + mptIns.nMidiChannel = mch; + if(mptIns.nMidiChannel >= 128) + { + // Handle old format where MIDI channel and Plugin index are stored in the same variable + mptIns.nMixPlug = mptIns.nMidiChannel - 128; + mptIns.nMidiChannel = 0; + } + + // Envelope point count. Limited to 25 in IT format. + const uint8 maxNodes = (modFormat & MOD_TYPE_MPT) ? MAX_ENVPOINTS : 25; + + // Volume Envelope + volenv.ConvertToMPT(mptIns.VolEnv, 0, maxNodes); + // Panning Envelope + panenv.ConvertToMPT(mptIns.PanEnv, 32, maxNodes); + // Pitch Envelope + pitchenv.ConvertToMPT(mptIns.PitchEnv, 32, maxNodes); + mptIns.PitchEnv.dwFlags.set(ENV_FILTER, (pitchenv.flags & ITEnvelope::envFilter) != 0); + + // Sample Map + for(int i = 0; i < 120; i++) + { + uint8 note = keyboard[i * 2]; + SAMPLEINDEX ins = keyboard[i * 2 + 1]; + if(ins < MAX_SAMPLES) + { + mptIns.Keyboard[i] = ins; + } + if(note < 120) + { + mptIns.NoteMap[i] = note + NOTE_MIN; + } else + { + mptIns.NoteMap[i] = static_cast(i + NOTE_MIN); + } + } + + return sizeof(ITInstrument); +} + + +// Convert OpenMPT's internal instrument representation to an ITInstrumentEx. Returns amount of bytes that need to be written to file. +uint32 ITInstrumentEx::ConvertToIT(const ModInstrument &mptIns, bool compatExport, const CSoundFile &sndFile) +{ + uint32 instSize = iti.ConvertToIT(mptIns, compatExport, sndFile); + + if(compatExport) + { + return instSize; + } + + // Sample Map + bool usedExtension = false; + iti.nos = 0; + std::vector smpCount(sndFile.GetNumSamples(), false); + for(int i = 0; i < 120; i++) + { + const SAMPLEINDEX smp = mptIns.Keyboard[i]; + keyboardhi[i] = 0; + if(smp < MAX_SAMPLES) + { + if(smp >= 256) + { + // We need to save the upper byte for this sample index. + iti.keyboard[i * 2 + 1] = static_cast(smp & 0xFF); + keyboardhi[i] = static_cast(smp >> 8); + usedExtension = true; + } + + if(smp && smp <= sndFile.GetNumSamples() && !smpCount[smp - 1]) + { + // We haven't considered this sample yet. Update number of samples. + smpCount[smp - 1] = true; + iti.nos++; + } + } + } + + if(usedExtension) + { + // If we actually had to extend the sample map, update the magic bytes and instrument size. + memcpy(iti.dummy, "XTPM", 4); + instSize = sizeof(ITInstrumentEx); + } + + return instSize; +} + + +// Convert an ITInstrumentEx to OpenMPT's internal instrument representation. Returns size of the instrument data that has been read. +uint32 ITInstrumentEx::ConvertToMPT(ModInstrument &mptIns, MODTYPE fromType) const +{ + uint32 insSize = iti.ConvertToMPT(mptIns, fromType); + + // Is this actually an extended instrument? + // Note: OpenMPT 1.20 - 1.22 accidentally wrote "MPTX" here (since revision 1203), while previous versions wrote the reversed version, "XTPM". + if(insSize == 0 || (memcmp(iti.dummy, "MPTX", 4) && memcmp(iti.dummy, "XTPM", 4))) + { + return insSize; + } + + // Olivier's MPT Instrument Extension + for(int i = 0; i < 120; i++) + { + mptIns.Keyboard[i] |= ((SAMPLEINDEX)keyboardhi[i] << 8); + } + + return sizeof(ITInstrumentEx); +} + + +// Convert OpenMPT's internal sample representation to an ITSample. +void ITSample::ConvertToIT(const ModSample &mptSmp, MODTYPE fromType, bool compress, bool compressIT215, bool allowExternal) +{ + MemsetZero(*this); + + // Header + memcpy(id, "IMPS", 4); + + mpt::String::WriteBuf(mpt::String::nullTerminated, filename) = mptSmp.filename; + //mpt::String::WriteBuf(mpt::String::nullTerminated, name) = m_szNames[nsmp]; + + // Volume / Panning + gvl = static_cast(mptSmp.nGlobalVol); + vol = static_cast(mptSmp.nVolume / 4); + dfp = static_cast(mptSmp.nPan / 4); + if(mptSmp.uFlags[CHN_PANNING]) dfp |= ITSample::enablePanning; + + // Sample Format / Loop Flags + if(mptSmp.HasSampleData() && !mptSmp.uFlags[CHN_ADLIB]) + { + flags = ITSample::sampleDataPresent; + if(mptSmp.uFlags[CHN_LOOP]) flags |= ITSample::sampleLoop; + if(mptSmp.uFlags[CHN_SUSTAINLOOP]) flags |= ITSample::sampleSustain; + if(mptSmp.uFlags[CHN_PINGPONGLOOP]) flags |= ITSample::sampleBidiLoop; + if(mptSmp.uFlags[CHN_PINGPONGSUSTAIN]) flags |= ITSample::sampleBidiSustain; + + if(mptSmp.uFlags[CHN_STEREO]) + { + flags |= ITSample::sampleStereo; + } + if(mptSmp.uFlags[CHN_16BIT]) + { + flags |= ITSample::sample16Bit; + } + cvt = ITSample::cvtSignedSample; + + if(compress) + { + flags |= ITSample::sampleCompressed; + if(compressIT215) + { + cvt |= ITSample::cvtDelta; + } + } + } else + { + flags = 0x00; + } + + // Frequency + C5Speed = mptSmp.nC5Speed ? mptSmp.nC5Speed : 8363; + + // Size and loops + length = mpt::saturate_cast(mptSmp.nLength); + loopbegin = mpt::saturate_cast(mptSmp.nLoopStart); + loopend = mpt::saturate_cast(mptSmp.nLoopEnd); + susloopbegin = mpt::saturate_cast(mptSmp.nSustainStart); + susloopend = mpt::saturate_cast(mptSmp.nSustainEnd); + + // Auto Vibrato settings + vit = AutoVibratoXM2IT[mptSmp.nVibType & 7]; + vis = std::min(mptSmp.nVibRate, uint8(64)); + vid = std::min(mptSmp.nVibDepth, uint8(32)); + vir = std::min(mptSmp.nVibSweep, uint8(255)); + + if((vid | vis) != 0 && (fromType & MOD_TYPE_XM)) + { + // Sweep is upside down in XM + if(mptSmp.nVibSweep != 0) + vir = mpt::saturate_cast(Util::muldivr_unsigned(mptSmp.nVibDepth, 256, mptSmp.nVibSweep)); + else + vir = 255; + } + + if(mptSmp.uFlags[CHN_ADLIB]) + { + length = 12; + flags = ITSample::sampleDataPresent; + cvt = ITSample::cvtOPLInstrument; + } else if(mptSmp.uFlags[SMP_KEEPONDISK]) + { +#ifndef MPT_EXTERNAL_SAMPLES + allowExternal = false; +#endif // MPT_EXTERNAL_SAMPLES + // Save external sample (filename at sample pointer) + if(allowExternal && mptSmp.HasSampleData()) + { + cvt = ITSample::cvtExternalSample; + } else + { + length = loopbegin = loopend = susloopbegin = susloopend = 0; + } + } +} + + +// Convert an ITSample to OpenMPT's internal sample representation. +uint32 ITSample::ConvertToMPT(ModSample &mptSmp) const +{ + if(memcmp(id, "IMPS", 4)) + { + return 0; + } + + mptSmp.Initialize(MOD_TYPE_IT); + mptSmp.SetDefaultCuePoints(); // For old IT/MPTM files + mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename); + + // Volume / Panning + mptSmp.nVolume = vol * 4; + LimitMax(mptSmp.nVolume, uint16(256)); + mptSmp.nGlobalVol = gvl; + LimitMax(mptSmp.nGlobalVol, uint16(64)); + mptSmp.nPan = (dfp & 0x7F) * 4; + LimitMax(mptSmp.nPan, uint16(256)); + if(dfp & ITSample::enablePanning) mptSmp.uFlags.set(CHN_PANNING); + + // Loop Flags + if(flags & ITSample::sampleLoop) mptSmp.uFlags.set(CHN_LOOP); + if(flags & ITSample::sampleSustain) mptSmp.uFlags.set(CHN_SUSTAINLOOP); + if(flags & ITSample::sampleBidiLoop) mptSmp.uFlags.set(CHN_PINGPONGLOOP); + if(flags & ITSample::sampleBidiSustain) mptSmp.uFlags.set(CHN_PINGPONGSUSTAIN); + + // Frequency + mptSmp.nC5Speed = C5Speed; + if(!mptSmp.nC5Speed) mptSmp.nC5Speed = 8363; + if(mptSmp.nC5Speed < 256) mptSmp.nC5Speed = 256; + + // Size and loops + mptSmp.nLength = length; + mptSmp.nLoopStart = loopbegin; + mptSmp.nLoopEnd = loopend; + mptSmp.nSustainStart = susloopbegin; + mptSmp.nSustainEnd = susloopend; + mptSmp.SanitizeLoops(); + + // Auto Vibrato settings + mptSmp.nVibType = static_cast(AutoVibratoIT2XM[vit & 7]); + mptSmp.nVibRate = vis; + mptSmp.nVibDepth = vid & 0x7F; + mptSmp.nVibSweep = vir; + + if(cvt == ITSample::cvtOPLInstrument) + { + // FM instrument in MPTM + mptSmp.uFlags.set(CHN_ADLIB); + } else if(cvt == ITSample::cvtExternalSample) + { + // Read external sample (filename at sample pointer) + mptSmp.uFlags.set(SMP_KEEPONDISK); + } + + return samplepointer; +} + + +// Retrieve the internal sample format flags for this instrument. +SampleIO ITSample::GetSampleFormat(uint16 cwtv) const +{ + SampleIO sampleIO( + (flags & ITSample::sample16Bit) ? SampleIO::_16bit : SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + (cvt & ITSample::cvtSignedSample) ? SampleIO::signedPCM: SampleIO::unsignedPCM); + + // Some old version of IT didn't clear the stereo flag when importing samples. Luckily, all other trackers are identifying as IT 2.14+, so let's check for old IT versions. + if((flags & ITSample::sampleStereo) && cwtv >= 0x214) + { + sampleIO |= SampleIO::stereoSplit; + } + + if(flags & ITSample::sampleCompressed) + { + // IT 2.14 packed sample + sampleIO |= (cvt & ITSample::cvtDelta) ? SampleIO::IT215 : SampleIO::IT214; + } else + { + // MODPlugin :( + if(!(flags & ITSample::sample16Bit) && cvt == ITSample::cvtADPCMSample) + { + sampleIO |= SampleIO::ADPCM; + } else + { + // ITTECH.TXT says these convert flags are "safe to ignore". IT doesn't ignore them, though, so why should we? :) + if(cvt & ITSample::cvtBigEndian) + { + sampleIO |= SampleIO::bigEndian; + } + if(cvt & ITSample::cvtDelta) + { + sampleIO |= SampleIO::deltaPCM; + } + if((cvt & ITSample::cvtPTM8to16) && (flags & ITSample::sample16Bit)) + { + sampleIO |= SampleIO::PTM8Dto16; + } + } + } + + return sampleIO; +} + + +// Convert an ITHistoryStruct to OpenMPT's internal edit history representation +void ITHistoryStruct::ConvertToMPT(FileHistory &mptHistory) const +{ + // Decode FAT date and time + MemsetZero(mptHistory.loadDate); + if(fatdate != 0 || fattime != 0) + { + mptHistory.loadDate.tm_year = ((fatdate >> 9) & 0x7F) + 80; + mptHistory.loadDate.tm_mon = Clamp((fatdate >> 5) & 0x0F, 1, 12) - 1; + mptHistory.loadDate.tm_mday = Clamp(fatdate & 0x1F, 1, 31); + mptHistory.loadDate.tm_hour = Clamp((fattime >> 11) & 0x1F, 0, 23); + mptHistory.loadDate.tm_min = Clamp((fattime >> 5) & 0x3F, 0, 59); + mptHistory.loadDate.tm_sec = Clamp((fattime & 0x1F) * 2, 0, 59); + } + mptHistory.openTime = static_cast(runtime * (HISTORY_TIMER_PRECISION / 18.2)); +} + + +// Convert OpenMPT's internal edit history representation to an ITHistoryStruct +void ITHistoryStruct::ConvertToIT(const FileHistory &mptHistory) +{ + // Create FAT file dates + if(mptHistory.HasValidDate()) + { + fatdate = static_cast(mptHistory.loadDate.tm_mday | ((mptHistory.loadDate.tm_mon + 1) << 5) | ((mptHistory.loadDate.tm_year - 80) << 9)); + fattime = static_cast((mptHistory.loadDate.tm_sec / 2) | (mptHistory.loadDate.tm_min << 5) | (mptHistory.loadDate.tm_hour << 11)); + } else + { + fatdate = 0; + fattime = 0; + } + runtime = static_cast(mptHistory.openTime * (18.2 / HISTORY_TIMER_PRECISION)); +} + + +uint32 DecodeITEditTimer(uint16 cwtv, uint32 editTime) +{ + if((cwtv & 0xFFF) >= 0x0208) + { + editTime ^= 0x4954524B; // 'ITRK' + editTime = mpt::rotr(editTime, 7); + editTime = ~editTime + 1; + editTime = mpt::rotl(editTime, 4); + editTime ^= 0x4A54484C; // 'JTHL' + } + return editTime; +} + + +OPENMPT_NAMESPACE_END -- cgit