/* * Load_mt2.cpp * ------------ * Purpose: MT2 (MadTracker 2) module loader * Notes : A couple of things are not handled properly or not at all, such as internal effects and automation envelopes * 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" #ifdef MPT_EXTERNAL_SAMPLES // For loading external samples #include "../common/mptPathString.h" #endif // MPT_EXTERNAL_SAMPLES #ifndef NO_VST #include "../mptrack/Vstplug.h" #endif // NO_VST OPENMPT_NAMESPACE_BEGIN struct MT2FileHeader { enum MT2HeaderFlags { packedPatterns = 0x01, automation = 0x02, drumsAutomation = 0x08, masterAutomation = 0x10, }; char signature[4]; // "MT20" uint32le userID; uint16le version; char trackerName[32]; // "MadTracker 2.0" char songName[64]; uint16le numOrders; uint16le restartPos; uint16le numPatterns; uint16le numChannels; uint16le samplesPerTick; uint8le ticksPerLine; uint8le linesPerBeat; uint32le flags; // See HeaderFlags uint16le numInstruments; uint16le numSamples; }; MPT_BINARY_STRUCT(MT2FileHeader, 126) struct MT2DrumsData { uint16le numDrumPatterns; uint16le DrumSamples[8]; uint8le DrumPatternOrder[256]; }; MPT_BINARY_STRUCT(MT2DrumsData, 274) struct MT2TrackSettings { uint16le volume; uint8le trackfx; // Built-in effect type is used uint8le output; uint16le fxID; uint16le trackEffectParam[64][8]; }; MPT_BINARY_STRUCT(MT2TrackSettings, 1030) struct MT2Command { uint8 note; // 0=nothing, 97=note off uint8 instr; uint8 vol; uint8 pan; uint8 fxcmd; uint8 fxparam1; uint8 fxparam2; }; MPT_BINARY_STRUCT(MT2Command, 7) struct MT2EnvPoint { uint16le x; uint16le y; }; MPT_BINARY_STRUCT(MT2EnvPoint, 4) struct MT2Instrument { enum EnvTypes { VolumeEnv = 1, PanningEnv = 2, PitchEnv = 4, FilterEnv = 8, }; uint16le numSamples; uint8le groupMap[96]; uint8le vibtype, vibsweep, vibdepth, vibrate; uint16le fadeout; uint16le nna; }; MPT_BINARY_STRUCT(MT2Instrument, 106) struct MT2IEnvelope { uint8le flags; uint8le numPoints; uint8le sustainPos; uint8le loopStart; uint8le loopEnd; uint8le reserved[3]; MT2EnvPoint points[16]; }; MPT_BINARY_STRUCT(MT2IEnvelope, 72) // Note: The order of these fields differs a bit in MTIOModule_MT2.cpp - maybe just typos, I'm not sure. // This struct follows the save format of MadTracker 2.6.1. struct MT2InstrSynth { uint8le synthID; uint8le effectID; // 0 = Lowpass filter, 1 = Highpass filter uint16le cutoff; // 100...11000 Hz uint8le resonance; // 0...128 uint8le attack; // 0...128 uint8le decay; // 0...128 uint8le midiChannel; // 0...15 int8le device; // VST slot (positive) or MIDI device (negative) int8le unknown1; // Missing in MTIOModule_MT2.cpp uint8le volume; // 0...255 int8le finetune; // -96...96 int8le transpose; // -48...48 uint8le unknown2; // Seems to be equal to instrument number. uint8le unknown3; uint8le midiProgram; uint8le reserved[16]; }; MPT_BINARY_STRUCT(MT2InstrSynth, 32) struct MT2Sample { uint32le length; uint32le frequency; uint8le depth; uint8le channels; uint8le flags; uint8le loopType; uint32le loopStart; uint32le loopEnd; uint16le volume; int8le panning; int8le note; int16le spb; }; MPT_BINARY_STRUCT(MT2Sample, 26) struct MT2Group { uint8le sample; uint8le vol; // 0...128 int8le pitch; // -128...127 uint8le reserved[5]; }; MPT_BINARY_STRUCT(MT2Group, 8) struct MT2VST { char dll[64]; char programName[28]; uint32le fxID; uint32le fxVersion; uint32le programNr; uint8le useChunks; uint8le track; int8le pan; // Not imported - could use pan mix mode for D/W ratio, but this is not implemented for instrument plugins! char reserved[17]; uint32le n; }; MPT_BINARY_STRUCT(MT2VST, 128) static bool ConvertMT2Command(CSoundFile *that, ModCommand &m, MT2Command &p) { bool hasLegacyTempo = false; // Note m.note = NOTE_NONE; if(p.note) m.note = (p.note > 96) ? NOTE_KEYOFF : (p.note + NOTE_MIN + 11); // Instrument m.instr = p.instr; // Volume Column if(p.vol >= 0x10 && p.vol <= 0x90) { m.volcmd = VOLCMD_VOLUME; m.vol = (p.vol - 0x10) / 2; } else if(p.vol >= 0xA0 && p.vol <= 0xAF) { m.volcmd = VOLCMD_VOLSLIDEDOWN; m.vol = (p.vol & 0x0F); } else if(p.vol >= 0xB0 && p.vol <= 0xBF) { m.volcmd = VOLCMD_VOLSLIDEUP; m.vol = (p.vol & 0x0F); } else if(p.vol >= 0xC0 && p.vol <= 0xCF) { m.volcmd = VOLCMD_FINEVOLDOWN; m.vol = (p.vol & 0x0F); } else if(p.vol >= 0xD0 && p.vol <= 0xDF) { m.volcmd = VOLCMD_FINEVOLUP; m.vol = (p.vol & 0x0F); } // Effects if(p.fxcmd || p.fxparam1 || p.fxparam2) { switch(p.fxcmd) { case 0x00: // FastTracker effect m.command = p.fxparam2; m.param = p.fxparam1; CSoundFile::ConvertModCommand(m); #ifdef MODPLUG_TRACKER m.Convert(MOD_TYPE_XM, MOD_TYPE_IT, *that); #else MPT_UNREFERENCED_PARAMETER(that); #endif // MODPLUG_TRACKER if(p.fxparam2 == 0x0F) hasLegacyTempo = true; break; case 0x01: // Portamento up (on every tick) m.command = CMD_PORTAMENTOUP; m.param = mpt::saturate_cast((p.fxparam2 << 4) | (p.fxparam1 >> 4)); break; case 0x02: // Portamento down (on every tick) m.command = CMD_PORTAMENTODOWN; m.param = mpt::saturate_cast((p.fxparam2 << 4) | (p.fxparam1 >> 4)); break; case 0x03: // Tone Portamento (on every tick) m.command = CMD_TONEPORTAMENTO; m.param = mpt::saturate_cast((p.fxparam2 << 4) | (p.fxparam1 >> 4)); break; case 0x04: // Vibrato m.command = CMD_VIBRATO; m.param = (p.fxparam2 & 0xF0) | (p.fxparam1 >> 4); break; case 0x08: // Panning + Polarity (we can only import panning for now) if(p.fxparam1) { m.command = CMD_PANNING8; m.param = p.fxparam1; } else if(p.fxparam2 == 1 || p.fxparam2 == 2) { // Invert left or right channel m.command = CMD_S3MCMDEX; m.param = 0x91; } break; case 0x0C: // Set volume (0x80 = 100%) m.command = CMD_VOLUME; m.param = p.fxparam2 / 2; break; case 0x0F: // Set tempo, LPB and ticks (we can only import tempo for now) if(p.fxparam2 != 0) { m.command = CMD_TEMPO; m.param = p.fxparam2; } else { m.command = CMD_SPEED; m.param = (p.fxparam1 & 0x0F); } break; case 0x10: // Impulse Tracker effect m.command = p.fxparam2; m.param = p.fxparam1; CSoundFile::S3MConvert(m, true); if(m.command == CMD_TEMPO || m.command == CMD_SPEED) hasLegacyTempo = true; break; case 0x1D: // Gapper (like IT Tremor with old FX, i.e. 1D 00 XY = ontime X + 1 ticks, offtime Y + 1 ticks) m.command = CMD_TREMOR; m.param = p.fxparam1; break; case 0x20: // Cutoff + Resonance (we can only import cutoff for now) m.command = CMD_MIDI; m.param = p.fxparam2 >> 1; break; case 0x22: // Cutoff + Resonance + Attack + Decay (we can only import cutoff for now) m.command = CMD_MIDI; m.param = (p.fxparam2 & 0xF0) >> 1; break; case 0x24: // Reverse m.command = CMD_S3MCMDEX; m.param = 0x9F; break; case 0x80: // Track volume m.command = CMD_CHANNELVOLUME; m.param = p.fxparam2 / 4u; break; case 0x9D: // Offset + delay m.volcmd = VOLCMD_OFFSET; m.vol = p.fxparam2 >> 3; m.command = CMD_S3MCMDEX; m.param = 0xD0 | std::min(p.fxparam1, uint8(0x0F)); break; case 0xCC: // MIDI CC //m.command = CMD_MIDI; break; // TODO: More MT2 Effects } } if(p.pan) { if(m.command == CMD_NONE) { m.command = CMD_PANNING8; m.param = p.pan; } else if(m.volcmd == VOLCMD_NONE) { m.volcmd = VOLCMD_PANNING; m.vol = p.pan / 4; } } return hasLegacyTempo; } // This doesn't really do anything but skipping the envelope chunk at the moment. static void ReadMT2Automation(uint16 version, FileReader &file) { uint32 flags; uint32 trkfxid; if(version >= 0x203) { flags = file.ReadUint32LE(); trkfxid = file.ReadUint32LE(); } else { flags = file.ReadUint16LE(); trkfxid = file.ReadUint16LE(); } MPT_UNREFERENCED_PARAMETER(trkfxid); while(flags != 0) { if(flags & 1) { file.Skip(4 + sizeof(MT2EnvPoint) * 64); } flags >>= 1; } } static bool ValidateHeader(const MT2FileHeader &fileHeader) { if(std::memcmp(fileHeader.signature, "MT20", 4) || fileHeader.version < 0x200 || fileHeader.version >= 0x300 || fileHeader.numChannels < 1 || fileHeader.numChannels > 64 || fileHeader.numOrders > 256 || fileHeader.numInstruments >= MAX_INSTRUMENTS || fileHeader.numSamples >= MAX_SAMPLES ) { return false; } return true; } static uint64 GetHeaderMinimumAdditionalSize(const MT2FileHeader &fileHeader) { MPT_UNREFERENCED_PARAMETER(fileHeader); return 256; } CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMT2(MemoryFileReader file, const uint64 *pfilesize) { MT2FileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return ProbeWantMoreData; } if(!ValidateHeader(fileHeader)) { return ProbeFailure; } return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader)); } bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) { file.Rewind(); MT2FileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return false; } if(!ValidateHeader(fileHeader)) { return false; } if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } if(loadFlags == onlyVerifyHeader) { return true; } InitializeGlobals(MOD_TYPE_MT2); InitializeChannels(); m_modFormat.formatName = mpt::format(U_("MadTracker %1.%2"))(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF)); m_modFormat.type = U_("mt2"); m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::Windows1252, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.trackerName)); m_modFormat.charset = mpt::Charset::Windows1252; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); m_nChannels = fileHeader.numChannels; m_nDefaultSpeed = Clamp(fileHeader.ticksPerLine, 1, 31); m_nDefaultTempo.Set(125); m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX | SONG_EXFILTERRANGE; m_nInstruments = fileHeader.numInstruments; m_nSamples = fileHeader.numSamples; m_nDefaultRowsPerBeat = Clamp(fileHeader.linesPerBeat, 1, 32); m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4; m_nVSTiVolume = 48; m_nSamplePreAmp = 48 * 2; // Double pre-amp because we will halve the volume of all non-drum instruments, because the volume of drum samples can exceed that of normal samples uint8 orders[256]; file.ReadArray(orders); ReadOrderFromArray(Order(), orders, fileHeader.numOrders); Order().SetRestartPos(fileHeader.restartPos); // This value is supposed to be the size of the drums data, but in old MT2.0 files it's 8 bytes too small. // MadTracker itself unconditionally reads 274 bytes here if the value is != 0, so we do the same. const bool hasDrumChannels = file.ReadUint16LE() != 0; FileReader drumData = file.ReadChunk(hasDrumChannels ? sizeof(MT2DrumsData) : 0); FileReader extraData = file.ReadChunk(file.ReadUint32LE()); const CHANNELINDEX channelsWithoutDrums = m_nChannels; static_assert(MAX_BASECHANNELS >= 64 + 8); if(hasDrumChannels) { m_nChannels += 8; } bool hasLegacyTempo = false; // Read patterns if(loadFlags & loadPatternData) Patterns.ResizeArray(fileHeader.numPatterns); for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++) { ROWINDEX numRows = file.ReadUint16LE(); FileReader chunk = file.ReadChunk((file.ReadUint32LE() + 1) & ~1); LimitMax(numRows, MAX_PATTERN_ROWS); if(!numRows || !(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows)) { continue; } if(fileHeader.flags & MT2FileHeader::packedPatterns) { ROWINDEX row = 0; CHANNELINDEX chn = 0; while(chunk.CanRead(1)) { MT2Command cmd; uint8 infobyte = chunk.ReadUint8(); uint8 repeatCount = 0; if(infobyte == 0xFF) { repeatCount = chunk.ReadUint8(); infobyte = chunk.ReadUint8(); } if(infobyte & 0x7F) { ModCommand *m = Patterns[pat].GetpModCommand(row, chn); MemsetZero(cmd); if(infobyte & 0x01) cmd.note = chunk.ReadUint8(); if(infobyte & 0x02) cmd.instr = chunk.ReadUint8(); if(infobyte & 0x04) cmd.vol = chunk.ReadUint8(); if(infobyte & 0x08) cmd.pan = chunk.ReadUint8(); if(infobyte & 0x10) cmd.fxcmd = chunk.ReadUint8(); if(infobyte & 0x20) cmd.fxparam1 = chunk.ReadUint8(); if(infobyte & 0x40) cmd.fxparam2 = chunk.ReadUint8(); hasLegacyTempo |= ConvertMT2Command(this, *m, cmd); const ModCommand &orig = *m; const ROWINDEX fillRows = std::min((uint32)repeatCount, (uint32)numRows - (row + 1)); for(ROWINDEX r = 0; r < fillRows; r++) { m += GetNumChannels(); // cppcheck false-positive // cppcheck-suppress selfAssignment *m = orig; } } row += repeatCount + 1; while(row >= numRows) { row -= numRows; chn++; } if(chn >= channelsWithoutDrums) break; } } else { for(ROWINDEX row = 0; row < numRows; row++) { auto rowData = Patterns[pat].GetRow(row); for(CHANNELINDEX chn = 0; chn < channelsWithoutDrums; chn++) { MT2Command cmd; chunk.ReadStruct(cmd); hasLegacyTempo |= ConvertMT2Command(this, rowData[chn], cmd); } } } } if(fileHeader.samplesPerTick > 1 && fileHeader.samplesPerTick < 5000) { if(hasLegacyTempo) { m_nDefaultTempo.SetRaw(Util::muldivr(110250, TEMPO::fractFact, fileHeader.samplesPerTick)); m_nTempoMode = tempoModeClassic; } else { m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * fileHeader.samplesPerTick)); m_nTempoMode = tempoModeModern; } } // Read extra data uint32 numVST = 0; std::vector trackRouting(GetNumChannels(), 0); while(extraData.CanRead(8)) { uint32 id = extraData.ReadUint32LE(); FileReader chunk = extraData.ReadChunk(extraData.ReadUint32LE()); switch(id) { case MagicLE("BPM+"): if(!hasLegacyTempo) { m_nTempoMode = tempoModeModern; double d = chunk.ReadDoubleLE(); if(d > 0.00000001) { m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * d)); } } break; case MagicLE("TFXM"): break; case MagicLE("TRKO"): break; case MagicLE("TRKS"): m_nSamplePreAmp = chunk.ReadUint16LE() / 256u; // 131072 is 0dB... I think (that's how MTIOModule_MT2.cpp reads) // Dirty workaround for modules that use track automation for a fade-in at the song start (e.g. Rock.mt2) if(!m_nSamplePreAmp) m_nSamplePreAmp = 48; m_nVSTiVolume = m_nSamplePreAmp / 2u; for(CHANNELINDEX c = 0; c < GetNumChannels(); c++) { MT2TrackSettings trackSettings; if(chunk.ReadStruct(trackSettings)) { ChnSettings[c].nVolume = static_cast(trackSettings.volume >> 10); // 32768 is 0dB trackRouting[c] = trackSettings.output; } } break; case MagicLE("TRKL"): for(CHANNELINDEX i = 0; i < m_nChannels && chunk.CanRead(1); i++) { std::string name; chunk.ReadNullString(name); ChnSettings[i].szName = mpt::String::ReadBuf(mpt::String::spacePadded, name.c_str(), name.length()); } break; case MagicLE("PATN"): for(PATTERNINDEX i = 0; i < fileHeader.numPatterns && chunk.CanRead(1) && Patterns.IsValidIndex(i); i++) { std::string name; chunk.ReadNullString(name); Patterns[i].SetName(name); } break; case MagicLE("MSG\0"): chunk.Skip(1); // Show message on startup m_songMessage.Read(chunk, chunk.BytesLeft(), SongMessage::leCRLF); break; case MagicLE("PICT"): break; case MagicLE("SUM\0"): { uint8 summaryMask[6]; chunk.ReadArray(summaryMask); std::string artist; chunk.ReadNullString(artist); if(artist != "Unregistered") { m_songArtist = mpt::ToUnicode(mpt::Charset::Windows1252, artist); } } break; case MagicLE("TMAP"): break; case MagicLE("MIDI"): break; case MagicLE("TREQ"): break; case MagicLE("VST2"): numVST = chunk.ReadUint32LE(); #ifndef NO_VST if(!(loadFlags & loadPluginData)) { break; } for(uint32 i = 0; i < std::min(numVST, uint32(MAX_MIXPLUGINS)); i++) { MT2VST vstHeader; if(chunk.ReadStruct(vstHeader)) { if(fileHeader.version >= 0x0250) chunk.Skip(16 * 4); // Parameter automation map for 16 parameters SNDMIXPLUGIN &mixPlug = m_MixPlugins[i]; mixPlug.Destroy(); std::string libraryName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, vstHeader.dll); mixPlug.Info.szName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, vstHeader.programName); if(libraryName.length() > 4 && libraryName[libraryName.length() - 4] == '.') { // Remove ".dll" from library name libraryName.resize(libraryName.length() - 4 ); } mixPlug.Info.szLibraryName = libraryName; mixPlug.Info.dwPluginId1 = Vst::kEffectMagic; mixPlug.Info.dwPluginId2 = vstHeader.fxID; if(vstHeader.track >= m_nChannels) { mixPlug.SetMasterEffect(true); } else { if(!ChnSettings[vstHeader.track].nMixPlugin) { ChnSettings[vstHeader.track].nMixPlugin = static_cast(i + 1); } else { // Channel already has plugin assignment - chain the plugins PLUGINDEX outPlug = ChnSettings[vstHeader.track].nMixPlugin - 1; while(true) { if(m_MixPlugins[outPlug].GetOutputPlugin() == PLUGINDEX_INVALID) { m_MixPlugins[outPlug].SetOutputPlugin(static_cast(i)); break; } outPlug = m_MixPlugins[outPlug].GetOutputPlugin(); } } } // Read plugin settings uint32 dataSize; if(vstHeader.useChunks) { // MT2 only ever calls effGetChunk for programs, and OpenMPT uses the defaultProgram value to determine // whether it should use effSetChunk for programs or banks... mixPlug.defaultProgram = -1; LimitMax(vstHeader.n, std::numeric_limits::max() - 4); dataSize = vstHeader.n + 4; } else { mixPlug.defaultProgram = vstHeader.programNr; LimitMax(vstHeader.n, (std::numeric_limits::max() / 4u) - 1); dataSize = vstHeader.n * 4 + 4; } mixPlug.pluginData.resize(dataSize); if(vstHeader.useChunks) { memcpy(mixPlug.pluginData.data(), "fEvN", 4); // 'NvEf' plugin data type chunk.ReadRaw(mixPlug.pluginData.data() + 4, vstHeader.n); } else { auto memFile = std::make_pair(mpt::as_span(mixPlug.pluginData), mpt::IO::Offset(0)); mpt::IO::WriteIntLE(memFile, 0); // Plugin data type for(uint32 param = 0; param < vstHeader.n; param++) { mpt::IO::Write(memFile, IEEE754binary32LE{chunk.ReadFloatLE()}); } } } else { break; } } #endif // NO_VST break; } } #ifndef NO_PLUGINS // Now that we have both the track settings and plugins, establish the track routing by applying the same plugins to the source track as to the target track: for(CHANNELINDEX c = 0; c < GetNumChannels(); c++) { int8 outTrack = trackRouting[c]; if(outTrack > c && outTrack < GetNumChannels() && ChnSettings[outTrack].nMixPlugin != 0) { if(ChnSettings[c].nMixPlugin == 0) { ChnSettings[c].nMixPlugin = ChnSettings[outTrack].nMixPlugin; } else { PLUGINDEX outPlug = ChnSettings[c].nMixPlugin - 1; for(;;) { if(m_MixPlugins[outPlug].GetOutputPlugin() == PLUGINDEX_INVALID) { m_MixPlugins[outPlug].SetOutputPlugin(ChnSettings[outTrack].nMixPlugin - 1); break; } outPlug = m_MixPlugins[outPlug].GetOutputPlugin(); } } } } #endif // NO_PLUGINS // Read drum channels INSTRUMENTINDEX drumMap[8] = { 0 }; uint16 drumSample[8] = { 0 }; if(hasDrumChannels) { MT2DrumsData drumHeader; drumData.ReadStruct(drumHeader); // Allocate some instruments to handle the drum samples for(INSTRUMENTINDEX i = 0; i < 8; i++) { drumMap[i] = GetNextFreeInstrument(m_nInstruments + 1); drumSample[i] = drumHeader.DrumSamples[i]; if(drumMap[i] != INSTRUMENTINDEX_INVALID) { ModInstrument *mptIns = AllocateInstrument(drumMap[i], drumHeader.DrumSamples[i] + 1); if(mptIns != nullptr) { mptIns->name = mpt::format("Drum #%1")(i+1); } } else { drumMap[i] = 0; } } // Get all the drum pattern chunks std::vector patternChunks(drumHeader.numDrumPatterns); for(uint32 pat = 0; pat < drumHeader.numDrumPatterns; pat++) { uint16 numRows = file.ReadUint16LE(); patternChunks[pat] = file.ReadChunk(numRows * 32); } std::vector patMapping(fileHeader.numPatterns, PATTERNINDEX_INVALID); for(uint32 ord = 0; ord < fileHeader.numOrders; ord++) { if(drumHeader.DrumPatternOrder[ord] >= drumHeader.numDrumPatterns || Order()[ord] >= fileHeader.numPatterns) continue; // Figure out where to write this drum pattern PATTERNINDEX writePat = Order()[ord]; if(patMapping[writePat] == PATTERNINDEX_INVALID) { patMapping[writePat] = drumHeader.DrumPatternOrder[ord]; } else if(patMapping[writePat] != drumHeader.DrumPatternOrder[ord]) { // Damn, this pattern has previously used a different drum pattern. Duplicate it... PATTERNINDEX newPat = Patterns.Duplicate(writePat); if(newPat != PATTERNINDEX_INVALID) { writePat = newPat; Order()[ord] = writePat; } } if(!Patterns.IsValidPat(writePat)) continue; FileReader &chunk = patternChunks[drumHeader.DrumPatternOrder[ord]]; chunk.Rewind(); const ROWINDEX numRows = static_cast(chunk.GetLength() / 32u); for(ROWINDEX row = 0; row < Patterns[writePat].GetNumRows(); row++) { ModCommand *m = Patterns[writePat].GetpModCommand(row, m_nChannels - 8); for(CHANNELINDEX chn = 0; chn < 8; chn++, m++) { *m = ModCommand::Empty(); if(row >= numRows) continue; uint8 drums[4]; chunk.ReadArray(drums); if(drums[0] & 0x80) { m->note = NOTE_MIDDLEC; m->instr = static_cast(drumMap[chn]); uint8 delay = drums[0] & 0x1F; if(delay) { LimitMax(delay, uint8(0x0F)); m->command = CMD_S3MCMDEX; m->param = 0xD0 | delay; } m->volcmd = VOLCMD_VOLUME; // Volume is 0...255, but 128 is equivalent to v64 - we compensate this by halving the global volume of all non-drum instruments m->vol = static_cast((static_cast(drums[1]) + 3) / 4u); } } } } } // Read automation envelopes if(fileHeader.flags & MT2FileHeader::automation) { const uint32 numEnvelopes = ((fileHeader.flags & MT2FileHeader::drumsAutomation) ? m_nChannels : channelsWithoutDrums) + ((fileHeader.version >= 0x0250) ? numVST : 0) + ((fileHeader.flags & MT2FileHeader::masterAutomation) ? 1 : 0); for(uint32 pat = 0; pat < fileHeader.numPatterns; pat++) { for(uint32 env = 0; env < numEnvelopes && file.CanRead(4); env++) { // TODO ReadMT2Automation(fileHeader.version, file); } } } // Read instruments std::vector instrChunks(255); for(INSTRUMENTINDEX i = 0; i < 255; i++) { char instrName[32]; file.ReadArray(instrName); uint32 dataLength = file.ReadUint32LE(); if(dataLength == 32) dataLength += 108 + sizeof(MT2IEnvelope) * 4; if(fileHeader.version > 0x0201 && dataLength) dataLength += 4; FileReader instrChunk = instrChunks[i] = file.ReadChunk(dataLength); ModInstrument *mptIns = nullptr; if(i < fileHeader.numInstruments) { // Default sample assignment if there is no data chunk? Fixes e.g. instrument 33 in Destiny - Dream Alone.mt2 mptIns = AllocateInstrument(i + 1, i + 1); } if(mptIns == nullptr) continue; mptIns->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrName); if(!dataLength) continue; MT2Instrument insHeader; instrChunk.ReadStruct(insHeader); uint16 flags = 0; if(fileHeader.version >= 0x0201) flags = instrChunk.ReadUint16LE(); uint32 envMask = MT2Instrument::VolumeEnv | MT2Instrument::PanningEnv; if(fileHeader.version >= 0x0202) envMask = instrChunk.ReadUint32LE(); mptIns->nFadeOut = insHeader.fadeout; const NewNoteAction NNA[4] = { NNA_NOTECUT, NNA_CONTINUE, NNA_NOTEOFF, NNA_NOTEFADE }; const DuplicateCheckType DCT[4] = { DCT_NONE, DCT_NOTE, DCT_SAMPLE, DCT_INSTRUMENT }; const DuplicateNoteAction DNA[4] = { DNA_NOTECUT, DNA_NOTEFADE /* actually continue, but IT doesn't have that */, DNA_NOTEOFF, DNA_NOTEFADE }; mptIns->nNNA = NNA[insHeader.nna & 3]; mptIns->nDCT = DCT[(insHeader.nna >> 8) & 3]; mptIns->nDNA = DNA[(insHeader.nna >> 12) & 3]; // Load envelopes for(uint32 env = 0; env < 4; env++) { if(envMask & 1) { MT2IEnvelope mt2Env; instrChunk.ReadStruct(mt2Env); const EnvelopeType envType[4] = { ENV_VOLUME, ENV_PANNING, ENV_PITCH, ENV_PITCH }; InstrumentEnvelope &mptEnv = mptIns->GetEnvelope(envType[env]); mptEnv.dwFlags.set(ENV_FILTER, (env == 3) && (mt2Env.flags & 1) != 0); mptEnv.dwFlags.set(ENV_ENABLED, (mt2Env.flags & 1) != 0); mptEnv.dwFlags.set(ENV_SUSTAIN, (mt2Env.flags & 2) != 0); mptEnv.dwFlags.set(ENV_LOOP, (mt2Env.flags & 4) != 0); mptEnv.resize(std::min(mt2Env.numPoints.get(), uint8(16))); mptEnv.nSustainStart = mptEnv.nSustainEnd = mt2Env.sustainPos; mptEnv.nLoopStart = mt2Env.loopStart; mptEnv.nLoopEnd = mt2Env.loopEnd; for(uint32 p = 0; p < mptEnv.size(); p++) { mptEnv[p].tick = mt2Env.points[p].x; mptEnv[p].value = static_cast(Clamp(mt2Env.points[p].y, 0, 64)); } } envMask >>= 1; } if(!mptIns->VolEnv.dwFlags[ENV_ENABLED] && mptIns->nNNA != NNA_NOTEFADE) { mptIns->nFadeOut = int16_max; } mptIns->SetCutoff(0x7F, true); mptIns->SetResonance(0, true); if(flags) { MT2InstrSynth synthData; instrChunk.ReadStruct(synthData); if(flags & 2) { mptIns->SetCutoff(FrequencyToCutOff(synthData.cutoff), true); mptIns->SetResonance(synthData.resonance, true); } mptIns->filterMode = synthData.effectID == 1 ? FilterMode::HighPass : FilterMode::LowPass; if(flags & 4) { // VSTi / MIDI synth enabled mptIns->nMidiChannel = synthData.midiChannel + 1; mptIns->nMixPlug = static_cast(synthData.device + 1); if(synthData.device < 0) { // TODO: This is a MIDI device - maybe use MIDI I/O plugin to emulate those? mptIns->nMidiProgram = synthData.midiProgram + 1; // MT2 only seems to use this for MIDI devices, not VSTis! } if(synthData.transpose) { for(uint32 n = 0; n < std::size(mptIns->NoteMap); n++) { int note = NOTE_MIN + n + synthData.transpose; Limit(note, NOTE_MIN, NOTE_MAX); mptIns->NoteMap[n] = static_cast(note); } } // Instruments with plugin assignments never play samples at the same time! mptIns->AssignSample(0); } } } // Read sample headers std::bitset<256> sampleNoInterpolation; std::bitset<256> sampleSynchronized; for(SAMPLEINDEX i = 0; i < 256; i++) { char sampleName[32]; file.ReadArray(sampleName); uint32 dataLength = file.ReadUint32LE(); FileReader sampleChunk = file.ReadChunk(dataLength); if(i < fileHeader.numSamples) { m_szNames[i + 1] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleName); } if(dataLength && i < fileHeader.numSamples) { ModSample &mptSmp = Samples[i + 1]; mptSmp.Initialize(MOD_TYPE_IT); MT2Sample sampleHeader; sampleChunk.ReadStruct(sampleHeader); mptSmp.nLength = sampleHeader.length; mptSmp.nC5Speed = sampleHeader.frequency; if(sampleHeader.depth > 1) { mptSmp.uFlags.set(CHN_16BIT); mptSmp.nLength /= 2u; } if(sampleHeader.channels > 1) { mptSmp.uFlags.set(CHN_STEREO); mptSmp.nLength /= 2u; } if(sampleHeader.loopType == 1) mptSmp.uFlags.set(CHN_LOOP); else if(sampleHeader.loopType == 2) mptSmp.uFlags.set(CHN_LOOP | CHN_PINGPONGLOOP); mptSmp.nLoopStart = sampleHeader.loopStart; mptSmp.nLoopEnd = sampleHeader.loopEnd; mptSmp.nVolume = sampleHeader.volume >> 7; if(sampleHeader.panning == -128) mptSmp.uFlags.set(CHN_SURROUND); else mptSmp.nPan = sampleHeader.panning + 128; mptSmp.uFlags.set(CHN_PANNING); mptSmp.RelativeTone = sampleHeader.note; if(sampleHeader.flags & 2) { // Sample is synchronized to beat // The synchronization part is not supported in OpenMPT, but synchronized samples also always play at the same pitch as C-5, which we CAN do! sampleSynchronized[i] = true; //mptSmp.nC5Speed = Util::muldiv(mptSmp.nC5Speed, sampleHeader.spb, 22050); } if(sampleHeader.flags & 5) { // External sample mptSmp.uFlags.set(SMP_KEEPONDISK); } if(sampleHeader.flags & 8) { sampleNoInterpolation[i] = true; for(INSTRUMENTINDEX drum = 0; drum < 8; drum++) { if(drumSample[drum] == i && Instruments[drumMap[drum]] != nullptr) { Instruments[drumMap[drum]]->resampling = SRCMODE_NEAREST; } } } } } // Read sample groups for(INSTRUMENTINDEX ins = 0; ins < fileHeader.numInstruments; ins++) { if(instrChunks[ins].GetLength()) { FileReader &chunk = instrChunks[ins]; MT2Instrument insHeader; chunk.Rewind(); chunk.ReadStruct(insHeader); std::vector groups; file.ReadVector(groups, insHeader.numSamples); ModInstrument *mptIns = Instruments[ins + 1]; // Instruments with plugin assignments never play samples at the same time! if(mptIns == nullptr || mptIns->nMixPlug != 0) continue; mptIns->nGlobalVol = 32; // Compensate for extended dynamic range of drum instruments mptIns->AssignSample(0); for(uint32 note = 0; note < 96; note++) { if(insHeader.groupMap[note] < insHeader.numSamples) { const MT2Group &group = groups[insHeader.groupMap[note]]; SAMPLEINDEX sample = group.sample + 1; mptIns->Keyboard[note + 11 + NOTE_MIN] = sample; if(sample > 0 && sample <= m_nSamples) { ModSample &mptSmp = Samples[sample]; mptSmp.nVibType = static_cast(insHeader.vibtype & 3); // In fact, MT2 only implements sine vibrato mptSmp.nVibSweep = insHeader.vibsweep; mptSmp.nVibDepth = insHeader.vibdepth; mptSmp.nVibRate = insHeader.vibrate; mptSmp.nGlobalVol = uint16(group.vol) * 2; mptSmp.nFineTune = group.pitch; if(sampleNoInterpolation[sample - 1]) { mptIns->resampling = SRCMODE_NEAREST; } if(sampleSynchronized[sample - 1]) { mptIns->NoteMap[note + 11 + NOTE_MIN] = NOTE_MIDDLEC; } } // TODO: volume, finetune for duplicated samples } } } } if(!(loadFlags & loadSampleData)) return true; // Read sample data for(SAMPLEINDEX i = 0; i < m_nSamples; i++) { ModSample &mptSmp = Samples[i + 1]; mptSmp.Transpose(-(mptSmp.RelativeTone - 49 - (mptSmp.nFineTune / 128.0)) / 12.0); mptSmp.nFineTune = 0; mptSmp.RelativeTone = 0; if(!mptSmp.uFlags[SMP_KEEPONDISK]) { SampleIO( mptSmp.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit, mptSmp.uFlags[CHN_STEREO] ? SampleIO::stereoSplit : SampleIO::mono, SampleIO::littleEndian, SampleIO::MT2) .ReadSample(mptSmp, file); } else { // External sample const uint32 filenameSize = file.ReadUint32LE(); file.Skip(12); // Reserved std::string filename; file.ReadString(filename, filenameSize); mptSmp.filename = filename; #if defined(MPT_EXTERNAL_SAMPLES) if(filename.length() >= 2 && filename.at(0) != '\\' // Relative path on same drive && filename.at(1) != ':') // Absolute path { // Relative path in same folder or sub folder filename = ".\\" + filename; } SetSamplePath(i + 1, mpt::PathString::FromLocaleSilent(filename)); #elif !defined(LIBOPENMPT_BUILD_TEST) AddToLog(LogWarning, mpt::format(U_("Loading external sample %1 ('%2') failed: External samples are not supported."))(i + 1, mpt::ToUnicode(GetCharsetFile(), filename))); #endif // MPT_EXTERNAL_SAMPLES } } return true; } OPENMPT_NAMESPACE_END