Updated libopenmpt to version 0.5.10

CQTexperiment
Christopher Snowhill 2021-07-07 14:35:06 -07:00
parent 922e657f0b
commit e0a138a1ac
28 changed files with 364 additions and 235 deletions

View File

@ -733,6 +733,7 @@ CPPCHECK_FLAGS += --std=c99 --std=c++17
CPPCHECK_FLAGS += --quiet
CPPCHECK_FLAGS += --enable=warning --inline-suppr --template='{file}:{line}: warning: {severity}: {message} [{id}]'
CPPCHECK_FLAGS += --suppress=missingIncludeSystem
CPPCHECK_FLAGS += --suppress=uninitMemberVar
CPPCHECK_FLAGS += $(CPPFLAGS)
CPPFLAGS += $(CPPFLAGS_ZLIB) $(CPPFLAGS_MPG123) $(CPPFLAGS_OGG) $(CPPFLAGS_VORBIS) $(CPPFLAGS_VORBISFILE)

View File

@ -1,4 +1,4 @@
MPT_SVNVERSION=15019
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.9
MPT_SVNDATE=2021-05-16T14:59:54.252327Z
MPT_SVNVERSION=15412
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.10
MPT_SVNDATE=2021-07-04T16:30:04.479627Z

View File

@ -1,10 +1,10 @@
#pragma once
#define OPENMPT_VERSION_SVNVERSION "15019"
#define OPENMPT_VERSION_REVISION 15019
#define OPENMPT_VERSION_SVNVERSION "15412"
#define OPENMPT_VERSION_REVISION 15412
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.9"
#define OPENMPT_VERSION_DATE "2021-05-16T14:59:54.252327Z"
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.10"
#define OPENMPT_VERSION_DATE "2021-07-04T16:30:04.479627Z"
#define OPENMPT_VERSION_IS_PACKAGE 1

View File

@ -79,7 +79,11 @@
#endif // MPT_OS_WINDOWS
// OpenMPT-only dependencies
#if !MPT_MSVC_BEFORE(2019,0)
// disabled for VS2017 because of multiple initialization of inline variables
// https://developercommunity.visualstudio.com/t/static-inline-variable-gets-destroyed-multiple-tim/297876
#define MPT_WITH_ASIO
#endif
#define MPT_WITH_DMO
#define MPT_WITH_LAME
#define MPT_WITH_LHASA

View File

@ -284,12 +284,10 @@ int GetMinimumAVXVersion()
{
int minimumAVXVersion = 0;
#if MPT_COMPILER_MSVC
#if defined(_M_IX86_FP)
#if defined(__AVX2__)
minimumAVXVersion = 2;
#elif defined(__AVX__)
minimumAVXVersion = 1;
#endif
#if defined(__AVX2__)
minimumAVXVersion = 2;
#elif defined(__AVX__)
minimumAVXVersion = 1;
#endif
#endif
return minimumAVXVersion;

View File

@ -22,18 +22,6 @@
#include <tchar.h>
#endif
#if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
#if defined(__MINGW32__) || defined(__MINGW64__)
// MinGW-w64 headers do not declare this for WinRT, which is wrong.
extern "C" {
WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lpBuffer, LPWSTR *lpFilePart);
#ifndef GetFullPathName
#define GetFullPathName GetFullPathNameW
#endif
}
#endif
#endif
OPENMPT_NAMESPACE_BEGIN
#if MPT_OS_WINDOWS
@ -44,7 +32,11 @@ namespace mpt
RawPathString PathString::AsNativePrefixed() const
{
if(path.length() <= MAX_PATH || path.substr(0, 4) == PL_("\\\\?\\"))
#if MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00)
// For WinRT on Windows 8, there is no official wy to determine an absolute path.
return path;
#else
if(path.length() < MAX_PATH || path.substr(0, 4) == PL_("\\\\?\\"))
{
// Path is short enough or already in prefixed form
return path;
@ -59,6 +51,7 @@ RawPathString PathString::AsNativePrefixed() const
// Regular file: C:\foo.bar -> \\?\C:\foo.bar
return PL_("\\\\?\\") + absPath;
}
#endif
}
@ -442,6 +435,8 @@ bool PathIsAbsolute(const mpt::PathString &path) {
#if MPT_OS_WINDOWS
#if !(MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00))
mpt::PathString GetAbsolutePath(const mpt::PathString &path)
{
DWORD size = GetFullPathName(path.AsNative().c_str(), 0, nullptr, nullptr);
@ -457,6 +452,8 @@ mpt::PathString GetAbsolutePath(const mpt::PathString &path)
return mpt::PathString::FromNative(fullPathName.data());
}
#endif
#ifdef MODPLUG_TRACKER
bool DeleteWholeDirectoryTree(mpt::PathString path)

View File

@ -321,9 +321,13 @@ bool PathIsAbsolute(const mpt::PathString &path);
#if MPT_OS_WINDOWS
#if !(MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00))
// Returns the absolute path for a potentially relative path and removes ".." or "." components. (same as GetFullPathNameW)
mpt::PathString GetAbsolutePath(const mpt::PathString &path);
#endif
#ifdef MODPLUG_TRACKER
// Deletes a complete directory tree. Handle with EXTREME care.

View File

@ -17,7 +17,7 @@ OPENMPT_NAMESPACE_BEGIN
// Version definitions. The only thing that needs to be changed when changing version number.
#define VER_MAJORMAJOR 1
#define VER_MAJOR 29
#define VER_MINOR 10
#define VER_MINOR 11
#define VER_MINORMINOR 00
OPENMPT_NAMESPACE_END

View File

@ -1,14 +1,12 @@
This folder contains the stb_vorbis library from
https://github.com/nothings/stb/blob/master/stb_vorbis.c v1.20
commit b42009b3b9d4ca35bc703f5310eedc74f584be58 (2020-07-13)
https://github.com/nothings/stb/blob/master/stb_vorbis.c v1.21
commit 8e51be04dc7dcee462e1f09e410faceab52cc6d2 (2021-07-02)
Modifications:
* Use of alloca has been replaced with malloc, as alloca is not in C99 and
fails to compile.
* Macro redefinition of alloca with mingw-w64 has been fixed.
* Macro redefinition of STB_VORBIS_NO_STDIO has been fixed.
* Bugfix https://github.com/nothings/stb/pull/1064 has been applied.
Modifications are always additions and have been marked with // OpenMPT.
For building, premake is used to generate Visual Studio project files.
See ../build/premake/ for details.

View File

@ -1,4 +1,4 @@
// Ogg Vorbis audio decoder - v1.20 - public domain
// Ogg Vorbis audio decoder - v1.21 - public domain
// http://nothings.org/stb_vorbis/
//
// Original version written by Sean Barrett in 2007.
@ -33,8 +33,10 @@
// Timur Gagiev Maxwell Koo Peter Waller
// github:audinowho Dougall Johnson David Reid
// github:Clownacy Pedro J. Estebanez Remi Verschelde
// AnthoFoxo
//
// Partial history:
// 1.21 - 2021-07-02 - fix bug for files with no comments
// 1.20 - 2020-07-11 - several small fixes
// 1.19 - 2020-02-05 - warnings
// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc.
@ -3651,13 +3653,12 @@ static int start_decoder(vorb *f)
f->vendor[len] = (char)'\0';
//user comments
f->comment_list_length = get32_packet(f);
f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length));
#if 0 // OpenMPT
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
#else // OpenMPT
// Bugfix from https://github.com/nothings/stb/pull/1064 // OpenMPT
if (f->comment_list_length > 0 && f->comment_list == NULL) return error(f, VORBIS_outofmem); // OpenMPT
#endif // OpenMPT
f->comment_list = NULL;
if (f->comment_list_length > 0)
{
f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length));
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
}
for(i=0; i < f->comment_list_length; ++i) {
len = get32_packet(f);

View File

@ -5,6 +5,29 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This
is just a high-level summary.
### libopenmpt 0.5.10 (2021-07-04)
* S3M: Honor the Stereo flag not being set. This improves the sound of some
tunes like Turbulence by Purple Motion.
* S3M: Detect MPT 1.0 alpha versions which didn't set the Stereo flag. In this
case, the unset Stereo flag is also ignored because MPT 1.0 alpha used the
default S3M channel panning anyway.
* S3M: Only for OPL instruments the high sample rate bits should be ignored;
for PCM instruments they are clamped to 65535 Hz.
* MOD: Do not apply ProTracker loop length quirk to MODs that could have been
made with Scream Tracker (fixes Soul-O-Matic by Purple Motion).
* AMF (DSMI): Format revisions 1 and 9, as well as early (technically
malformed) revision 10 files, are now supported. Surround panning commands
and instrument number without note are now converted correctly.
* AMF (DSMI): Patterns and samples were not read correctly in some files
(e.g. AVOID.AMF).
* GDM: Correctly import extra-fine portamentos.
* mpg123: Update to v1.28.0 (2021-06-05).
* ogg: Update to v1.3.5 (2021-06-04).
* stb_vorbis: Update v1.21 commit 8e51be04dc7dcee462e1f09e410faceab52cc6d2
(2021-07-02).
### libopenmpt 0.5.9 (2021-05-16)
* `Makefile` `CONFIG=emscripten` does not pass linker options to the compiler

View File

@ -19,7 +19,7 @@
/*! \brief libopenmpt minor version number */
#define OPENMPT_API_VERSION_MINOR 5
/*! \brief libopenmpt patch version number */
#define OPENMPT_API_VERSION_PATCH 9
#define OPENMPT_API_VERSION_PATCH 10
/*! \brief libopenmpt pre-release tag */
#define OPENMPT_API_VERSION_PREREL ""
/*! \brief libopenmpt pre-release flag */

View File

@ -1,8 +1,8 @@
LIBOPENMPT_VERSION_MAJOR=0
LIBOPENMPT_VERSION_MINOR=5
LIBOPENMPT_VERSION_PATCH=9
LIBOPENMPT_VERSION_PATCH=10
LIBOPENMPT_VERSION_PREREL=
LIBOPENMPT_LTVER_CURRENT=2
LIBOPENMPT_LTVER_REVISION=9
LIBOPENMPT_LTVER_REVISION=10
LIBOPENMPT_LTVER_AGE=2

View File

@ -225,7 +225,7 @@ private:
public:
template <typename Trd>
DitherTemplate(Trd & rd)
: prng(rd)
: prng(mpt::make_prng<mpt::fast_prng>(rd))
{
return;
}

View File

@ -433,7 +433,7 @@ void CReverb::Process(MixSampleInt *MixSoundBuffer, uint32 nSamples)
if (nIn > 0) ProcessPreDelay(&g_RefDelay, MixReverbBuffer, nIn);
// Process Reverb Reflections and Late Reverberation
int32 *pRvbOut = MixReverbBuffer;
uint32 nRvbSamples = nOut, nCount = 0;
uint32 nRvbSamples = nOut;
while (nRvbSamples > 0)
{
uint32 nPosRef = g_RefDelay.nRefOutPos & SNDMIX_REVERB_DELAY_MASK;
@ -451,7 +451,6 @@ void CReverb::Process(MixSampleInt *MixSoundBuffer, uint32 nSamples)
// Update delay positions
g_RefDelay.nRefOutPos = (g_RefDelay.nRefOutPos + n) & SNDMIX_REVERB_DELAY_MASK;
g_RefDelay.nDelayPos = (g_RefDelay.nDelayPos + n) & SNDMIX_REFLECTIONS_DELAY_MASK;
nCount += n*2;
pRvbOut += n*2;
nRvbSamples -= n;
}

View File

@ -22,8 +22,6 @@
#include "SampleIO.h"
#include "modsmp_ctrl.h"
#include <math.h>
OPENMPT_NAMESPACE_BEGIN
#ifdef MODPLUG_TRACKER
@ -1877,7 +1875,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
{
// Try to combine stereo samples
uint8 pan1 = GetPanning(nIns, nRgn), pan2 = GetPanning(nIns, iDup);
if((pan1 == 0 || pan1 == 255) && (pan2 == 0 || pan2 == 255))
if((pan1 < 16 && pan2 >= 240) || (pan2 < 16 && pan1 >= 240))
{
ModSample &sample = sndFile.GetSample(nSmp);
ctrlSmp::ConvertToStereo(sample, sndFile);

View File

@ -65,21 +65,6 @@ struct AsylumSampleHeader
MPT_BINARY_STRUCT(AsylumSampleHeader, 37)
// DSMI AMF File Header
struct AMFFileHeader
{
char amf[3];
uint8le version;
char title[32];
uint8le numSamples;
uint8le numOrders;
uint16le numTracks;
uint8le numChannels;
};
MPT_BINARY_STRUCT(AMFFileHeader, 41)
static bool ValidateHeader(const AsylumFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.signature, "ASYLUM Music Format V1.0\0", 25)
@ -216,11 +201,95 @@ bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags)
}
// DSMI AMF File Header
struct AMFFileHeader
{
char amf[3];
uint8le version;
char title[32];
uint8le numSamples;
uint8le numOrders;
uint16le numTracks;
uint8le numChannels;
};
MPT_BINARY_STRUCT(AMFFileHeader, 41)
// DSMI AMF Sample Header (v1-v9)
struct AMFSampleHeaderOld
{
uint8le type;
char name[32];
char filename[13];
uint32le index;
uint16le length;
uint16le sampleRate;
uint8le volume;
uint16le loopStart;
uint16le loopEnd;
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
mptSmp.nLength = length;
mptSmp.nC5Speed = sampleRate;
mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
if(mptSmp.nLoopEnd == uint16_max)
mptSmp.nLoopStart = mptSmp.nLoopEnd = 0;
else if(type != 0 && mptSmp.nLoopEnd > mptSmp.nLoopStart + 2 && mptSmp.nLoopEnd <= mptSmp.nLength)
mptSmp.uFlags.set(CHN_LOOP);
}
};
MPT_BINARY_STRUCT(AMFSampleHeaderOld, 59)
// DSMI AMF Sample Header (v10+)
struct AMFSampleHeaderNew
{
uint8le type;
char name[32];
char filename[13];
uint32le index;
uint32le length;
uint16le sampleRate;
uint8le volume;
uint32le loopStart;
uint32le loopEnd;
void ConvertToMPT(ModSample &mptSmp, bool truncated) const
{
mptSmp.Initialize();
mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
mptSmp.nLength = length;
mptSmp.nC5Speed = sampleRate;
mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
mptSmp.nLoopStart = loopStart;
mptSmp.nLoopEnd = loopEnd;
if(truncated && mptSmp.nLoopStart > 0)
mptSmp.nLoopEnd = mptSmp.nLength;
if(type != 0 && mptSmp.nLoopEnd > mptSmp.nLoopStart + 2 && mptSmp.nLoopEnd <= mptSmp.nLength)
mptSmp.uFlags.set(CHN_LOOP);
}
// Check if sample headers might be truncated
bool IsValid(uint8 numSamples) const
{
return type <= 1 && index <= numSamples && length <= 0x100000 && volume <= 64 && loopStart <= length && loopEnd <= length;
}
};
MPT_BINARY_STRUCT(AMFSampleHeaderNew, 65)
// Read a single AMF track (channel) into a pattern.
static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &fileChunk)
{
fileChunk.Rewind();
ModCommand::INSTR lastInstr = 0;
while(fileChunk.CanRead(3))
{
const auto [row, command, value] = fileChunk.ReadArray<uint8, 3>();
@ -241,25 +310,17 @@ static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &file
m.note = command + NOTE_MIN;
if(value != 0xFF)
{
if(!m.instr) m.instr = lastInstr;
m.volcmd = VOLCMD_VOLUME;
m.vol = value;
}
}
} else if(command == 0x7F)
{
// Duplicate row
int8 rowDelta = static_cast<int8>(value);
int16 copyRow = static_cast<int16>(row) + rowDelta;
if(copyRow >= 0 && copyRow < static_cast<int16>(pattern.GetNumRows()))
{
m = *pattern.GetpModCommand(copyRow, chn);
}
// Instrument without note retrigger in MOD (no need to do anything here, should be preceded by 0x80 command)
} else if(command == 0x80)
{
// Instrument
m.instr = value + 1;
lastInstr = m.instr;
} else
{
// Effect
@ -277,12 +338,9 @@ static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &file
uint8 param = value;
if(cmd < CountOf(effTrans))
{
cmd = effTrans[cmd];
} else
{
else
cmd = CMD_NONE;
}
// Fix some commands...
switch(command & 0x7F)
@ -362,15 +420,25 @@ static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &file
// 17: Panning
case 0x17:
param = (param + 64) & 0x7F;
if(m.command != CMD_NONE)
if(param == 100)
{
if(m.volcmd == VOLCMD_NONE || m.volcmd == VOLCMD_PANNING)
// History lesson intermission: According to Otto Chrons, he remembers that he added support
// for 8A4 / XA4 "surround" panning in DMP for MOD and S3M files before any other trackers did,
// So DSMI / DMP are most likely the original source of these 7-bit panning + surround commands!
param = 0xA4;
} else
{
param = static_cast<uint8>(std::clamp(static_cast<int8>(param) + 64, 0, 128));
if(m.command != CMD_NONE)
{
m.volcmd = VOLCMD_PANNING;
m.vol = param / 2;
// Move to volume column if required
if(m.volcmd == VOLCMD_NONE || m.volcmd == VOLCMD_PANNING)
{
m.volcmd = VOLCMD_PANNING;
m.vol = param / 2;
}
cmd = CMD_NONE;
}
cmd = CMD_NONE;
}
break;
}
@ -388,9 +456,8 @@ static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &file
static bool ValidateHeader(const AMFFileHeader &fileHeader)
{
if(std::memcmp(fileHeader.amf, "AMF", 3)
|| fileHeader.version < 8 || fileHeader.version > 14
|| ((fileHeader.numChannels < 1 || fileHeader.numChannels > 32) && fileHeader.version >= 10)
)
|| (fileHeader.version < 8 && fileHeader.version != 1) || fileHeader.version > 14
|| ((fileHeader.numChannels < 1 || fileHeader.numChannels > 32) && fileHeader.version >= 9))
{
return false;
}
@ -444,7 +511,7 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.title);
if(fileHeader.version < 10)
if(fileHeader.version < 9)
{
// Old format revisions are fixed to 4 channels
m_nChannels = 4;
@ -458,16 +525,13 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
const CHANNELINDEX readChannels = fileHeader.version >= 12 ? 32 : 16;
for(CHANNELINDEX chn = 0; chn < readChannels; chn++)
{
int16 pan = (file.ReadInt8() + 64) * 2;
if(pan < 0) pan = 0;
if(pan > 256)
{
pan = 128;
int8 pan = file.ReadInt8();
if(pan == 100)
ChnSettings[chn].dwFlags = CHN_SURROUND;
}
ChnSettings[chn].nPan = static_cast<uint16>(pan);
else
ChnSettings[chn].nPan = static_cast<uint16>(std::clamp((pan + 64) * 2, 0, 256));
}
} else if(fileHeader.version == 10)
} else if(fileHeader.version >= 9)
{
uint8 panPos[16];
file.ReadArray(panPos);
@ -476,14 +540,13 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
ChnSettings[chn].nPan = (panPos[chn] & 1) ? 0x40 : 0xC0;
}
}
// To check: Was the channel table introduced in revision 1.0 or 0.9? I only have 0.8 files, in which it is missing...
MPT_ASSERT(fileHeader.version != 9);
// Get Tempo/Speed
if(fileHeader.version >= 13)
{
auto [tempo, speed] = file.ReadArray<uint8, 2>();
if(tempo < 32) tempo = 125;
if(tempo < 32)
tempo = 125;
m_nDefaultTempo.Set(tempo);
m_nDefaultSpeed = speed;
} else
@ -513,60 +576,40 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
}
// Read Sample Headers
std::vector<uint32> samplePos(GetNumSamples(), 0);
uint32 maxSamplePos = 0;
bool truncatedSampleHeaders = false;
if(fileHeader.version == 10)
{
// M2AMF 1.3 included with DMP 2.32 wrote new (v10+) sample headers, but using the old struct length.
const auto startPos = file.GetPosition();
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
AMFSampleHeaderNew sample;
if(file.ReadStruct(sample) && !sample.IsValid(fileHeader.numSamples))
{
truncatedSampleHeaders = true;
break;
}
}
file.Seek(startPos);
}
std::vector<uint32> sampleMap(GetNumSamples(), 0);
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
ModSample &sample = Samples[smp];
sample.Initialize();
uint8 type = file.ReadUint8();
file.ReadString<mpt::String::maybeNullTerminated>(m_szNames[smp], 32);
file.ReadString<mpt::String::nullTerminated>(sample.filename, 13);
samplePos[smp - 1] = file.ReadUint32LE();
if(fileHeader.version < 10)
{
sample.nLength = file.ReadUint16LE();
AMFSampleHeaderOld sample;
file.ReadStruct(sample);
sample.ConvertToMPT(Samples[smp]);
m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sample.name);
sampleMap[smp - 1] = sample.index;
} else
{
sample.nLength = file.ReadUint32LE();
}
sample.nC5Speed = file.ReadUint16LE();
sample.nVolume = std::min(file.ReadUint8(), uint8(64)) * 4u;
if(fileHeader.version < 10)
{
// Various sources (Miodrag Vallat's amf.txt, old ModPlug code) suggest that the loop information
// format revision 1.0 should only consist of a 16-bit value for the loop start (loop end would
// automatically equal sample length), but the only v1.0 files I have ("the tribal zone" and
// "the way its gonna b" by Maelcum) do not confirm this - the sample headers are laid out exactly
// as in the newer revisions in these two files. Even in format revision 0.8 (output by MOD2AMF v1.02)
// There are loop start and loop end values (although they are 16-Bit). Maybe this only applies to
// even older revision of the format?
sample.nLoopStart = file.ReadUint16LE();
sample.nLoopEnd = file.ReadUint16LE();
} else
{
sample.nLoopStart = file.ReadUint32LE();
sample.nLoopEnd = file.ReadUint32LE();
}
// Length of v1.0+ sample header: 65 bytes
// Length of old sample header: 59 bytes
if(type != 0)
{
if(sample.nLoopEnd > sample.nLoopStart + 2 && sample.nLoopEnd <= sample.nLength)
{
sample.uFlags.set(CHN_LOOP);
} else
{
sample.nLoopStart = sample.nLoopEnd = 0;
}
maxSamplePos = std::max(maxSamplePos, samplePos[smp - 1]);
AMFSampleHeaderNew sample;
file.ReadStructPartial(sample, truncatedSampleHeaders ? sizeof(AMFSampleHeaderOld) : sizeof(AMFSampleHeaderNew));
sample.ConvertToMPT(Samples[smp], truncatedSampleHeaders);
m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sample.name);
sampleMap[smp - 1] = sample.index;
}
}
@ -584,10 +627,11 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
std::vector<FileReader> trackData(trackCount);
for(uint16 i = 0; i < trackCount; i++)
{
// Track size is a 24-Bit value describing the number of byte triplets in this track.
uint8 trackSize[3];
file.ReadArray(trackSize);
trackData[i] = file.ReadChunk((trackSize[0] | (trackSize[1] << 8) | (trackSize[2] << 16)) * 3);
// Track size is a 16-Bit value describing the number of byte triplets in this track, followed by a track type byte.
uint16 numEvents = file.ReadUint16LE();
file.Skip(1);
if(numEvents)
trackData[i] = file.ReadChunk(numEvents * 3 + (fileHeader.version == 1 ? 3 : 0));
}
if(loadFlags & loadSampleData)
@ -599,24 +643,18 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags)
SampleIO::littleEndian,
SampleIO::unsignedPCM);
// Why is all of this sample loading business so weird in AMF?
// Surely there must be some great idea behind it which isn't handled here or used in the wild
// (re-using the same sample data for different sample slots maybe?)
// First, try compacting the sample indices so that the loop won't have 2^32 iterations in the worst case.
std::vector<uint32> samplePosCompact = samplePos;
std::sort(samplePosCompact.begin(), samplePosCompact.end());
auto end = std::unique(samplePosCompact.begin(), samplePosCompact.end());
for(auto pos = samplePosCompact.begin(); pos != end && file.CanRead(1); pos++)
// Note: in theory a sample can be reused by several instruments and appear in a different order in the file
// However, M2AMF doesn't take advantage of this and just writes instruments in the order they appear,
// without de-duplicating identical sample data.
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples() && file.CanRead(1); smp++)
{
for(SAMPLEINDEX smp = 0; smp < GetNumSamples() && file.CanRead(1); smp++)
auto startPos = file.GetPosition();
for(SAMPLEINDEX target = 0; target < GetNumSamples(); target++)
{
if(*pos == samplePos[smp])
{
sampleIO.ReadSample(Samples[smp + 1], file);
break;
}
if(sampleMap[target] != smp)
continue;
file.Seek(startPos);
sampleIO.ReadSample(Samples[target + 1], file);
}
}
}

View File

@ -442,9 +442,20 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags)
break;
case CMD_MODCMDEX:
if(!modSpecs.HasCommand(CMD_MODCMDEX))
switch(m.param >> 4)
{
m.ExtendedMODtoS3MEffect();
case 0x8:
m.command = CMD_PORTAMENTOUP;
m.param = 0xE0 | (m.param & 0x0F);
break;
case 0x9:
m.command = CMD_PORTAMENTODOWN;
m.param = 0xE0 | (m.param & 0x0F);
break;
default:
if(!modSpecs.HasCommand(CMD_MODCMDEX))
m.ExtendedMODtoS3MEffect();
break;
}
break;
@ -459,54 +470,21 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags)
case CMD_S3MCMDEX:
// Some really special commands
switch(m.param >> 4)
if(m.param == 0x01)
{
case 0x0:
switch(m.param & 0x0F)
{
case 0x0: // Surround Off
case 0x1: // Surround On
m.param += 0x90;
break;
case 0x2: // Set normal loop - not implemented in BWSB or 2GDM.
case 0x3: // Set bidi loop - ditto
m.command = CMD_NONE;
break;
case 0x4: // Play sample forwards
m.command = CMD_S3MCMDEX;
m.param = 0x9E;
break;
case 0x5: // Play sample backwards
m.command = CMD_S3MCMDEX;
m.param = 0x9F;
break;
case 0x6: // Monaural sample - also not implemented.
case 0x7: // Stereo sample - ditto
case 0x8: // Stop sample on end - ditto
case 0x9: // Loop sample on end - ditto
default:
m.command = CMD_NONE;
break;
}
break;
case 0x8: // 4-Bit Panning
if(!modSpecs.HasCommand(CMD_S3MCMDEX))
{
// Surround (implemented in 2GDM but not in BWSB itself)
m.param = 0x91;
} else if((m.param & 0xF0) == 0x80)
{
// 4-Bit Panning
if (!modSpecs.HasCommand(CMD_S3MCMDEX))
m.command = CMD_MODCMDEX;
}
break;
case 0xD: // Adjust frequency (increment in hz) - also not implemented.
default:
} else
{
// All other effects are implemented neither in 2GDM nor in BWSB.
m.command = CMD_NONE;
break;
}
break;
case 0x1F:
m.command = CMD_TEMPO;
break;
}
// Move pannings to volume column - should never happen
@ -520,9 +498,7 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags)
if(!(effByte & effectDone)) break; // no other effect follows
}
}
}
}
}

View File

@ -833,6 +833,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
bool isInconexia = IsMagic(magic, "M\0\0\0") || IsMagic(magic, "8\0\0\0");
// A loop length of zero will freeze ProTracker, so assume that modules having such a value were not meant to be played on Amiga. Fixes LHS_MI.MOD
bool hasRepLen0 = false;
// Empty sample slots typically should have a default volume of 0 in ProTracker
bool hasEmptySampleWithVolume = false;
if(modMagicResult.setMODVBlankTiming)
{
m_playBehaviour.set(kMODVBlankTiming);
@ -867,7 +869,9 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
if(sampleHeader.length && !sampleHeader.loopLength)
hasRepLen0 = true;
else if(!sampleHeader.length && sampleHeader.volume == 64)
hasEmptySampleWithVolume = true;
if(maybeWOW)
{
// Some WOW files rely on sample length 1 being counted as well
@ -1193,8 +1197,11 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
// Fix sample 6 in MOD.shorttune2, which has a replen longer than the sample itself.
// ProTracker reads beyond the end of the sample when playing. Normally samples are
// adjacent in PT's memory, so we simply read into the next sample in the file.
// On the other hand, the loop points in Purple Motions's SOUL-O-M.MOD are completely broken and shouldn't be treated like this.
// As it was most likely written in Scream Tracker, it has empty sample slots with a default volume of 64, which we use for
// rejecting this quirk for that file.
FileReader::off_t nextSample = file.GetPosition() + sampleIO.CalculateEncodedSize(sample.nLength);
if(isMdKd && onlyAmigaNotes)
if(isMdKd && onlyAmigaNotes && !hasEmptySampleWithVolume)
sample.nLength = std::max(sample.nLength, sample.nLoopEnd);
sampleIO.ReadSample(sample, file);

View File

@ -244,9 +244,17 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
case S3MFileHeader::trkScreamTracker:
if(fileHeader.cwtv == S3MFileHeader::trkST3_20 && fileHeader.special == 0 && (fileHeader.ordNum & 0x0F) == 0 && fileHeader.ultraClicks == 0 && (fileHeader.flags & ~0x50) == 0)
{
// MPT 1.16 and older versions of OpenMPT - Simply keep default (filter) MIDI macros
m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
madeWithTracker = U_("ModPlug Tracker / OpenMPT");
// MPT and OpenMPT before 1.17.03.02 - Simply keep default (filter) MIDI macros
if((fileHeader.masterVolume & 0x80) != 0)
{
m_dwLastSavedWithVersion = MPT_V("1.16.00.00");
madeWithTracker = U_("ModPlug Tracker / OpenMPT 1.17");
} else
{
// MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 beta1 does.
m_dwLastSavedWithVersion = MPT_V("1.00.00.00");
madeWithTracker = U_("ModPlug Tracker 1.0 alpha");
}
keepMidiMacros = true;
nonCompatTracker = true;
m_playBehaviour.set(kST3LimitPeriod);
@ -255,6 +263,9 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
madeWithTracker = U_("Velvet Studio");
} else
{
// ST3.20 should only ever write ultra-click values 16, 24 and 32 (corresponding to 8, 12 and 16 in the GUI), ST3.01/3.03 should only write 0.
// However, we won't fingerprint these values here as it's unlikely that there is any other tracker out there disguising as ST3 and using a strange ultra-click value.
// Also, re-saving a file with a strange ultra-click value in ST3 doesn't fix this value unless the user manually changes it, or if it's below 16.
madeWithTracker = U_("Scream Tracker");
formatTrackerStr = true;
isST3 = true;
@ -276,7 +287,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
}
if(fileHeader.cwtv >= S3MFileHeader::trkIT2_07 && fileHeader.reserved3 != 0)
{
// Starting from version 2.07, IT stores the total edit time of a module in the "reserved" field
// Starting from version 2.07, IT stores the total edit time of a module in the "reserved" field
uint32 editTime = DecodeITEditTimer(fileHeader.cwtv, fileHeader.reserved3);
FileHistory hist;
@ -402,9 +413,15 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
// However, this version check is missing in ST3, so any mono file with a master volume of 18 will be converted to a stereo file with master volume 32.
else if(fileHeader.masterVolume == 2 || fileHeader.masterVolume == (2 | 0x10))
m_nSamplePreAmp = 0x20;
else if(!(fileHeader.masterVolume & 0x7F))
m_nSamplePreAmp = 48;
else
m_nSamplePreAmp = std::max(fileHeader.masterVolume & 0x7F, 0x10); // Bit 7 = Stereo (we always use stereo)
const bool isStereo = (fileHeader.masterVolume & 0x80) != 0 || m_dwLastSavedWithVersion;
if(!isStereo)
m_nSamplePreAmp = Util::muldivr_unsigned(m_nSamplePreAmp, 8, 11);
// Approximately as loud as in DOSBox and a real SoundBlaster 16
m_nVSTiVolume = 36;
if(isSchism && fileHeader.cwtv < SchismVersionFromDate<2018, 11, 12>::Version(S3MFileHeader::trkSchismTracker))
@ -421,7 +438,8 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
if(fileHeader.channels[i] != 0xFF)
{
m_nChannels = i + 1;
ChnSettings[i].nPan = (ctype & 8) ? 0xCC : 0x33; // 200 : 56
if(isStereo)
ChnSettings[i].nPan = (ctype & 8) ? 0xCC : 0x33; // 200 : 56
}
if(fileHeader.channels[i] & 0x80)
{
@ -695,7 +713,7 @@ bool CSoundFile::SaveS3M(std::ostream &f) const
fileHeader.speed = static_cast<uint8>(Clamp(m_nDefaultSpeed, 1u, 254u));
fileHeader.tempo = static_cast<uint8>(Clamp(m_nDefaultTempo.GetInt(), 33u, 255u));
fileHeader.masterVolume = static_cast<uint8>(Clamp(m_nSamplePreAmp, 16u, 127u) | 0x80);
fileHeader.ultraClicks = 8;
fileHeader.ultraClicks = 16;
fileHeader.usePanningTable = S3MFileHeader::idPanning;
mpt::IO::Write(f, fileHeader);

View File

@ -31,7 +31,7 @@ struct PageHeader
uint8le header_type;
uint64le granule_position;
uint32le bitstream_serial_number;
uint32le page_seqauence_number;
uint32le page_sequence_number;
uint32le CRC_checksum;
uint8le page_segments;
};

View File

@ -52,9 +52,14 @@ void S3MSampleHeader::ConvertToMPT(ModSample &mptSmp, bool isST3) const
// C-5 frequency
mptSmp.nC5Speed = c5speed;
// ST3 ignores the high 16 bits
if(isST3)
mptSmp.nC5Speed &= 0xFFFF;
{
// ST3 ignores or clamps the high 16 bits depending on the instrument type
if(sampleType == typeAdMel)
mptSmp.nC5Speed &= 0xFFFF;
else
LimitMax(mptSmp.nC5Speed, uint16_max);
}
if(mptSmp.nC5Speed == 0)
mptSmp.nC5Speed = 8363;

View File

@ -53,7 +53,14 @@ typedef off_t mpg123_off_t;
typedef size_t mpg123_size_t;
// Check for exactly _MSC_VER as libmpg123 does, in order to also catch clang-cl.
#ifdef _MSC_VER
// ssize_t definition in libmpg123.h.in should never have existed at all.
// It got removed from libmpg23.h.in after 1.28.0 and before 1.28.1.
typedef ptrdiff_t mpg123_ssize_t;
#else
typedef ssize_t mpg123_ssize_t;
#endif
class ComponentMPG123
: public ComponentBuiltin

View File

@ -1928,8 +1928,7 @@ bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayN
// Read SSND chunk
FileReader soundChunk(chunks.GetChunk(AIFFChunk::idSSND));
AIFFSoundChunk sampleHeader;
if(!soundChunk.ReadStruct(sampleHeader)
|| !soundChunk.CanRead(sampleHeader.offset))
if(!soundChunk.ReadStruct(sampleHeader))
{
return false;
}
@ -1950,7 +1949,7 @@ bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayN
endian,
SampleIO::signedPCM);
if(!memcmp(compression, "fl32", 4) || !memcmp(compression, "FL32", 4) || !memcmp(compression, "fl64", 4))
if(!memcmp(compression, "fl32", 4) || !memcmp(compression, "FL32", 4) || !memcmp(compression, "fl64", 4) || !memcmp(compression, "FL64", 4))
{
sampleIO |= SampleIO::floatPCM;
} else if(!memcmp(compression, "alaw", 4) || !memcmp(compression, "ALAW", 4))
@ -1961,6 +1960,9 @@ bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayN
{
sampleIO |= SampleIO::uLaw;
sampleIO |= SampleIO::_16bit;
} else if(!memcmp(compression, "raw ", 4))
{
sampleIO |= SampleIO::unsignedPCM;
}
if(mayNormalize)
@ -1968,7 +1970,10 @@ bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayN
sampleIO.MayNormalize();
}
soundChunk.Skip(sampleHeader.offset);
if(soundChunk.CanRead(sampleHeader.offset))
{
soundChunk.Skip(sampleHeader.offset);
}
ModSample &mptSample = Samples[nSample];
DestroySampleThreadsafe(nSample);

View File

@ -279,11 +279,6 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
forbiddenCommands.set(CMD_NOTESLIDEDOWN); forbiddenCommands.set(CMD_NOTESLIDEDOWNRETRIG);
forbiddenVolCommands.set(VOLCMD_PORTAUP); forbiddenVolCommands.set(VOLCMD_PORTADOWN);
// Optimize away channels for which it's pointless to adjust sample positions
for(CHANNELINDEX i = 0; i < GetNumChannels(); i++)
{
if(ChnSettings[i].dwFlags[CHN_MUTE]) memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL;
}
if(target.mode == GetLengthTarget::SeekPosition && target.pos.order < orderList.size())
{
// If we know where to seek, we can directly rule out any channels on which a new note would be triggered right at the start.

View File

@ -954,6 +954,8 @@ public:
void SetSpeed(PlayState &playState, uint32 param) const;
static TEMPO ConvertST2Tempo(uint8 tempo);
void ProcessRamping(ModChannel &chn) const;
protected:
// Global variable initializer for loader functions
void SetType(MODTYPE type);
@ -985,8 +987,6 @@ protected:
void ProcessVibrato(CHANNELINDEX nChn, int &period, Tuning::RATIOTYPE &vibratoFactor);
void ProcessSampleAutoVibrato(ModChannel &chn, int &period, Tuning::RATIOTYPE &vibratoFactor, int &nPeriodFrac) const;
void ProcessRamping(ModChannel &chn) const;
SamplePosition GetChannelIncrement(const ModChannel &chn, uint32 period, int periodFrac) const;
protected:

View File

@ -292,7 +292,7 @@ void LFOPlugin::SaveAllParameters()
return;
m_pMixStruct->defaultProgram = -1;
m_pMixStruct->pluginData.assign(chunk.cbegin(), chunk.cend());
m_pMixStruct->pluginData.assign(chunk.begin(), chunk.end());
}

View File

@ -2154,6 +2154,61 @@ static MPT_NOINLINE void TestMisc2()
VERIFY_EQUAL(uri.query, U_(""));
VERIFY_EQUAL(uri.fragment, U_(""));
}
{
URI uri = ParseURI(U_("scheme://host"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));
VERIFY_EQUAL(uri.username, U_(""));
VERIFY_EQUAL(uri.password, U_(""));
VERIFY_EQUAL(uri.host, U_("host"));
VERIFY_EQUAL(uri.port, U_(""));
VERIFY_EQUAL(uri.path, U_(""));
VERIFY_EQUAL(uri.query, U_(""));
VERIFY_EQUAL(uri.fragment, U_(""));
}
{
URI uri = ParseURI(U_("scheme://host?query"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));
VERIFY_EQUAL(uri.username, U_(""));
VERIFY_EQUAL(uri.password, U_(""));
VERIFY_EQUAL(uri.host, U_("host"));
VERIFY_EQUAL(uri.port, U_(""));
VERIFY_EQUAL(uri.path, U_(""));
VERIFY_EQUAL(uri.query, U_("query"));
VERIFY_EQUAL(uri.fragment, U_(""));
}
{
URI uri = ParseURI(U_("scheme://host#fragment"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));
VERIFY_EQUAL(uri.username, U_(""));
VERIFY_EQUAL(uri.password, U_(""));
VERIFY_EQUAL(uri.host, U_("host"));
VERIFY_EQUAL(uri.port, U_(""));
VERIFY_EQUAL(uri.path, U_(""));
VERIFY_EQUAL(uri.query, U_(""));
VERIFY_EQUAL(uri.fragment, U_("fragment"));
}
{
URI uri = ParseURI(U_("scheme://host?#"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));
VERIFY_EQUAL(uri.username, U_(""));
VERIFY_EQUAL(uri.password, U_(""));
VERIFY_EQUAL(uri.host, U_("host"));
VERIFY_EQUAL(uri.port, U_(""));
VERIFY_EQUAL(uri.path, U_(""));
VERIFY_EQUAL(uri.query, U_(""));
VERIFY_EQUAL(uri.fragment, U_(""));
}
{
URI uri = ParseURI(U_("scheme://host#?"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));
VERIFY_EQUAL(uri.username, U_(""));
VERIFY_EQUAL(uri.password, U_(""));
VERIFY_EQUAL(uri.host, U_("host"));
VERIFY_EQUAL(uri.port, U_(""));
VERIFY_EQUAL(uri.path, U_(""));
VERIFY_EQUAL(uri.query, U_(""));
VERIFY_EQUAL(uri.fragment, U_("?"));
}
{
URI uri = ParseURI(U_("scheme://username:password@[2001:db8::1]:port/path?query#fragment"));
VERIFY_EQUAL(uri.scheme, U_("scheme"));