/* * Load_far.cpp * ------------ * Purpose: Farandole (FAR) module loader * Notes : (currently none) * Authors: OpenMPT Devs (partly inspired by Storlek's FAR loader from Schism Tracker) * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Loaders.h" OPENMPT_NAMESPACE_BEGIN // FAR File Header struct FARFileHeader { uint8le magic[4]; char songName[40]; uint8le eof[3]; uint16le headerLength; uint8le version; uint8le onOff[16]; uint8le editingState[9]; // Stuff we don't care about uint8le defaultSpeed; uint8le chnPanning[16]; uint8le patternState[4]; // More stuff we don't care about uint16le messageLength; }; MPT_BINARY_STRUCT(FARFileHeader, 98) struct FAROrderHeader { uint8le orders[256]; uint8le numPatterns; // supposed to be "number of patterns stored in the file"; apparently that's wrong uint8le numOrders; uint8le restartPos; uint16le patternSize[256]; }; MPT_BINARY_STRUCT(FAROrderHeader, 771) // FAR Sample header struct FARSampleHeader { // Sample flags enum SampleFlags { smp16Bit = 0x01, smpLoop = 0x08, }; char name[32]; uint32le length; uint8le finetune; uint8le volume; uint32le loopStart; uint32le loopEnd; uint8le type; uint8le loop; // Convert sample header to OpenMPT's internal format. void ConvertToMPT(ModSample &mptSmp) const { mptSmp.Initialize(); mptSmp.nLength = length; mptSmp.nLoopStart = loopStart; mptSmp.nLoopEnd = loopEnd; mptSmp.nC5Speed = 8363 * 2; mptSmp.nVolume = volume * 16; if(type & smp16Bit) { mptSmp.nLength /= 2; mptSmp.nLoopStart /= 2; mptSmp.nLoopEnd /= 2; } if((loop & 8) && mptSmp.nLoopEnd > mptSmp.nLoopStart) { mptSmp.uFlags.set(CHN_LOOP); } } // Retrieve the internal sample format flags for this sample. SampleIO GetSampleFormat() const { return SampleIO( (type & smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::signedPCM); } }; MPT_BINARY_STRUCT(FARSampleHeader, 48) static bool ValidateHeader(const FARFileHeader &fileHeader) { if(std::memcmp(fileHeader.magic, "FAR\xFE", 4) != 0 || std::memcmp(fileHeader.eof, "\x0D\x0A\x1A", 3) ) { return false; } if(fileHeader.headerLength < sizeof(FARFileHeader)) { return false; } return true; } static uint64 GetHeaderMinimumAdditionalSize(const FARFileHeader &fileHeader) { return fileHeader.headerLength - sizeof(FARFileHeader); } CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderFAR(MemoryFileReader file, const uint64 *pfilesize) { FARFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return ProbeWantMoreData; } if(!ValidateHeader(fileHeader)) { return ProbeFailure; } return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader)); } bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) { file.Rewind(); FARFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return false; } if(!ValidateHeader(fileHeader)) { return false; } if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } if(loadFlags == onlyVerifyHeader) { return true; } // Globals InitializeGlobals(MOD_TYPE_FAR); m_nChannels = 16; m_nSamplePreAmp = 32; m_nDefaultSpeed = fileHeader.defaultSpeed; m_nDefaultTempo.Set(80); m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; m_modFormat.formatName = U_("Farandole Composer"); m_modFormat.type = U_("far"); m_modFormat.charset = mpt::Charset::CP437; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); // Read channel settings for(CHANNELINDEX chn = 0; chn < 16; chn++) { ChnSettings[chn].Reset(); ChnSettings[chn].dwFlags = fileHeader.onOff[chn] ? ChannelFlags(0) : CHN_MUTE; ChnSettings[chn].nPan = ((fileHeader.chnPanning[chn] & 0x0F) << 4) + 8; } // Read song message if(fileHeader.messageLength != 0) { m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength, 132, 0); // 132 characters per line... wow. :) } // Read orders FAROrderHeader orderHeader; if(!file.ReadStruct(orderHeader)) { return false; } ReadOrderFromArray(Order(), orderHeader.orders, orderHeader.numOrders, 0xFF, 0xFE); Order().SetRestartPos(orderHeader.restartPos); file.Seek(fileHeader.headerLength); // Pattern effect LUT static constexpr EffectCommand farEffects[] = { CMD_NONE, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_TONEPORTAMENTO, CMD_RETRIG, CMD_VIBRATO, // depth CMD_VIBRATO, // speed CMD_VOLUMESLIDE, // up CMD_VOLUMESLIDE, // down CMD_VIBRATO, // sustained (?) CMD_NONE, // actually slide-to-volume CMD_S3MCMDEX, // panning CMD_S3MCMDEX, // note offset => note delay? CMD_NONE, // fine tempo down CMD_NONE, // fine tempo up CMD_SPEED, }; // Read patterns for(PATTERNINDEX pat = 0; pat < 256; pat++) { if(!orderHeader.patternSize[pat]) { continue; } FileReader patternChunk = file.ReadChunk(orderHeader.patternSize[pat]); // Calculate pattern length in rows (every event is 4 bytes, and we have 16 channels) ROWINDEX numRows = (orderHeader.patternSize[pat] - 2) / (16 * 4); if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows)) { continue; } // Read break row and unused value (used to be pattern tempo) ROWINDEX breakRow = patternChunk.ReadUint8(); patternChunk.Skip(1); if(breakRow > 0 && breakRow < numRows - 2) { breakRow++; } else { breakRow = ROWINDEX_INVALID; } // Read pattern data for(ROWINDEX row = 0; row < numRows; row++) { PatternRow rowBase = Patterns[pat].GetRow(row); for(CHANNELINDEX chn = 0; chn < 16; chn++) { ModCommand &m = rowBase[chn]; const auto [note, instr, volume, effect] = patternChunk.ReadArray(); if(note > 0 && note <= 72) { m.note = note + 35 + NOTE_MIN; m.instr = instr + 1; } if(m.note != NOTE_NONE || volume > 0) { m.volcmd = VOLCMD_VOLUME; m.vol = (Clamp(volume, uint8(1), uint8(16)) - 1u) * 4u; } m.param = effect & 0x0F; switch(effect >> 4) { case 0x03: // Porta to note m.param <<= 2; break; case 0x04: // Retrig m.param = 6 / (1 + (m.param & 0xf)) + 1; // ugh? break; case 0x06: // Vibrato speed case 0x07: // Volume slide up m.param *= 8; break; case 0x0A: // Volume-portamento (what!) m.volcmd = VOLCMD_VOLUME; m.vol = (m.param << 2) + 4; break; case 0x0B: // Panning m.param |= 0x80; break; case 0x0C: // Note offset m.param = 6 / (1 + m.param) + 1; m.param |= 0x0D; } m.command = farEffects[effect >> 4]; } } Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(breakRow).RetryNextRow()); } if(!(loadFlags & loadSampleData)) { return true; } // Read samples uint8 sampleMap[8]; // Sample usage bitset file.ReadArray(sampleMap); for(SAMPLEINDEX smp = 0; smp < 64; smp++) { if(!(sampleMap[smp >> 3] & (1 << (smp & 7)))) { continue; } FARSampleHeader sampleHeader; if(!file.ReadStruct(sampleHeader)) { return true; } m_nSamples = smp + 1; ModSample &sample = Samples[m_nSamples]; m_szNames[m_nSamples] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name); sampleHeader.ConvertToMPT(sample); sampleHeader.GetSampleFormat().ReadSample(sample, file); } return true; } OPENMPT_NAMESPACE_END