From: <sv...@op...> - 2024-04-16 21:37:23
|
Author: sagamusix Date: Tue Apr 16 22:21:35 2024 New Revision: 20610 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=20610 Log: [Fix] MO3: It was impossible to duplicate sample data from a sample compressed with Ogg Vorbis. [Ref] MO3: Samples are now read after all other music data as a preparation for an upcoming change. Modified: trunk/OpenMPT/soundlib/Load_mo3.cpp Modified: trunk/OpenMPT/soundlib/Load_mo3.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_mo3.cpp Tue Apr 16 15:27:34 2024 (r20609) +++ trunk/OpenMPT/soundlib/Load_mo3.cpp Tue Apr 16 22:21:35 2024 (r20610) @@ -67,26 +67,26 @@ { 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, + 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, + 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) + uint8le numChannels; // 1...64 (limited by channel panning and volume) uint16le numOrders; uint16le restartPos; uint16le numPatterns; @@ -95,12 +95,12 @@ 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 + 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]; }; @@ -112,14 +112,14 @@ { enum MO3EnvelopeFlags { - envEnabled = 0x01, - envSustain = 0x02, - envLoop = 0x04, - envFilter = 0x10, - envCarry = 0x20, + envEnabled = 0x01, + envSustain = 0x02, + envLoop = 0x04, + envFilter = 0x10, + envCarry = 0x20, }; - uint8le flags; // See MO3EnvelopeFlags + uint8le flags; // See MO3EnvelopeFlags uint8le numNodes; uint8le sustainStart; uint8le sustainEnd; @@ -157,11 +157,11 @@ { enum MO3InstrumentFlags { - playOnMIDI = 0x01, - mute = 0x02, + playOnMIDI = 0x01, + mute = 0x02, }; - uint32le flags; // See MO3InstrumentFlags + uint32le flags; // See MO3InstrumentFlags uint16le sampleMap[120][2]; MO3Envelope volEnv; MO3Envelope panEnv; @@ -172,23 +172,23 @@ uint8le sweep; uint8le depth; uint8le rate; - } vibrato; // Applies to all samples of this instrument (XM) + } 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 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 + 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 @@ -281,23 +281,23 @@ smpCompressionMask = 0x1000 | 0x2000 | 0x4000 | 0x8000 }; - uint32le freqFinetune; // Frequency in S3M and IT, finetune (0...255) in MOD, MTM, XM + 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 + uint8le defaultVolume; // 0...64 + uint16le panning; // 0...256 if enabled, 0xFFFF otherwise uint32le length; uint32le loopStart; uint32le loopEnd; - uint16le flags; // See MO3SampleFlags + 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 + 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 + 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 @@ -353,13 +353,13 @@ // 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 +struct MO3SampleInfo { FileReader chunk; - uint16 headerSize; - int16 sharedHeader; - MO3SampleChunk(const FileReader &chunk_ = FileReader(), uint16 headerSize_ = 0, int16 sharedHeader_ = 0) - : chunk(chunk_), headerSize(headerSize_), sharedHeader(sharedHeader_) {} + const MO3Sample smpHeader; + const int16 sharedHeader; + MO3SampleInfo(MO3Sample smpHeader, int16 sharedHeader) + : smpHeader{smpHeader}, sharedHeader{sharedHeader} {} }; @@ -1271,10 +1271,9 @@ if(isSampleMode) m_nInstruments = 0; - std::vector<MO3SampleChunk> sampleChunks(m_nSamples); + std::vector<MO3SampleInfo> sampleInfos; const bool frequencyIsHertz = (version >= 5 || !(fileHeader.flags & MO3FileHeader::linearSlides)); - bool unsupportedSamples = false; for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { ModSample &sample = Samples[smp]; @@ -1298,130 +1297,314 @@ sharedOggHeader = musicChunk.ReadInt16LE(); } - if(!(loadFlags & loadSampleData)) - continue; + if(loadFlags & loadSampleData) + { + sampleInfos.reserve(m_nSamples); + sampleInfos.emplace_back(smpHeader, sharedOggHeader); + } + } - const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); - if(!compression && smpHeader.compressedSize == 0) + if(m_nType == MOD_TYPE_XM) + { + // Transfer XM instrument vibrato to samples + for(INSTRUMENTINDEX ins = 0; ins < m_nInstruments; ins++) { - // 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) + PropagateXMAutoVibrato(ins + 1, static_cast<VibratoType>(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) { - // Duplicate sample - sample.CopyWaveform(Samples[smp + smpHeader.compressedSize]); - } else if(smpHeader.compressedSize > 0) + // Channel plugins + for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + { + ChnSettings[chn].nMixPlugin = static_cast<PLUGINDEX>(musicChunk.ReadUint32LE()); + } + } + while(musicChunk.CanRead(1)) { - 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<SmpLength>(maxLength)); + 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 + } + } - if(compression == MO3Sample::smpDeltaCompression) + 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) { - if(sample.AllocateSample()) + case MOD_TYPE_IT: + cwtv = chunk.ReadUint16LE(); + cmwt = chunk.ReadUint16LE(); + /*switch(cwtv >> 12) { - if(smpHeader.flags & MO3Sample::smp16Bit) - UnpackMO3DeltaSample<MO3Delta16BitParams>(sampleData, sample.sample16(), sample.nLength, numChannels); - else - UnpackMO3DeltaSample<MO3Delta8BitParams>(sampleData, sample.sample8(), sample.nLength, numChannels); - } - } else if(compression == MO3Sample::smpDeltaPrediction) + + }*/ + break; + case MOD_TYPE_S3M: + cwtv = chunk.ReadUint16LE(); + break; + case MOD_TYPE_XM: + chunk.ReadString<mpt::String::spacePadded>(madeWithTracker, mpt::Charset::CP437, std::min(FileReader::pos_type(32), chunk.GetLength())); + break; + case MOD_TYPE_MTM: { - if(sample.AllocateSample()) - { - if(smpHeader.flags & MO3Sample::smp16Bit) - UnpackMO3DeltaPredictionSample<MO3Delta16BitParams>(sampleData, sample.sample16(), sample.nLength, numChannels); - else - UnpackMO3DeltaPredictionSample<MO3Delta8BitParams>(sampleData, sample.sample8(), sample.nLength, numChannels); - } - } else if(compression == MO3Sample::smpCompressionOgg || compression == MO3Sample::smpSharedOgg) + 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<MIDIMacroConfigData>(m_MidiCfg); + m_MidiCfg.Sanitize(); + break; + case MagicLE("OMPT"): + // Read pattern names: "PNAM" + if(chunk.ReadMagic("PNAM")) { - // 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) + FileReader patterns = chunk.ReadChunk(chunk.ReadUint32LE()); + const PATTERNINDEX namedPats = std::min(static_cast<PATTERNINDEX>(patterns.GetLength() / MAX_PATTERNNAME), Patterns.Size()); + + for(PATTERNINDEX pat = 0; pat < namedPats; pat++) { - // 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()); + char patName[MAX_PATTERNNAME]; + patterns.ReadString<mpt::String::maybeNullTerminated>(patName, MAX_PATTERNNAME); + Patterns[pat].SetName(patName); } + } - 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 + // Read channel names: "CNAM" + if(chunk.ReadMagic("CNAM")) + { + FileReader channels = chunk.ReadChunk(chunk.ReadUint32LE()); + const CHANNELINDEX namedChans = std::min(static_cast<CHANNELINDEX>(channels.GetLength() / MAX_CHANNELNAME), GetNumChannels()); + for(CHANNELINDEX chn = 0; chn < namedChans; chn++) { - unsupportedSamples = true; + channels.ReadString<mpt::String::maybeNullTerminated>(ChnSettings[chn].szName, MAX_CHANNELNAME); } - } else if(compression == MO3Sample::smpOPLInstrument) + } + + 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++) { - OPLPatch patch; - if(sampleData.ReadArray(patch)) + if(ModInstrument *ins = Instruments[i]) { - sample.SetAdlib(true, patch); + // 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 = static_cast<uint8>((ins->nPanSwing + 3) / 4u); } - } else - { - unsupportedSamples = true; } } + 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); } - // Now we can load Ogg samples with shared headers. - if(loadFlags & loadSampleData) + 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()) { - for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + 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(!(loadFlags & loadSampleData)) + return true; + + bool unsupportedSamples = false; + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + MO3SampleInfo &smpInfo = sampleInfos[smp - 1]; + const MO3Sample &smpHeader = smpInfo.smpHeader; + const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); + + if(!compression && smpHeader.compressedSize == 0) { - MO3SampleChunk &sampleChunk = sampleChunks[smp - 1]; - // Is this an Ogg sample? - if(!sampleChunk.chunk.IsValid()) - continue; + // 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) + { + // Compressed sample; we read those in a second pass because Ogg samples with shared headers may reference a later sample's header + smpInfo.chunk = file.ReadChunk(smpHeader.compressedSize); + } + } + + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + ModSample &sample = Samples[smp]; + MO3SampleInfo &smpInfo = sampleInfos[smp - 1]; + const MO3Sample &smpHeader = smpInfo.smpHeader; - SAMPLEINDEX sharedOggHeader = (smp + sampleChunk.sharedHeader > 0) ? static_cast<SAMPLEINDEX>(smp + sampleChunk.sharedHeader) : smp; + if(smpHeader.compressedSize < 0 && (smp + smpHeader.compressedSize) > 0) + { + // Duplicate sample + sample.CopyWaveform(Samples[smp + smpHeader.compressedSize]); + continue; + } + + // Not a compressed sample? + if(!smpInfo.chunk.IsValid()) + continue; + + if(smpHeader.flags & MO3Sample::smp16Bit) + sample.uFlags.set(CHN_16BIT); + if(smpHeader.flags & MO3Sample::smpStereo) + sample.uFlags.set(CHN_STEREO); + + FileReader &sampleData = smpInfo.chunk; + const uint8 numChannels = sample.GetNumChannels(); + const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); + + 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<SmpLength>(maxLength)); + } + + if(compression == MO3Sample::smpDeltaCompression) + { + if(sample.AllocateSample()) + { + if(smpHeader.flags & MO3Sample::smp16Bit) + UnpackMO3DeltaSample<MO3Delta16BitParams>(sampleData, sample.sample16(), sample.nLength, numChannels); + else + UnpackMO3DeltaSample<MO3Delta8BitParams>(sampleData, sample.sample8(), sample.nLength, numChannels); + } + } else if(compression == MO3Sample::smpDeltaPrediction) + { + if(sample.AllocateSample()) + { + if(smpHeader.flags & MO3Sample::smp16Bit) + UnpackMO3DeltaPredictionSample<MO3Delta16BitParams>(sampleData, sample.sample16(), sample.nLength, numChannels); + else + UnpackMO3DeltaPredictionSample<MO3Delta8BitParams>(sampleData, sample.sample8(), sample.nLength, numChannels); + } + } else if(compression == MO3Sample::smpCompressionOgg || compression == MO3Sample::smpSharedOgg) + { + const uint16 sharedHeaderSize = smpHeader.encoderDelay; + SAMPLEINDEX sharedOggHeader = (smp + smpInfo.sharedHeader > 0) ? static_cast<SAMPLEINDEX>(smp + smpInfo.sharedHeader) : smp; // 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; + const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples && sharedHeaderSize > 0; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) @@ -1448,8 +1631,8 @@ 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); + sampleInfos[sharedOggHeader - 1].chunk.Rewind(); + FileReader sharedChunk = sampleInfos[sharedOggHeader - 1].chunk.ReadChunk(sharedHeaderSize); sharedChunk.Rewind(); std::vector<uint32> streamSerials; @@ -1472,7 +1655,7 @@ } streamSerials.clear(); - while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) + while(Ogg::ReadPageAndSkipJunk(smpInfo.chunk, oggPageInfo, oggPageData)) { auto it = std::find(streamSerials.begin(), streamSerials.end(), oggPageInfo.header.bitstream_serial_number); if(it == streamSerials.end()) @@ -1497,8 +1680,8 @@ 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); + sampleInfos[sharedOggHeader - 1].chunk.Rewind(); + FileReader sharedChunk = sampleInfos[sharedOggHeader - 1].chunk.ReadChunk(sharedHeaderSize); sharedChunk.Rewind(); std::vector<uint32> dataStreamSerials; @@ -1508,7 +1691,7 @@ // Gather bitstream serial numbers form sample data chunk dataStreamSerials.clear(); - while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) + while(Ogg::ReadPageAndSkipJunk(smpInfo.chunk, oggPageInfo, oggPageData)) { if(!mpt::contains(dataStreamSerials, oggPageInfo.header.bitstream_serial_number)) { @@ -1566,8 +1749,8 @@ std::string mergedStreamData = mergedStream.str(); mergedData.insert(mergedData.end(), mergedStreamData.begin(), mergedStreamData.end()); - sampleChunk.chunk.Rewind(); - FileReader::PinnedView sampleChunkView = sampleChunk.chunk.GetPinnedView(); + smpInfo.chunk.Rewind(); + FileReader::PinnedView sampleChunkView = smpInfo.chunk.GetPinnedView(); mpt::span<const char> sampleChunkViewSpan = mpt::byte_cast<mpt::span<const char>>(sampleChunkView.span()); mergedData.insert(mergedData.end(), sampleChunkViewSpan.begin(), sampleChunkViewSpan.end()); @@ -1575,21 +1758,20 @@ } FileReader mergedDataChunk(mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(mergedData))); - FileReader &sampleData = sharedHeader ? mergedDataChunk : sampleChunk.chunk; - FileReader &headerChunk = sampleData; + FileReader &sampleChunk = sharedHeader ? mergedDataChunk : smpInfo.chunk; + FileReader &headerChunk = sampleChunk; #else // !(MPT_WITH_VORBIS && MPT_WITH_VORBISFILE) - FileReader &sampleData = sampleChunk.chunk; - FileReader &headerChunk = sharedHeader ? sampleChunks[sharedOggHeader - 1].chunk : sampleData; + FileReader &headerChunk = sharedHeader ? sampleInfos[sharedOggHeader - 1].chunk : sampleData; #if defined(MPT_WITH_STBVORBIS) - std::size_t initialRead = sharedHeader ? sampleChunk.headerSize : headerChunk.GetLength(); + std::size_t initialRead = sharedHeader ? sharedHeaderSize : headerChunk.GetLength(); #endif // MPT_WITH_STBVORBIS #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE headerChunk.Rewind(); - if(sharedHeader && !headerChunk.CanRead(sampleChunk.headerSize)) + if(sharedHeader && !headerChunk.CanRead(sharedHeaderSize)) continue; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) @@ -1601,14 +1783,13 @@ &VorbisfileFilereaderTell}; OggVorbis_File vf; MemsetZero(vf); - if(ov_open_callbacks(mpt::void_ptr<FileReader>(&sampleData), &vf, nullptr, 0, callbacks) == 0) + if(ov_open_callbacks(mpt::void_ptr<FileReader>(&sampleChunk), &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; @@ -1688,7 +1869,6 @@ 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)) @@ -1725,218 +1905,52 @@ unsupportedSamples = true; #endif // VORBIS - } - } - - if(m_nType == MOD_TYPE_XM) - { - // Transfer XM instrument vibrato to samples - for(INSTRUMENTINDEX ins = 0; ins < m_nInstruments; ins++) + } else if(compression == MO3Sample::smpCompressionMPEG) { - PropagateXMAutoVibrato(ins + 1, static_cast<VibratoType>(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<PLUGINDEX>(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<mpt::String::spacePadded>(madeWithTracker, mpt::Charset::CP437, std::min(FileReader::pos_type(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<MIDIMacroConfigData>(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<PATTERNINDEX>(patterns.GetLength() / MAX_PATTERNNAME), Patterns.Size()); - - for(PATTERNINDEX pat = 0; pat < namedPats; pat++) - { - char patName[MAX_PATTERNNAME]; - patterns.ReadString<mpt::String::maybeNullTerminated>(patName, MAX_PATTERNNAME); - Patterns[pat].SetName(patName); + // 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 encoderDelay = smpHeader.encoderDelay; + uint16 frameDelay = frame.numSamples * 2; + if(frame.isLAME && encoderDelay >= frameDelay) + { + // The info frame does not produce any output, but still counts towards the encoder delay. + encoderDelay -= frameDelay; + sampleData.Seek(frame.frameSize); + mpegData = sampleData.ReadChunk(sampleData.BytesLeft()); + } + + if(ReadMP3Sample(smp, mpegData, true, true) || ReadMediaFoundationSample(smp, mpegData, true)) + { + if(encoderDelay > 0 && encoderDelay < sample.GetSampleSizeInBytes()) + { + SmpLength delay = encoderDelay / sample.GetBytesPerSample(); + memmove(sample.sampleb(), sample.sampleb() + encoderDelay, sample.GetSampleSizeInBytes() - encoderDelay); + sample.nLength -= delay; } - } - - // Read channel names: "CNAM" - if(chunk.ReadMagic("CNAM")) - { - FileReader channels = chunk.ReadChunk(chunk.ReadUint32LE()); - const CHANNELINDEX namedChans = std::min(static_cast<CHANNELINDEX>(channels.GetLength() / MAX_CHANNELNAME), GetNumChannels()); - for(CHANNELINDEX chn = 0; chn < namedChans; chn++) - { - channels.ReadString<mpt::String::maybeNullTerminated>(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) + LimitMax(sample.nLength, smpHeader.length); + } else { - madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); + unsupportedSamples = true; } - 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) + } else if(compression == MO3Sample::smpOPLInstrument) { - // 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++) + OPLPatch patch; + if(sampleData.ReadArray(patch)) { - 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 = static_cast<uint8>((ins->nPanSwing + 3) / 4u); - } + sample.SetAdlib(true, patch); } - } - if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) + } else { - m_playBehaviour.reset(kITOffset); - m_playBehaviour.reset(kFT2ST3OffsetOutOfRange); + unsupportedSamples = true; } - 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.")); |