Initial community commit
This commit is contained in:
parent
537bcbc862
commit
fc06254474
16440 changed files with 4239995 additions and 2 deletions
483
Src/external_dependencies/openmpt-trunk/soundlib/Load_sfx.cpp
Normal file
483
Src/external_dependencies/openmpt-trunk/soundlib/Load_sfx.cpp
Normal file
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// File Header
|
||||
struct SFXFileHeader
|
||||
{
|
||||
uint8be numOrders;
|
||||
uint8be restartPos;
|
||||
uint8be orderList[128];
|
||||
};
|
||||
|
||||
MPT_BINARY_STRUCT(SFXFileHeader, 130)
|
||||
|
||||
// Sample Header
|
||||
struct SFXSampleHeader
|
||||
{
|
||||
char name[22];
|
||||
char dummy[2]; // Supposedly sample length, but almost always incorrect
|
||||
uint8be finetune;
|
||||
uint8be volume;
|
||||
uint16be loopStart;
|
||||
uint16be 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 = length;
|
||||
mptSmp.nFineTune = MOD2XMFineTune(finetune);
|
||||
mptSmp.nVolume = 4u * std::min(volume.get(), uint8(64));
|
||||
|
||||
SmpLength lStart = loopStart;
|
||||
SmpLength lLength = loopLength * 2u;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MPT_BINARY_STRUCT(SFXSampleHeader, 30)
|
||||
|
||||
static uint8 ClampSlideParam(uint8 value, uint8 lowNote, uint8 highNote)
|
||||
{
|
||||
uint16 lowPeriod, highPeriod;
|
||||
|
||||
if(lowNote < highNote &&
|
||||
lowNote >= 24 + NOTE_MIN &&
|
||||
highNote >= 24 + NOTE_MIN &&
|
||||
lowNote < std::size(ProTrackerPeriodTable) + 24 + NOTE_MIN &&
|
||||
highNote < std::size(ProTrackerPeriodTable) + 24 + NOTE_MIN)
|
||||
{
|
||||
lowPeriod = ProTrackerPeriodTable[lowNote - 24 - NOTE_MIN];
|
||||
highPeriod = ProTrackerPeriodTable[highNote - 24 - NOTE_MIN];
|
||||
|
||||
// with a fixed speed of 6 ticks/row, and excluding the first row,
|
||||
// 1xx/2xx param has a max value of (low-high)/5 to avoid sliding too far
|
||||
return std::min(value, static_cast<uint8>((lowPeriod - highPeriod) / 5));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static bool ValidateHeader(const SFXFileHeader &fileHeader)
|
||||
{
|
||||
if(fileHeader.numOrders > 128)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderSFX(MemoryFileReader file, const uint64 *pfilesize)
|
||||
{
|
||||
SAMPLEINDEX numSamples = 0;
|
||||
if(numSamples == 0)
|
||||
{
|
||||
file.Rewind();
|
||||
if(!file.CanRead(0x40))
|
||||
{
|
||||
return ProbeWantMoreData;
|
||||
}
|
||||
if(file.Seek(0x3c) && file.ReadMagic("SONG"))
|
||||
{
|
||||
numSamples = 15;
|
||||
}
|
||||
}
|
||||
if(numSamples == 0)
|
||||
{
|
||||
file.Rewind();
|
||||
if(!file.CanRead(0x80))
|
||||
{
|
||||
return ProbeWantMoreData;
|
||||
}
|
||||
if(file.Seek(0x7C) && file.ReadMagic("SO31"))
|
||||
{
|
||||
numSamples = 31;
|
||||
}
|
||||
}
|
||||
if(numSamples == 0)
|
||||
{
|
||||
return ProbeFailure;
|
||||
}
|
||||
file.Rewind();
|
||||
for(SAMPLEINDEX smp = 0; smp < numSamples; smp++)
|
||||
{
|
||||
if(file.ReadUint32BE() > 131072)
|
||||
{
|
||||
return ProbeFailure;
|
||||
}
|
||||
}
|
||||
file.Skip(4);
|
||||
if(!file.CanRead(2))
|
||||
{
|
||||
return ProbeWantMoreData;
|
||||
}
|
||||
uint16 speed = file.ReadUint16BE();
|
||||
if(speed < 178)
|
||||
{
|
||||
return ProbeFailure;
|
||||
}
|
||||
if(!file.CanRead(sizeof(SFXSampleHeader) * numSamples))
|
||||
{
|
||||
return ProbeWantMoreData;
|
||||
}
|
||||
file.Skip(sizeof(SFXSampleHeader) * numSamples);
|
||||
SFXFileHeader fileHeader;
|
||||
if(!file.ReadStruct(fileHeader))
|
||||
{
|
||||
return ProbeWantMoreData;
|
||||
}
|
||||
if(!ValidateHeader(fileHeader))
|
||||
{
|
||||
return ProbeFailure;
|
||||
}
|
||||
MPT_UNREFERENCED_PARAMETER(pfilesize);
|
||||
return ProbeSuccess;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32 sampleLen[31];
|
||||
|
||||
file.Rewind();
|
||||
for(SAMPLEINDEX smp = 0; smp < m_nSamples; smp++)
|
||||
{
|
||||
sampleLen[smp] = file.ReadUint32BE();
|
||||
if(sampleLen[smp] > 131072)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nChannels = 4;
|
||||
m_nInstruments = 0;
|
||||
m_nDefaultSpeed = 6;
|
||||
m_nMinPeriod = 14 * 4;
|
||||
m_nMaxPeriod = 3424 * 4;
|
||||
m_nSamplePreAmp = 64;
|
||||
|
||||
// Setup channel pan positions and volume
|
||||
SetupMODPanning(true);
|
||||
|
||||
file.Skip(4);
|
||||
uint16 speed = file.ReadUint16BE();
|
||||
if(speed < 178)
|
||||
return false;
|
||||
m_nDefaultTempo = TEMPO((14565.0 * 122.0) / speed);
|
||||
|
||||
file.Skip(14);
|
||||
|
||||
uint32 invalidChars = 0;
|
||||
for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
|
||||
{
|
||||
SFXSampleHeader sampleHeader;
|
||||
|
||||
file.ReadStruct(sampleHeader);
|
||||
sampleHeader.ConvertToMPT(Samples[smp], sampleLen[smp - 1]);
|
||||
|
||||
// Get rid of weird characters in sample names.
|
||||
for(char &c : sampleHeader.name)
|
||||
{
|
||||
if(c > 0 && c < ' ')
|
||||
{
|
||||
c = ' ';
|
||||
invalidChars++;
|
||||
}
|
||||
}
|
||||
if(invalidChars >= 128)
|
||||
return false;
|
||||
m_szNames[smp] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
|
||||
}
|
||||
|
||||
// Broken conversions of the "Operation Stealth" soundtrack (BOND23 / BOND32)
|
||||
// There is a converter that shifts all note values except FFFD (empty note) to the left by 1 bit,
|
||||
// but it should not do that for FFFE (STP) notes - as a consequence, they turn into pattern breaks (FFFC).
|
||||
const bool fixPatternBreaks = (m_szNames[1] == "BASSE2.AMI") || (m_szNames[1] == "PRA1.AMI");
|
||||
|
||||
SFXFileHeader fileHeader;
|
||||
if(!file.ReadStruct(fileHeader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!ValidateHeader(fileHeader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(loadFlags == onlyVerifyHeader)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
PATTERNINDEX numPatterns = 0;
|
||||
for(ORDERINDEX ord = 0; ord < fileHeader.numOrders; ord++)
|
||||
{
|
||||
numPatterns = std::max(numPatterns, static_cast<PATTERNINDEX>(fileHeader.orderList[ord] + 1));
|
||||
}
|
||||
|
||||
if(fileHeader.restartPos < fileHeader.numOrders)
|
||||
Order().SetRestartPos(fileHeader.restartPos);
|
||||
else
|
||||
Order().SetRestartPos(0);
|
||||
|
||||
ReadOrderFromArray(Order(), fileHeader.orderList, 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};
|
||||
uint8 version = 0;
|
||||
|
||||
// Reading patterns
|
||||
if(loadFlags & loadPatternData)
|
||||
Patterns.ResizeArray(numPatterns);
|
||||
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];
|
||||
auto data = file.ReadArray<uint8, 4>();
|
||||
|
||||
if(data[0] == 0xFF)
|
||||
{
|
||||
lastNote[chn] = slideRate[chn] = 0;
|
||||
|
||||
if(fixPatternBreaks && data[1] == 0xFC)
|
||||
data[1] = 0xFE;
|
||||
|
||||
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;
|
||||
version = 9;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ReadMODPatternEntry(data, m);
|
||||
if(m.note != NOTE_NONE)
|
||||
{
|
||||
lastNote[chn] = m.note;
|
||||
slideRate[chn] = 0;
|
||||
if(m.note < NOTE_MIDDLEC - 12)
|
||||
{
|
||||
version = std::max(version, uint8(8));
|
||||
}
|
||||
}
|
||||
|
||||
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 LED filter
|
||||
// Give precedence to 7xy/8xy slides
|
||||
if(slideRate[chn])
|
||||
{
|
||||
m.command = m.param = 0;
|
||||
break;
|
||||
}
|
||||
m.command = CMD_MODCMDEX;
|
||||
m.param = 0;
|
||||
break;
|
||||
|
||||
case 0x4: // Disable LED filter
|
||||
// Give precedence to 7xy/8xy slides
|
||||
if(slideRate[chn])
|
||||
{
|
||||
m.command = m.param = 0;
|
||||
break;
|
||||
}
|
||||
m.command = CMD_MODCMDEX;
|
||||
m.param = 1;
|
||||
break;
|
||||
|
||||
case 0x5: // Increase volume
|
||||
if(m.instr)
|
||||
{
|
||||
m.command = CMD_VOLUME;
|
||||
m.param = std::min(ModCommand::PARAM(0x3F), static_cast<ModCommand::PARAM>((Samples[m.instr].nVolume / 4u) + m.param));
|
||||
|
||||
// Give precedence to 7xy/8xy slides (and move this to the volume column)
|
||||
if(slideRate[chn])
|
||||
{
|
||||
m.volcmd = VOLCMD_VOLUME;
|
||||
m.vol = m.param;
|
||||
m.command = m.param = 0;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
{
|
||||
m.command = m.param = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x6: // Decrease volume
|
||||
if(m.instr)
|
||||
{
|
||||
m.command = CMD_VOLUME;
|
||||
if((Samples[m.instr].nVolume / 4u) >= m.param)
|
||||
m.param = static_cast<ModCommand::PARAM>(Samples[m.instr].nVolume / 4u) - m.param;
|
||||
else
|
||||
m.param = 0;
|
||||
|
||||
// Give precedence to 7xy/8xy slides (and move this to the volume column)
|
||||
if(slideRate[chn])
|
||||
{
|
||||
m.volcmd = VOLCMD_VOLUME;
|
||||
m.vol = m.param;
|
||||
m.command = m.param = 0;
|
||||
break;
|
||||
}
|
||||
} 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;
|
||||
slideRate[chn] = m.param & 0xF;
|
||||
m.param = ClampSlideParam(slideRate[chn], slideTo[chn], lastNote[chn]);
|
||||
break;
|
||||
|
||||
case 0x8: // 8xy: Slide up x semitones at speed y
|
||||
slideTo[chn] = lastNote[chn] + (m.param >> 4);
|
||||
|
||||
m.command = CMD_PORTAMENTOUP;
|
||||
slideRate[chn] = m.param & 0xF;
|
||||
m.param = ClampSlideParam(slideRate[chn], lastNote[chn], slideTo[chn]);
|
||||
break;
|
||||
|
||||
case 0x9: // 9xy: Auto slide
|
||||
version = std::max(version, uint8(8));
|
||||
[[fallthrough]];
|
||||
default:
|
||||
m.command = CMD_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue 7xy/8xy slides if needed
|
||||
if(m.command == CMD_NONE && 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);
|
||||
}
|
||||
}
|
||||
|
||||
m_modFormat.formatName = m_nSamples == 15 ? MPT_UFORMAT("SoundFX 1.{}")(version) : U_("SoundFX 2.0 / MultiMedia Sound");
|
||||
m_modFormat.type = m_nSamples == 15 ? UL_("sfx") : UL_("sfx2");
|
||||
m_modFormat.charset = mpt::Charset::Amiga_no_C1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OPENMPT_NAMESPACE_END
|
Loading…
Add table
Add a link
Reference in a new issue