Bundle libOpenMPT as a dynamic framework, which should be safe once again, now that there is only one version to bundle. Also, now it is using the versions of libvorbisfile and libmpg123 that are bundled with the player, instead of compiling minimp3 and stbvorbis. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
607 lines
16 KiB
C++
607 lines
16 KiB
C++
/*
|
|
* Load_dtm.cpp
|
|
* ------------
|
|
* Purpose: Digital Tracker / Digital Home Studio module Loader (DTM)
|
|
* 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"
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
enum PatternFormats : uint32
|
|
{
|
|
DTM_PT_PATTERN_FORMAT = 0,
|
|
DTM_204_PATTERN_FORMAT = MagicBE("2.04"),
|
|
DTM_206_PATTERN_FORMAT = MagicBE("2.06"),
|
|
};
|
|
|
|
|
|
struct DTMFileHeader
|
|
{
|
|
char magic[4];
|
|
uint32be headerSize;
|
|
uint16be type; // 0 = module
|
|
uint8be stereoMode; // FF = panoramic stereo, 00 = old stereo
|
|
uint8be bitDepth; // Typically 8, sometimes 16, but is not actually used anywhere?
|
|
uint16be reserved; // Usually 0, but not in unknown title 1.dtm and unknown title 2.dtm
|
|
uint16be speed;
|
|
uint16be tempo;
|
|
uint32be forcedSampleRate; // Seems to be ignored in newer files
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMFileHeader, 22)
|
|
|
|
|
|
// IFF-style Chunk
|
|
struct DTMChunk
|
|
{
|
|
// 32-Bit chunk identifiers
|
|
enum ChunkIdentifiers
|
|
{
|
|
idS_Q_ = MagicBE("S.Q."),
|
|
idPATT = MagicBE("PATT"),
|
|
idINST = MagicBE("INST"),
|
|
idIENV = MagicBE("IENV"),
|
|
idDAPT = MagicBE("DAPT"),
|
|
idDAIT = MagicBE("DAIT"),
|
|
idTEXT = MagicBE("TEXT"),
|
|
idPATN = MagicBE("PATN"),
|
|
idTRKN = MagicBE("TRKN"),
|
|
idVERS = MagicBE("VERS"),
|
|
idSV19 = MagicBE("SV19"),
|
|
};
|
|
|
|
uint32be id;
|
|
uint32be length;
|
|
|
|
size_t GetLength() const
|
|
{
|
|
return length;
|
|
}
|
|
|
|
ChunkIdentifiers GetID() const
|
|
{
|
|
return static_cast<ChunkIdentifiers>(id.get());
|
|
}
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMChunk, 8)
|
|
|
|
|
|
struct DTMSample
|
|
{
|
|
uint32be reserved; // 0x204 for first sample, 0x208 for second, etc...
|
|
uint32be length; // in bytes
|
|
uint8be finetune; // -8....7
|
|
uint8be volume; // 0...64
|
|
uint32be loopStart; // in bytes
|
|
uint32be loopLength; // ditto
|
|
char name[22];
|
|
uint8be stereo;
|
|
uint8be bitDepth;
|
|
uint16be transpose;
|
|
uint16be unknown;
|
|
uint32be sampleRate;
|
|
|
|
void ConvertToMPT(ModSample &mptSmp, uint32 forcedSampleRate, uint32 formatVersion) const
|
|
{
|
|
mptSmp.Initialize(MOD_TYPE_IT);
|
|
mptSmp.nLength = length;
|
|
mptSmp.nLoopStart = loopStart;
|
|
mptSmp.nLoopEnd = mptSmp.nLoopStart + loopLength;
|
|
// In revolution to come.dtm, the file header says samples rate is 24512 Hz, but samples say it's 50000 Hz
|
|
// Digital Home Studio ignores the header setting in 2.04-/2.06-style modules
|
|
mptSmp.nC5Speed = (formatVersion == DTM_PT_PATTERN_FORMAT && forcedSampleRate > 0) ? forcedSampleRate : sampleRate;
|
|
int32 transposeAmount = MOD2XMFineTune(finetune);
|
|
if(formatVersion == DTM_206_PATTERN_FORMAT && transpose > 0 && transpose != 48)
|
|
{
|
|
// Digital Home Studio applies this unconditionally, but some old songs sound wrong then (delirium.dtm).
|
|
// Digital Tracker 2.03 ignores the setting.
|
|
// Maybe this should not be applied for "real" Digital Tracker modules?
|
|
transposeAmount += (48 - transpose) * 128;
|
|
}
|
|
mptSmp.Transpose(transposeAmount * (1.0 / (12.0 * 128.0)));
|
|
mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
|
|
if(stereo & 1)
|
|
{
|
|
mptSmp.uFlags.set(CHN_STEREO);
|
|
mptSmp.nLength /= 2u;
|
|
mptSmp.nLoopStart /= 2u;
|
|
mptSmp.nLoopEnd /= 2u;
|
|
}
|
|
if(bitDepth > 8)
|
|
{
|
|
mptSmp.uFlags.set(CHN_16BIT);
|
|
mptSmp.nLength /= 2u;
|
|
mptSmp.nLoopStart /= 2u;
|
|
mptSmp.nLoopEnd /= 2u;
|
|
}
|
|
if(mptSmp.nLoopEnd > mptSmp.nLoopStart + 1)
|
|
{
|
|
mptSmp.uFlags.set(CHN_LOOP);
|
|
} else
|
|
{
|
|
mptSmp.nLoopStart = mptSmp.nLoopEnd = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMSample, 50)
|
|
|
|
|
|
struct DTMInstrument
|
|
{
|
|
uint16be insNum;
|
|
uint8be unknown1;
|
|
uint8be envelope; // 0xFF = none
|
|
uint8be sustain; // 0xFF = no sustain point
|
|
uint16be fadeout;
|
|
uint8be vibRate;
|
|
uint8be vibDepth;
|
|
uint8be modulationRate;
|
|
uint8be modulationDepth;
|
|
uint8be breathRate;
|
|
uint8be breathDepth;
|
|
uint8be volumeRate;
|
|
uint8be volumeDepth;
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMInstrument, 15)
|
|
|
|
|
|
struct DTMEnvelope
|
|
{
|
|
struct DTMEnvPoint
|
|
{
|
|
uint8be value;
|
|
uint8be tick;
|
|
};
|
|
uint16be numPoints;
|
|
DTMEnvPoint points[16];
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMEnvelope::DTMEnvPoint, 2)
|
|
MPT_BINARY_STRUCT(DTMEnvelope, 34)
|
|
|
|
|
|
struct DTMText
|
|
{
|
|
uint16be textType; // 0 = pattern, 1 = free, 2 = song
|
|
uint32be textLength;
|
|
uint16be tabWidth;
|
|
uint16be reserved;
|
|
uint16be oddLength;
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(DTMText, 12)
|
|
|
|
|
|
static bool ValidateHeader(const DTMFileHeader &fileHeader)
|
|
{
|
|
if(std::memcmp(fileHeader.magic, "D.T.", 4)
|
|
|| fileHeader.headerSize < sizeof(fileHeader) - 8u
|
|
|| fileHeader.headerSize > 256 // Excessively long song title?
|
|
|| fileHeader.type != 0)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderDTM(MemoryFileReader file, const uint64 *pfilesize)
|
|
{
|
|
DTMFileHeader fileHeader;
|
|
if(!file.ReadStruct(fileHeader))
|
|
{
|
|
return ProbeWantMoreData;
|
|
}
|
|
if(!ValidateHeader(fileHeader))
|
|
{
|
|
return ProbeFailure;
|
|
}
|
|
MPT_UNREFERENCED_PARAMETER(pfilesize);
|
|
return ProbeSuccess;
|
|
}
|
|
|
|
|
|
bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags)
|
|
{
|
|
file.Rewind();
|
|
|
|
DTMFileHeader fileHeader;
|
|
if(!file.ReadStruct(fileHeader))
|
|
{
|
|
return false;
|
|
}
|
|
if(!ValidateHeader(fileHeader))
|
|
{
|
|
return false;
|
|
}
|
|
if(loadFlags == onlyVerifyHeader)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
InitializeGlobals(MOD_TYPE_DTM);
|
|
InitializeChannels();
|
|
m_SongFlags.set(SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS);
|
|
m_playBehaviour.reset(kITVibratoTremoloPanbrello);
|
|
// Various files have a default speed or tempo of 0
|
|
if(fileHeader.tempo)
|
|
m_nDefaultTempo.Set(fileHeader.tempo);
|
|
if(fileHeader.speed)
|
|
m_nDefaultSpeed = fileHeader.speed;
|
|
if(fileHeader.stereoMode == 0)
|
|
SetupMODPanning(true);
|
|
|
|
file.ReadString<mpt::String::maybeNullTerminated>(m_songName, fileHeader.headerSize - (sizeof(fileHeader) - 8u));
|
|
|
|
auto chunks = ChunkReader(file).ReadChunks<DTMChunk>(1);
|
|
|
|
// Read order list
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idS_Q_))
|
|
{
|
|
uint16 ordLen = chunk.ReadUint16BE();
|
|
uint16 restartPos = chunk.ReadUint16BE();
|
|
chunk.Skip(4); // Reserved
|
|
ReadOrderFromFile<uint8>(Order(), chunk, ordLen);
|
|
Order().SetRestartPos(restartPos);
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Read pattern properties
|
|
uint32 patternFormat;
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idPATT))
|
|
{
|
|
m_nChannels = chunk.ReadUint16BE();
|
|
if(m_nChannels < 1 || m_nChannels > 32)
|
|
{
|
|
return false;
|
|
}
|
|
Patterns.ResizeArray(chunk.ReadUint16BE()); // Number of stored patterns, may be lower than highest pattern number
|
|
patternFormat = chunk.ReadUint32BE();
|
|
if(patternFormat != DTM_PT_PATTERN_FORMAT && patternFormat != DTM_204_PATTERN_FORMAT && patternFormat != DTM_206_PATTERN_FORMAT)
|
|
{
|
|
return false;
|
|
}
|
|
} else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Read global info
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idSV19))
|
|
{
|
|
chunk.Skip(2); // Ticks per quarter note, typically 24
|
|
uint32 fractionalTempo = chunk.ReadUint32BE();
|
|
m_nDefaultTempo = TEMPO(m_nDefaultTempo.GetInt() + fractionalTempo / 4294967296.0);
|
|
|
|
uint16be panning[32];
|
|
chunk.ReadArray(panning);
|
|
for(CHANNELINDEX chn = 0; chn < 32 && chn < GetNumChannels(); chn++)
|
|
{
|
|
// Panning is in range 0...180, 90 = center
|
|
ChnSettings[chn].nPan = static_cast<uint16>(128 + Util::muldivr(std::min(static_cast<int>(panning[chn]), int(180)) - 90, 128, 90));
|
|
}
|
|
|
|
chunk.Skip(16);
|
|
// Chunk ends here for old DTM modules
|
|
if(chunk.CanRead(2))
|
|
{
|
|
m_nDefaultGlobalVolume = std::min(chunk.ReadUint16BE(), static_cast<uint16>(MAX_GLOBAL_VOLUME));
|
|
}
|
|
chunk.Skip(128);
|
|
uint16be volume[32];
|
|
if(chunk.ReadArray(volume))
|
|
{
|
|
for(CHANNELINDEX chn = 0; chn < 32 && chn < GetNumChannels(); chn++)
|
|
{
|
|
// Volume is in range 0...128, 64 = normal
|
|
ChnSettings[chn].nVolume = static_cast<uint8>(std::min(static_cast<int>(volume[chn]), int(128)) / 2);
|
|
}
|
|
m_nSamplePreAmp *= 2; // Compensate for channel volume range
|
|
}
|
|
}
|
|
|
|
// Read song message
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idTEXT))
|
|
{
|
|
DTMText text;
|
|
chunk.ReadStruct(text);
|
|
if(text.oddLength == 0xFFFF)
|
|
{
|
|
chunk.Skip(1);
|
|
}
|
|
m_songMessage.Read(chunk, chunk.BytesLeft(), SongMessage::leCRLF);
|
|
}
|
|
|
|
// Read sample headers
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idINST))
|
|
{
|
|
uint16 numSamples = chunk.ReadUint16BE();
|
|
bool newSamples = (numSamples >= 0x8000);
|
|
numSamples &= 0x7FFF;
|
|
if(numSamples >= MAX_SAMPLES || !chunk.CanRead(numSamples * (sizeof(DTMSample) + (newSamples ? 2u : 0u))))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_nSamples = numSamples;
|
|
for(SAMPLEINDEX smp = 1; smp <= numSamples; smp++)
|
|
{
|
|
SAMPLEINDEX realSample = newSamples ? (chunk.ReadUint16BE() + 1u) : smp;
|
|
DTMSample dtmSample;
|
|
chunk.ReadStruct(dtmSample);
|
|
if(realSample < 1 || realSample >= MAX_SAMPLES)
|
|
{
|
|
continue;
|
|
}
|
|
m_nSamples = std::max(m_nSamples, realSample);
|
|
ModSample &mptSmp = Samples[realSample];
|
|
dtmSample.ConvertToMPT(mptSmp, fileHeader.forcedSampleRate, patternFormat);
|
|
m_szNames[realSample] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, dtmSample.name);
|
|
}
|
|
|
|
if(chunk.ReadUint16BE() == 0x0004)
|
|
{
|
|
// Digital Home Studio instruments
|
|
m_nInstruments = std::min(static_cast<INSTRUMENTINDEX>(m_nSamples), static_cast<INSTRUMENTINDEX>(MAX_INSTRUMENTS - 1));
|
|
|
|
FileReader envChunk = chunks.GetChunk(DTMChunk::idIENV);
|
|
while(chunk.CanRead(sizeof(DTMInstrument)))
|
|
{
|
|
DTMInstrument instr;
|
|
chunk.ReadStruct(instr);
|
|
if(instr.insNum < GetNumInstruments())
|
|
{
|
|
ModSample &sample = Samples[instr.insNum + 1];
|
|
sample.nVibDepth = instr.vibDepth;
|
|
sample.nVibRate = instr.vibRate;
|
|
sample.nVibSweep = 255;
|
|
|
|
ModInstrument *mptIns = AllocateInstrument(instr.insNum + 1, instr.insNum + 1);
|
|
if(mptIns != nullptr)
|
|
{
|
|
InstrumentEnvelope &mptEnv = mptIns->VolEnv;
|
|
mptIns->nFadeOut = std::min(static_cast<uint16>(instr.fadeout), uint16(0xFFF));
|
|
if(instr.envelope != 0xFF && envChunk.Seek(2 + sizeof(DTMEnvelope) * instr.envelope))
|
|
{
|
|
DTMEnvelope env;
|
|
envChunk.ReadStruct(env);
|
|
mptEnv.dwFlags.set(ENV_ENABLED);
|
|
mptEnv.resize(std::min({ static_cast<std::size_t>(env.numPoints), std::size(env.points), static_cast<std::size_t>(MAX_ENVPOINTS) }));
|
|
for(size_t i = 0; i < mptEnv.size(); i++)
|
|
{
|
|
mptEnv[i].value = std::min(uint8(64), static_cast<uint8>(env.points[i].value));
|
|
mptEnv[i].tick = env.points[i].tick;
|
|
}
|
|
|
|
if(instr.sustain != 0xFF)
|
|
{
|
|
mptEnv.dwFlags.set(ENV_SUSTAIN);
|
|
mptEnv.nSustainStart = mptEnv.nSustainEnd = instr.sustain;
|
|
}
|
|
if(!mptEnv.empty())
|
|
{
|
|
mptEnv.dwFlags.set(ENV_LOOP);
|
|
mptEnv.nLoopStart = mptEnv.nLoopEnd = static_cast<uint8>(mptEnv.size() - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read pattern data
|
|
for(auto &chunk : chunks.GetAllChunks(DTMChunk::idDAPT))
|
|
{
|
|
chunk.Skip(4); // FF FF FF FF
|
|
PATTERNINDEX patNum = chunk.ReadUint16BE();
|
|
ROWINDEX numRows = chunk.ReadUint16BE();
|
|
if(patternFormat == DTM_206_PATTERN_FORMAT)
|
|
{
|
|
// The stored data is actually not row-based, but tick-based.
|
|
numRows /= m_nDefaultSpeed;
|
|
}
|
|
if(!(loadFlags & loadPatternData) || patNum > 255 || !Patterns.Insert(patNum, numRows))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(patternFormat == DTM_206_PATTERN_FORMAT)
|
|
{
|
|
chunk.Skip(4);
|
|
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
|
|
{
|
|
uint16 length = chunk.ReadUint16BE();
|
|
if(length % 2u) length++;
|
|
FileReader rowChunk = chunk.ReadChunk(length);
|
|
int tick = 0;
|
|
std::div_t position = { 0, 0 };
|
|
while(rowChunk.CanRead(6) && static_cast<ROWINDEX>(position.quot) < numRows)
|
|
{
|
|
ModCommand *m = Patterns[patNum].GetpModCommand(position.quot, chn);
|
|
|
|
const auto [note, volume, instr, command, param, delay] = rowChunk.ReadArray<uint8, 6>();
|
|
if(note > 0 && note <= 96)
|
|
{
|
|
m->note = note + NOTE_MIN + 12;
|
|
if(position.rem)
|
|
{
|
|
m->command = CMD_MODCMDEX;
|
|
m->param = 0xD0 | static_cast<ModCommand::PARAM>(std::min(position.rem, 15));
|
|
}
|
|
} else if(note & 0x80)
|
|
{
|
|
// Lower 7 bits contain note, probably intended for MIDI-like note-on/note-off events
|
|
if(position.rem)
|
|
{
|
|
m->command = CMD_MODCMDEX;
|
|
m->param = 0xC0 | static_cast<ModCommand::PARAM>(std::min(position.rem, 15));
|
|
} else
|
|
{
|
|
m->note = NOTE_NOTECUT;
|
|
}
|
|
}
|
|
if(volume)
|
|
{
|
|
m->volcmd = VOLCMD_VOLUME;
|
|
m->vol = std::min(volume, uint8(64)); // Volume can go up to 255, but we do not support over-amplification at the moment.
|
|
}
|
|
if(instr)
|
|
{
|
|
m->instr = instr;
|
|
}
|
|
if(command || param)
|
|
{
|
|
m->command = command;
|
|
m->param = param;
|
|
ConvertModCommand(*m);
|
|
#ifdef MODPLUG_TRACKER
|
|
m->Convert(MOD_TYPE_MOD, MOD_TYPE_IT, *this);
|
|
#endif
|
|
// G is 8-bit volume
|
|
// P is tremor (need to disable oldfx)
|
|
}
|
|
if(delay & 0x80)
|
|
tick += (delay & 0x7F) * 0x100 + rowChunk.ReadUint8();
|
|
else
|
|
tick += delay;
|
|
position = std::div(tick, m_nDefaultSpeed);
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
ModCommand *m = Patterns[patNum].GetpModCommand(0, 0);
|
|
for(ROWINDEX row = 0; row < numRows; row++)
|
|
{
|
|
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++, m++)
|
|
{
|
|
const auto data = chunk.ReadArray<uint8, 4>();
|
|
if(patternFormat == DTM_204_PATTERN_FORMAT)
|
|
{
|
|
const auto [note, instrVol, instrCmd, param] = data;
|
|
if(note > 0 && note < 0x80)
|
|
{
|
|
m->note = (note >> 4) * 12 + (note & 0x0F) + NOTE_MIN + 11;
|
|
}
|
|
uint8 vol = instrVol >> 2;
|
|
if(vol)
|
|
{
|
|
m->volcmd = VOLCMD_VOLUME;
|
|
m->vol = vol - 1u;
|
|
}
|
|
m->instr = ((instrVol & 0x03) << 4) | (instrCmd >> 4);
|
|
m->command = instrCmd & 0x0F;
|
|
m->param = param;
|
|
} else
|
|
{
|
|
ReadMODPatternEntry(data, *m);
|
|
m->instr |= data[0] & 0x30; // Allow more than 31 instruments
|
|
}
|
|
ConvertModCommand(*m);
|
|
// Fix commands without memory and slide nibble precedence
|
|
switch(m->command)
|
|
{
|
|
case CMD_PORTAMENTOUP:
|
|
case CMD_PORTAMENTODOWN:
|
|
if(!m->param)
|
|
{
|
|
m->command = CMD_NONE;
|
|
}
|
|
break;
|
|
case CMD_VOLUMESLIDE:
|
|
case CMD_TONEPORTAVOL:
|
|
case CMD_VIBRATOVOL:
|
|
if(m->param & 0xF0)
|
|
{
|
|
m->param &= 0xF0;
|
|
} else if(!m->param)
|
|
{
|
|
m->command = CMD_NONE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#ifdef MODPLUG_TRACKER
|
|
m->Convert(MOD_TYPE_MOD, MOD_TYPE_IT, *this);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read pattern names
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idPATN))
|
|
{
|
|
PATTERNINDEX pat = 0;
|
|
std::string name;
|
|
while(chunk.CanRead(1) && pat < Patterns.Size())
|
|
{
|
|
chunk.ReadNullString(name, 32);
|
|
Patterns[pat].SetName(name);
|
|
pat++;
|
|
}
|
|
}
|
|
|
|
// Read channel names
|
|
if(FileReader chunk = chunks.GetChunk(DTMChunk::idTRKN))
|
|
{
|
|
CHANNELINDEX chn = 0;
|
|
std::string name;
|
|
while(chunk.CanRead(1) && chn < GetNumChannels())
|
|
{
|
|
chunk.ReadNullString(name, 32);
|
|
ChnSettings[chn].szName = name;
|
|
chn++;
|
|
}
|
|
}
|
|
|
|
// Read sample data
|
|
for(auto &chunk : chunks.GetAllChunks(DTMChunk::idDAIT))
|
|
{
|
|
SAMPLEINDEX smp = chunk.ReadUint16BE();
|
|
if(smp >= GetNumSamples() || !(loadFlags & loadSampleData))
|
|
{
|
|
continue;
|
|
}
|
|
ModSample &mptSmp = Samples[smp + 1];
|
|
SampleIO(
|
|
mptSmp.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
|
|
mptSmp.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved: SampleIO::mono,
|
|
SampleIO::bigEndian,
|
|
SampleIO::signedPCM).ReadSample(mptSmp, chunk);
|
|
}
|
|
|
|
// Is this accurate?
|
|
mpt::ustring tracker;
|
|
if(patternFormat == DTM_206_PATTERN_FORMAT)
|
|
{
|
|
tracker = U_("Digital Home Studio");
|
|
} else if(FileReader chunk = chunks.GetChunk(DTMChunk::idVERS))
|
|
{
|
|
uint32 version = chunk.ReadUint32BE();
|
|
tracker = MPT_UFORMAT("Digital Tracker {}.{}")(version >> 4, version & 0x0F);
|
|
} else
|
|
{
|
|
tracker = U_("Digital Tracker");
|
|
}
|
|
m_modFormat.formatName = U_("Digital Tracker");
|
|
m_modFormat.type = U_("dtm");
|
|
m_modFormat.madeWithTracker = std::move(tracker);
|
|
m_modFormat.charset = mpt::Charset::Amiga_no_C1;
|
|
|
|
return true;
|
|
}
|
|
|
|
OPENMPT_NAMESPACE_END
|