/* * Load_digi.cpp * ------------- * Purpose: Digi Booster module loader * Notes : Basically these are like ProTracker MODs with a few extra features such as more channels, longer samples and a few more effects. * 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 // DIGI File Header struct DIGIFileHeader { char signature[20]; char versionStr[4]; // Supposed to be "V1.6" or similar, but other values like "TAP!" have been found as well. uint8be versionInt; // e.g. 0x16 = 1.6 uint8be numChannels; uint8be packEnable; char unknown[19]; uint8be lastPatIndex; uint8be lastOrdIndex; uint8be orders[128]; uint32be smpLength[31]; uint32be smpLoopStart[31]; uint32be smpLoopLength[31]; uint8be smpVolume[31]; uint8be smpFinetune[31]; }; MPT_BINARY_STRUCT(DIGIFileHeader, 610) static void ReadDIGIPatternEntry(FileReader &file, ModCommand &m) { CSoundFile::ReadMODPatternEntry(file, m); CSoundFile::ConvertModCommand(m); if(m.command == CMD_MODCMDEX) { switch(m.param & 0xF0) { case 0x30: // E3x: Play sample backwards (E30 stops sample when it reaches the beginning, any other value plays it from the beginning including regular loop) // The play direction is also reset if a new note is played on the other channel linked to this channel. // The behaviour is rather broken when there is no note next to the ommand. m.command = CMD_DIGIREVERSESAMPLE; m.param &= 0x0F; break; case 0x40: // E40: Stop playing sample if(m.param == 0x40) { m.note = NOTE_NOTECUT; m.command = CMD_NONE; } break; case 0x80: // E8x: High sample offset m.command = CMD_S3MCMDEX; m.param = 0xA0 | (m.param & 0x0F); } } else if(m.command == CMD_PANNING8) { // 8xx "Robot" effect (not supported) m.command = CMD_NONE; } } static bool ValidateHeader(const DIGIFileHeader &fileHeader) { if(std::memcmp(fileHeader.signature, "DIGI Booster module\0", 20) || !fileHeader.numChannels || fileHeader.numChannels > 8 || fileHeader.lastOrdIndex > 127) { return false; } return true; } CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderDIGI(MemoryFileReader file, const uint64 *pfilesize) { DIGIFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return ProbeWantMoreData; } if(!ValidateHeader(fileHeader)) { return ProbeFailure; } MPT_UNREFERENCED_PARAMETER(pfilesize); return ProbeSuccess; } bool CSoundFile::ReadDIGI(FileReader &file, ModLoadingFlags loadFlags) { file.Rewind(); DIGIFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) { return false; } if(!ValidateHeader(fileHeader)) { return false; } if(loadFlags == onlyVerifyHeader) { return true; } // Globals InitializeGlobals(MOD_TYPE_DIGI); InitializeChannels(); m_nChannels = fileHeader.numChannels; m_nSamples = 31; m_nSamplePreAmp = 256 / m_nChannels; m_modFormat.formatName = U_("DigiBooster"); m_modFormat.type = U_("digi"); m_modFormat.madeWithTracker = MPT_UFORMAT("Digi Booster {}.{}")(fileHeader.versionInt >> 4, fileHeader.versionInt & 0x0F); m_modFormat.charset = mpt::Charset::Amiga_no_C1; ReadOrderFromArray(Order(), fileHeader.orders, fileHeader.lastOrdIndex + 1); // Read sample headers for(SAMPLEINDEX smp = 0; smp < 31; smp++) { ModSample &sample = Samples[smp + 1]; sample.Initialize(MOD_TYPE_MOD); sample.nLength = fileHeader.smpLength[smp]; sample.nLoopStart = fileHeader.smpLoopStart[smp]; sample.nLoopEnd = sample.nLoopStart + fileHeader.smpLoopLength[smp]; if(fileHeader.smpLoopLength[smp]) { sample.uFlags.set(CHN_LOOP); } sample.SanitizeLoops(); sample.nVolume = std::min(fileHeader.smpVolume[smp].get(), uint8(64)) * 4; sample.nFineTune = MOD2XMFineTune(fileHeader.smpFinetune[smp]); } // Read song + sample names file.ReadString(m_songName, 32); for(SAMPLEINDEX smp = 1; smp <= 31; smp++) { file.ReadString(m_szNames[smp], 30); } if(loadFlags & loadPatternData) Patterns.ResizeArray(fileHeader.lastPatIndex + 1); for(PATTERNINDEX pat = 0; pat <= fileHeader.lastPatIndex; pat++) { FileReader patternChunk; if(fileHeader.packEnable) { patternChunk = file.ReadChunk(file.ReadUint16BE()); } else { patternChunk = file.ReadChunk(4 * 64 * GetNumChannels()); } if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) { continue; } if(fileHeader.packEnable) { uint8 eventMask[64]; patternChunk.ReadArray(eventMask); // Compressed patterns are stored in row-major order... for(ROWINDEX row = 0; row < 64; row++) { PatternRow patRow = Patterns[pat].GetRow(row); uint8 bit = 0x80; for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++, bit >>= 1) { if(eventMask[row] & bit) { ModCommand &m = patRow[chn]; ReadDIGIPatternEntry(patternChunk, m); } } } } else { // ...but uncompressed patterns are stored in column-major order. WTF! for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { for(ROWINDEX row = 0; row < 64; row++) { ReadDIGIPatternEntry(patternChunk, *Patterns[pat].GetpModCommand(row, chn)); } } } } if(loadFlags & loadSampleData) { // Reading Samples const SampleIO sampleIO( SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::signedPCM); for(SAMPLEINDEX smp = 1; smp <= 31; smp++) { sampleIO.ReadSample(Samples[smp], file); } } return true; } OPENMPT_NAMESPACE_END