/* * Load_mo3.cpp * ------------ * Purpose: MO3 module loader. * Notes : (currently none) * Authors: Johannes Schultz / OpenMPT Devs * Based on documentation and the decompression routines from the * open-source UNMO3 project (https://github.com/lclevy/unmo3). * The modified decompression code has been relicensed to the BSD * license with permission from Laurent Clévy. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Loaders.h" #include "../common/ComponentManager.h" #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" #include "Tables.h" #include "../common/version.h" #include "mpt/audio/span.hpp" #include "MPEGFrame.h" #include "OggStream.h" #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) #include #endif #if defined(MPT_WITH_VORBIS) #if MPT_COMPILER_CLANG #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif // MPT_COMPILER_CLANG #include #if MPT_COMPILER_CLANG #pragma clang diagnostic pop #endif // MPT_COMPILER_CLANG #endif #if defined(MPT_WITH_VORBISFILE) #if MPT_COMPILER_CLANG #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif // MPT_COMPILER_CLANG #include #if MPT_COMPILER_CLANG #pragma clang diagnostic pop #endif // MPT_COMPILER_CLANG #include "openmpt/soundbase/Copy.hpp" #endif #ifdef MPT_WITH_STBVORBIS #include #include "openmpt/soundbase/Copy.hpp" #endif // MPT_WITH_STBVORBIS OPENMPT_NAMESPACE_BEGIN struct MO3FileHeader { enum MO3HeaderFlags { linearSlides = 0x0001, isS3M = 0x0002, s3mFastSlides = 0x0004, isMTM = 0x0008, // Actually this is simply "not XM". But if none of the S3M, MOD and IT flags are set, it's an MTM. s3mAmigaLimits = 0x0010, // 0x20 and 0x40 have been used in old versions for things that can be inferred from the file format anyway. // The official UNMO3 ignores them. isMOD = 0x0080, isIT = 0x0100, instrumentMode = 0x0200, itCompatGxx = 0x0400, itOldFX = 0x0800, modplugMode = 0x10000, unknown = 0x20000, // Always set (internal BASS flag to designate modules) modVBlank = 0x80000, hasPlugins = 0x100000, extFilterRange = 0x200000, }; uint8le numChannels; // 1...64 (limited by channel panning and volume) uint16le numOrders; uint16le restartPos; uint16le numPatterns; uint16le numTracks; uint16le numInstruments; uint16le numSamples; uint8le defaultSpeed; uint8le defaultTempo; uint32le flags; // See MO3HeaderFlags uint8le globalVol; // 0...128 in IT, 0...64 in S3M uint8le panSeparation; // 0...128 in IT int8le sampleVolume; // Only used in IT uint8le chnVolume[64]; // 0...64 uint8le chnPan[64]; // 0...256, 127 = surround uint8le sfxMacros[16]; uint8le fixedMacros[128][2]; }; MPT_BINARY_STRUCT(MO3FileHeader, 422) struct MO3Envelope { enum MO3EnvelopeFlags { envEnabled = 0x01, envSustain = 0x02, envLoop = 0x04, envFilter = 0x10, envCarry = 0x20, }; uint8le flags; // See MO3EnvelopeFlags uint8le numNodes; uint8le sustainStart; uint8le sustainEnd; uint8le loopStart; uint8le loopEnd; int16le points[25][2]; // Convert MO3 envelope data into OpenMPT's internal envelope format void ConvertToMPT(InstrumentEnvelope &mptEnv, uint8 envShift) const { if(flags & envEnabled) mptEnv.dwFlags.set(ENV_ENABLED); if(flags & envSustain) mptEnv.dwFlags.set(ENV_SUSTAIN); if(flags & envLoop) mptEnv.dwFlags.set(ENV_LOOP); if(flags & envFilter) mptEnv.dwFlags.set(ENV_FILTER); if(flags & envCarry) mptEnv.dwFlags.set(ENV_CARRY); mptEnv.resize(std::min(numNodes.get(), uint8(25))); mptEnv.nSustainStart = sustainStart; mptEnv.nSustainEnd = sustainEnd; mptEnv.nLoopStart = loopStart; mptEnv.nLoopEnd = loopEnd; for(uint32 ev = 0; ev < mptEnv.size(); ev++) { mptEnv[ev].tick = points[ev][0]; if(ev > 0 && mptEnv[ev].tick < mptEnv[ev - 1].tick) mptEnv[ev].tick = mptEnv[ev - 1].tick + 1; mptEnv[ev].value = static_cast(Clamp(points[ev][1] >> envShift, 0, 64)); } } }; MPT_BINARY_STRUCT(MO3Envelope, 106) struct MO3Instrument { enum MO3InstrumentFlags { playOnMIDI = 0x01, mute = 0x02, }; uint32le flags; // See MO3InstrumentFlags uint16le sampleMap[120][2]; MO3Envelope volEnv; MO3Envelope panEnv; MO3Envelope pitchEnv; struct XMVibratoSettings { uint8le type; uint8le sweep; uint8le depth; uint8le rate; } vibrato; // Applies to all samples of this instrument (XM) uint16le fadeOut; uint8le midiChannel; uint8le midiBank; uint8le midiPatch; uint8le midiBend; uint8le globalVol; // 0...128 uint16le panning; // 0...256 if enabled, 0xFFFF otherwise uint8le nna; uint8le pps; uint8le ppc; uint8le dct; uint8le dca; uint16le volSwing; // 0...100 uint16le panSwing; // 0...256 uint8le cutoff; // 0...127, + 128 if enabled uint8le resonance; // 0...127, + 128 if enabled // Convert MO3 instrument data into OpenMPT's internal instrument format void ConvertToMPT(ModInstrument &mptIns, MODTYPE type) const { if(type == MOD_TYPE_XM) { for(size_t i = 0; i < 96; i++) { mptIns.Keyboard[i + 12] = sampleMap[i][1] + 1; } } else { for(size_t i = 0; i < 120; i++) { mptIns.NoteMap[i] = static_cast(sampleMap[i][0] + NOTE_MIN); mptIns.Keyboard[i] = sampleMap[i][1] + 1; } } volEnv.ConvertToMPT(mptIns.VolEnv, 0); panEnv.ConvertToMPT(mptIns.PanEnv, 0); pitchEnv.ConvertToMPT(mptIns.PitchEnv, 5); mptIns.nFadeOut = fadeOut; if(midiChannel >= 128) { // Plugin mptIns.nMixPlug = midiChannel - 127; } else if(midiChannel < 17 && (flags & playOnMIDI)) { // XM, or IT with recent encoder mptIns.nMidiChannel = midiChannel + MidiFirstChannel; } else if(midiChannel > 0 && midiChannel < 17) { // IT encoded with MO3 version prior to 2.4.1 (yes, channel 0 is represented the same way as "no channel") mptIns.nMidiChannel = midiChannel + MidiFirstChannel; } if(mptIns.nMidiChannel != MidiNoChannel) { if(type == MOD_TYPE_XM) { mptIns.nMidiProgram = midiPatch + 1; } else { if(midiBank < 128) mptIns.wMidiBank = midiBank + 1; if(midiPatch < 128) mptIns.nMidiProgram = midiPatch + 1; } mptIns.midiPWD = midiBend; } if(type == MOD_TYPE_IT) mptIns.nGlobalVol = std::min(static_cast(globalVol), uint8(128)) / 2u; if(panning <= 256) { mptIns.nPan = panning; mptIns.dwFlags.set(INS_SETPANNING); } mptIns.nNNA = static_cast(nna.get()); mptIns.nPPS = pps; mptIns.nPPC = ppc; mptIns.nDCT = static_cast(dct.get()); mptIns.nDNA = static_cast(dca.get()); mptIns.nVolSwing = static_cast(std::min(volSwing.get(), uint16(100))); mptIns.nPanSwing = static_cast(std::min(panSwing.get(), uint16(256)) / 4u); mptIns.SetCutoff(cutoff & 0x7F, (cutoff & 0x80) != 0); mptIns.SetResonance(resonance & 0x7F, (resonance & 0x80) != 0); } }; MPT_BINARY_STRUCT(MO3Instrument, 826) struct MO3Sample { enum MO3SampleFlags { smp16Bit = 0x01, smpLoop = 0x10, smpPingPongLoop = 0x20, smpSustain = 0x100, smpSustainPingPong = 0x200, smpStereo = 0x400, smpCompressionMPEG = 0x1000, // MPEG 1.0 / 2.0 / 2.5 sample smpCompressionOgg = 0x1000 | 0x2000, // Ogg sample smpSharedOgg = 0x1000 | 0x2000 | 0x4000, // Ogg sample with shared vorbis header smpDeltaCompression = 0x2000, // Deltas + compression smpDeltaPrediction = 0x4000, // Delta prediction + compression smpOPLInstrument = 0x8000, // OPL patch data smpCompressionMask = 0x1000 | 0x2000 | 0x4000 | 0x8000 }; uint32le freqFinetune; // Frequency in S3M and IT, finetune (0...255) in MOD, MTM, XM int8le transpose; uint8le defaultVolume; // 0...64 uint16le panning; // 0...256 if enabled, 0xFFFF otherwise uint32le length; uint32le loopStart; uint32le loopEnd; uint16le flags; // See MO3SampleFlags uint8le vibType; uint8le vibSweep; uint8le vibDepth; uint8le vibRate; uint8le globalVol; // 0...64 in IT, in XM it represents the instrument number uint32le sustainStart; uint32le sustainEnd; int32le compressedSize; uint16le encoderDelay; // MP3: Ignore first n bytes of decoded output. Ogg: Shared Ogg header size // Convert MO3 sample data into OpenMPT's internal instrument format void ConvertToMPT(ModSample &mptSmp, MODTYPE type, bool frequencyIsHertz) const { mptSmp.Initialize(); mptSmp.SetDefaultCuePoints(); if(type & (MOD_TYPE_IT | MOD_TYPE_S3M)) { if(frequencyIsHertz) mptSmp.nC5Speed = freqFinetune; else mptSmp.nC5Speed = mpt::saturate_round(8363.0 * std::pow(2.0, static_cast(freqFinetune + 1408) / 1536.0)); } else { mptSmp.nFineTune = static_cast(freqFinetune); if(type != MOD_TYPE_MTM) mptSmp.nFineTune -= 128; mptSmp.RelativeTone = transpose; } mptSmp.nVolume = std::min(defaultVolume.get(), uint8(64)) * 4u; if(panning <= 256) { mptSmp.nPan = panning; mptSmp.uFlags.set(CHN_PANNING); } mptSmp.nLength = length; mptSmp.nLoopStart = loopStart; mptSmp.nLoopEnd = loopEnd; if(flags & smpLoop) mptSmp.uFlags.set(CHN_LOOP); if(flags & smpPingPongLoop) mptSmp.uFlags.set(CHN_PINGPONGLOOP); if(flags & smpSustain) mptSmp.uFlags.set(CHN_SUSTAINLOOP); if(flags & smpSustainPingPong) mptSmp.uFlags.set(CHN_PINGPONGSUSTAIN); mptSmp.nVibType = static_cast(AutoVibratoIT2XM[vibType & 7]); mptSmp.nVibSweep = vibSweep; mptSmp.nVibDepth = vibDepth; mptSmp.nVibRate = vibRate; if(type == MOD_TYPE_IT) mptSmp.nGlobalVol = std::min(static_cast(globalVol), uint8(64)); mptSmp.nSustainStart = sustainStart; mptSmp.nSustainEnd = sustainEnd; } }; MPT_BINARY_STRUCT(MO3Sample, 41) // We need all this information for Ogg-compressed samples with shared headers: // A shared header can be taken from a sample that has not been read yet, so // we first need to read all headers, and then load the Ogg samples afterwards. struct MO3SampleChunk { FileReader chunk; uint16 headerSize; int16 sharedHeader; MO3SampleChunk(const FileReader &chunk_ = FileReader(), uint16 headerSize_ = 0, int16 sharedHeader_ = 0) : chunk(chunk_), headerSize(headerSize_), sharedHeader(sharedHeader_) {} }; // Unpack macros // shift control bits until it is empty: // a 0 bit means literal : the next data byte is copied // a 1 means compressed data // then the next 2 bits determines what is the LZ ptr // ('00' same as previous, else stored in stream) #define READ_CTRL_BIT \ data <<= 1; \ carry = (data > 0xFF); \ data &= 0xFF; \ if(data == 0) \ { \ uint8 nextByte; \ if(!file.Read(nextByte)) \ break; \ data = nextByte; \ data = (data << 1) + 1; \ carry = (data > 0xFF); \ data &= 0xFF; \ } // length coded within control stream: // most significant bit is 1 // then the first bit of each bits pair (noted n1), // until second bit is 0 (noted n0) #define DECODE_CTRL_BITS \ { \ strLen++; \ do \ { \ READ_CTRL_BIT; \ strLen = mpt::lshift_signed(strLen, 1) + carry; \ READ_CTRL_BIT; \ } while(carry); \ } static bool UnpackMO3Data(FileReader &file, std::vector &uncompressed, const uint32 size) { if(!size) return false; uint16 data = 0; int8 carry = 0; // x86 carry (used to propagate the most significant bit from one byte to another) int32 strLen = 0; // length of previous string int32 strOffset; // string offset uint32 previousPtr = 0; // Read first uncompressed byte uncompressed.push_back(file.ReadUint8()); uint32 remain = size - 1; while(remain > 0) { READ_CTRL_BIT; if(!carry) { // a 0 ctrl bit means 'copy', not compressed byte if(uint8 b; file.Read(b)) uncompressed.push_back(b); else break; remain--; } else { // a 1 ctrl bit means compressed bytes are following uint8 lengthAdjust = 0; // length adjustment DECODE_CTRL_BITS; // read length, and if strLen > 3 (coded using more than 1 bits pair) also part of the offset value strLen -= 3; if(strLen < 0) { // means LZ ptr with same previous relative LZ ptr (saved one) strOffset = previousPtr; // restore previous Ptr strLen++; } else { // LZ ptr in ctrl stream if(uint8 b; file.Read(b)) strOffset = mpt::lshift_signed(strLen, 8) | b; // read less significant offset byte from stream else break; strLen = 0; strOffset = ~strOffset; if(strOffset < -1280) lengthAdjust++; lengthAdjust++; // length is always at least 1 if(strOffset < -32000) lengthAdjust++; previousPtr = strOffset; // save current Ptr } // read the next 2 bits as part of strLen READ_CTRL_BIT; strLen = mpt::lshift_signed(strLen, 1) + carry; READ_CTRL_BIT; strLen = mpt::lshift_signed(strLen, 1) + carry; if(strLen == 0) { // length does not fit in 2 bits DECODE_CTRL_BITS; // decode length: 1 is the most significant bit, strLen += 2; // then first bit of each bits pairs (noted n1), until n0. } strLen += lengthAdjust; // length adjustment if(remain < static_cast(strLen) || strLen <= 0) break; if(strOffset >= 0 || -static_cast(uncompressed.size()) > strOffset) break; // Copy previous string // Need to do this in two steps as source and destination may overlap (e.g. strOffset = -1, strLen = 2 repeats last character twice) uncompressed.insert(uncompressed.end(), strLen, 0); remain -= strLen; auto src = uncompressed.cend() - strLen + strOffset; auto dst = uncompressed.end() - strLen; do { strLen--; *dst++ = *src++; } while(strLen > 0); } } #ifdef MPT_BUILD_FUZZER // When using a fuzzer, we should not care if the decompressed buffer has the correct size. // This makes finding new interesting test cases much easier. return true; #else return remain == 0; #endif // MPT_BUILD_FUZZER } struct MO3Delta8BitParams { using sample_t = int8; using unsigned_t = uint8; static constexpr int shift = 7; static constexpr uint8 dhInit = 4; static inline void Decode(FileReader &file, int8 &carry, uint16 &data, uint8 & /*dh*/, unsigned_t &val) { do { READ_CTRL_BIT; val = (val << 1) + carry; READ_CTRL_BIT; } while(carry); } }; struct MO3Delta16BitParams { using sample_t = int16; using unsigned_t = uint16; static constexpr int shift = 15; static constexpr uint8 dhInit = 8; static inline void Decode(FileReader &file, int8 &carry, uint16 &data, uint8 &dh, unsigned_t &val) { if(dh < 5) { do { READ_CTRL_BIT; val = (val << 1) + carry; READ_CTRL_BIT; val = (val << 1) + carry; READ_CTRL_BIT; } while(carry); } else { do { READ_CTRL_BIT; val = (val << 1) + carry; READ_CTRL_BIT; } while(carry); } } }; template static void UnpackMO3DeltaSample(FileReader &file, typename Properties::sample_t *dst, uint32 length, uint8 numChannels) { uint8 dh = Properties::dhInit, cl = 0; int8 carry = 0; uint16 data = 0; typename Properties::unsigned_t val; typename Properties::sample_t previous = 0; for(uint8 chn = 0; chn < numChannels; chn++) { typename Properties::sample_t *p = dst + chn; const typename Properties::sample_t *const pEnd = p + length * numChannels; while(p < pEnd) { val = 0; Properties::Decode(file, carry, data, dh, val); cl = dh; while(cl > 0) { READ_CTRL_BIT; val = (val << 1) + carry; cl--; } cl = 1; if(val >= 4) { cl = Properties::shift; while(((1 << cl) & val) == 0 && cl > 1) cl--; } dh = dh + cl; dh >>= 1; // next length in bits of encoded delta second part carry = val & 1; // sign of delta 1=+, 0=not val >>= 1; if(carry == 0) val = ~val; // negative delta val += previous; // previous value + delta *p = val; p += numChannels; previous = val; } } } template static void UnpackMO3DeltaPredictionSample(FileReader &file, typename Properties::sample_t *dst, uint32 length, uint8 numChannels) { uint8 dh = Properties::dhInit, cl = 0; int8 carry; uint16 data = 0; int32 next = 0; typename Properties::unsigned_t val = 0; typename Properties::sample_t sval = 0, delta = 0, previous = 0; for(uint8 chn = 0; chn < numChannels; chn++) { typename Properties::sample_t *p = dst + chn; const typename Properties::sample_t *const pEnd = p + length * numChannels; while(p < pEnd) { val = 0; Properties::Decode(file, carry, data, dh, val); cl = dh; // length in bits of: delta second part (right most bits of delta) and sign bit while(cl > 0) { READ_CTRL_BIT; val = (val << 1) + carry; cl--; } cl = 1; if(val >= 4) { cl = Properties::shift; while(((1 << cl) & val) == 0 && cl > 1) cl--; } dh = dh + cl; dh >>= 1; // next length in bits of encoded delta second part carry = val & 1; // sign of delta 1=+, 0=not val >>= 1; if(carry == 0) val = ~val; // negative delta delta = static_cast(val); val = val + static_cast(next); // predicted value + delta *p = val; p += numChannels; sval = static_cast(val); next = (sval * (1 << 1)) + (delta >> 1) - previous; // corrected next value Limit(next, std::numeric_limits::min(), std::numeric_limits::max()); previous = sval; } } } #undef READ_CTRL_BIT #undef DECODE_CTRL_BITS #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) static size_t VorbisfileFilereaderRead(void *ptr, size_t size, size_t nmemb, void *datasource) { FileReader &file = *reinterpret_cast(datasource); return file.ReadRaw(mpt::span(mpt::void_cast(ptr), size * nmemb)).size() / size; } static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int whence) { FileReader &file = *reinterpret_cast(datasource); switch(whence) { case SEEK_SET: if(!mpt::in_range(offset)) { return -1; } return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; case SEEK_CUR: if(offset < 0) { if(offset == std::numeric_limits::min()) { return -1; } if(!mpt::in_range(0 - offset)) { return -1; } return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; } else { if(!mpt::in_range(offset)) { return -1; } return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; } break; case SEEK_END: if(!mpt::in_range(offset)) { return -1; } if(!mpt::in_range(file.GetLength() + offset)) { return -1; } return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; default: return -1; } } static long VorbisfileFilereaderTell(void *datasource) { FileReader &file = *reinterpret_cast(datasource); FileReader::off_t result = file.GetPosition(); if(!mpt::in_range(result)) { return -1; } return static_cast(result); } #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE struct MO3ContainerHeader { char magic[3]; // MO3 uint8le version; uint32le musicSize; }; MPT_BINARY_STRUCT(MO3ContainerHeader, 8) static bool ValidateHeader(const MO3ContainerHeader &containerHeader) { if(std::memcmp(containerHeader.magic, "MO3", 3)) { return false; } if(containerHeader.musicSize <= sizeof(MO3FileHeader) || containerHeader.musicSize >= uint32_max / 2u) { return false; } if(containerHeader.version > 5) { return false; } return true; } CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMO3(MemoryFileReader file, const uint64 *pfilesize) { MO3ContainerHeader containerHeader; if(!file.ReadStruct(containerHeader)) { return ProbeWantMoreData; } if(!ValidateHeader(containerHeader)) { return ProbeFailure; } MPT_UNREFERENCED_PARAMETER(pfilesize); return ProbeSuccess; } bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) { file.Rewind(); MO3ContainerHeader containerHeader; if(!file.ReadStruct(containerHeader)) { return false; } if(!ValidateHeader(containerHeader)) { return false; } if(loadFlags == onlyVerifyHeader) { return true; } const uint8 version = containerHeader.version; uint32 compressedSize = uint32_max, reserveSize = 1024 * 1024; // Generous estimate based on biggest pre-v5 MO3s found in the wild (~350K music data) if(version >= 5) { // Size of compressed music chunk compressedSize = file.ReadUint32LE(); if(!file.CanRead(compressedSize)) return false; // Generous estimate based on highest real-world compression ratio I found in a module (~20:1) reserveSize = std::min(Util::MaxValueOfType(reserveSize) / 32u, compressedSize) * 32u; } std::vector musicData; // We don't always reserve the whole uncompressed size as claimed by the module to guard against broken files // that e.g. claim that the uncompressed size is 1GB while the MO3 file itself is only 100 bytes. // As the LZ compression used in MO3 doesn't allow for establishing a clear upper bound for the maximum size, // this is probably the only sensible way we can prevent DoS due to huge allocations. musicData.reserve(std::min(reserveSize, containerHeader.musicSize.get())); if(!UnpackMO3Data(file, musicData, containerHeader.musicSize)) { return false; } if(version >= 5) { file.Seek(12 + compressedSize); } InitializeGlobals(); InitializeChannels(); FileReader musicChunk(mpt::as_span(musicData)); musicChunk.ReadNullString(m_songName); musicChunk.ReadNullString(m_songMessage); MO3FileHeader fileHeader; if(!musicChunk.ReadStruct(fileHeader) || fileHeader.numChannels == 0 || fileHeader.numChannels > MAX_BASECHANNELS || fileHeader.numInstruments >= MAX_INSTRUMENTS || fileHeader.numSamples >= MAX_SAMPLES) { return false; } m_nChannels = fileHeader.numChannels; Order().SetRestartPos(fileHeader.restartPos); m_nInstruments = fileHeader.numInstruments; m_nSamples = fileHeader.numSamples; m_nDefaultSpeed = fileHeader.defaultSpeed ? fileHeader.defaultSpeed : 6; m_nDefaultTempo.Set(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125, 0); if(fileHeader.flags & MO3FileHeader::isIT) SetType(MOD_TYPE_IT); else if(fileHeader.flags & MO3FileHeader::isS3M) SetType(MOD_TYPE_S3M); else if(fileHeader.flags & MO3FileHeader::isMOD) SetType(MOD_TYPE_MOD); else if(fileHeader.flags & MO3FileHeader::isMTM) SetType(MOD_TYPE_MTM); else SetType(MOD_TYPE_XM); m_SongFlags.set(SONG_IMPORTED); if(fileHeader.flags & MO3FileHeader::linearSlides) m_SongFlags.set(SONG_LINEARSLIDES); if((fileHeader.flags & MO3FileHeader::s3mAmigaLimits) && m_nType == MOD_TYPE_S3M) m_SongFlags.set(SONG_AMIGALIMITS); if((fileHeader.flags & MO3FileHeader::s3mFastSlides) && m_nType == MOD_TYPE_S3M) m_SongFlags.set(SONG_FASTVOLSLIDES); if(!(fileHeader.flags & MO3FileHeader::itOldFX) && m_nType == MOD_TYPE_IT) m_SongFlags.set(SONG_ITOLDEFFECTS); if(!(fileHeader.flags & MO3FileHeader::itCompatGxx) && m_nType == MOD_TYPE_IT) m_SongFlags.set(SONG_ITCOMPATGXX); if(fileHeader.flags & MO3FileHeader::extFilterRange) m_SongFlags.set(SONG_EXFILTERRANGE); if(fileHeader.flags & MO3FileHeader::modVBlank) m_playBehaviour.set(kMODVBlankTiming); if(m_nType == MOD_TYPE_IT) m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(128)) * 2; else if(m_nType == MOD_TYPE_S3M) m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(64)) * 4; if(fileHeader.sampleVolume < 0) m_nSamplePreAmp = fileHeader.sampleVolume + 52; else m_nSamplePreAmp = static_cast(std::exp(fileHeader.sampleVolume * 3.1 / 20.0)) + 51; // Header only has room for 64 channels, like in IT const CHANNELINDEX headerChannels = std::min(m_nChannels, CHANNELINDEX(64)); for(CHANNELINDEX i = 0; i < headerChannels; i++) { if(m_nType == MOD_TYPE_IT) ChnSettings[i].nVolume = std::min(fileHeader.chnVolume[i].get(), uint8(64)); if(m_nType != MOD_TYPE_XM) { if(fileHeader.chnPan[i] == 127) ChnSettings[i].dwFlags = CHN_SURROUND; else if(fileHeader.chnPan[i] == 255) ChnSettings[i].nPan = 256; else ChnSettings[i].nPan = fileHeader.chnPan[i]; } } bool anyMacros = false; for(uint32 i = 0; i < 16; i++) { if(fileHeader.sfxMacros[i]) anyMacros = true; } for(uint32 i = 0; i < 128; i++) { if(fileHeader.fixedMacros[i][1]) anyMacros = true; } if(anyMacros) { for(uint32 i = 0; i < 16; i++) { if(fileHeader.sfxMacros[i]) m_MidiCfg.SFx[i] = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1)); else m_MidiCfg.SFx[i] = ""; } for(uint32 i = 0; i < 128; i++) { if(fileHeader.fixedMacros[i][1]) m_MidiCfg.Zxx[i] = MPT_AFORMAT("F0F0{}{}")(mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][1] - 1), mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][0].get())); else m_MidiCfg.Zxx[i] = ""; } } const bool hasOrderSeparators = !(m_nType & (MOD_TYPE_MOD | MOD_TYPE_XM)); ReadOrderFromFile(Order(), musicChunk, fileHeader.numOrders, hasOrderSeparators ? 0xFF : uint16_max, hasOrderSeparators ? 0xFE : uint16_max); // Track assignments for all patterns FileReader trackChunk = musicChunk.ReadChunk(fileHeader.numPatterns * fileHeader.numChannels * sizeof(uint16)); FileReader patLengthChunk = musicChunk.ReadChunk(fileHeader.numPatterns * sizeof(uint16)); std::vector tracks(fileHeader.numTracks); for(auto &track : tracks) { uint32 len = musicChunk.ReadUint32LE(); track = musicChunk.ReadChunk(len); } /* MO3 pattern commands: 01 = Note 02 = Instrument 03 = CMD_ARPEGGIO (IT, XM, S3M, MOD, MTM) 04 = CMD_PORTAMENTOUP (XM, MOD, MTM) [for formats with separate fine slides] 05 = CMD_PORTAMENTODOWN (XM, MOD, MTM) [for formats with separate fine slides] 06 = CMD_TONEPORTAMENTO (IT, XM, S3M, MOD, MTM) / VOLCMD_TONEPORTA (IT, XM) 07 = CMD_VIBRATO (IT, XM, S3M, MOD, MTM) / VOLCMD_VIBRATODEPTH (IT) 08 = CMD_TONEPORTAVOL (XM, MOD, MTM) 09 = CMD_VIBRATOVOL (XM, MOD, MTM) 0A = CMD_TREMOLO (IT, XM, S3M, MOD, MTM) 0B = CMD_PANNING8 (IT, XM, S3M, MOD, MTM) / VOLCMD_PANNING (IT, XM) 0C = CMD_OFFSET (IT, XM, S3M, MOD, MTM) 0D = CMD_VOLUMESLIDE (XM, MOD, MTM) 0E = CMD_POSITIONJUMP (IT, XM, S3M, MOD, MTM) 0F = CMD_VOLUME (XM, MOD, MTM) / VOLCMD_VOLUME (IT, XM, S3M) 10 = CMD_PATTERNBREAK (IT, XM, MOD, MTM) - BCD-encoded in MOD/XM/S3M/MTM! 11 = CMD_MODCMDEX (XM, MOD, MTM) 12 = CMD_TEMPO (XM, MOD, MTM) / CMD_SPEED (XM, MOD, MTM) 13 = CMD_TREMOR (XM) 14 = VOLCMD_VOLSLIDEUP x=X0 (XM) / VOLCMD_VOLSLIDEDOWN x=0X (XM) 15 = VOLCMD_FINEVOLUP x=X0 (XM) / VOLCMD_FINEVOLDOWN x=0X (XM) 16 = CMD_GLOBALVOLUME (IT, XM, S3M) 17 = CMD_GLOBALVOLSLIDE (XM) 18 = CMD_KEYOFF (XM) 19 = CMD_SETENVPOSITION (XM) 1A = CMD_PANNINGSLIDE (XM) 1B = VOLCMD_PANSLIDELEFT x=0X (XM) / VOLCMD_PANSLIDERIGHT x=X0 (XM) 1C = CMD_RETRIG (XM) 1D = CMD_XFINEPORTAUPDOWN X1x (XM) 1E = CMD_XFINEPORTAUPDOWN X2x (XM) 1F = VOLCMD_VIBRATOSPEED (XM) 20 = VOLCMD_VIBRATODEPTH (XM) 21 = CMD_SPEED (IT, S3M) 22 = CMD_VOLUMESLIDE (IT, S3M) 23 = CMD_PORTAMENTODOWN (IT, S3M) [for formats without separate fine slides] 24 = CMD_PORTAMENTOUP (IT, S3M) [for formats without separate fine slides] 25 = CMD_TREMOR (IT, S3M) 26 = CMD_RETRIG (IT, S3M) 27 = CMD_FINEVIBRATO (IT, S3M) 28 = CMD_CHANNELVOLUME (IT, S3M) 29 = CMD_CHANNELVOLSLIDE (IT, S3M) 2A = CMD_PANNINGSLIDE (IT, S3M) 2B = CMD_S3MCMDEX (IT, S3M) 2C = CMD_TEMPO (IT, S3M) 2D = CMD_GLOBALVOLSLIDE (IT, S3M) 2E = CMD_PANBRELLO (IT, XM, S3M) 2F = CMD_MIDI (IT, XM, S3M) 30 = VOLCMD_FINEVOLUP x=0...9 (IT) / VOLCMD_FINEVOLDOWN x=10...19 (IT) / VOLCMD_VOLSLIDEUP x=20...29 (IT) / VOLCMD_VOLSLIDEDOWN x=30...39 (IT) 31 = VOLCMD_PORTADOWN (IT) 32 = VOLCMD_PORTAUP (IT) 33 = Unused XM command "W" (XM) 34 = Any other IT volume column command to support OpenMPT extensions (IT) 35 = CMD_XPARAM (IT) 36 = CMD_SMOOTHMIDI (IT) 37 = CMD_DELAYCUT (IT) 38 = CMD_FINETUNE (MPTM) 39 = CMD_FINETUNE_SMOOTH (MPTM) Note: S3M/IT CMD_TONEPORTAVOL / CMD_VIBRATOVOL are encoded as two commands: K= 07 00 22 x L= 06 00 22 x */ static constexpr ModCommand::COMMAND effTrans[] = { CMD_NONE, CMD_NONE, 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_MODCMDEX, CMD_TEMPO, CMD_TREMOR, VOLCMD_VOLSLIDEUP, VOLCMD_FINEVOLUP, CMD_GLOBALVOLUME, CMD_GLOBALVOLSLIDE, CMD_KEYOFF, CMD_SETENVPOSITION, CMD_PANNINGSLIDE, VOLCMD_PANSLIDELEFT, CMD_RETRIG, CMD_XFINEPORTAUPDOWN, CMD_XFINEPORTAUPDOWN, VOLCMD_VIBRATOSPEED, VOLCMD_VIBRATODEPTH, CMD_SPEED, CMD_VOLUMESLIDE, CMD_PORTAMENTODOWN, CMD_PORTAMENTOUP, CMD_TREMOR, CMD_RETRIG, CMD_FINEVIBRATO, CMD_CHANNELVOLUME, CMD_CHANNELVOLSLIDE, CMD_PANNINGSLIDE, CMD_S3MCMDEX, CMD_TEMPO, CMD_GLOBALVOLSLIDE, CMD_PANBRELLO, CMD_MIDI, VOLCMD_FINEVOLUP, VOLCMD_PORTADOWN, VOLCMD_PORTAUP, CMD_NONE, VOLCMD_OFFSET, CMD_XPARAM, CMD_SMOOTHMIDI, CMD_DELAYCUT, CMD_FINETUNE, CMD_FINETUNE_SMOOTH, }; uint8 noteOffset = NOTE_MIN; if(m_nType == MOD_TYPE_MTM) noteOffset = 13 + NOTE_MIN; else if(m_nType != MOD_TYPE_IT) noteOffset = 12 + NOTE_MIN; bool onlyAmigaNotes = true; if(loadFlags & loadPatternData) Patterns.ResizeArray(fileHeader.numPatterns); for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++) { const ROWINDEX numRows = patLengthChunk.ReadUint16LE(); if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows)) continue; for(CHANNELINDEX chn = 0; chn < fileHeader.numChannels; chn++) { uint16 trackIndex = trackChunk.ReadUint16LE(); if(trackIndex >= tracks.size()) continue; FileReader &track = tracks[trackIndex]; track.Rewind(); ROWINDEX row = 0; ModCommand *patData = Patterns[pat].GetpModCommand(0, chn); while(row < numRows) { const uint8 b = track.ReadUint8(); if(!b) break; const uint8 numCommands = (b & 0x0F), rep = (b >> 4); ModCommand m = ModCommand::Empty(); for(uint8 c = 0; c < numCommands; c++) { uint8 cmd[2]; track.ReadArray(cmd); // Import pattern commands switch(cmd[0]) { case 0x01: // Note m.note = cmd[1]; if(m.note < 120) m.note += noteOffset; else if(m.note == 0xFF) m.note = NOTE_KEYOFF; else if(m.note == 0xFE) m.note = NOTE_NOTECUT; else m.note = NOTE_FADE; if(!m.IsAmigaNote()) onlyAmigaNotes = false; break; case 0x02: // Instrument m.instr = cmd[1] + 1; break; case 0x06: // Tone portamento if(m.volcmd == VOLCMD_NONE && m_nType == MOD_TYPE_XM && !(cmd[1] & 0x0F)) { m.volcmd = VOLCMD_TONEPORTAMENTO; m.vol = cmd[1] >> 4; break; } else if(m.volcmd == VOLCMD_NONE && m_nType == MOD_TYPE_IT) { for(uint8 i = 0; i < 10; i++) { if(ImpulseTrackerPortaVolCmd[i] == cmd[1]) { m.volcmd = VOLCMD_TONEPORTAMENTO; m.vol = i; break; } } if(m.volcmd != VOLCMD_NONE) break; } m.command = CMD_TONEPORTAMENTO; m.param = cmd[1]; break; case 0x07: // Vibrato if(m.volcmd == VOLCMD_NONE && cmd[1] < 10 && m_nType == MOD_TYPE_IT) { m.volcmd = VOLCMD_VIBRATODEPTH; m.vol = cmd[1]; } else { m.command = CMD_VIBRATO; m.param = cmd[1]; } break; case 0x0B: // Panning if(m.volcmd == VOLCMD_NONE) { if(m_nType == MOD_TYPE_IT && cmd[1] == 0xFF) { m.volcmd = VOLCMD_PANNING; m.vol = 64; break; } if((m_nType == MOD_TYPE_IT && !(cmd[1] & 0x03)) || (m_nType == MOD_TYPE_XM && !(cmd[1] & 0x0F))) { m.volcmd = VOLCMD_PANNING; m.vol = cmd[1] / 4; break; } } m.command = CMD_PANNING8; m.param = cmd[1]; break; case 0x0F: // Volume if(m_nType != MOD_TYPE_MOD && m.volcmd == VOLCMD_NONE && cmd[1] <= 64) { m.volcmd = VOLCMD_VOLUME; m.vol = cmd[1]; } else { m.command = CMD_VOLUME; m.param = cmd[1]; } break; case 0x10: // Pattern break m.command = CMD_PATTERNBREAK; m.param = cmd[1]; if(m_nType != MOD_TYPE_IT) m.param = ((m.param >> 4) * 10) + (m.param & 0x0F); break; case 0x12: // Combined Tempo / Speed command m.param = cmd[1]; if(m.param < 0x20) m.command = CMD_SPEED; else m.command = CMD_TEMPO; break; case 0x14: case 0x15: // XM volume column volume slides if(cmd[1] & 0xF0) { m.volcmd = static_cast((cmd[0] == 0x14) ? VOLCMD_VOLSLIDEUP : VOLCMD_FINEVOLUP); m.vol = cmd[1] >> 4; } else { m.volcmd = static_cast((cmd[0] == 0x14) ? VOLCMD_VOLSLIDEDOWN : VOLCMD_FINEVOLDOWN); m.vol = cmd[1] & 0x0F; } break; case 0x1B: // XM volume column panning slides if(cmd[1] & 0xF0) { m.volcmd = VOLCMD_PANSLIDERIGHT; m.vol = cmd[1] >> 4; } else { m.volcmd = VOLCMD_PANSLIDELEFT; m.vol = cmd[1] & 0x0F; } break; case 0x1D: // XM extra fine porta up m.command = CMD_XFINEPORTAUPDOWN; m.param = 0x10 | cmd[1]; break; case 0x1E: // XM extra fine porta down m.command = CMD_XFINEPORTAUPDOWN; m.param = 0x20 | cmd[1]; break; case 0x1F: case 0x20: // XM volume column vibrato m.volcmd = effTrans[cmd[0]]; m.vol = cmd[1]; break; case 0x22: // IT / S3M volume slide if(m.command == CMD_TONEPORTAMENTO) m.command = CMD_TONEPORTAVOL; else if(m.command == CMD_VIBRATO) m.command = CMD_VIBRATOVOL; else m.command = CMD_VOLUMESLIDE; m.param = cmd[1]; break; case 0x30: // IT volume column volume slides m.vol = cmd[1] % 10; if(cmd[1] < 10) m.volcmd = VOLCMD_FINEVOLUP; else if(cmd[1] < 20) m.volcmd = VOLCMD_FINEVOLDOWN; else if(cmd[1] < 30) m.volcmd = VOLCMD_VOLSLIDEUP; else if(cmd[1] < 40) m.volcmd = VOLCMD_VOLSLIDEDOWN; break; case 0x31: case 0x32: // IT volume column portamento m.volcmd = effTrans[cmd[0]]; m.vol = cmd[1]; break; case 0x34: // Any unrecognized IT volume command if(cmd[1] >= 223 && cmd[1] <= 232) { m.volcmd = VOLCMD_OFFSET; m.vol = cmd[1] - 223; } break; default: if(cmd[0] < std::size(effTrans)) { m.command = effTrans[cmd[0]]; m.param = cmd[1]; } break; } } #ifdef MODPLUG_TRACKER if(m_nType == MOD_TYPE_MTM) m.Convert(MOD_TYPE_MTM, MOD_TYPE_S3M, *this); #endif ROWINDEX targetRow = std::min(row + rep, numRows); while(row < targetRow) { *patData = m; patData += fileHeader.numChannels; row++; } } } } if(GetType() == MOD_TYPE_MOD && GetNumChannels() == 4 && onlyAmigaNotes) { m_SongFlags.set(SONG_AMIGALIMITS | SONG_ISAMIGA); } const bool isSampleMode = (m_nType != MOD_TYPE_XM && !(fileHeader.flags & MO3FileHeader::instrumentMode)); std::vector instrVibrato(m_nType == MOD_TYPE_XM ? m_nInstruments : 0); for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++) { ModInstrument *pIns = nullptr; if(isSampleMode || (pIns = AllocateInstrument(ins)) == nullptr) { // Even in IT sample mode, instrument headers are still stored.... while(musicChunk.ReadUint8() != 0) ; if(version >= 5) { while(musicChunk.ReadUint8() != 0) ; } musicChunk.Skip(sizeof(MO3Instrument)); continue; } std::string name; musicChunk.ReadNullString(name); pIns->name = name; if(version >= 5) { musicChunk.ReadNullString(name); pIns->filename = name; } MO3Instrument insHeader; if(!musicChunk.ReadStruct(insHeader)) break; insHeader.ConvertToMPT(*pIns, m_nType); if(m_nType == MOD_TYPE_XM) instrVibrato[ins - 1] = insHeader.vibrato; } if(isSampleMode) m_nInstruments = 0; std::vector sampleChunks(m_nSamples); const bool frequencyIsHertz = (version >= 5 || !(fileHeader.flags & MO3FileHeader::linearSlides)); bool unsupportedSamples = false; for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { ModSample &sample = Samples[smp]; std::string name; musicChunk.ReadNullString(name); m_szNames[smp] = name; if(version >= 5) { musicChunk.ReadNullString(name); sample.filename = name; } MO3Sample smpHeader; if(!musicChunk.ReadStruct(smpHeader)) break; smpHeader.ConvertToMPT(sample, m_nType, frequencyIsHertz); int16 sharedOggHeader = 0; if(version >= 5 && (smpHeader.flags & MO3Sample::smpCompressionMask) == MO3Sample::smpSharedOgg) { sharedOggHeader = musicChunk.ReadInt16LE(); } if(!(loadFlags & loadSampleData)) continue; const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); if(!compression && smpHeader.compressedSize == 0) { // Uncompressed sample SampleIO( (smpHeader.flags & MO3Sample::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit, (smpHeader.flags & MO3Sample::smpStereo) ? SampleIO::stereoSplit : SampleIO::mono, SampleIO::littleEndian, SampleIO::signedPCM) .ReadSample(Samples[smp], file); } else if(smpHeader.compressedSize < 0 && (smp + smpHeader.compressedSize) > 0) { // Duplicate sample sample.CopyWaveform(Samples[smp + smpHeader.compressedSize]); } else if(smpHeader.compressedSize > 0) { if(smpHeader.flags & MO3Sample::smp16Bit) sample.uFlags.set(CHN_16BIT); if(smpHeader.flags & MO3Sample::smpStereo) sample.uFlags.set(CHN_STEREO); FileReader sampleData = file.ReadChunk(smpHeader.compressedSize); const uint8 numChannels = sample.GetNumChannels(); if(compression == MO3Sample::smpDeltaCompression || compression == MO3Sample::smpDeltaPrediction) { // In the best case, MO3 compression represents each sample point as two bits. // As a result, if we have a file length of n, we know that the sample can be at most n*4 sample points long. auto maxLength = sampleData.GetLength(); uint8 maxSamplesPerByte = 4 / numChannels; if(Util::MaxValueOfType(maxLength) / maxSamplesPerByte >= maxLength) maxLength *= maxSamplesPerByte; else maxLength = Util::MaxValueOfType(maxLength); LimitMax(sample.nLength, mpt::saturate_cast(maxLength)); } if(compression == MO3Sample::smpDeltaCompression) { if(sample.AllocateSample()) { if(smpHeader.flags & MO3Sample::smp16Bit) UnpackMO3DeltaSample(sampleData, sample.sample16(), sample.nLength, numChannels); else UnpackMO3DeltaSample(sampleData, sample.sample8(), sample.nLength, numChannels); } } else if(compression == MO3Sample::smpDeltaPrediction) { if(sample.AllocateSample()) { if(smpHeader.flags & MO3Sample::smp16Bit) UnpackMO3DeltaPredictionSample(sampleData, sample.sample16(), sample.nLength, numChannels); else UnpackMO3DeltaPredictionSample(sampleData, sample.sample8(), sample.nLength, numChannels); } } else if(compression == MO3Sample::smpCompressionOgg || compression == MO3Sample::smpSharedOgg) { // Since shared Ogg headers can stem from a sample that has not been read yet, postpone Ogg import. sampleChunks[smp - 1] = MO3SampleChunk(sampleData, smpHeader.encoderDelay, sharedOggHeader); } else if(compression == MO3Sample::smpCompressionMPEG) { // Old MO3 encoders didn't remove LAME info frames. This is unfortunate since the encoder delay // specified in the sample header does not take the gapless information from the LAME info frame // into account. We should not depend on the MP3 decoder's capabilities to read or ignore such frames: // - libmpg123 has MPG123_IGNORE_INFOFRAME but that requires API version 31 (mpg123 v1.14) or higher // - Media Foundation does (currently) not read LAME gapless information at all // So we just play safe and remove such frames. FileReader mpegData(sampleData); MPEGFrame frame(sampleData); uint16 frameDelay = frame.numSamples * 2; if(frame.isLAME && smpHeader.encoderDelay >= frameDelay) { // The info frame does not produce any output, but still counts towards the encoder delay. smpHeader.encoderDelay -= frameDelay; sampleData.Seek(frame.frameSize); mpegData = sampleData.ReadChunk(sampleData.BytesLeft()); } if(ReadMP3Sample(smp, mpegData, true, true) || ReadMediaFoundationSample(smp, mpegData, true)) { if(smpHeader.encoderDelay > 0 && smpHeader.encoderDelay < sample.GetSampleSizeInBytes()) { SmpLength delay = smpHeader.encoderDelay / sample.GetBytesPerSample(); memmove(sample.sampleb(), sample.sampleb() + smpHeader.encoderDelay, sample.GetSampleSizeInBytes() - smpHeader.encoderDelay); sample.nLength -= delay; } LimitMax(sample.nLength, smpHeader.length); } else { unsupportedSamples = true; } } else if(compression == MO3Sample::smpOPLInstrument) { OPLPatch patch; if(sampleData.ReadArray(patch)) { sample.SetAdlib(true, patch); } } else { unsupportedSamples = true; } } } // Now we can load Ogg samples with shared headers. if(loadFlags & loadSampleData) { for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { MO3SampleChunk &sampleChunk = sampleChunks[smp - 1]; // Is this an Ogg sample? if(!sampleChunk.chunk.IsValid()) continue; SAMPLEINDEX sharedOggHeader = smp + sampleChunk.sharedHeader; // Which chunk are we going to read the header from? // Note: Every Ogg stream has a unique serial number. // stb_vorbis (currently) ignores this serial number so we can just stitch // together our sample without adjusting the shared header's serial number. const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples && sampleChunk.headerSize > 0; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) std::vector mergedData; if(sharedHeader) { // Prepend the shared header to the actual sample data and adjust bitstream serial numbers. // We do not handle multiple muxed logical streams as they do not exist in practice in mo3. // We assume sequence numbers are consecutive at the end of the headers. // Corrupted pages get dropped as required by Ogg spec. We cannot do any further sane parsing on them anyway. // We do not match up multiple muxed stream properly as this would need parsing of actual packet data to determine or guess the codec. // Ogg Vorbis files may contain at least an additional Ogg Skeleton stream. It is not clear whether these actually exist in MO3. // We do not validate packet structure or logical bitstream structure (i.e. sequence numbers and granule positions). // TODO: At least handle Skeleton streams here, as they violate our stream ordering assumptions here. #if 0 // This block may still turn out to be useful as it does a more thourough validation of the stream than the optimized version below. // We copy the whole data into a single consecutive buffer in order to keep things simple when interfacing libvorbisfile. // We could in theory only adjust the header and pass 2 chunks to libvorbisfile. // Another option would be to demux both chunks on our own (or using libogg) and pass the raw packet data to libvorbis directly. std::ostringstream mergedStream(std::ios::binary); mergedStream.imbue(std::locale::classic()); sampleChunks[sharedOggHeader - 1].chunk.Rewind(); FileReader sharedChunk = sampleChunks[sharedOggHeader - 1].chunk.ReadChunk(sampleChunk.headerSize); sharedChunk.Rewind(); std::vector streamSerials; Ogg::PageInfo oggPageInfo; std::vector oggPageData; streamSerials.clear(); while(Ogg::ReadPageAndSkipJunk(sharedChunk, oggPageInfo, oggPageData)) { auto it = std::find(streamSerials.begin(), streamSerials.end(), oggPageInfo.header.bitstream_serial_number); if(it == streamSerials.end()) { streamSerials.push_back(oggPageInfo.header.bitstream_serial_number); it = streamSerials.begin() + (streamSerials.size() - 1); } uint32 newSerial = it - streamSerials.begin() + 1; oggPageInfo.header.bitstream_serial_number = newSerial; Ogg::UpdatePageCRC(oggPageInfo, oggPageData); Ogg::WritePage(mergedStream, oggPageInfo, oggPageData); } streamSerials.clear(); while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) { auto it = std::find(streamSerials.begin(), streamSerials.end(), oggPageInfo.header.bitstream_serial_number); if(it == streamSerials.end()) { streamSerials.push_back(oggPageInfo.header.bitstream_serial_number); it = streamSerials.begin() + (streamSerials.size() - 1); } uint32 newSerial = it - streamSerials.begin() + 1; oggPageInfo.header.bitstream_serial_number = newSerial; Ogg::UpdatePageCRC(oggPageInfo, oggPageData); Ogg::WritePage(mergedStream, oggPageInfo, oggPageData); } std::string mergedStreamData = mergedStream.str(); mergedData.insert(mergedData.end(), mergedStreamData.begin(), mergedStreamData.end()); #else // We assume same ordering of streams in both header and data if // multiple streams are present. std::ostringstream mergedStream(std::ios::binary); mergedStream.imbue(std::locale::classic()); sampleChunks[sharedOggHeader - 1].chunk.Rewind(); FileReader sharedChunk = sampleChunks[sharedOggHeader - 1].chunk.ReadChunk(sampleChunk.headerSize); sharedChunk.Rewind(); std::vector dataStreamSerials; std::vector headStreamSerials; Ogg::PageInfo oggPageInfo; std::vector oggPageData; // Gather bitstream serial numbers form sample data chunk dataStreamSerials.clear(); while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) { if(!mpt::contains(dataStreamSerials, oggPageInfo.header.bitstream_serial_number)) { dataStreamSerials.push_back(oggPageInfo.header.bitstream_serial_number); } } // Apply the data bitstream serial numbers to the header headStreamSerials.clear(); while(Ogg::ReadPageAndSkipJunk(sharedChunk, oggPageInfo, oggPageData)) { auto it = std::find(headStreamSerials.begin(), headStreamSerials.end(), oggPageInfo.header.bitstream_serial_number); if(it == headStreamSerials.end()) { headStreamSerials.push_back(oggPageInfo.header.bitstream_serial_number); it = headStreamSerials.begin() + (headStreamSerials.size() - 1); } uint32 newSerial = 0; if(dataStreamSerials.size() >= static_cast(it - headStreamSerials.begin())) { // Found corresponding stream in data chunk. newSerial = dataStreamSerials[it - headStreamSerials.begin()]; } else { // No corresponding stream in data chunk. Find a free serialno. std::size_t extraIndex = (it - headStreamSerials.begin()) - dataStreamSerials.size(); for(newSerial = 1; newSerial < 0xffffffffu; ++newSerial) { if(!mpt::contains(dataStreamSerials, newSerial)) { extraIndex -= 1; } if(extraIndex == 0) { break; } } } oggPageInfo.header.bitstream_serial_number = newSerial; Ogg::UpdatePageCRC(oggPageInfo, oggPageData); Ogg::WritePage(mergedStream, oggPageInfo, oggPageData); } if(headStreamSerials.size() > 1) { AddToLog(LogWarning, MPT_UFORMAT("Sample {}: Ogg Vorbis data with shared header and multiple logical bitstreams in header chunk found. This may be handled incorrectly.")(smp)); } else if(dataStreamSerials.size() > 1) { AddToLog(LogWarning, MPT_UFORMAT("Sample {}: Ogg Vorbis sample with shared header and multiple logical bitstreams found. This may be handled incorrectly.")(smp)); } else if((dataStreamSerials.size() == 1) && (headStreamSerials.size() == 1) && (dataStreamSerials[0] != headStreamSerials[0])) { AddToLog(LogInformation, MPT_UFORMAT("Sample {}: Ogg Vorbis data with shared header and different logical bitstream serials found.")(smp)); } std::string mergedStreamData = mergedStream.str(); mergedData.insert(mergedData.end(), mergedStreamData.begin(), mergedStreamData.end()); sampleChunk.chunk.Rewind(); FileReader::PinnedView sampleChunkView = sampleChunk.chunk.GetPinnedView(); mpt::span sampleChunkViewSpan = mpt::byte_cast>(sampleChunkView.span()); mergedData.insert(mergedData.end(), sampleChunkViewSpan.begin(), sampleChunkViewSpan.end()); #endif } FileReader mergedDataChunk(mpt::byte_cast(mpt::as_span(mergedData))); FileReader &sampleData = sharedHeader ? mergedDataChunk : sampleChunk.chunk; FileReader &headerChunk = sampleData; #else // !(MPT_WITH_VORBIS && MPT_WITH_VORBISFILE) FileReader &sampleData = sampleChunk.chunk; FileReader &headerChunk = sharedHeader ? sampleChunks[sharedOggHeader - 1].chunk : sampleData; #if defined(MPT_WITH_STBVORBIS) std::size_t initialRead = sharedHeader ? sampleChunk.headerSize : headerChunk.GetLength(); #endif // MPT_WITH_STBVORBIS #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE headerChunk.Rewind(); if(sharedHeader && !headerChunk.CanRead(sampleChunk.headerSize)) continue; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) ov_callbacks callbacks = { &VorbisfileFilereaderRead, &VorbisfileFilereaderSeek, nullptr, &VorbisfileFilereaderTell}; OggVorbis_File vf; MemsetZero(vf); if(ov_open_callbacks(&sampleData, &vf, nullptr, 0, callbacks) == 0) { if(ov_streams(&vf) == 1) { // we do not support chained vorbis samples vorbis_info *vi = ov_info(&vf, -1); if(vi && vi->rate > 0 && vi->channels > 0) { ModSample &sample = Samples[smp]; sample.AllocateSample(); SmpLength offset = 0; int channels = vi->channels; int current_section = 0; long decodedSamples = 0; bool eof = false; while(!eof && offset < sample.nLength && sample.HasSampleData()) { float **output = nullptr; long ret = ov_read_float(&vf, &output, 1024, ¤t_section); if(ret == 0) { eof = true; } else if(ret < 0) { // stream error, just try to continue } else { decodedSamples = ret; LimitMax(decodedSamples, mpt::saturate_cast(sample.nLength - offset)); if(decodedSamples > 0 && channels == sample.GetNumChannels()) { if(sample.uFlags[CHN_16BIT]) { CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } else { CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } } offset += decodedSamples; } } } else { unsupportedSamples = true; } } else { AddToLog(LogWarning, MPT_UFORMAT("Sample {}: Unsupported Ogg Vorbis chained stream found.")(smp)); unsupportedSamples = true; } ov_clear(&vf); } else { unsupportedSamples = true; } #elif defined(MPT_WITH_STBVORBIS) // NOTE/TODO: stb_vorbis does not handle inferred negative PCM sample // position at stream start. (See // ). // This means that, for remuxed and re-aligned/cutted (at stream start) // Vorbis files, stb_vorbis will include superfluous samples at the // beginning. MO3 files with this property are yet to be spotted in the // wild, thus, this behaviour is currently not problematic. int consumed = 0, error = 0; stb_vorbis *vorb = nullptr; if(sharedHeader) { FileReader::PinnedView headChunkView = headerChunk.GetPinnedView(initialRead); vorb = stb_vorbis_open_pushdata(mpt::byte_cast(headChunkView.data()), mpt::saturate_cast(headChunkView.size()), &consumed, &error, nullptr); headerChunk.Skip(consumed); } FileReader::PinnedView sampleDataView = sampleData.GetPinnedView(); const std::byte *data = sampleDataView.data(); std::size_t dataLeft = sampleDataView.size(); if(!sharedHeader) { vorb = stb_vorbis_open_pushdata(mpt::byte_cast(data), mpt::saturate_cast(dataLeft), &consumed, &error, nullptr); sampleData.Skip(consumed); data += consumed; dataLeft -= consumed; } if(vorb) { // Header has been read, proceed to reading the sample data ModSample &sample = Samples[smp]; sample.AllocateSample(); SmpLength offset = 0; while((error == VORBIS__no_error || (error == VORBIS_need_more_data && dataLeft > 0)) && offset < sample.nLength && sample.HasSampleData()) { int channels = 0, decodedSamples = 0; float **output; consumed = stb_vorbis_decode_frame_pushdata(vorb, mpt::byte_cast(data), mpt::saturate_cast(dataLeft), &channels, &output, &decodedSamples); sampleData.Skip(consumed); data += consumed; dataLeft -= consumed; LimitMax(decodedSamples, mpt::saturate_cast(sample.nLength - offset)); if(decodedSamples > 0 && channels == sample.GetNumChannels()) { if(sample.uFlags[CHN_16BIT]) { CopyAudio(mpt::audio_span_interleaved(sample.sample16() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } else { CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } } offset += decodedSamples; error = stb_vorbis_get_error(vorb); } stb_vorbis_close(vorb); } else { unsupportedSamples = true; } #else // !VORBIS unsupportedSamples = true; #endif // VORBIS } } if(m_nType == MOD_TYPE_XM) { // Transfer XM instrument vibrato to samples for(INSTRUMENTINDEX ins = 0; ins < m_nInstruments; ins++) { PropagateXMAutoVibrato(ins + 1, static_cast(instrVibrato[ins].type.get()), instrVibrato[ins].sweep, instrVibrato[ins].depth, instrVibrato[ins].rate); } } if((fileHeader.flags & MO3FileHeader::hasPlugins) && musicChunk.CanRead(1)) { // Plugin data uint8 pluginFlags = musicChunk.ReadUint8(); if(pluginFlags & 1) { // Channel plugins for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) { ChnSettings[chn].nMixPlugin = static_cast(musicChunk.ReadUint32LE()); } } while(musicChunk.CanRead(1)) { PLUGINDEX plug = musicChunk.ReadUint8(); if(!plug) break; FileReader pluginChunk = musicChunk.ReadChunk(musicChunk.ReadUint32LE()); #ifndef NO_PLUGINS if(plug <= MAX_MIXPLUGINS) { ReadMixPluginChunk(pluginChunk, m_MixPlugins[plug - 1]); } #endif // NO_PLUGINS } } mpt::ustring madeWithTracker; uint16 cwtv = 0; uint16 cmwt = 0; while(musicChunk.CanRead(8)) { uint32 id = musicChunk.ReadUint32LE(); uint32 len = musicChunk.ReadUint32LE(); FileReader chunk = musicChunk.ReadChunk(len); switch(id) { case MagicLE("VERS"): // Tracker magic bytes (depending on format) switch(m_nType) { case MOD_TYPE_IT: cwtv = chunk.ReadUint16LE(); cmwt = chunk.ReadUint16LE(); /*switch(cwtv >> 12) { }*/ break; case MOD_TYPE_S3M: cwtv = chunk.ReadUint16LE(); break; case MOD_TYPE_XM: chunk.ReadString(madeWithTracker, mpt::Charset::CP437, std::min(FileReader::off_t(32), chunk.GetLength())); break; case MOD_TYPE_MTM: { uint8 mtmVersion = chunk.ReadUint8(); madeWithTracker = MPT_UFORMAT("MultiTracker {}.{}")(mtmVersion >> 4, mtmVersion & 0x0F); } break; default: break; } break; case MagicLE("PRHI"): m_nDefaultRowsPerBeat = chunk.ReadUint8(); m_nDefaultRowsPerMeasure = chunk.ReadUint8(); break; case MagicLE("MIDI"): // Full MIDI config chunk.ReadStruct(m_MidiCfg); m_MidiCfg.Sanitize(); break; case MagicLE("OMPT"): // Read pattern names: "PNAM" if(chunk.ReadMagic("PNAM")) { FileReader patterns = chunk.ReadChunk(chunk.ReadUint32LE()); const PATTERNINDEX namedPats = std::min(static_cast(patterns.GetLength() / MAX_PATTERNNAME), Patterns.Size()); for(PATTERNINDEX pat = 0; pat < namedPats; pat++) { char patName[MAX_PATTERNNAME]; patterns.ReadString(patName, MAX_PATTERNNAME); Patterns[pat].SetName(patName); } } // Read channel names: "CNAM" if(chunk.ReadMagic("CNAM")) { FileReader channels = chunk.ReadChunk(chunk.ReadUint32LE()); const CHANNELINDEX namedChans = std::min(static_cast(channels.GetLength() / MAX_CHANNELNAME), GetNumChannels()); for(CHANNELINDEX chn = 0; chn < namedChans; chn++) { channels.ReadString(ChnSettings[chn].szName, MAX_CHANNELNAME); } } LoadExtendedInstrumentProperties(chunk); LoadExtendedSongProperties(chunk, true); if(cwtv > 0x0889 && cwtv <= 0x8FF) { m_nType = MOD_TYPE_MPT; LoadMPTMProperties(chunk, cwtv); } if(m_dwLastSavedWithVersion) { madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); } break; } } if((GetType() == MOD_TYPE_IT && cwtv >= 0x0100 && cwtv < 0x0214) || (GetType() == MOD_TYPE_S3M && cwtv >= 0x3100 && cwtv < 0x3214) || (GetType() == MOD_TYPE_S3M && cwtv >= 0x1300 && cwtv < 0x1320)) { // Ignore MIDI data in files made with IT older than version 2.14 and old ST3 versions. m_MidiCfg.ClearZxxMacros(); } if(fileHeader.flags & MO3FileHeader::modplugMode) { // Apply some old ModPlug (mis-)behaviour if(!m_dwLastSavedWithVersion) { // These fixes are only applied when the OpenMPT version number is not known, as otherwise the song upgrade feature will take care of it. for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) { if(ModInstrument *ins = Instruments[i]) { // Fix pitch / filter envelope being shortened by one tick (for files before v1.20) ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType()); // Fix excessive pan swing range (for files before v1.26) ins->nPanSwing = (ins->nPanSwing + 3) / 4u; } } } if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) { m_playBehaviour.reset(kITOffset); m_playBehaviour.reset(kFT2ST3OffsetOutOfRange); } if(m_dwLastSavedWithVersion < MPT_V("1.23.00.00")) m_playBehaviour.reset(kFT2Periods); if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) m_playBehaviour.reset(kITInstrWithNoteOff); } if(madeWithTracker.empty()) madeWithTracker = MPT_UFORMAT("MO3 v{}")(version); else madeWithTracker = MPT_UFORMAT("MO3 v{} ({})")(version, madeWithTracker); m_modFormat.formatName = MPT_UFORMAT("Un4seen MO3 v{}")(version); m_modFormat.type = U_("mo3"); switch(GetType()) { case MOD_TYPE_MTM: m_modFormat.originalType = U_("mtm"); m_modFormat.originalFormatName = U_("MultiTracker"); break; case MOD_TYPE_MOD: m_modFormat.originalType = U_("mod"); m_modFormat.originalFormatName = U_("Generic MOD"); break; case MOD_TYPE_XM: m_modFormat.originalType = U_("xm"); m_modFormat.originalFormatName = U_("FastTracker 2"); break; case MOD_TYPE_S3M: m_modFormat.originalType = U_("s3m"); m_modFormat.originalFormatName = U_("Scream Tracker 3"); break; case MOD_TYPE_IT: m_modFormat.originalType = U_("it"); if(cmwt) m_modFormat.originalFormatName = MPT_UFORMAT("Impulse Tracker {}.{}")(cmwt >> 8, mpt::ufmt::hex0<2>(cmwt & 0xFF)); else m_modFormat.originalFormatName = U_("Impulse Tracker"); break; case MOD_TYPE_MPT: m_modFormat.originalType = U_("mptm"); m_modFormat.originalFormatName = U_("OpenMPT MPTM"); break; default: MPT_ASSERT_NOTREACHED(); } m_modFormat.madeWithTracker = std::move(madeWithTracker); if(m_dwLastSavedWithVersion) m_modFormat.charset = mpt::Charset::Windows1252; else if(GetType() == MOD_TYPE_MOD) m_modFormat.charset = mpt::Charset::Amiga_no_C1; else m_modFormat.charset = mpt::Charset::CP437; if(unsupportedSamples) { AddToLog(LogWarning, U_("Some compressed samples could not be loaded because they use an unsupported codec.")); } return true; } OPENMPT_NAMESPACE_END