1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123 |
- /*
- * Load_ams.cpp
- * ------------
- * Purpose: AMS (Extreme's Tracker / Velvet Studio) module loader
- * Notes : Extreme was renamed to Velvet Development at some point,
- * and thus they also renamed their tracker from
- * "Extreme's Tracker" to "Velvet Studio".
- * While the two programs look rather similiar, the structure of both
- * programs' "AMS" format is significantly different in some places -
- * Velvet Studio is a rather advanced tracker in comparison to Extreme's Tracker.
- * The source code of Velvet Studio has been released into the
- * public domain in 2013: https://github.com/Patosc/VelvetStudio/commits/master
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Loaders.h"
- OPENMPT_NAMESPACE_BEGIN
- // Read AMS or AMS2 (newVersion = true) pattern. At least this part of the format is more or less identical between the two trackers...
- static void ReadAMSPattern(CPattern &pattern, bool newVersion, FileReader &patternChunk)
- {
- enum
- {
- emptyRow = 0xFF, // No commands on row
- endOfRowMask = 0x80, // If set, no more commands on this row
- noteMask = 0x40, // If set, no note+instr in this command
- channelMask = 0x1F, // Mask for extracting channel
- // Note flags
- readNextCmd = 0x80, // One more command follows
- noteDataMask = 0x7F, // Extract note
- // Command flags
- volCommand = 0x40, // Effect is compressed volume command
- commandMask = 0x3F, // Command or volume mask
- };
- // Effect translation table for extended (non-Protracker) effects
- static constexpr ModCommand::COMMAND effTrans[] =
- {
- CMD_S3MCMDEX, // Forward / Backward
- CMD_PORTAMENTOUP, // Extra fine slide up
- CMD_PORTAMENTODOWN, // Extra fine slide up
- CMD_RETRIG, // Retrigger
- CMD_NONE,
- CMD_TONEPORTAVOL, // Toneporta with fine volume slide
- CMD_VIBRATOVOL, // Vibrato with fine volume slide
- CMD_NONE,
- CMD_PANNINGSLIDE,
- CMD_NONE,
- CMD_VOLUMESLIDE, // Two times finder volume slide than Axx
- CMD_NONE,
- CMD_CHANNELVOLUME, // Channel volume (0...127)
- CMD_PATTERNBREAK, // Long pattern break (in hex)
- CMD_S3MCMDEX, // Fine slide commands
- CMD_NONE, // Fractional BPM
- CMD_KEYOFF, // Key off at tick xx
- CMD_PORTAMENTOUP, // Porta up, but uses all octaves (?)
- CMD_PORTAMENTODOWN, // Porta down, but uses all octaves (?)
- CMD_NONE,
- CMD_NONE,
- CMD_NONE,
- CMD_NONE,
- CMD_NONE,
- CMD_NONE,
- CMD_NONE,
- CMD_GLOBALVOLSLIDE, // Global volume slide
- CMD_NONE,
- CMD_GLOBALVOLUME, // Global volume (0... 127)
- };
- ModCommand dummy;
- for(ROWINDEX row = 0; row < pattern.GetNumRows(); row++)
- {
- PatternRow baseRow = pattern.GetRow(row);
- while(patternChunk.CanRead(1))
- {
- const uint8 flags = patternChunk.ReadUint8();
- if(flags == emptyRow)
- {
- break;
- }
- const CHANNELINDEX chn = (flags & channelMask);
- ModCommand &m = chn < pattern.GetNumChannels() ? baseRow[chn] : dummy;
- bool moreCommands = true;
- if(!(flags & noteMask))
- {
- // Read note + instr
- uint8 note = patternChunk.ReadUint8();
- moreCommands = (note & readNextCmd) != 0;
- note &= noteDataMask;
- if(note == 1)
- {
- m.note = NOTE_KEYOFF;
- } else if(note >= 2 && note <= 121 && newVersion)
- {
- m.note = note - 2 + NOTE_MIN;
- } else if(note >= 12 && note <= 108 && !newVersion)
- {
- m.note = note + 12 + NOTE_MIN;
- }
- m.instr = patternChunk.ReadUint8();
- }
- while(moreCommands)
- {
- // Read one more effect command
- ModCommand origCmd = m;
- const uint8 command = patternChunk.ReadUint8(), effect = (command & commandMask);
- moreCommands = (command & readNextCmd) != 0;
- if(command & volCommand)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = effect;
- } else
- {
- m.param = patternChunk.ReadUint8();
- if(effect < 0x10)
- {
- // PT commands
- m.command = effect;
- CSoundFile::ConvertModCommand(m);
- // Post-fix some commands
- switch(m.command)
- {
- case CMD_PANNING8:
- // 4-Bit panning
- m.command = CMD_PANNING8;
- m.param = (m.param & 0x0F) * 0x11;
- break;
- case CMD_VOLUME:
- m.command = CMD_NONE;
- m.volcmd = VOLCMD_VOLUME;
- m.vol = static_cast<ModCommand::VOL>(std::min((m.param + 1) / 2, 64));
- break;
- case CMD_MODCMDEX:
- if(m.param == 0x80)
- {
- // Break sample loop (cut after loop)
- m.command = CMD_NONE;
- } else
- {
- m.ExtendedMODtoS3MEffect();
- }
- break;
- }
- } else if(effect < 0x10 + mpt::array_size<decltype(effTrans)>::size)
- {
- // Extended commands
- m.command = effTrans[effect - 0x10];
- // Post-fix some commands
- switch(effect)
- {
- case 0x10:
- // Play sample forwards / backwards
- if(m.param <= 0x01)
- {
- m.param |= 0x9E;
- } else
- {
- m.command = CMD_NONE;
- }
- break;
- case 0x11:
- case 0x12:
- // Extra fine slides
- m.param = static_cast<ModCommand::PARAM>(std::min(uint8(0x0F), m.param) | 0xE0);
- break;
- case 0x15:
- case 0x16:
- // Fine slides
- m.param = static_cast<ModCommand::PARAM>((std::min(0x10, m.param + 1) / 2) | 0xF0);
- break;
- case 0x1E:
- // More fine slides
- switch(m.param >> 4)
- {
- case 0x1:
- // Fine porta up
- m.command = CMD_PORTAMENTOUP;
- m.param |= 0xF0;
- break;
- case 0x2:
- // Fine porta down
- m.command = CMD_PORTAMENTODOWN;
- m.param |= 0xF0;
- break;
- case 0xA:
- // Extra fine volume slide up
- m.command = CMD_VOLUMESLIDE;
- m.param = ((((m.param & 0x0F) + 1) / 2) << 4) | 0x0F;
- break;
- case 0xB:
- // Extra fine volume slide down
- m.command = CMD_VOLUMESLIDE;
- m.param = (((m.param & 0x0F) + 1) / 2) | 0xF0;
- break;
- default:
- m.command = CMD_NONE;
- break;
- }
- break;
- case 0x1C:
- // Adjust channel volume range
- m.param = static_cast<ModCommand::PARAM>(std::min((m.param + 1) / 2, 64));
- break;
- }
- }
- // Try merging commands first
- ModCommand::CombineEffects(m.command, m.param, origCmd.command, origCmd.param);
- if(ModCommand::GetEffectWeight(origCmd.command) > ModCommand::GetEffectWeight(m.command))
- {
- if(m.volcmd == VOLCMD_NONE && ModCommand::ConvertVolEffect(m.command, m.param, true))
- {
- // Volume column to the rescue!
- m.volcmd = m.command;
- m.vol = m.param;
- }
- m.command = origCmd.command;
- m.param = origCmd.param;
- }
- }
- }
- if(flags & endOfRowMask)
- {
- // End of row
- break;
- }
- }
- }
- }
- /////////////////////////////////////////////////////////////////////
- // AMS (Extreme's Tracker) 1.x loader
- // AMS File Header
- struct AMSFileHeader
- {
- uint8le versionLow;
- uint8le versionHigh;
- uint8le channelConfig;
- uint8le numSamps;
- uint16le numPats;
- uint16le numOrds;
- uint8le midiChannels;
- uint16le extraSize;
- };
- MPT_BINARY_STRUCT(AMSFileHeader, 11)
- // AMS Sample Header
- struct AMSSampleHeader
- {
- enum SampleFlags
- {
- smp16BitOld = 0x04, // AMS 1.0 (at least according to docs, I yet have to find such a file)
- smp16Bit = 0x80, // AMS 1.1+
- smpPacked = 0x03,
- };
- uint32le length;
- uint32le loopStart;
- uint32le loopEnd;
- uint8le panFinetune; // High nibble = pan position, low nibble = finetune value
- uint16le sampleRate;
- uint8le volume; // 0...127
- uint8le flags; // See SampleFlags
- // Convert sample header to OpenMPT's internal format.
- void ConvertToMPT(ModSample &mptSmp) const
- {
- mptSmp.Initialize();
- mptSmp.nLength = length;
- mptSmp.nLoopStart = std::min(loopStart, length);
- mptSmp.nLoopEnd = std::min(loopEnd, length);
- mptSmp.nVolume = (std::min(uint8(127), volume.get()) * 256 + 64) / 127;
- if(panFinetune & 0xF0)
- {
- mptSmp.nPan = (panFinetune & 0xF0);
- mptSmp.uFlags = CHN_PANNING;
- }
- mptSmp.nC5Speed = 2 * sampleRate;
- if(sampleRate == 0)
- {
- mptSmp.nC5Speed = 2 * 8363;
- }
- uint32 newC4speed = ModSample::TransposeToFrequency(0, MOD2XMFineTune(panFinetune & 0x0F));
- mptSmp.nC5Speed = (mptSmp.nC5Speed * newC4speed) / 8363;
- if(mptSmp.nLoopStart < mptSmp.nLoopEnd)
- {
- mptSmp.uFlags.set(CHN_LOOP);
- }
- if(flags & (smp16Bit | smp16BitOld))
- {
- mptSmp.uFlags.set(CHN_16BIT);
- }
- }
- };
- MPT_BINARY_STRUCT(AMSSampleHeader, 17)
- static bool ValidateHeader(const AMSFileHeader &fileHeader)
- {
- if(fileHeader.versionHigh != 0x01)
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const AMSFileHeader &fileHeader)
- {
- return fileHeader.extraSize + 3u + fileHeader.numSamps * (1u + sizeof(AMSSampleHeader)) + fileHeader.numOrds * 2u + fileHeader.numPats * 4u;
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderAMS(MemoryFileReader file, const uint64 *pfilesize)
- {
- if(!file.CanRead(7))
- {
- return ProbeWantMoreData;
- }
- if(!file.ReadMagic("Extreme"))
- {
- return ProbeFailure;
- }
- AMSFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadAMS(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- if(!file.ReadMagic("Extreme"))
- {
- return false;
- }
- AMSFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(!ValidateHeader(fileHeader))
- {
- return false;
- }
- if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
- {
- return false;
- }
- if(!file.Skip(fileHeader.extraSize))
- {
- return false;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
- InitializeGlobals(MOD_TYPE_AMS);
- m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS;
- m_nChannels = (fileHeader.channelConfig & 0x1F) + 1;
- m_nSamples = fileHeader.numSamps;
- SetupMODPanning(true);
- m_modFormat.formatName = U_("Extreme's Tracker");
- m_modFormat.type = U_("ams");
- m_modFormat.madeWithTracker = MPT_UFORMAT("Extreme's Tracker {}.{}")(fileHeader.versionHigh, fileHeader.versionLow);
- m_modFormat.charset = mpt::Charset::CP437;
- std::vector<bool> packSample(fileHeader.numSamps);
- static_assert(MAX_SAMPLES > 255);
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- AMSSampleHeader sampleHeader;
- file.ReadStruct(sampleHeader);
- sampleHeader.ConvertToMPT(Samples[smp]);
- packSample[smp - 1] = (sampleHeader.flags & AMSSampleHeader::smpPacked) != 0;
- }
- // Texts
- file.ReadSizedString<uint8le, mpt::String::spacePadded>(m_songName);
- // Read sample names
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- file.ReadSizedString<uint8le, mpt::String::spacePadded>(m_szNames[smp]);
- }
- // Read channel names
- for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
- {
- ChnSettings[chn].Reset();
- file.ReadSizedString<uint8le, mpt::String::spacePadded>(ChnSettings[chn].szName);
- }
- // Read pattern names and create patterns
- Patterns.ResizeArray(fileHeader.numPats);
- for(PATTERNINDEX pat = 0; pat < fileHeader.numPats; pat++)
- {
- char name[11];
- const bool ok = file.ReadSizedString<uint8le, mpt::String::spacePadded>(name);
- // Create pattern now, so name won't be reset later.
- if(Patterns.Insert(pat, 64) && ok)
- {
- Patterns[pat].SetName(name);
- }
- }
- // Read packed song message
- const uint16 packedLength = file.ReadUint16LE();
- if(packedLength && file.CanRead(packedLength))
- {
- std::vector<uint8> textIn;
- file.ReadVector(textIn, packedLength);
- std::string textOut;
- textOut.reserve(packedLength);
- for(auto c : textIn)
- {
- if(c & 0x80)
- {
- textOut.insert(textOut.end(), (c & 0x7F), ' ');
- } else
- {
- textOut.push_back(c);
- }
- }
- textOut = mpt::ToCharset(mpt::Charset::CP437, mpt::Charset::CP437AMS, textOut);
- // Packed text doesn't include any line breaks!
- m_songMessage.ReadFixedLineLength(mpt::byte_cast<const std::byte*>(textOut.c_str()), textOut.length(), 76, 0);
- }
- // Read Order List
- ReadOrderFromFile<uint16le>(Order(), file, fileHeader.numOrds);
- // Read patterns
- for(PATTERNINDEX pat = 0; pat < fileHeader.numPats && file.CanRead(4); pat++)
- {
- uint32 patLength = file.ReadUint32LE();
- FileReader patternChunk = file.ReadChunk(patLength);
- if((loadFlags & loadPatternData) && Patterns.IsValidPat(pat))
- {
- ReadAMSPattern(Patterns[pat], false, patternChunk);
- }
- }
- if(loadFlags & loadSampleData)
- {
- // Read Samples
- for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
- {
- SampleIO(
- Samples[smp].uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- packSample[smp - 1] ? SampleIO::AMS : SampleIO::signedPCM)
- .ReadSample(Samples[smp], file);
- }
- }
- return true;
- }
- /////////////////////////////////////////////////////////////////////
- // AMS (Velvet Studio) 2.0 - 2.02 loader
- // AMS2 File Header
- struct AMS2FileHeader
- {
- enum FileFlags
- {
- linearSlides = 0x40,
- };
- uint8le versionLow; // Version of format (Hi = MainVer, Low = SubVer e.g. 0202 = 2.02)
- uint8le versionHigh; // ditto
- uint8le numIns; // Nr of Instruments (0-255)
- uint16le numPats; // Nr of Patterns (1-1024)
- uint16le numOrds; // Nr of Positions (1-65535)
- // Rest of header differs between format revision 2.01 and 2.02
- };
- MPT_BINARY_STRUCT(AMS2FileHeader, 7)
- // AMS2 Instument Envelope
- struct AMS2Envelope
- {
- uint8 speed; // Envelope speed (currently not supported, always the same as current BPM)
- uint8 sustainPoint; // Envelope sustain point
- uint8 loopStart; // Envelope loop Start
- uint8 loopEnd; // Envelope loop End
- uint8 numPoints; // Envelope length
- // Read envelope and do partial conversion.
- void ConvertToMPT(InstrumentEnvelope &mptEnv, FileReader &file)
- {
- file.ReadStruct(*this);
- // Read envelope points
- uint8 data[64][3];
- file.ReadStructPartial(data, numPoints * 3);
- if(numPoints <= 1)
- {
- // This is not an envelope.
- return;
- }
- static_assert(MAX_ENVPOINTS >= std::size(data));
- mptEnv.resize(std::min(numPoints, mpt::saturate_cast<uint8>(std::size(data))));
- mptEnv.nLoopStart = loopStart;
- mptEnv.nLoopEnd = loopEnd;
- mptEnv.nSustainStart = mptEnv.nSustainEnd = sustainPoint;
- for(uint32 i = 0; i < mptEnv.size(); i++)
- {
- if(i != 0)
- {
- mptEnv[i].tick = mptEnv[i - 1].tick + static_cast<uint16>(std::max(1, data[i][0] | ((data[i][1] & 0x01) << 8)));
- }
- mptEnv[i].value = data[i][2];
- }
- }
- };
- MPT_BINARY_STRUCT(AMS2Envelope, 5)
- // AMS2 Instrument Data
- struct AMS2Instrument
- {
- enum EnvelopeFlags
- {
- envLoop = 0x01,
- envSustain = 0x02,
- envEnabled = 0x04,
-
- // Flag shift amounts
- volEnvShift = 0,
- panEnvShift = 1,
- vibEnvShift = 2,
- vibAmpMask = 0x3000,
- vibAmpShift = 12,
- fadeOutMask = 0xFFF,
- };
- uint8le shadowInstr; // Shadow Instrument. If non-zero, the value=the shadowed inst.
- uint16le vibampFadeout; // Vib.Amplify + Volume fadeout in one variable!
- uint16le envFlags; // See EnvelopeFlags
- void ApplyFlags(InstrumentEnvelope &mptEnv, EnvelopeFlags shift) const
- {
- const int flags = envFlags >> (shift * 3);
- mptEnv.dwFlags.set(ENV_ENABLED, (flags & envEnabled) != 0);
- mptEnv.dwFlags.set(ENV_LOOP, (flags & envLoop) != 0);
- mptEnv.dwFlags.set(ENV_SUSTAIN, (flags & envSustain) != 0);
- // "Break envelope" should stop the envelope loop when encountering a note-off... We can only use the sustain loop to emulate this behaviour.
- if(!(flags & envSustain) && (flags & envLoop) != 0 && (flags & (1 << (9 - shift * 2))) != 0)
- {
- mptEnv.nSustainStart = mptEnv.nLoopStart;
- mptEnv.nSustainEnd = mptEnv.nLoopEnd;
- mptEnv.dwFlags.set(ENV_SUSTAIN);
- mptEnv.dwFlags.reset(ENV_LOOP);
- }
- }
- };
- MPT_BINARY_STRUCT(AMS2Instrument, 5)
- // AMS2 Sample Header
- struct AMS2SampleHeader
- {
- enum SampleFlags
- {
- smpPacked = 0x03,
- smp16Bit = 0x04,
- smpLoop = 0x08,
- smpBidiLoop = 0x10,
- smpReverse = 0x40,
- };
- uint32le length;
- uint32le loopStart;
- uint32le loopEnd;
- uint16le sampledRate; // Whyyyy?
- uint8le panFinetune; // High nibble = pan position, low nibble = finetune value
- uint16le c4speed; // Why is all of this so redundant?
- int8le relativeTone; // q.e.d.
- uint8le volume; // 0...127
- uint8le flags; // See SampleFlags
- // Convert sample header to OpenMPT's internal format.
- void ConvertToMPT(ModSample &mptSmp) const
- {
- mptSmp.Initialize();
- mptSmp.nLength = length;
- mptSmp.nLoopStart = std::min(loopStart, length);
- mptSmp.nLoopEnd = std::min(loopEnd, length);
- mptSmp.nC5Speed = c4speed * 2;
- if(c4speed == 0)
- {
- mptSmp.nC5Speed = 8363 * 2;
- }
- // Why, oh why, does this format need a c5speed and transpose/finetune at the same time...
- uint32 newC4speed = ModSample::TransposeToFrequency(relativeTone, MOD2XMFineTune(panFinetune & 0x0F));
- mptSmp.nC5Speed = (mptSmp.nC5Speed * newC4speed) / 8363;
- mptSmp.nVolume = (std::min(volume.get(), uint8(127)) * 256 + 64) / 127;
- if(panFinetune & 0xF0)
- {
- mptSmp.nPan = (panFinetune & 0xF0);
- mptSmp.uFlags = CHN_PANNING;
- }
- if(flags & smp16Bit) mptSmp.uFlags.set(CHN_16BIT);
- if((flags & smpLoop) && mptSmp.nLoopStart < mptSmp.nLoopEnd)
- {
- mptSmp.uFlags.set(CHN_LOOP);
- if(flags & smpBidiLoop) mptSmp.uFlags.set(CHN_PINGPONGLOOP);
- if(flags & smpReverse) mptSmp.uFlags.set(CHN_REVERSE);
- }
- }
- };
- MPT_BINARY_STRUCT(AMS2SampleHeader, 20)
- // AMS2 Song Description Header
- struct AMS2Description
- {
- uint32le packedLen; // Including header
- uint32le unpackedLen;
- uint8le packRoutine; // 01
- uint8le preProcessing; // None!
- uint8le packingMethod; // RLE
- };
- MPT_BINARY_STRUCT(AMS2Description, 11)
- static bool ValidateHeader(const AMS2FileHeader &fileHeader)
- {
- if(fileHeader.versionHigh != 2 || fileHeader.versionLow > 2)
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const AMS2FileHeader &fileHeader)
- {
- return 36u + sizeof(AMS2Description) + fileHeader.numIns * 2u + fileHeader.numOrds * 2u + fileHeader.numPats * 4u;
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderAMS2(MemoryFileReader file, const uint64 *pfilesize)
- {
- if(!file.CanRead(7))
- {
- return ProbeWantMoreData;
- }
- if(!file.ReadMagic("AMShdr\x1A"))
- {
- return ProbeFailure;
- }
- if(!file.CanRead(1))
- {
- return ProbeWantMoreData;
- }
- const uint8 songNameLength = file.ReadUint8();
- if(!file.Skip(songNameLength))
- {
- return ProbeWantMoreData;
- }
- AMS2FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadAMS2(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- if(!file.ReadMagic("AMShdr\x1A"))
- {
- return false;
- }
- std::string songName;
- if(!file.ReadSizedString<uint8le, mpt::String::spacePadded>(songName))
- {
- return false;
- }
- AMS2FileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(!ValidateHeader(fileHeader))
- {
- return false;
- }
- if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
- {
- return false;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
-
- InitializeGlobals(MOD_TYPE_AMS);
-
- m_songName = songName;
- m_nInstruments = fileHeader.numIns;
- m_nChannels = 32;
- SetupMODPanning(true);
- m_modFormat.formatName = U_("Velvet Studio");
- m_modFormat.type = U_("ams");
- m_modFormat.madeWithTracker = MPT_UFORMAT("Velvet Studio {}.{}")(fileHeader.versionHigh.get(), mpt::ufmt::dec0<2>(fileHeader.versionLow.get()));
- m_modFormat.charset = mpt::Charset::CP437;
- uint16 headerFlags;
- if(fileHeader.versionLow >= 2)
- {
- uint16 tempo = std::max(uint16(32 << 8), file.ReadUint16LE()); // 8.8 tempo
- m_nDefaultTempo.SetRaw((tempo * TEMPO::fractFact) >> 8);
- m_nDefaultSpeed = std::max(uint8(1), file.ReadUint8());
- file.Skip(3); // Default values for pattern editor
- headerFlags = file.ReadUint16LE();
- } else
- {
- m_nDefaultTempo.Set(std::max(uint8(32), file.ReadUint8()));
- m_nDefaultSpeed = std::max(uint8(1), file.ReadUint8());
- headerFlags = file.ReadUint8();
- }
- m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS | ((headerFlags & AMS2FileHeader::linearSlides) ? SONG_LINEARSLIDES : SongFlags(0));
- // Instruments
- std::vector<SAMPLEINDEX> firstSample; // First sample of instrument
- std::vector<uint16> sampleSettings; // Shadow sample map... Lo byte = Instrument, Hi byte, lo nibble = Sample index in instrument, Hi byte, hi nibble = Sample pack status
- enum
- {
- instrIndexMask = 0xFF, // Shadow instrument
- sampleIndexMask = 0x7F00, // Sample index in instrument
- sampleIndexShift = 8,
- packStatusMask = 0x8000, // If bit is set, sample is packed
- };
- static_assert(MAX_INSTRUMENTS > 255);
- for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++)
- {
- ModInstrument *instrument = AllocateInstrument(ins);
- if(instrument == nullptr
- || !file.ReadSizedString<uint8le, mpt::String::spacePadded>(instrument->name))
- {
- break;
- }
- uint8 numSamples = file.ReadUint8();
- uint8 sampleAssignment[120];
- MemsetZero(sampleAssignment); // Only really needed for v2.0, where the lowest and highest octave aren't cleared.
- if(numSamples == 0
- || (fileHeader.versionLow > 0 && !file.ReadArray(sampleAssignment)) // v2.01+: 120 Notes
- || (fileHeader.versionLow == 0 && !file.ReadRaw(mpt::span(sampleAssignment + 12, 96)).size())) // v2.0: 96 Notes
- {
- continue;
- }
- static_assert(mpt::array_size<decltype(instrument->Keyboard)>::size >= std::size(sampleAssignment));
- for(size_t i = 0; i < 120; i++)
- {
- instrument->Keyboard[i] = sampleAssignment[i] + GetNumSamples() + 1;
- }
- AMS2Envelope volEnv, panEnv, vibratoEnv;
- volEnv.ConvertToMPT(instrument->VolEnv, file);
- panEnv.ConvertToMPT(instrument->PanEnv, file);
- vibratoEnv.ConvertToMPT(instrument->PitchEnv, file);
- AMS2Instrument instrHeader;
- file.ReadStruct(instrHeader);
- instrument->nFadeOut = (instrHeader.vibampFadeout & AMS2Instrument::fadeOutMask);
- const int16 vibAmp = 1 << ((instrHeader.vibampFadeout & AMS2Instrument::vibAmpMask) >> AMS2Instrument::vibAmpShift);
- instrHeader.ApplyFlags(instrument->VolEnv, AMS2Instrument::volEnvShift);
- instrHeader.ApplyFlags(instrument->PanEnv, AMS2Instrument::panEnvShift);
- instrHeader.ApplyFlags(instrument->PitchEnv, AMS2Instrument::vibEnvShift);
- // Scale envelopes to correct range
- for(auto &p : instrument->VolEnv)
- {
- p.value = std::min(uint8(ENVELOPE_MAX), static_cast<uint8>((p.value * ENVELOPE_MAX + 64u) / 127u));
- }
- for(auto &p : instrument->PanEnv)
- {
- p.value = std::min(uint8(ENVELOPE_MAX), static_cast<uint8>((p.value * ENVELOPE_MAX + 128u) / 255u));
- }
- for(auto &p : instrument->PitchEnv)
- {
- #ifdef MODPLUG_TRACKER
- p.value = std::min(uint8(ENVELOPE_MAX), static_cast<uint8>(32 + Util::muldivrfloor(static_cast<int8>(p.value - 128), vibAmp, 255)));
- #else
- // Try to keep as much precision as possible... divide by 8 since that's the highest possible vibAmp factor.
- p.value = static_cast<uint8>(128 + Util::muldivrfloor(static_cast<int8>(p.value - 128), vibAmp, 8));
- #endif
- }
- // Sample headers - we will have to read them even for shadow samples, and we will have to load them several times,
- // as it is possible that shadow samples use different sample settings like base frequency or panning.
- const SAMPLEINDEX firstSmp = GetNumSamples() + 1;
- for(SAMPLEINDEX smp = 0; smp < numSamples; smp++)
- {
- if(firstSmp + smp >= MAX_SAMPLES)
- {
- file.Skip(sizeof(AMS2SampleHeader));
- break;
- }
- file.ReadSizedString<uint8le, mpt::String::spacePadded>(m_szNames[firstSmp + smp]);
- AMS2SampleHeader sampleHeader;
- file.ReadStruct(sampleHeader);
- sampleHeader.ConvertToMPT(Samples[firstSmp + smp]);
- uint16 settings = (instrHeader.shadowInstr & instrIndexMask)
- | ((smp << sampleIndexShift) & sampleIndexMask)
- | ((sampleHeader.flags & AMS2SampleHeader::smpPacked) ? packStatusMask : 0);
- sampleSettings.push_back(settings);
- }
- firstSample.push_back(firstSmp);
- m_nSamples = static_cast<SAMPLEINDEX>(std::min(MAX_SAMPLES - 1, GetNumSamples() + numSamples));
- }
- // Text
- // Read composer name
- if(std::string composer; file.ReadSizedString<uint8le, mpt::String::spacePadded>(composer))
- {
- m_songArtist = mpt::ToUnicode(mpt::Charset::CP437AMS2, composer);
- }
- // Channel names
- for(CHANNELINDEX chn = 0; chn < 32; chn++)
- {
- ChnSettings[chn].Reset();
- file.ReadSizedString<uint8le, mpt::String::spacePadded>(ChnSettings[chn].szName);
- }
- // RLE-Packed description text
- AMS2Description descriptionHeader;
- if(!file.ReadStruct(descriptionHeader))
- {
- return true;
- }
- if(descriptionHeader.packedLen > sizeof(descriptionHeader) && file.CanRead(descriptionHeader.packedLen - sizeof(descriptionHeader)))
- {
- const uint32 textLength = descriptionHeader.packedLen - static_cast<uint32>(sizeof(descriptionHeader));
- std::vector<uint8> textIn;
- file.ReadVector(textIn, textLength);
- // In the best case, every byte triplet can decode to 255 bytes, which is a ratio of exactly 1:85
- const uint32 maxLength = std::min(textLength, Util::MaxValueOfType(textLength) / 85u) * 85u;
- std::string textOut;
- textOut.reserve(std::min(maxLength, descriptionHeader.unpackedLen.get()));
- size_t readLen = 0;
- while(readLen < textLength)
- {
- uint8 c = textIn[readLen++];
- if(c == 0xFF && textLength - readLen >= 2)
- {
- c = textIn[readLen++];
- uint32 count = textIn[readLen++];
- textOut.insert(textOut.end(), count, c);
- } else
- {
- textOut.push_back(c);
- }
- }
- textOut = mpt::ToCharset(mpt::Charset::CP437, mpt::Charset::CP437AMS2, textOut);
- // Packed text doesn't include any line breaks!
- m_songMessage.ReadFixedLineLength(mpt::byte_cast<const std::byte*>(textOut.c_str()), textOut.length(), 74, 0);
- }
- // Read Order List
- ReadOrderFromFile<uint16le>(Order(), file, fileHeader.numOrds);
- // Read Patterns
- if(loadFlags & loadPatternData)
- Patterns.ResizeArray(fileHeader.numPats);
- for(PATTERNINDEX pat = 0; pat < fileHeader.numPats && file.CanRead(4); pat++)
- {
- uint32 patLength = file.ReadUint32LE();
- FileReader patternChunk = file.ReadChunk(patLength);
- if(loadFlags & loadPatternData)
- {
- const ROWINDEX numRows = patternChunk.ReadUint8() + 1;
- // We don't need to know the number of channels or commands.
- patternChunk.Skip(1);
- if(!Patterns.Insert(pat, numRows))
- {
- continue;
- }
- char patternName[11];
- if(patternChunk.ReadSizedString<uint8le, mpt::String::spacePadded>(patternName))
- Patterns[pat].SetName(patternName);
- ReadAMSPattern(Patterns[pat], true, patternChunk);
- }
- }
- if(!(loadFlags & loadSampleData))
- {
- return true;
- }
- // Read Samples
- for(SAMPLEINDEX smp = 0; smp < GetNumSamples(); smp++)
- {
- if((sampleSettings[smp] & instrIndexMask) == 0)
- {
- // Only load samples that aren't part of a shadow instrument
- SampleIO(
- (Samples[smp + 1].uFlags & CHN_16BIT) ? SampleIO::_16bit : SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- (sampleSettings[smp] & packStatusMask) ? SampleIO::AMS : SampleIO::signedPCM)
- .ReadSample(Samples[smp + 1], file);
- }
- }
- // Copy shadow samples
- for(SAMPLEINDEX smp = 0; smp < GetNumSamples(); smp++)
- {
- INSTRUMENTINDEX sourceInstr = (sampleSettings[smp] & instrIndexMask);
- if(sourceInstr == 0
- || --sourceInstr >= firstSample.size())
- {
- continue;
- }
- SAMPLEINDEX sourceSample = ((sampleSettings[smp] & sampleIndexMask) >> sampleIndexShift) + firstSample[sourceInstr];
- if(sourceSample > GetNumSamples() || !Samples[sourceSample].HasSampleData())
- {
- continue;
- }
- // Copy over original sample
- ModSample &sample = Samples[smp + 1];
- ModSample &source = Samples[sourceSample];
- sample.uFlags.set(CHN_16BIT, source.uFlags[CHN_16BIT]);
- sample.nLength = source.nLength;
- if(sample.AllocateSample())
- {
- memcpy(sample.sampleb(), source.sampleb(), source.GetSampleSizeInBytes());
- }
- }
- return true;
- }
- /////////////////////////////////////////////////////////////////////
- // AMS Sample unpacking
- void AMSUnpack(const int8 * const source, size_t sourceSize, void * const dest, const size_t destSize, char packCharacter)
- {
- std::vector<int8> tempBuf(destSize, 0);
- size_t depackSize = destSize;
- // Unpack Loop
- {
- const int8 *in = source;
- int8 *out = tempBuf.data();
- size_t i = sourceSize, j = destSize;
- while(i != 0 && j != 0)
- {
- int8 ch = *(in++);
- if(--i != 0 && ch == packCharacter)
- {
- uint8 repCount = *(in++);
- repCount = static_cast<uint8>(std::min(static_cast<size_t>(repCount), j));
- if(--i != 0 && repCount)
- {
- ch = *(in++);
- i--;
- while(repCount-- != 0)
- {
- *(out++) = ch;
- j--;
- }
- } else
- {
- *(out++) = packCharacter;
- j--;
- }
- } else
- {
- *(out++) = ch;
- j--;
- }
- }
- // j should only be non-zero for truncated samples
- depackSize -= j;
- }
- // Bit Unpack Loop
- {
- int8 *out = tempBuf.data();
- uint16 bitcount = 0x80;
- size_t k = 0;
- uint8 *dst = static_cast<uint8 *>(dest);
- for(size_t i = 0; i < depackSize; i++)
- {
- uint8 al = *out++;
- uint16 dh = 0;
- for(uint16 count = 0; count < 8; count++)
- {
- uint16 bl = al & bitcount;
- bl = ((bl | (bl << 8)) >> ((dh + 8 - count) & 7)) & 0xFF;
- bitcount = ((bitcount | (bitcount << 8)) >> 1) & 0xFF;
- dst[k++] |= bl;
- if(k >= destSize)
- {
- k = 0;
- dh++;
- }
- }
- bitcount = ((bitcount | (bitcount << 8)) >> dh) & 0xFF;
- }
- }
- // Delta Unpack
- {
- int8 old = 0;
- int8 *out = static_cast<int8 *>(dest);
- for(size_t i = depackSize; i != 0; i--)
- {
- int pos = *reinterpret_cast<uint8 *>(out);
- if(pos != 128 && (pos & 0x80) != 0)
- {
- pos = -(pos & 0x7F);
- }
- old -= static_cast<int8>(pos);
- *(out++) = old;
- }
- }
- }
- OPENMPT_NAMESPACE_END
|