diff --git OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk index 7ec6552..4c0be60 100644 --- OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk +++ OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk @@ -73,6 +73,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk index 0c09b5e..0b1d8e6 100644 --- OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk +++ OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk @@ -71,6 +71,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android-unmo3.mk OpenMPT/build/android_ndk/Android-unmo3.mk index f28d829..0f89ca3 100644 --- OpenMPT/build/android_ndk/Android-unmo3.mk +++ OpenMPT/build/android_ndk/Android-unmo3.mk @@ -104,6 +104,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android.mk OpenMPT/build/android_ndk/Android.mk index 6ef628b..f5214ca 100644 --- OpenMPT/build/android_ndk/Android.mk +++ OpenMPT/build/android_ndk/Android.mk @@ -72,6 +72,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/autotools/Makefile.am OpenMPT/build/autotools/Makefile.am index 86dbdd2..afdb965 100644 --- OpenMPT/build/autotools/Makefile.am +++ OpenMPT/build/autotools/Makefile.am @@ -184,6 +184,7 @@ libopenmpt_la_SOURCES += soundlib/Load_plm.cpp libopenmpt_la_SOURCES += soundlib/Load_psm.cpp libopenmpt_la_SOURCES += soundlib/Load_ptm.cpp libopenmpt_la_SOURCES += soundlib/Load_s3m.cpp +libopenmpt_la_SOURCES += soundlib/Load_sfx.cpp libopenmpt_la_SOURCES += soundlib/Load_stm.cpp libopenmpt_la_SOURCES += soundlib/Load_ult.cpp libopenmpt_la_SOURCES += soundlib/Load_umx.cpp @@ -417,6 +418,7 @@ libopenmpttest_SOURCES += soundlib/Load_plm.cpp libopenmpttest_SOURCES += soundlib/Load_psm.cpp libopenmpttest_SOURCES += soundlib/Load_ptm.cpp libopenmpttest_SOURCES += soundlib/Load_s3m.cpp +libopenmpttest_SOURCES += soundlib/Load_sfx.cpp libopenmpttest_SOURCES += soundlib/Load_stm.cpp libopenmpttest_SOURCES += soundlib/Load_ult.cpp libopenmpttest_SOURCES += soundlib/Load_umx.cpp diff --git OpenMPT/libopenmpt/foo_openmpt.cpp OpenMPT/libopenmpt/foo_openmpt.cpp index ac7ce34..4517eff 100644 --- OpenMPT/libopenmpt/foo_openmpt.cpp +++ OpenMPT/libopenmpt/foo_openmpt.cpp @@ -319,6 +319,9 @@ DECLARE_FILE_TYPE("OpenMPT compatible module files", "*.imf" ";" "*.j2b" ";" "*.plm" ";" + "*.sfx" ";" + "*.sfx2" ";" + "*.mms" ";" "*.gdm" ";" "*.umx" ";" "*.mo3" ";" diff --git OpenMPT/soundlib/Load_sfx.cpp OpenMPT/soundlib/Load_sfx.cpp new file mode 100644 index 0000000..884dc8c --- /dev/null +++ OpenMPT/soundlib/Load_sfx.cpp @@ -0,0 +1,381 @@ +/* + * Load_sfx.cpp + * ------------ + * Purpose: SFX / MMS (SoundFX / MultiMedia Sound) module loader + * Notes : Mostly based on the Soundtracker loader, some effect behavior is based on Flod's implementation. + * Authors: Devin Acker + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "stdafx.h" +#include "Loaders.h" +#include "Tables.h" + +OPENMPT_NAMESPACE_BEGIN + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(push, 1) +#endif + +// File Header +struct PACKED SFXFileHeader +{ + uint8 numOrders; + uint8 restartPos; + uint8 orderList[128]; +}; + +STATIC_ASSERT(sizeof(SFXFileHeader) == 130); + +// Sample Header +struct PACKED SFXSampleHeader +{ + char name[22]; + uint16 dummy; // supposedly sample length, but almost always incorrect + uint8 finetune; + uint8 volume; + uint16 loopStart; + uint16 loopLength; + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesBE(loopStart); + SwapBytesBE(loopLength); + } + + // Convert an MOD sample header to OpenMPT's internal sample header. + void ConvertToMPT(ModSample &mptSmp, uint32 length) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = static_cast(length); + mptSmp.nFineTune = MOD2XMFineTune(finetune & 0x0F); + mptSmp.nVolume = 4 * MIN(volume, 64); + + SmpLength lStart = loopStart; + SmpLength lLength = loopLength * 2; + + if(mptSmp.nLength) + { + mptSmp.nLoopStart = lStart; + mptSmp.nLoopEnd = lStart + lLength; + + 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.nLoopEnd < 4 || mptSmp.nLoopEnd - mptSmp.nLoopStart < 4) + { + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 0; + } + + if(mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + } + } + } +}; + +STATIC_ASSERT(sizeof(SFXSampleHeader) == 30); + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(pop) +#endif + +static uint32 ReadSample(FileReader &file, SFXSampleHeader &sampleHeader, uint32 length, ModSample &sample, char (&sampleName)[MAX_SAMPLENAME]) +//------------------------------------------------------------------------------------------------------------------------------ +{ + file.ReadConvertEndianness(sampleHeader); + sampleHeader.ConvertToMPT(sample, length); + + mpt::String::Read(sampleName, sampleHeader.name); + // Get rid of weird characters in sample names. + uint32 invalidChars = 0; + for(uint32 i = 0; i < CountOf(sampleName); i++) + { + if(sampleName[i] > 0 && sampleName[i] < ' ') + { + sampleName[i] = ' '; + invalidChars++; + } + } + return invalidChars; +} + +bool CSoundFile::ReadSFX(FileReader &file, ModLoadingFlags loadFlags) +//------------------------------------------------------------------- +{ + if(file.Seek(0x3C), file.ReadMagic("SONG")) + { + InitializeGlobals(MOD_TYPE_SFX); + m_nSamples = 15; + } else if(file.Seek(0x7C), file.ReadMagic("SO31")) + { + InitializeGlobals(MOD_TYPE_SFX); + m_nSamples = 31; + } else + { + return false; + } + + size_t totalSampleLen = 0; + uint32 sampleLen[31]; + + file.Rewind(); + for(SAMPLEINDEX smp = 0; smp < m_nSamples; smp++) + { + sampleLen[smp] = file.ReadUint32BE(); + if(sampleLen[smp] > 65535) + return false; + + totalSampleLen += sampleLen[smp]; + } + + m_nChannels = 4; + m_nInstruments = 0; + m_nDefaultSpeed = 6; + m_nMinPeriod = 14 * 4; + m_nMaxPeriod = 3424 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.reset(); + + // Setup channel pan positions and volume + SetupMODPanning(true); + + file.Skip(4); + uint16 speed = file.ReadUint16BE(); + m_nDefaultTempo = TEMPO(14565.0 * 122 / speed); + + file.Skip(14); + + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + SFXSampleHeader sampleHeader; + ReadSample(file, sampleHeader, sampleLen[smp-1], Samples[smp], m_szNames[smp]); + } + + SFXFileHeader fileHeader; + file.ReadStruct(fileHeader); + + if(fileHeader.numOrders > 128) + return false; + + PATTERNINDEX numPatterns = 0; + for(ORDERINDEX ord = 0; ord < fileHeader.numOrders; ord++) + { + numPatterns = MAX(numPatterns, fileHeader.orderList[ord]+1); + } + + if(loadFlags == onlyVerifyHeader) + return true; + + if(fileHeader.restartPos < fileHeader.numOrders) + Order.SetRestartPos(fileHeader.restartPos); + else + Order.SetRestartPos(0); + + Order.ReadFromArray(fileHeader.orderList); + Order.resize(fileHeader.numOrders); + + // SFX v2 / MMS modules have 4 extra bytes here for some reason + if(m_nSamples == 31) + file.Skip(4); + + uint8 lastNote[4] = {0}; + uint8 slideTo[4] = {0}; + uint8 slideRate[4] = {0}; + + // Reading patterns + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + file.Skip(64 * 4 * 4); + continue; + } + + for(ROWINDEX row = 0; row < 64; row++) + { + PatternRow rowBase = Patterns[pat].GetpModCommand(row, 0); + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + ModCommand &m = rowBase[chn]; + uint8 data[4]; + file.ReadArray(data); + + if(data[0] == 0xFF) + { + m.note = NOTE_NONE; + m.instr = m.command = m.param = 0; + lastNote[chn] = slideRate[chn] = 0; + + switch(data[1]) + { + case 0xFE: // STP (note cut) + m.command = CMD_VOLUME; + continue; + + case 0xFD: // PIC (null) + continue; + + case 0xFC: // BRK (pattern break) + m.command = CMD_PATTERNBREAK; + continue; + } + } + + // Read Period + uint16 period = (((static_cast(data[0]) & 0x0F) << 8) | data[1]); + m.note = NOTE_NONE; + if(period > 0 && period != 0xFFF) + { + m.note = 6 * 12 + 35 + NOTE_MIN; + for(int i = 0; i < 6 * 12; i++) + { + if(period >= ProTrackerPeriodTable[i]) + { + if(period != ProTrackerPeriodTable[i] && i != 0) + { + uint16 p1 = ProTrackerPeriodTable[i - 1]; + uint16 p2 = ProTrackerPeriodTable[i]; + if(p1 - period < (period - p2)) + { + m.note = static_cast(i + 35 + NOTE_MIN); + break; + } + } + m.note = static_cast(i + 36 + NOTE_MIN); + break; + } + } + } + // Read Instrument + m.instr = (data[2] >> 4) | (data[0] & 0x10); + // Read Effect + m.command = data[2] & 0x0F; + m.param = data[3]; + + if(m.note) + { + lastNote[chn] = m.note; + slideRate[chn] = 0; + } + + if(m.command || m.param) + { + switch(m.command) + { + case 0x1: // arpeggio + m.command = CMD_ARPEGGIO; + break; + + case 0x2: // portamento (like Ultimate Soundtracker) + if(m.param & 0xF0) + { + m.command = CMD_PORTAMENTODOWN; + m.param >>= 4; + } else if(m.param & 0xF) + { + m.command = CMD_PORTAMENTOUP; + m.param &= 0x0F; + } else + { + m.command = m.param = 0; + } + break; + + case 0x3: // enable filter/LED + m.command = CMD_MODCMDEX; + m.param = 0; + break; + + case 0x4: // disable filter/LED + m.command = CMD_MODCMDEX; + m.param = 1; + break; + + case 0x5: // increase volume + if(m.instr) + { + m.command = CMD_VOLUME; + m.param = MIN(0x3F, (Samples[m.instr].nVolume >> 2) + m.param); + } else + { + m.command = m.param = 0; + } + break; + + case 0x6: // decrease volume + if(m.instr) + { + m.command = CMD_VOLUME; + if((Samples[m.instr].nVolume >> 2) >= m.param) + m.param = (Samples[m.instr].nVolume >> 2) - m.param; + else + m.param = 0; + } else + { + m.command = m.param = 0; + } + break; + + case 0x7: // 7xy: slide down x semitones at speed y + slideTo[chn] = lastNote[chn] - (m.param >> 4); + + m.command = CMD_PORTAMENTODOWN; + m.param = slideRate[chn] = m.param & 0xF; + break; + + case 0x8: // 8xy: slide up x semitones at speed y + slideTo[chn] = lastNote[chn] + (m.param >> 4); + + m.command = CMD_PORTAMENTOUP; + m.param = slideRate[chn] = m.param & 0xF; + break; + + default: + m.command = m.param = 0; + break; + } + } + + // continue 7xy/8xy slides if needed + if(!m.command && slideRate[chn]) + { + if(slideTo[chn]) + { + m.note = lastNote[chn] = slideTo[chn]; + m.param = slideRate[chn]; + slideTo[chn] = 0; + } + m.command = CMD_TONEPORTAMENTO; + } + } + } + } + + // 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..398132e 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_SFX = 0x40000000, }; DECLARE_FLAGSET(MODTYPE) diff --git OpenMPT/soundlib/Sndfile.cpp OpenMPT/soundlib/Sndfile.cpp index d85039d..8c61101 100644 --- OpenMPT/soundlib/Sndfile.cpp +++ OpenMPT/soundlib/Sndfile.cpp @@ -313,6 +313,7 @@ bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags) && !ReadMod(file, loadFlags) && !Read669(file, loadFlags) && !ReadICE(file, loadFlags) + && !ReadSFX(file, loadFlags) && !ReadM15(file, loadFlags)) { m_nType = MOD_TYPE_NONE; @@ -1041,6 +1042,7 @@ MODTYPE CSoundFile::GetBestSaveFormat() const return GetType(); case MOD_TYPE_AMF0: case MOD_TYPE_DIGI: + case MOD_TYPE_SFX: return MOD_TYPE_MOD; 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 eee6e13..ab655e9 100644 --- OpenMPT/soundlib/Sndfile.h +++ OpenMPT/soundlib/Sndfile.h @@ -669,6 +669,7 @@ public: bool ReadDIGI(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadPLM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMID(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadSFX(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..ab1dd15 100644 --- OpenMPT/soundlib/Tables.cpp +++ OpenMPT/soundlib/Tables.cpp @@ -87,6 +87,9 @@ 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_SFX, "SoundFX", "sfx" }, + { MOD_TYPE_SFX, "SoundFX", "sfx2" }, + { MOD_TYPE_SFX, "MultiMedia Sound", "mms" }, #ifndef NO_ARCHIVE_SUPPORT // Compressed modules @@ -147,6 +150,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_SFX , mpt::CharsetISO8859_1 }, // Amiga // DOS { MOD_TYPE_MOD , mpt::CharsetISO8859_1 }, { MOD_TYPE_MED , mpt::CharsetISO8859_1 },