diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp new file mode 100644 index 00000000..ddb79653 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/XMTools.cpp @@ -0,0 +1,431 @@ +/* + * XMTools.cpp + * ----------- + * Purpose: Definition of XM file structures and helper functions + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "XMTools.h" +#include "Sndfile.h" +#include "../common/version.h" +#include <algorithm> + + +OPENMPT_NAMESPACE_BEGIN + + +// Convert OpenMPT's internal envelope representation to XM envelope data. +void XMInstrument::ConvertEnvelopeToXM(const InstrumentEnvelope &mptEnv, uint8le &numPoints, uint8le &flags, uint8le &sustain, uint8le &loopStart, uint8le &loopEnd, EnvType env) +{ + numPoints = static_cast<uint8>(std::min(std::size_t(12), static_cast<std::size_t>(mptEnv.size()))); + + // Envelope Data + for(uint8 i = 0; i < numPoints; i++) + { + switch(env) + { + case EnvTypeVol: + volEnv[i * 2] = std::min(mptEnv[i].tick, uint16_max); + volEnv[i * 2 + 1] = std::min(mptEnv[i].value, uint8(64)); + break; + case EnvTypePan: + panEnv[i * 2] = std::min(mptEnv[i].tick, uint16_max); + panEnv[i * 2 + 1] = std::min(mptEnv[i].value, uint8(63)); + break; + } + } + + // Envelope Flags + if(mptEnv.dwFlags[ENV_ENABLED]) flags |= XMInstrument::envEnabled; + if(mptEnv.dwFlags[ENV_SUSTAIN]) flags |= XMInstrument::envSustain; + if(mptEnv.dwFlags[ENV_LOOP]) flags |= XMInstrument::envLoop; + + // Envelope Loops + sustain = std::min(uint8(12), mptEnv.nSustainStart); + loopStart = std::min(uint8(12), mptEnv.nLoopStart); + loopEnd = std::min(uint8(12), mptEnv.nLoopEnd); + +} + + +// Convert OpenMPT's internal sample representation to an XMInstrument. +uint16 XMInstrument::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport) +{ + MemsetZero(*this); + + // FFF is maximum in the FT2 GUI, but it can also accept other values. MilkyTracker just allows 0...4095 and 32767 ("cut") + volFade = static_cast<uint16>(std::min(mptIns.nFadeOut, uint32(32767))); + + // Convert envelopes + ConvertEnvelopeToXM(mptIns.VolEnv, volPoints, volFlags, volSustain, volLoopStart, volLoopEnd, EnvTypeVol); + ConvertEnvelopeToXM(mptIns.PanEnv, panPoints, panFlags, panSustain, panLoopStart, panLoopEnd, EnvTypePan); + + // Create sample assignment table + auto sampleList = GetSampleList(mptIns, compatibilityExport); + for(std::size_t i = 0; i < std::size(sampleMap); i++) + { + if(mptIns.Keyboard[i + 12] > 0) + { + auto sample = std::find(sampleList.begin(), sampleList.end(), mptIns.Keyboard[i + 12]); + if(sample != sampleList.end()) + { + // Yep, we want to export this sample. + sampleMap[i] = static_cast<uint8>(sample - sampleList.begin()); + } + } + } + + if(mptIns.nMidiChannel != MidiNoChannel) + { + midiEnabled = 1; + midiChannel = (mptIns.nMidiChannel != MidiMappedChannel ? (mptIns.nMidiChannel - MidiFirstChannel) : 0); + } + midiProgram = (mptIns.nMidiProgram != 0 ? mptIns.nMidiProgram - 1 : 0); + pitchWheelRange = std::min(mptIns.midiPWD, int8(36)); + + return static_cast<uint16>(sampleList.size()); +} + + +// Get a list of samples that should be written to the file. +std::vector<SAMPLEINDEX> XMInstrument::GetSampleList(const ModInstrument &mptIns, bool compatibilityExport) const +{ + std::vector<SAMPLEINDEX> sampleList; // List of samples associated with this instrument + std::vector<bool> addedToList; // Which samples did we already add to the sample list? + + uint8 numSamples = 0; + for(std::size_t i = 0; i < std::size(sampleMap); i++) + { + const SAMPLEINDEX smp = mptIns.Keyboard[i + 12]; + if(smp > 0) + { + if(smp > addedToList.size()) + { + addedToList.resize(smp, false); + } + + if(!addedToList[smp - 1] && numSamples < (compatibilityExport ? 16 : 32)) + { + // We haven't considered this sample yet. + addedToList[smp - 1] = true; + numSamples++; + sampleList.push_back(smp); + } + } + } + return sampleList; +} + + +// Convert XM envelope data to an OpenMPT's internal envelope representation. +void XMInstrument::ConvertEnvelopeToMPT(InstrumentEnvelope &mptEnv, uint8 numPoints, uint8 flags, uint8 sustain, uint8 loopStart, uint8 loopEnd, EnvType env) const +{ + mptEnv.resize(std::min(numPoints, uint8(12))); + + // Envelope Data + for(uint32 i = 0; i < mptEnv.size(); i++) + { + switch(env) + { + case EnvTypeVol: + mptEnv[i].tick = volEnv[i * 2]; + mptEnv[i].value = static_cast<EnvelopeNode::value_t>(volEnv[i * 2 + 1]); + break; + case EnvTypePan: + mptEnv[i].tick = panEnv[i * 2]; + mptEnv[i].value = static_cast<EnvelopeNode::value_t>(panEnv[i * 2 + 1]); + break; + } + + if(i > 0 && mptEnv[i].tick < mptEnv[i - 1].tick && !(mptEnv[i].tick & 0xFF00)) + { + // libmikmod code says: "Some broken XM editing program will only save the low byte of the position + // value. Try to compensate by adding the missing high byte." + // Note: MPT 1.07's XI instrument saver omitted the high byte of envelope nodes. + // This might be the source for some broken envelopes in IT and XM files. + mptEnv[i].tick |= mptEnv[i - 1].tick & 0xFF00; + if(mptEnv[i].tick < mptEnv[i - 1].tick) + mptEnv[i].tick += 0x100; + } + } + + // Envelope Flags + mptEnv.dwFlags.reset(); + if((flags & XMInstrument::envEnabled) != 0 && !mptEnv.empty()) mptEnv.dwFlags.set(ENV_ENABLED); + + // Envelope Loops + if(sustain < 12) + { + if((flags & XMInstrument::envSustain) != 0) mptEnv.dwFlags.set(ENV_SUSTAIN); + mptEnv.nSustainStart = mptEnv.nSustainEnd = sustain; + } + + if(loopEnd < 12 && loopEnd >= loopStart) + { + if((flags & XMInstrument::envLoop) != 0) mptEnv.dwFlags.set(ENV_LOOP); + mptEnv.nLoopStart = loopStart; + mptEnv.nLoopEnd = loopEnd; + } +} + + +// Convert an XMInstrument to OpenMPT's internal instrument representation. +void XMInstrument::ConvertToMPT(ModInstrument &mptIns) const +{ + mptIns.nFadeOut = volFade; + + // Convert envelopes + ConvertEnvelopeToMPT(mptIns.VolEnv, volPoints, volFlags, volSustain, volLoopStart, volLoopEnd, EnvTypeVol); + ConvertEnvelopeToMPT(mptIns.PanEnv, panPoints, panFlags, panSustain, panLoopStart, panLoopEnd, EnvTypePan); + + // Create sample assignment table + for(std::size_t i = 0; i < std::size(sampleMap); i++) + { + mptIns.Keyboard[i + 12] = sampleMap[i]; + } + + if(midiEnabled) + { + mptIns.nMidiChannel = midiChannel + MidiFirstChannel; + Limit(mptIns.nMidiChannel, uint8(MidiFirstChannel), uint8(MidiLastChannel)); + mptIns.nMidiProgram = static_cast<uint8>(std::min(static_cast<uint16>(midiProgram), uint16(127)) + 1); + } + mptIns.midiPWD = static_cast<int8>(pitchWheelRange); +} + + +// Apply auto-vibrato settings from sample to file. +void XMInstrument::ApplyAutoVibratoToXM(const ModSample &mptSmp, MODTYPE fromType) +{ + vibType = mptSmp.nVibType; + vibSweep = mptSmp.nVibSweep; + vibDepth = mptSmp.nVibDepth; + vibRate = mptSmp.nVibRate; + + if((vibDepth | vibRate) != 0 && !(fromType & MOD_TYPE_XM)) + { + if(mptSmp.nVibSweep != 0) + vibSweep = mpt::saturate_cast<decltype(vibSweep)::base_type>(Util::muldivr_unsigned(mptSmp.nVibDepth, 256, mptSmp.nVibSweep)); + else + vibSweep = 255; + } +} + + +// Apply auto-vibrato settings from file to a sample. +void XMInstrument::ApplyAutoVibratoToMPT(ModSample &mptSmp) const +{ + mptSmp.nVibType = static_cast<VibratoType>(vibType.get()); + mptSmp.nVibSweep = vibSweep; + mptSmp.nVibDepth = vibDepth; + mptSmp.nVibRate = vibRate; +} + + +// Write stuff to the header that's always necessary (also for empty instruments) +void XMInstrumentHeader::Finalise() +{ + size = sizeof(XMInstrumentHeader); + if(numSamples > 0) + { + sampleHeaderSize = sizeof(XMSample); + } else + { + // TODO: FT2 completely ignores MIDI settings (and also the less important stuff) if not at least one (empty) sample is assigned to this instrument! + size -= sizeof(XMInstrument); + sampleHeaderSize = 0; + } +} + + +// Convert OpenMPT's internal sample representation to an XMInstrumentHeader. +void XMInstrumentHeader::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport) +{ + numSamples = instrument.ConvertToXM(mptIns, compatibilityExport); + mpt::String::WriteBuf(mpt::String::spacePadded, name) = mptIns.name; + + type = mptIns.nMidiProgram; // If FT2 writes crap here, we can do so, too! (we probably shouldn't, though. This is just for backwards compatibility with old MPT versions.) +} + + +// Convert an XMInstrumentHeader to OpenMPT's internal instrument representation. +void XMInstrumentHeader::ConvertToMPT(ModInstrument &mptIns) const +{ + instrument.ConvertToMPT(mptIns); + + // Create sample assignment table + for(std::size_t i = 0; i < std::size(instrument.sampleMap); i++) + { + if(instrument.sampleMap[i] < numSamples) + { + mptIns.Keyboard[i + 12] = instrument.sampleMap[i]; + } else + { + mptIns.Keyboard[i + 12] = 0; + } + } + + mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name); + + // Old MPT backwards compatibility + if(!instrument.midiEnabled) + { + mptIns.nMidiProgram = type; + } +} + + +// Convert OpenMPT's internal sample representation to an XIInstrumentHeader. +void XIInstrumentHeader::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport) +{ + numSamples = instrument.ConvertToXM(mptIns, compatibilityExport); + + memcpy(signature, "Extended Instrument: ", 21); + mpt::String::WriteBuf(mpt::String::spacePadded, name) = mptIns.name; + eof = 0x1A; + + const std::string openMptTrackerName = mpt::ToCharset(mpt::Charset::CP437, Version::Current().GetOpenMPTVersionString()); + mpt::String::WriteBuf(mpt::String::spacePadded, trackerName) = openMptTrackerName; + + version = 0x102; +} + + +// Convert an XIInstrumentHeader to OpenMPT's internal instrument representation. +void XIInstrumentHeader::ConvertToMPT(ModInstrument &mptIns) const +{ + instrument.ConvertToMPT(mptIns); + + // Fix sample assignment table + for(std::size_t i = 12; i < std::size(instrument.sampleMap) + 12; i++) + { + if(mptIns.Keyboard[i] >= numSamples) + { + mptIns.Keyboard[i] = 0; + } + } + + mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name); +} + + +// Convert OpenMPT's internal sample representation to an XMSample. +void XMSample::ConvertToXM(const ModSample &mptSmp, MODTYPE fromType, bool compatibilityExport) +{ + MemsetZero(*this); + + // Volume / Panning + vol = static_cast<uint8>(std::min(mptSmp.nVolume / 4u, 64u)); + pan = static_cast<uint8>(std::min(mptSmp.nPan, uint16(255))); + + // Sample Frequency + if((fromType & (MOD_TYPE_MOD | MOD_TYPE_XM))) + { + finetune = mptSmp.nFineTune; + relnote = mptSmp.RelativeTone; + } else + { + std::tie(relnote, finetune) = ModSample::FrequencyToTranspose(mptSmp.nC5Speed); + } + + flags = 0; + if(mptSmp.uFlags[CHN_PINGPONGLOOP]) + flags |= XMSample::sampleBidiLoop; + else if(mptSmp.uFlags[CHN_LOOP]) + flags |= XMSample::sampleLoop; + + // Sample Length and Loops + length = mpt::saturate_cast<uint32>(mptSmp.nLength); + loopStart = mpt::saturate_cast<uint32>(mptSmp.nLoopStart); + loopLength = mpt::saturate_cast<uint32>(mptSmp.nLoopEnd - mptSmp.nLoopStart); + + if(mptSmp.uFlags[CHN_16BIT]) + { + flags |= XMSample::sample16Bit; + length *= 2; + loopStart *= 2; + loopLength *= 2; + } + + if(mptSmp.uFlags[CHN_STEREO] && !compatibilityExport) + { + flags |= XMSample::sampleStereo; + length *= 2; + loopStart *= 2; + loopLength *= 2; + } +} + + +// Convert an XMSample to OpenMPT's internal sample representation. +void XMSample::ConvertToMPT(ModSample &mptSmp) const +{ + mptSmp.Initialize(MOD_TYPE_XM); + + // Volume + mptSmp.nVolume = vol * 4; + LimitMax(mptSmp.nVolume, uint16(256)); + + // Panning + mptSmp.nPan = pan; + mptSmp.uFlags = CHN_PANNING; + + // Sample Frequency + mptSmp.nFineTune = finetune; + mptSmp.RelativeTone = relnote; + + // Sample Length and Loops + mptSmp.nLength = length; + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = mptSmp.nLoopStart + loopLength; + + if((flags & XMSample::sample16Bit)) + { + mptSmp.nLength /= 2; + mptSmp.nLoopStart /= 2; + mptSmp.nLoopEnd /= 2; + } + + if((flags & XMSample::sampleStereo)) + { + mptSmp.nLength /= 2; + mptSmp.nLoopStart /= 2; + mptSmp.nLoopEnd /= 2; + } + + if((flags & (XMSample::sampleLoop | XMSample::sampleBidiLoop)) && mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + if((flags & XMSample::sampleBidiLoop)) + { + mptSmp.uFlags.set(CHN_PINGPONGLOOP); + } + } + + mptSmp.filename = ""; +} + + +// Retrieve the internal sample format flags for this instrument. +SampleIO XMSample::GetSampleFormat() const +{ + if(reserved == sampleADPCM && !(flags & (XMSample::sample16Bit | XMSample::sampleStereo))) + { + // MODPlugin :( + return SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::ADPCM); + } + + return SampleIO( + (flags & XMSample::sample16Bit) ? SampleIO::_16bit : SampleIO::_8bit, + (flags & XMSample::sampleStereo) ? SampleIO::stereoSplit : SampleIO::mono, + SampleIO::littleEndian, + SampleIO::deltaPCM); +} + + +OPENMPT_NAMESPACE_END |