diff --git OpenMPT/libopenmpt/foo_openmpt.cpp OpenMPT/libopenmpt/foo_openmpt.cpp index ac7ce34..8947cdf 100644 --- OpenMPT/libopenmpt/foo_openmpt.cpp +++ OpenMPT/libopenmpt/foo_openmpt.cpp @@ -319,6 +319,7 @@ DECLARE_FILE_TYPE("OpenMPT compatible module files", "*.imf" ";" "*.j2b" ";" "*.plm" ";" + "*.stp" ";" "*.gdm" ";" "*.umx" ";" "*.mo3" ";" diff --git OpenMPT/soundlib/Load_stp.cpp OpenMPT/soundlib/Load_stp.cpp new file mode 100644 index 0000000..457ecdf --- /dev/null +++ OpenMPT/soundlib/Load_stp.cpp @@ -0,0 +1,716 @@ +/* + * Load_stp.cpp + * ------------ + * Purpose: STP (Soundtracker Pro II) module loader + * Notes : doesn't support multiple loops per sample, and various exotic effects. + * fractional speed values and combined auto effects are handled whenever possible, + * but some effects may be omitted (and there may be tempo accuracy issues). + * Authors: Devin Acker + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + * + * Wisdom from the Soundtracker Pro II manual: + * "To create shorter patterns, simply create shorter patterns." + */ + +#include "stdafx.h" +#include "Loaders.h" +#include "Tables.h" + +OPENMPT_NAMESPACE_BEGIN + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(push, 1) +#endif + +// File header (except for "STP3" magic) +struct PACKED STPFileHeader { + uint16 version; + uint8 numOrders; + uint8 patternLength; + uint8 orderList[128]; + uint16 speed; + uint16 speedFrac; + uint16 timerCount; + uint16 flags; + uint32 reserved; + uint16 midiCount; // always 50 + uint8 midi[50]; + uint16 numSamples; + uint16 sampleStructSize; + + void ConvertEndianness() + { + SwapBytesBE(version); + SwapBytesBE(speed); + SwapBytesBE(speedFrac); + SwapBytesBE(timerCount); + SwapBytesBE(flags); + SwapBytesBE(midiCount); + SwapBytesBE(numSamples); + SwapBytesBE(sampleStructSize); + } +}; + +STATIC_ASSERT(sizeof(STPFileHeader) == 200); + +// Sample header (versions 0 and 1) +struct PACKED STPSampleHeaderOld { + char pathName[31]; + uint8 flags; + char fileName[30]; + uint32 length; + uint8 volume; + uint8 reserved1; + uint32 loopStart; + uint32 loopLength; + uint16 defaultCmd; + uint32 reserved2; + + void ConvertEndianness() + { + SwapBytesBE(length); + SwapBytesBE(loopStart); + SwapBytesBE(loopLength); + SwapBytesBE(defaultCmd); + } + + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = length; + mptSmp.nVolume = 4 * MIN(volume, 64); + + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopStart + loopLength; + + if(mptSmp.nLoopStart >= mptSmp.nLength) + { + mptSmp.nLoopStart = mptSmp.nLength - 1; + } + if(mptSmp.nLoopEnd > mptSmp.nLength) + { + mptSmp.nLoopEnd = mptSmp.nLength; + } + + if(mptSmp.nLoopStart > mptSmp.nLoopEnd) + { + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 0; + } + else if(mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + } + } + + void Read(FileReader &file) { + file.ReadConvertEndianness(*this); + pathName[30] = '\0'; + } +}; + +STATIC_ASSERT(sizeof(STPSampleHeaderOld) == 82); + +// Sample header (version 2), not packed due to variable string length and alignment in file +struct STPSampleHeader { + char pathName[256]; + uint8 flags; + char fileName[30]; + uint32 length; + uint8 volume; + uint8 reserved1; + uint32 loopStart; + uint32 loopLength; + uint16 defaultCmd; + uint16 defaultPeriod; + uint8 finetune; + uint8 reserved2; + + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = length; + mptSmp.nFineTune = static_cast(finetune << 3); + mptSmp.nVolume = 4 * MIN(volume, 64); + + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopStart + loopLength; + + if(mptSmp.nLoopStart >= mptSmp.nLength) + { + mptSmp.nLoopStart = mptSmp.nLength - 1; + } + if(mptSmp.nLoopEnd > mptSmp.nLength) + { + mptSmp.nLoopEnd = mptSmp.nLength; + } + + if(mptSmp.nLoopStart > mptSmp.nLoopEnd) + { + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 0; + } + else if(mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + } + } + + void Read(FileReader &file) { + std::string str; + + file.ReadNullString(str, 255); + std::strncpy(pathName, str.c_str(), 255); + pathName[255] = '\0'; + + flags = file.ReadUint8(); + + file.ReadNullString(str, 29); + std::strncpy(fileName, str.c_str(), 29); + pathName[29] = '\0'; + + // seek to even boundary + if(file.GetPosition() & 1) + file.Skip(1); + + length = file.ReadUint32BE(); + volume = file.ReadUint8(); + reserved1 = file.ReadUint8(); + + loopStart = file.ReadUint32BE(); + loopLength = file.ReadUint32BE(); + + defaultCmd = file.ReadUint16BE(); + defaultPeriod = file.ReadUint16BE(); + + finetune = file.ReadUint8(); + reserved2 = file.ReadUint8(); + } +}; + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(pop) +#endif + +template +static void ReadSample(FileReader &file, T &sampleHeader, ModSample &sample, char (&sampleName)[MAX_SAMPLENAME]) +//-------------------------------------------------------------------------------------------------------------- +{ + sampleHeader.Read(file); + sampleHeader.ConvertToMPT(sample); + + mpt::String::Read(sampleName, sampleHeader.fileName); + // Get rid of weird characters in sample names. + for(size_t i = 0; i < CountOf(sampleName); i++) + { + if(sampleName[i] > 0 && sampleName[i] < ' ') + { + sampleName[i] = ' '; + } + } +} + +static TEMPO ConvertTempo(uint16 ciaSpeed) +//--------------------------------------- +{ + // 3546 is the resulting CIA timer value when using 4F7D (tempo 125 bpm) command in STProII + return TEMPO(125.0 * (3546.0 / ciaSpeed)); +} + +bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) +//------------------------------------------------------------------- +{ + file.Rewind(); + if(!file.ReadMagic("STP3")) + return false; + + STPFileHeader fileHeader; + file.ReadConvertEndianness(fileHeader); + if(fileHeader.version > 2 || + fileHeader.numOrders > 128 || fileHeader.numSamples > MAX_SAMPLES || + fileHeader.midiCount != 50) + return false; + + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_STP); + + m_nChannels = 4; + m_nSamples = 0; + m_nInstruments = 0; + + m_nDefaultSpeed = fileHeader.speed; + m_nDefaultTempo = ConvertTempo(fileHeader.timerCount); + + m_nMinPeriod = 14 * 4; + m_nMaxPeriod = 3424 * 4; + + Order.ReadFromArray(fileHeader.orderList, fileHeader.numOrders); + + // Load sample headers + for(SAMPLEINDEX smp = 1; smp <= fileHeader.numSamples; smp++) + { + // this is 1-based the same as smp + SAMPLEINDEX actualSmp = file.ReadUint16BE(); + if(actualSmp > MAX_SAMPLES) + return false; + + m_nSamples = MAX(m_nSamples, actualSmp); + + if(fileHeader.version == 2) + { + static STPSampleHeader sampleHeader; + uint32 headerSize = file.ReadUint32BE(); + + ReadSample(file, sampleHeader, Samples[actualSmp], m_szNames[actualSmp]); + // TODO: verify string lengths against headerSize? + (void)headerSize; + } + else + { + static STPSampleHeaderOld sampleHeaderOld; + ReadSample(file, sampleHeaderOld, Samples[actualSmp], m_szNames[actualSmp]); + } + + if(fileHeader.version >= 1) + { + uint16 numLoops = file.ReadUint16BE(); + // ignore these + file.Skip(numLoops*8); + } + } + + // Load patterns + uint16 numPatterns = 128; + if (fileHeader.version == 0) + numPatterns = file.ReadUint16BE(); + + uint16 patternLength = fileHeader.patternLength; + uint16 channels = 4; + + uint8 globalVolSlide = 0; + std::vector autoFinePorta; + std::vector autoPortaUp; + std::vector autoPortaDown; + std::vector autoVolSlide; + std::vector autoVibrato; + std::vector vibratoMem; + std::vector autoTremolo; + std::vector autoTonePorta; + std::vector tonePortaMem; + + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + PATTERNINDEX actualPat = pat; + + if(fileHeader.version > 0) + { + actualPat = file.ReadUint16BE(); + if(actualPat == 0xFFFF) + break; + + patternLength = file.ReadUint16BE(); + channels = file.ReadUint16BE(); + + m_nChannels = MIN(m_nChannels, channels); + } + + if(!(loadFlags & loadPatternData) || !Patterns.Insert(actualPat, patternLength)) + { + file.Skip(channels * patternLength * 4); + continue; + } + + autoFinePorta.resize(channels, 0); + autoPortaUp.resize(channels, 0); + autoPortaDown.resize(channels, 0); + autoVolSlide.resize(channels, 0); + autoVibrato.resize(channels, 0); + vibratoMem.resize(channels, 0); + autoTremolo.resize(channels, 0); + autoTonePorta.resize(channels, 0); + tonePortaMem.resize(channels, 0); + + for(ROWINDEX row = 0; row < patternLength; row++) + { + PatternRow rowBase = Patterns[actualPat].GetpModCommand(row, 0); + + bool didGlobalVolSlide = false; + bool shouldDelay = false; + + // if a fractional speed value is in use then determine if we should stick a fine pattern delay somewhere + switch(fileHeader.speedFrac & 3) + { + // 1/4 + case 1: shouldDelay = (row & 3) == 0; break; + // 1/2 + case 2: shouldDelay = (row & 1) == 0; break; + // 3/4 + case 3: shouldDelay = (row & 3) != 3; break; + } + + for(CHANNELINDEX chn = 0; chn < channels; chn++) + { + ModCommand &m = rowBase[chn]; + + m.instr = file.ReadUint8(); + m.note = file.ReadUint8(); + m.command = file.ReadUint8(); + m.param = file.ReadUint8(); + + if(m.note) + { + m.note += 25; + + autoFinePorta[chn] = 0; + autoPortaUp[chn] = 0; + autoPortaDown[chn] = 0; + autoVolSlide[chn] = 0; + autoVibrato[chn] = vibratoMem[chn] = 0; + autoTremolo[chn] = 0; + autoTonePorta[chn] = tonePortaMem[chn] = 0; + } + + uint8 swap = (m.param >> 4) | (m.param << 4); + + if((m.command & 0xF0) == 0xF0) + { + m.param = Util::Round(ConvertTempo(m.param | (((uint16)m.command & 0xF) << 8)).ToDouble()); + m.command = CMD_TEMPO; + } else switch(m.command) + { + case 0x00: // arpeggio + if(m.param) + m.command = CMD_ARPEGGIO; + break; + + case 0x01: // portamento up + m.command = CMD_PORTAMENTOUP; + break; + + case 0x02: // portamento down + m.command = CMD_PORTAMENTODOWN; + break; + + case 0x03: // auto fine portamento up + autoFinePorta[chn] = 0x10 | MIN(m.param, 15); + autoPortaUp[chn] = 0; + autoPortaDown[chn] = 0; + autoTonePorta[chn] = 0; + + m.command = m.param = 0; + break; + + case 0x04: // auto fine portamento down + autoFinePorta[chn] = 0x20 | MIN(m.param, 15); + autoPortaUp[chn] = 0; + autoPortaDown[chn] = 0; + autoTonePorta[chn] = 0; + + m.command = m.param = 0; + break; + + case 0x05: // auto portamento up + autoFinePorta[chn] = 0; + autoPortaUp[chn] = m.param; + autoPortaDown[chn] = 0; + autoTonePorta[chn] = 0; + + m.command = m.param = 0; + break; + + case 0x06: // auto portamento down + autoFinePorta[chn] = 0; + autoPortaUp[chn] = 0; + autoPortaDown[chn] = m.param; + autoTonePorta[chn] = 0; + + m.command = m.param = 0; + break; + + case 0x07: // set global volume + m.command = CMD_GLOBALVOLUME; + globalVolSlide = 0; + break; + + case 0x08: // auto global volume slide + globalVolSlide = swap; + m.command = m.param = 0; + break; + + case 0x09: // fine portamento up + m.command = CMD_MODCMDEX; + m.param = 0x10 | MIN(m.param, 15); + break; + + case 0x0A: // fine portamento down + m.command = CMD_MODCMDEX; + m.param = 0x20 | MIN(m.param, 15); + break; + + case 0x0B: // auto volume slide + autoVolSlide[chn] = swap; + m.command = m.param = 0; + break; + + case 0x0C: // set volume + m.volcmd = VOLCMD_VOLUME; + m.vol = m.param; + autoVolSlide[chn] = 0; + m.command = m.param = 0; + break; + + case 0x0D: // volume slide (param is swapped compared to .mod) + if(m.param & 0xF0) + { + m.volcmd = VOLCMD_VOLSLIDEDOWN; + m.vol = m.param >> 4; + } else if(m.param & 0x0F) + { + m.volcmd = VOLCMD_VOLSLIDEUP; + m.vol = m.param & 0xF; + } + autoVolSlide[chn] = 0; + m.command = m.param = 0; + break; + + case 0x0E: // set filter (also uses opposite value compared to .mod) + m.command = CMD_MODCMDEX; + m.param = 1 ^ MIN(m.param, 1); + break; + + case 0x0F: // set speed + m.command = CMD_SPEED; + fileHeader.speedFrac = m.param & 0xF; + m.param >>= 4; + break; + + case 0x10: // auto vibrato + autoVibrato[chn] = m.param; + vibratoMem[chn] = 0; + m.command = m.param = 0; + break; + + case 0x11: // auto tremolo + if(m.param & 0xF) + autoTremolo[chn] = m.param; + else + autoTremolo[chn] = 0; + m.command = m.param = 0; + break; + + case 0x12: // pattern break + m.command = CMD_PATTERNBREAK; + break; + + case 0x13: // auto tone portamento + autoFinePorta[chn] = 0; + autoPortaUp[chn] = 0; + autoPortaDown[chn] = 0; + autoTonePorta[chn] = m.param; + + tonePortaMem[chn] = 0; + m.command = m.param = 0; + break; + + case 0x14: // position jump + m.command = CMD_POSITIONJUMP; + break; + + case 0x1D: // fine volume slide + m.command = CMD_MODCMDEX; + if(m.param & 0xF0) + m.param = 0xB0 | (m.param >> 4); + else if(m.param & 0x0F) + m.param = 0xA0 | (m.param & 0xF); + break; + + case 0x20: // "delayed fade" + // just behave like either a normal fade or a notecut + // depending on the speed + if(m.param & 0xF0) + { + autoVolSlide[chn] = m.param >> 4; + m.command = m.param = 0; + } else { + m.command = CMD_MODCMDEX; + m.param = 0xC0 | (m.param & 0xF); + } + break; + + case 0x21: // note delay + m.command = CMD_MODCMDEX; + m.param = 0xD0 | MIN(m.param, 15); + break; + + case 0x22: // retrigger note + m.command = CMD_RETRIG; + break; + + case 0x49: // set sample offset + m.command = CMD_OFFSET; + break; + + case 0x4E: // other protracker commands (pattern loop / delay) + if((m.param & 0xF0) == 0x60 || (m.param & 0xF0) == 0xE0) + m.command = CMD_MODCMDEX; + else + m.command = m.param = 0; + break; + + case 0x4F: // set speed/tempo + if(m.param < 0x20) + { + m.command = CMD_SPEED; + fileHeader.speedFrac = 0; + } else + { + m.command = CMD_TEMPO; + } + break; + + default: + m.command = m.param = 0; + break; + } + + bool didVolSlide = false; + + // try to put volume slide in volume command + if(autoVolSlide[chn] && !m.volcmd) + { + if(autoVolSlide[chn] & 0xF0) + { + m.volcmd = VOLCMD_VOLSLIDEDOWN; + m.vol = autoVolSlide[chn] >> 4; + } else + { + m.volcmd = VOLCMD_VOLSLIDEUP; + m.vol = autoVolSlide[chn] & 0xF; + } + didVolSlide = true; + } + + // try to place/combine all remaining running effects. + if(!m.command) + { + if(autoPortaUp[chn]) + { + m.command = CMD_PORTAMENTOUP; + m.param = autoPortaUp[chn]; + + } else if(autoPortaDown[chn]) + { + m.command = CMD_PORTAMENTODOWN; + m.param = autoPortaDown[chn]; + + } else if(autoFinePorta[chn]) + { + m.command = CMD_MODCMDEX; + m.param = autoFinePorta[chn]; + + } else if(autoTonePorta[chn]) + { + // try a volslide+portamento + if (!didVolSlide && autoVolSlide[chn] && tonePortaMem[chn]) + { + m.command = CMD_TONEPORTAVOL; + m.param = autoVolSlide[chn]; + didVolSlide = true; + } else + { + m.command = CMD_TONEPORTAMENTO; + m.param = tonePortaMem[chn] = autoTonePorta[chn]; + } + + } else if(autoVibrato[chn]) + { + // try a volslide+vibrato + if (!didVolSlide && autoVolSlide[chn] && vibratoMem[chn]) + { + m.command = CMD_VIBRATOVOL; + m.param = autoVolSlide[chn]; + didVolSlide = true; + } else + { + m.command = CMD_VIBRATO; + m.param = vibratoMem[chn] = autoVibrato[chn]; + } + + } else if(!didVolSlide && autoVolSlide[chn]) + { + m.command = CMD_VOLUMESLIDE; + m.param = autoVolSlide[chn]; + didVolSlide = true; + + } else if(autoTremolo[chn]) + { + m.command = CMD_TREMOLO; + m.param = autoTremolo[chn]; + + } else if(shouldDelay) + { + // insert a fine pattern delay here + m.command = CMD_S3MCMDEX; + m.param = 0x61; + shouldDelay = false; + + } else if(!didGlobalVolSlide && globalVolSlide) + { + m.command = CMD_GLOBALVOLSLIDE; + m.param = globalVolSlide; + didGlobalVolSlide = true; + } + } + } + + // TODO: create/use extra channels for global volslide/delay if needed + } + } + + // after we know how many channels there really are... + m_nSamplePreAmp = 256 / m_nChannels; + // Setup channel pan positions and volume + SetupMODPanning(true); + + // Skip over scripts and drumpad info + if(fileHeader.version > 0) + { + uint16 scriptNum; + uint32 length; + + while(file.CanRead(2)) + { + scriptNum = file.ReadUint16BE(); + if(scriptNum == 0xFFFF) + break; + + file.Skip(2); + length = file.ReadUint32BE(); + file.Skip(length); + } + + // skip drumpad stuff + file.Skip(17*2); + } + + // Reading samples + if(loadFlags & loadSampleData) + { + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) if(Samples[smp].nLength) + { + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git OpenMPT/soundlib/Snd_defs.h OpenMPT/soundlib/Snd_defs.h index 4499f0b..23eeb62 100644 --- OpenMPT/soundlib/Snd_defs.h +++ OpenMPT/soundlib/Snd_defs.h @@ -97,6 +97,7 @@ enum MODTYPE MOD_TYPE_DIGI = 0x8000000, MOD_TYPE_UAX = 0x10000000, // sampleset as module MOD_TYPE_PLM = 0x20000000, + MOD_TYPE_STP = 0x40000000, }; DECLARE_FLAGSET(MODTYPE) diff --git OpenMPT/soundlib/Snd_fx.cpp OpenMPT/soundlib/Snd_fx.cpp index 1631620..80bcb87 100644 --- OpenMPT/soundlib/Snd_fx.cpp +++ OpenMPT/soundlib/Snd_fx.cpp @@ -3358,7 +3358,7 @@ void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const else param = pChn->nOldPortaUpDown; - const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI)); + const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP)); // Process MIDI pitch bend for instrument plugins MidiPortamento(nChn, param, doFineSlides); @@ -3417,7 +3417,7 @@ void CSoundFile::PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, cons else param = pChn->nOldPortaUpDown; - const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI)); + const bool doFineSlides = !doFinePortamentoAsRegular && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP)); // Process MIDI pitch bend for instrument plugins MidiPortamento(nChn, -static_cast(param), doFineSlides); @@ -3889,7 +3889,7 @@ void CSoundFile::VolumeSlide(ModChannel *pChn, ModCommand::PARAM param) else param = pChn->nOldVolumeSlide; - if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI))) + if((GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_DIGI | MOD_TYPE_STP))) { // MOD / XM nibble priority if((param & 0xF0) != 0) @@ -3902,7 +3902,7 @@ void CSoundFile::VolumeSlide(ModChannel *pChn, ModCommand::PARAM param) } int newvolume = pChn->nVolume; - if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_MED))) + if(!(GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_MED | MOD_TYPE_STP))) { if ((param & 0x0F) == 0x0F) //Fine upslide or slide -15 { @@ -5445,7 +5445,7 @@ uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPerio //------------------------------------------------------------------------------------------ { if (!period) return 0; - if (GetType() & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_DIGI|MOD_TYPE_MTM|MOD_TYPE_AMF0)) + if (GetType() & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_DIGI|MOD_TYPE_MTM|MOD_TYPE_AMF0|MOD_TYPE_STP)) { return ((3546895L * 4) << FREQ_FRACBITS) / period; } else if (GetType() == MOD_TYPE_XM) diff --git OpenMPT/soundlib/Sndfile.cpp OpenMPT/soundlib/Sndfile.cpp index 006d046..39e4a03 100644 --- OpenMPT/soundlib/Sndfile.cpp +++ OpenMPT/soundlib/Sndfile.cpp @@ -317,7 +317,8 @@ bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags) && !ReadMod(file, loadFlags) && !Read669(file, loadFlags) && !ReadICE(file, loadFlags) - && !ReadM15(file, loadFlags)) + && !ReadM15(file, loadFlags) + && !ReadSTP(file, loadFlags)) { m_nType = MOD_TYPE_NONE; m_ContainerType = MOD_CONTAINERTYPE_NONE; @@ -1046,6 +1047,8 @@ MODTYPE CSoundFile::GetBestSaveFormat() const case MOD_TYPE_AMF0: case MOD_TYPE_DIGI: return MOD_TYPE_MOD; + case MOD_TYPE_STP: + return MOD_TYPE_XM; case MOD_TYPE_MED: if(m_nDefaultTempo == TEMPO(125, 0) && m_nDefaultSpeed == 6 && !m_nInstruments) { diff --git OpenMPT/soundlib/Sndfile.h OpenMPT/soundlib/Sndfile.h index 1ea882a..1c03f2a 100644 --- OpenMPT/soundlib/Sndfile.h +++ OpenMPT/soundlib/Sndfile.h @@ -659,6 +659,7 @@ public: bool ReadDIGI(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadPLM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMID(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadSTP(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); static std::vector GetSupportedExtensions(bool otherFormats); static mpt::Charset GetCharsetFromModType(MODTYPE modtype); diff --git OpenMPT/soundlib/Tables.cpp OpenMPT/soundlib/Tables.cpp index abf91a9..06250bd 100644 --- OpenMPT/soundlib/Tables.cpp +++ OpenMPT/soundlib/Tables.cpp @@ -87,6 +87,7 @@ static const ModFormatInfo modFormatInfo[] = { MOD_TYPE_IMF, "Imago Orpheus", "imf" }, { MOD_TYPE_J2B, "Galaxy Sound System", "j2b" }, { MOD_TYPE_PLM, "Disorder Tracker 2", "plm" }, + { MOD_TYPE_STP, "Soundtracker Pro II", "stp" }, #ifndef NO_ARCHIVE_SUPPORT // Compressed modules @@ -147,6 +148,7 @@ static const ModCharsetInfo ModCharsetInfos[] = { MOD_TYPE_OKT , mpt::CharsetISO8859_1 }, { MOD_TYPE_DBM , mpt::CharsetISO8859_1 }, { MOD_TYPE_DIGI, mpt::CharsetISO8859_1 }, + { MOD_TYPE_STP, mpt::CharsetISO8859_1 }, // Amiga // DOS { MOD_TYPE_MOD , mpt::CharsetISO8859_1 }, { MOD_TYPE_MED , mpt::CharsetISO8859_1 },