Updated libopenmpt to version 0.5.3

CQTexperiment
Christopher Snowhill 2020-11-19 01:35:54 -08:00
parent 4eb7a3eebf
commit ee0782efbf
34 changed files with 409 additions and 307 deletions

View File

@ -1,4 +1,4 @@
MPT_SVNVERSION=13555
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.2
MPT_SVNDATE=2020-08-30T13:42:32.941871Z
MPT_SVNVERSION=13775
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.3
MPT_SVNDATE=2020-10-25T14:02:16.624929Z

View File

@ -1,10 +1,10 @@
#pragma once
#define OPENMPT_VERSION_SVNVERSION "13555"
#define OPENMPT_VERSION_REVISION 13555
#define OPENMPT_VERSION_SVNVERSION "13775"
#define OPENMPT_VERSION_REVISION 13775
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.2"
#define OPENMPT_VERSION_DATE "2020-08-30T13:42:32.941871Z"
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.3"
#define OPENMPT_VERSION_DATE "2020-10-25T14:02:16.624929Z"
#define OPENMPT_VERSION_IS_PACKAGE 1

View File

@ -563,10 +563,7 @@ namespace FileReader
static_assert(mpt::is_binary_safe<T>::value);
if(f.CanRead(sizeof(destArray)))
{
for(auto &element : destArray)
{
Read(f, element);
}
f.ReadRaw(mpt::as_raw_memory(destArray));
return true;
} else
{
@ -584,10 +581,7 @@ namespace FileReader
static_assert(mpt::is_binary_safe<T>::value);
if(f.CanRead(sizeof(destArray)))
{
for(auto &element : destArray)
{
Read(f, element);
}
f.ReadRaw(mpt::as_raw_memory(destArray));
return true;
} else
{
@ -606,10 +600,7 @@ namespace FileReader
destVector.resize(destSize);
if(f.CanRead(sizeof(T) * destSize))
{
for(auto &element : destVector)
{
Read(f, element);
}
f.ReadRaw(mpt::as_raw_memory(destVector));
return true;
} else
{
@ -679,18 +670,9 @@ namespace FileReader
typename TFileCursor::off_t avail = f.GetRaw(bytes, sizeof(bytes));
typename TFileCursor::off_t readPos = 1;
size_t writtenBits = 0;
uint8 b = mpt::byte_cast<uint8>(bytes[0]);
target = (b & 0x7F);
// Count actual bits used in most significant byte (i.e. this one)
for(size_t bit = 0; bit < 7; bit++)
{
if((b & (1u << bit)) != 0)
{
writtenBits = bit + 1;
}
}
size_t writtenBits = static_cast<size_t>(mpt::bit_width(target)); // Bits used in the most significant byte
while(readPos < avail && (b & 0x80) != 0)
{

View File

@ -388,8 +388,11 @@ void SsbWrite::OnWroteItem(const ID &id, const Postype& posBeforeWrite)
{
const Offtype nRawEntrySize = oStrm.tellp() - posBeforeWrite;
if (nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > std::numeric_limits<DataSize>::max())
{ AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE); return; }
MPT_MAYBE_CONSTANT_IF(nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > std::numeric_limits<DataSize>::max())
{
AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE);
return;
}
if(GetFlag(RwfRMapHasSize) && (nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > (std::numeric_limits<DataSize>::max() >> 2)))
{ AddWriteNote(SNW_DATASIZETYPE_OVERFLOW); return; }
@ -557,8 +560,11 @@ void SsbRead::BeginRead(const ID &id, const uint64& nVersion)
const Offtype rawEndOfHdrData = iStrm.tellg() - m_posStart;
if (rawEndOfHdrData < 0 || static_cast<uint64>(rawEndOfHdrData) > std::numeric_limits<RposType>::max())
{ AddReadNote(SNR_INSUFFICIENT_RPOSTYPE); return; }
MPT_MAYBE_CONSTANT_IF(rawEndOfHdrData < 0 || static_cast<uint64>(rawEndOfHdrData) > std::numeric_limits<RposType>::max())
{
AddReadNote(SNR_INSUFFICIENT_RPOSTYPE);
return;
}
m_rposEndofHdrData = static_cast<RposType>(rawEndOfHdrData);
m_rposMapBegin = (GetFlag(RwfRwHasMap)) ? static_cast<RposType>(tempU64) : m_rposEndofHdrData;

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 03
#define VER_MINOR 05
#define VER_MINORMINOR 00
OPENMPT_NAMESPACE_END

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash
cd "${0%/*}"
AFL_VERSION="$(wget --quiet -O - "https://api.github.com/repos/vanhauser-thc/AFLplusplus/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')"
AFL_VERSION="$(wget --quiet -O - "https://api.github.com/repos/AFLplusplus/AFLplusplus/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')"
AFL_FILENAME="$AFL_VERSION.tar.gz"
AFL_URL="https://github.com/vanhauser-thc/AFLplusplus/archive/$AFL_FILENAME"
AFL_URL="https://github.com/AFLplusplus/AFLplusplus/archive/$AFL_FILENAME"
rm $AFL_FILENAME
wget $AFL_URL || exit

View File

@ -5,6 +5,31 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This
is just a high-level summary.
### libopenmpt 0.5.3 (2020-10-25)
* [**Sec**] Possible hang if a MED file claimed to contain 256 songs. (r13704)
* [**Bug**] libopenmpt: `openmpt::is_extension_supported2()` exported symbol
was missing (C++).
* [**Bug**] `openmpt::module::set_position_seconds` sometimes behaved as if
the song end was reached when seeking into a pattern loop and in some other
corner cases.
* Increase threshold for ignoring panning commands from 820 to 830.
* Subsong names now fall back to the first pattern's name if empty.
* MO3: Avoid certain ModPlug hacks from being fixed up twice, which could lead
to e.g. very narrow pan swing range for old OpenMPT IT files saved with a
recent MO3 encoder version.
* MO3: Some files with corrupted envelope data could be rejected completely
(normally libopenmpt should fix up the envelope data).
* MO3: Song metadata didn't correctly identify MPTM as source format (it
appeared as IT instead).
* STM: Change tempo computation to behave like Scream Tracker 2.3 instead of
Scream Tracker 2.2, as the playback frequencies we use for sample playback
are closer to those of Scream Tracker 2.3.
* PLM: Percentage offset (Mxx) was slightly off.
* WOW: Fix loading of several files and harden WOW detection.
### libopenmpt 0.5.2 (2020-08-30)
* [**Change**] `Makefile` `CONFIG=emscripten` now supports

View File

@ -1567,7 +1567,7 @@ static int get_pattern_row_channel_effect_type( openmpt_module_ext * mod_ext, in
int set_current_speed( openmpt_module_ext * mod_ext, int32_t speed ) {
static int set_current_speed( openmpt_module_ext * mod_ext, int32_t speed ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_current_speed( speed );
@ -1577,7 +1577,7 @@ int set_current_speed( openmpt_module_ext * mod_ext, int32_t speed ) {
}
return 0;
}
int set_current_tempo( openmpt_module_ext * mod_ext, int32_t tempo ) {
static int set_current_tempo( openmpt_module_ext * mod_ext, int32_t tempo ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_current_tempo( tempo );
@ -1587,7 +1587,7 @@ int set_current_tempo( openmpt_module_ext * mod_ext, int32_t tempo ) {
}
return 0;
}
int set_tempo_factor( openmpt_module_ext * mod_ext, double factor ) {
static int set_tempo_factor( openmpt_module_ext * mod_ext, double factor ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_tempo_factor( factor );
@ -1597,7 +1597,7 @@ int set_tempo_factor( openmpt_module_ext * mod_ext, double factor ) {
}
return 0;
}
double get_tempo_factor( openmpt_module_ext * mod_ext ) {
static double get_tempo_factor( openmpt_module_ext * mod_ext ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_tempo_factor();
@ -1606,7 +1606,7 @@ double get_tempo_factor( openmpt_module_ext * mod_ext ) {
}
return 0.0;
}
int set_pitch_factor( openmpt_module_ext * mod_ext, double factor ) {
static int set_pitch_factor( openmpt_module_ext * mod_ext, double factor ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_pitch_factor( factor );
@ -1616,7 +1616,7 @@ int set_pitch_factor( openmpt_module_ext * mod_ext, double factor ) {
}
return 0;
}
double get_pitch_factor( openmpt_module_ext * mod_ext ) {
static double get_pitch_factor( openmpt_module_ext * mod_ext ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_pitch_factor();
@ -1625,7 +1625,7 @@ double get_pitch_factor( openmpt_module_ext * mod_ext ) {
}
return 0.0;
}
int set_global_volume( openmpt_module_ext * mod_ext, double volume ) {
static int set_global_volume( openmpt_module_ext * mod_ext, double volume ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_global_volume( volume );
@ -1635,7 +1635,7 @@ int set_global_volume( openmpt_module_ext * mod_ext, double volume ) {
}
return 0;
}
double get_global_volume( openmpt_module_ext * mod_ext ) {
static double get_global_volume( openmpt_module_ext * mod_ext ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_global_volume();
@ -1644,7 +1644,7 @@ double get_global_volume( openmpt_module_ext * mod_ext ) {
}
return 0.0;
}
int set_channel_volume( openmpt_module_ext * mod_ext, int32_t channel, double volume ) {
static int set_channel_volume( openmpt_module_ext * mod_ext, int32_t channel, double volume ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_channel_volume( channel, volume );
@ -1654,7 +1654,7 @@ int set_channel_volume( openmpt_module_ext * mod_ext, int32_t channel, double vo
}
return 0;
}
double get_channel_volume( openmpt_module_ext * mod_ext, int32_t channel ) {
static double get_channel_volume( openmpt_module_ext * mod_ext, int32_t channel ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_channel_volume( channel );
@ -1663,7 +1663,7 @@ double get_channel_volume( openmpt_module_ext * mod_ext, int32_t channel ) {
}
return 0.0;
}
int set_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel, int mute ) {
static int set_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel, int mute ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_channel_mute_status( channel, mute ? true : false );
@ -1673,7 +1673,7 @@ int set_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel, int
}
return 0;
}
int get_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel ) {
static int get_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_channel_mute_status( channel ) ? 1 : 0;
@ -1682,7 +1682,7 @@ int get_channel_mute_status( openmpt_module_ext * mod_ext, int32_t channel ) {
}
return -1;
}
int set_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument, int mute ) {
static int set_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument, int mute ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_instrument_mute_status( instrument, mute ? true : false );
@ -1692,7 +1692,7 @@ int set_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument
}
return 0;
}
int get_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument ) {
static int get_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_instrument_mute_status( instrument ) ? 1 : 0;
@ -1701,7 +1701,7 @@ int get_instrument_mute_status( openmpt_module_ext * mod_ext, int32_t instrument
}
return -1;
}
int32_t play_note( openmpt_module_ext * mod_ext, int32_t instrument, int32_t note, double volume, double panning ) {
static int32_t play_note( openmpt_module_ext * mod_ext, int32_t instrument, int32_t note, double volume, double panning ) {
try {
openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->play_note( instrument, note, volume, panning );
@ -1710,7 +1710,7 @@ int32_t play_note( openmpt_module_ext * mod_ext, int32_t instrument, int32_t not
}
return -1;
}
int stop_note( openmpt_module_ext * mod_ext, int32_t channel ) {
static int stop_note( openmpt_module_ext * mod_ext, int32_t channel ) {
try {
openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->stop_note( channel );

View File

@ -126,7 +126,7 @@ std::vector<std::string> get_supported_extensions() {
bool is_extension_supported( const std::string & extension ) {
return openmpt::module_impl::is_extension_supported( extension );
}
bool is_extension_supported( std::string_view extension ) {
bool is_extension_supported2( std::string_view extension ) {
return openmpt::module_impl::is_extension_supported( extension );
}

View File

@ -1106,11 +1106,11 @@ double module_impl::set_position_seconds( double seconds ) {
} else {
subsong = &subsongs[m_current_subsong];
}
GetLengthType t = m_sndFile->GetLength( eNoAdjust, GetLengthTarget( seconds ).StartPos( static_cast<SEQUENCEINDEX>( subsong->sequence ), static_cast<ORDERINDEX>( subsong->start_order ), static_cast<ROWINDEX>( subsong->start_row ) ) ).back();
GetLengthType t = m_sndFile->GetLength( m_ctl_seek_sync_samples ? eAdjustSamplePositions : eAdjust, GetLengthTarget( seconds ).StartPos( static_cast<SEQUENCEINDEX>( subsong->sequence ), static_cast<ORDERINDEX>( subsong->start_order ), static_cast<ROWINDEX>( subsong->start_row ) ) ).back();
m_sndFile->m_PlayState.m_nCurrentOrder = t.lastOrder;
m_sndFile->SetCurrentOrder( t.lastOrder );
m_sndFile->m_PlayState.m_nNextRow = t.lastRow;
m_currentPositionSeconds = base_seconds + m_sndFile->GetLength( m_ctl_seek_sync_samples ? eAdjustSamplePositions : eAdjust, GetLengthTarget( t.lastOrder, t.lastRow ).StartPos( static_cast<SEQUENCEINDEX>( subsong->sequence ), static_cast<ORDERINDEX>( subsong->start_order ), static_cast<ROWINDEX>( subsong->start_row ) ) ).back().duration;
m_currentPositionSeconds = base_seconds + t.duration;
return m_currentPositionSeconds;
}
double module_impl::set_position_order_row( std::int32_t order, std::int32_t row ) {
@ -1367,8 +1367,15 @@ std::vector<std::string> module_impl::get_subsong_names() const {
std::vector<std::string> retval;
std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ? std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
retval.reserve( subsongs.size() );
for ( const auto & subsong : subsongs ) {
retval.push_back( mpt::ToCharset( mpt::Charset::UTF8, m_sndFile->Order( static_cast<SEQUENCEINDEX>( subsong.sequence ) ).GetName() ) );
const auto & order = m_sndFile->Order( static_cast<SEQUENCEINDEX>( subsong.sequence ) );
retval.push_back( mpt::ToCharset( mpt::Charset::UTF8, order.GetName() ) );
if ( retval.back().empty() ) {
// use first pattern name instead
if ( order.IsValidPat( static_cast<SEQUENCEINDEX>( subsong.start_order ) ) )
retval.back() = mpt::ToCharset( mpt::Charset::UTF8, m_sndFile->GetCharsetInternal(), m_sndFile->Patterns[ order[ subsong.start_order ] ].GetName() );
}
}
return retval;
}

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 2
#define OPENMPT_API_VERSION_PATCH 3
/*! \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=2
LIBOPENMPT_VERSION_PATCH=3
LIBOPENMPT_VERSION_PREREL=
LIBOPENMPT_LTVER_CURRENT=2
LIBOPENMPT_LTVER_REVISION=2
LIBOPENMPT_LTVER_REVISION=3
LIBOPENMPT_LTVER_AGE=2

View File

@ -190,7 +190,7 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags)
continue;
}
const ModCommand::COMMAND effTrans[] =
static constexpr ModCommand::COMMAND effTrans[] =
{
CMD_PORTAMENTOUP, // Slide up (param * 80) Hz on every tick
CMD_PORTAMENTODOWN, // Slide down (param * 80) Hz on every tick

View File

@ -313,7 +313,7 @@ bool CSoundFile::ReadDSM(FileReader &file, ModLoadingFlags loadFlags)
}
}
patNum++;
} else if(!memcmp(chunkHeader.magic, "INST", 4) && GetNumSamples() < SAMPLEINDEX(MAX_SAMPLES - 1))
} else if(!memcmp(chunkHeader.magic, "INST", 4) && CanAddMoreSamples())
{
// Read sample
m_nSamples++;

View File

@ -118,16 +118,12 @@ struct MMDSong
MMD0Song GetMMD0Song() const
{
static_assert(sizeof(MMD0Song) == sizeof(song));
MMD0Song result;
std::memcpy(&result, song, sizeof(result));
return result;
return mpt::bit_cast<MMD0Song>(song);
}
MMD2Song GetMMD2Song() const
{
static_assert(sizeof(MMD2Song) == sizeof(song));
MMD2Song result;
std::memcpy(&result, song, sizeof(result));
return result;
return mpt::bit_cast<MMD2Song>(song);
}
uint16be defaultTempo;
int8be playTranspose; // The global play transpose value for current song
@ -575,24 +571,24 @@ static void MEDReadNextSong(FileReader &file, MMD0FileHeader &fileHeader, MMD0Ex
}
static CHANNELINDEX MEDScanNumChannels(FileReader &file, const uint8 version)
static std::pair<CHANNELINDEX, SEQUENCEINDEX> MEDScanNumChannels(FileReader &file, const uint8 version)
{
MMD0FileHeader fileHeader;
MMD0Exp expData;
MMDSong songHeader;
file.Rewind();
uint32 songOffset = 0;
MEDReadNextSong(file, fileHeader, expData, songHeader);
auto numSongs = fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1;
SEQUENCEINDEX numSongs = std::min(MAX_SEQUENCES, mpt::saturate_cast<SEQUENCEINDEX>(fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1));
CHANNELINDEX numChannels = 4;
// Scan patterns for max number of channels
for(SEQUENCEINDEX song = 0; song < numSongs; song++)
{
const PATTERNINDEX numPatterns = songHeader.numBlocks;
if(songHeader.numSamples > 63 || numPatterns > 0x7FFF)
return 0;
return {};
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{
@ -604,11 +600,16 @@ static CHANNELINDEX MEDScanNumChannels(FileReader &file, const uint8 version)
numChannels = std::max(numChannels, static_cast<CHANNELINDEX>(version < 1 ? file.ReadUint8() : file.ReadUint16BE()));
}
if(!expData.nextModOffset || !file.Seek(expData.nextModOffset))
// If song offsets are going backwards, reject the file
if(expData.nextModOffset <= songOffset || !file.Seek(expData.nextModOffset))
{
numSongs = song + 1;
break;
}
songOffset = expData.nextModOffset;
MEDReadNextSong(file, fileHeader, expData, songHeader);
}
return numChannels;
return {numChannels, numSongs};
}
@ -680,9 +681,10 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(expData);
}
m_nChannels = MEDScanNumChannels(file, version);
if(m_nChannels < 1 || m_nChannels > MAX_BASECHANNELS)
const auto [numChannels, numSongs] = MEDScanNumChannels(file, version);
if(numChannels < 1 || numChannels > MAX_BASECHANNELS)
return false;
m_nChannels = numChannels;
// Start with the instruments, as those are shared between songs
@ -993,8 +995,6 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
}
}
const auto numSongs = std::min<SEQUENCEINDEX>(MAX_SEQUENCES, fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1);
file.Rewind();
PATTERNINDEX basePattern = 0;
for(SEQUENCEINDEX song = 0; song < numSongs; song++)

View File

@ -292,8 +292,9 @@ uint32 CSoundFile::MapMidiInstrument(uint8 program, uint16 bank, uint8 midiChann
if (program + 1 == p->nMidiProgram && bank + 1 == p->wMidiBank && p->nMidiDrumKey == 0) return i;
}
}
if ((m_nInstruments + 1 >= MAX_INSTRUMENTS) || (m_nSamples + 1 >= MAX_SAMPLES)) return 0;
if(!CanAddMoreInstruments() || !CanAddMoreSamples())
return 0;
pIns = AllocateInstrument(m_nInstruments + 1);
if(pIns == nullptr)
{
@ -347,7 +348,7 @@ struct MThd
uint16be division; // Delta timing value: positive = units/beat; negative = smpte compatible units
};
MPT_BINARY_STRUCT(MThd, 10);
MPT_BINARY_STRUCT(MThd, 10)
using tick_t = uint32;

View File

@ -838,34 +838,16 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
m_nDefaultSpeed = fileHeader.defaultSpeed ? fileHeader.defaultSpeed : 6;
m_nDefaultTempo.Set(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125, 0);
mpt::ustring originalFormatType;
mpt::ustring originalFormatName;
if(fileHeader.flags & MO3FileHeader::isIT)
{
SetType(MOD_TYPE_IT);
originalFormatType = U_("it");
originalFormatName = U_("Impulse Tracker");
} else if(fileHeader.flags & MO3FileHeader::isS3M)
{
else if(fileHeader.flags & MO3FileHeader::isS3M)
SetType(MOD_TYPE_S3M);
originalFormatType = U_("s3m");
originalFormatName = U_("ScreamTracker 3");
} else if(fileHeader.flags & MO3FileHeader::isMOD)
{
else if(fileHeader.flags & MO3FileHeader::isMOD)
SetType(MOD_TYPE_MOD);
originalFormatType = U_("mod");
originalFormatName = U_("Generic MOD");
} else if(fileHeader.flags & MO3FileHeader::isMTM)
{
else if(fileHeader.flags & MO3FileHeader::isMTM)
SetType(MOD_TYPE_MTM);
originalFormatType = U_("mtm");
originalFormatName = U_("MultiTracker");
} else
{
else
SetType(MOD_TYPE_XM);
originalFormatType = U_("xm");
originalFormatName = U_("FastTracker 2");
}
if(fileHeader.flags & MO3FileHeader::linearSlides)
m_SongFlags.set(SONG_LINEARSLIDES);
@ -1828,7 +1810,6 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
mpt::ustring madeWithTracker;
uint16 cwtv = 0;
uint16 cmwt = 0;
MPT_UNUSED_VARIABLE(cmwt);
while(musicChunk.CanRead(8))
{
uint32 id = musicChunk.ReadUint32LE();
@ -1926,16 +1907,18 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
if(fileHeader.flags & MO3FileHeader::modplugMode)
{
// Apply some old ModPlug (mis-)behaviour
for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++)
if(!m_dwLastSavedWithVersion)
{
if(ModInstrument *ins = Instruments[i])
// These fixes are only applied when the OpenMPT version number is not known, as otherwise the song upgrade feature will take care of it.
for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++)
{
// Fix pitch / filter envelope being shortened by one tick
if(m_dwLastSavedWithVersion < MPT_V("1.20.00.00"))
if(ModInstrument *ins = Instruments[i])
{
// Fix pitch / filter envelope being shortened by one tick (for files before v1.20)
ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType());
// Fix excessive pan swing range
if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00"))
// Fix excessive pan swing range (for files before v1.26)
ins->nPanSwing = (ins->nPanSwing + 3) / 4u;
}
}
}
if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00"))
@ -1956,8 +1939,39 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
m_modFormat.formatName = mpt::format(U_("Un4seen MO3 v%1"))(version);
m_modFormat.type = U_("mo3");
m_modFormat.originalType = std::move(originalFormatType);
m_modFormat.originalFormatName = std::move(originalFormatName);
switch(GetType())
{
case MOD_TYPE_MTM:
m_modFormat.originalType = U_("mtm");
m_modFormat.originalFormatName = U_("MultiTracker");
break;
case MOD_TYPE_MOD:
m_modFormat.originalType = U_("mod");
m_modFormat.originalFormatName = U_("Generic MOD");
break;
case MOD_TYPE_XM:
m_modFormat.originalType = U_("xm");
m_modFormat.originalFormatName = U_("FastTracker 2");
break;
case MOD_TYPE_S3M:
m_modFormat.originalType = U_("s3m");
m_modFormat.originalFormatName = U_("ScreamTracker 3");
break;
case MOD_TYPE_IT:
m_modFormat.originalType = U_("it");
if(cmwt)
m_modFormat.originalFormatName = mpt::format(U_("Impulse Tracker %1.%2"))(cmwt >> 8, mpt::ufmt::hex0<2>(cmwt & 0xFF));
else
m_modFormat.originalFormatName = U_("Impulse Tracker");
break;
case MOD_TYPE_MPT:
m_modFormat.originalType = U_("mptm");
m_modFormat.originalFormatName = U_("OpenMPT MPTM");
break;
default:
MPT_ASSERT_NOTREACHED();
}
m_modFormat.madeWithTracker = std::move(madeWithTracker);
if(m_dwLastSavedWithVersion)
m_modFormat.charset = mpt::Charset::Windows1252;

View File

@ -19,7 +19,7 @@
#ifdef MPT_EXTERNAL_SAMPLES
// For loading external data in Startrekker files
#include "../common/mptPathString.h"
#endif // MPT_EXTERNAL_SAMPLES
#endif // MPT_EXTERNAL_SAMPLES
OPENMPT_NAMESPACE_BEGIN
@ -61,6 +61,7 @@ void CSoundFile::ConvertModCommand(ModCommand &m)
case 'P' - 55: m.command = CMD_PANNINGSLIDE; break;
case 'R' - 55: m.command = CMD_RETRIG; break;
case 'T' - 55: m.command = CMD_TREMOR; break;
case 'W' - 55: m.command = CMD_DUMMY; break;
case 'X' - 55: m.command = CMD_XFINEPORTAUPDOWN; break;
case 'Y' - 55: m.command = CMD_PANBRELLO; break; //34
case 'Z' - 55: m.command = CMD_MIDI; break; //35
@ -137,6 +138,7 @@ void CSoundFile::ModSaveCommand(uint8 &command, uint8 &param, bool toXM, bool co
case CMD_PANNINGSLIDE: command = 'P' - 55; break;
case CMD_RETRIG: command = 'R' - 55; break;
case CMD_TREMOR: command = 'T' - 55; break;
case CMD_DUMMY: command = 'W' - 55; break;
case CMD_XFINEPORTAUPDOWN: command = 'X' - 55;
if(compatibilityExport && param >= 0x30) // X1x and X2x are legit, everything above are MPT extensions, which don't belong here.
param = 0; // Don't set command to 0 to indicate that there *was* some X command here...
@ -197,7 +199,7 @@ void CSoundFile::ModSaveCommand(uint8 &command, uint8 &param, bool toXM, bool co
}
}
#endif // MODPLUG_NO_FILESAVE
#endif // MODPLUG_NO_FILESAVE
// File Header
@ -342,6 +344,9 @@ struct MODSampleHeader
MPT_BINARY_STRUCT(MODSampleHeader, 30)
// Pattern data of a 4-channel MOD file
using MODPatternData = std::array<std::array<std::array<uint8, 4>, 4>, 64>;
// Synthesized StarTrekker instruments
struct AMInstrument
{
@ -369,7 +374,7 @@ struct AMInstrument
sample.nLoopStart = 0;
sample.nLoopEnd = sample.nLength;
sample.uFlags.set(CHN_LOOP);
sample.nVolume = 256; // prelude.mod has volume 0 in sample header
sample.nVolume = 256; // prelude.mod has volume 0 in sample header
sample.nVibDepth = mpt::saturate_cast<uint8>(vibAmp * 2);
sample.nVibRate = static_cast<uint8>(vibSpeed);
sample.nVibType = VIB_SINE;
@ -518,8 +523,36 @@ static uint32 ReadSample(FileReader &file, MODSampleHeader &sampleHeader, ModSam
}
// Count malformed bytes in MOD pattern data
static uint32 CountMalformedMODPatternData(const MODPatternData &patternData, const bool allow31Samples)
{
const uint8 mask = allow31Samples ? 0xE0 : 0xF0;
uint32 malformedBytes = 0;
for(const auto &row : patternData)
{
for(const auto &data : row)
{
if(data[0] & mask)
malformedBytes++;
}
}
return malformedBytes;
}
// Check if number of malformed bytes in MOD pattern data exceeds some threshold
template <typename TFileReader>
static bool ValidateMODPatternData(TFileReader &file, const uint32 threshold, const bool allow31Samples)
{
MODPatternData patternData;
if(!file.Read(patternData))
return false;
return CountMalformedMODPatternData(patternData, allow31Samples) <= threshold;
}
// Parse the order list to determine how many patterns are used in the file.
static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERINDEX numOrders, SmpLength totalSampleLen, CHANNELINDEX &numChannels, bool checkForWOW)
static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERINDEX numOrders, SmpLength totalSampleLen, CHANNELINDEX &numChannels, SmpLength wowSampleLen = 0)
{
PATTERNINDEX numPatterns = 0; // Total number of patterns in file (determined by going through the whole order list) with pattern number < 128
PATTERNINDEX officialPatterns = 0; // Number of patterns only found in the "official" part of the order list (i.e. order positions < claimed order length)
@ -548,12 +581,16 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
const size_t patternStartOffset = file.GetPosition();
const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset;
if(checkForWOW && sizeWithoutPatterns + numPatterns * 8 * 256 == file.GetLength())
if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == (file.GetLength() & ~1))
{
// Check if this is a Mod's Grave WOW file... Never seen one of those, but apparently they *do* exist.
// WOW files should use the M.K. magic but are actually 8CHN files.
numChannels = 8;
} else if(numPatterns != officialPatterns && numChannels == 4 && !checkForWOW)
// Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files.
// We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end
// (e.g. ponylips.mod, MD5 c039af363b1d99a492dafc5b5f9dd949, SHA1 1bee1941c47bc6f913735ce0cf1880b248b8fc93)
file.Seek(patternStartOffset + numPatterns * 4 * 256);
if(ValidateMODPatternData(file, 16, true))
numChannels = 8;
file.Seek(patternStartOffset);
} else if(numPatterns != officialPatterns && numChannels == 4 && !wowSampleLen)
{
// Fix SoundTracker modules where "hidden" patterns should be ignored.
// razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b)
@ -568,20 +605,8 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
// Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data.
// If that is the case, we assume it's part of the sample data and only consider the "official" patterns.
file.Seek(patternStartOffset + officialPatterns * 1024);
int illegalBytes = 0;
for(int i = 0; i < 256; i++)
{
const auto data = file.ReadArray<uint8, 4>();
if(data[0] & 0xE0)
{
illegalBytes++;
if(illegalBytes > 64)
{
numPatterns = officialPatterns;
break;
}
}
}
if(!ValidateMODPatternData(file, 64, true))
numPatterns = officialPatterns;
file.Seek(patternStartOffset);
}
@ -673,29 +698,29 @@ struct MODMagicResult
static bool CheckMODMagic(const char magic[4], MODMagicResult &result)
{
if(IsMagic(magic, "M.K.") // ProTracker and compatible
|| IsMagic(magic, "M!K!") // ProTracker (>64 patterns)
|| IsMagic(magic, "PATT") // ProTracker 3.6
|| IsMagic(magic, "NSMS") // kingdomofpleasure.mod by bee hunter
|| IsMagic(magic, "LARD")) // judgement_day_gvine.mod by 4-mat
if(IsMagic(magic, "M.K.") // ProTracker and compatible
|| IsMagic(magic, "M!K!") // ProTracker (>64 patterns)
|| IsMagic(magic, "PATT") // ProTracker 3.6
|| IsMagic(magic, "NSMS") // kingdomofpleasure.mod by bee hunter
|| IsMagic(magic, "LARD")) // judgement_day_gvine.mod by 4-mat
{
result.madeWithTracker = UL_("Generic ProTracker or compatible");
result.numChannels = 4;
} else if(IsMagic(magic, "M&K!") // "His Master's Noise" musicdisk
|| IsMagic(magic, "FEST") // "His Master's Noise" musicdisk
|| IsMagic(magic, "N.T."))
} else if(IsMagic(magic, "M&K!") // "His Master's Noise" musicdisk
|| IsMagic(magic, "FEST") // "His Master's Noise" musicdisk
|| IsMagic(magic, "N.T."))
{
result.madeWithTracker = UL_("NoiseTracker");
result.isNoiseTracker = true;
result.numChannels = 4;
} else if(IsMagic(magic, "OKTA")
|| IsMagic(magic, "OCTA"))
|| IsMagic(magic, "OCTA"))
{
// Oktalyzer
result.madeWithTracker = UL_("Oktalyzer");
result.numChannels = 8;
} else if(IsMagic(magic, "CD81")
|| IsMagic(magic, "CD61"))
|| IsMagic(magic, "CD61"))
{
// Octalyser on Atari STe/Falcon
result.madeWithTracker = UL_("Octalyser (Atari)");
@ -724,8 +749,8 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result)
result.madeWithTracker = UL_("Generic MOD-compatible Tracker");
result.isGenericMultiChannel = true;
result.numChannels = magic[0] - '0';
} else if(magic[0] >= '1' && magic[0] <= '9' && magic[1]>='0' && magic[1] <= '9'
&& (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2)))
} else if(magic[0] >= '1' && magic[0] <= '9' && magic[1] >= '0' && magic[1] <= '9'
&& (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2)))
{
// xxCN / xxCH - Many trackers
result.madeWithTracker = UL_("Generic MOD-compatible Tracker");
@ -819,13 +844,14 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
const bool isMdKd = IsMagic(magic, "M.K.");
// Adjust finetune values for modules saved with "His Master's Noisetracker"
const bool isHMNT = IsMagic(magic, "M&K!") || IsMagic(magic, "FEST");
bool maybeWOW = isMdKd;
// Reading song title
file.Seek(0);
file.ReadString<mpt::String::spacePadded>(m_songName, 20);
// Load Sample Headers
SmpLength totalSampleLen = 0;
SmpLength totalSampleLen = 0, wowSampleLen = 0;
m_nSamples = 31;
uint32 invalidBytes = 0;
for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
@ -835,15 +861,22 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
totalSampleLen += Samples[smp].nLength;
if(isHMNT)
{
Samples[smp].nFineTune = -static_cast<int8>(sampleHeader.finetune << 3);
} else if(Samples[smp].nLength > 65535)
{
else if(Samples[smp].nLength > 65535)
isNoiseTracker = false;
}
if(sampleHeader.length && !sampleHeader.loopLength)
{
hasRepLen0 = true;
if(maybeWOW)
{
// Some WOW files rely on sample length 1 being counted as well
wowSampleLen += sampleHeader.length * 2;
// WOW files are converted 669 files, which don't support finetune or default volume
if(sampleHeader.finetune)
maybeWOW = false;
else if(sampleHeader.length > 0 && sampleHeader.volume != 64)
maybeWOW = false;
}
}
// If there is too much binary garbage in the sample headers, reject the file.
@ -857,6 +890,11 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(fileHeader);
file.Skip(4); // Magic bytes (we already parsed these)
if(fileHeader.restartPos > 0)
maybeWOW = false;
if(!maybeWOW)
wowSampleLen = 0;
ReadOrderFromArray(Order(), fileHeader.orderList);
ORDERINDEX realOrders = fileHeader.numOrders;
@ -875,11 +913,12 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
}
// Get number of patterns (including some order list sanity checks)
PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, isMdKd);
if(isMdKd && GetNumChannels() == 8)
PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, wowSampleLen);
if(maybeWOW && GetNumChannels() == 8)
{
// M.K. with 8 channels = Grave Composer
// M.K. with 8 channels = Mod's Grave
modMagicResult.madeWithTracker = UL_("Mod's Grave");
isGenericMultiChannel = true;
}
if(isFLT8)
@ -1086,7 +1125,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
lastInstrument[chn] = m.instr;
}
}
if(hasSpeedOnRow && hasTempoOnRow) definitelyCIA = true;
if(hasSpeedOnRow && hasTempoOnRow)
definitelyCIA = true;
}
}
@ -1100,7 +1140,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
m_playBehaviour.set(kMODOutOfRangeNoteDelay);
m_playBehaviour.set(kMODTempoOnSecondTick);
// Arbitrary threshold for deciding that 8xx effects are only used as sync markers
if(maxPanning < 0x20)
if(maxPanning < 0x30)
{
m_playBehaviour.set(kMODIgnorePanning);
if(fileHeader.restartPos != 0x7F)
@ -1265,7 +1305,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
// Check if a name string is valid (i.e. doesn't contain binary garbage data)
template<size_t N>
template <size_t N>
static uint32 CountInvalidChars(const char (&name)[N])
{
uint32 invalidChars = 0;
@ -1284,13 +1324,13 @@ static uint32 CountInvalidChars(const char (&name)[N])
// Thanks for Fraggie for this information! (https://www.un4seen.com/forum/?topic=14471.msg100829#msg100829)
enum STVersions
{
UST1_00, // Ultimate Soundtracker 1.0-1.21 (K. Obarski)
UST1_80, // Ultimate Soundtracker 1.8-2.0 (K. Obarski)
ST2_00_Exterminator, // SoundTracker 2.0 (The Exterminator), D.O.C. Sountracker II (Unknown/D.O.C.)
ST_III, // Defjam Soundtracker III (Il Scuro/Defjam), Alpha Flight SoundTracker IV (Alpha Flight), D.O.C. SoundTracker IV (Unknown/D.O.C.), D.O.C. SoundTracker VI (Unknown/D.O.C.)
ST_IX, // D.O.C. SoundTracker IX (Unknown/D.O.C.)
MST1_00, // Master Soundtracker 1.0 (Tip/The New Masters)
ST2_00, // SoundTracker 2.0, 2.1, 2.2 (Unknown/D.O.C.)
UST1_00, // Ultimate Soundtracker 1.0-1.21 (K. Obarski)
UST1_80, // Ultimate Soundtracker 1.8-2.0 (K. Obarski)
ST2_00_Exterminator, // SoundTracker 2.0 (The Exterminator), D.O.C. Sountracker II (Unknown/D.O.C.)
ST_III, // Defjam Soundtracker III (Il Scuro/Defjam), Alpha Flight SoundTracker IV (Alpha Flight), D.O.C. SoundTracker IV (Unknown/D.O.C.), D.O.C. SoundTracker VI (Unknown/D.O.C.)
ST_IX, // D.O.C. SoundTracker IX (Unknown/D.O.C.)
MST1_00, // Master Soundtracker 1.0 (Tip/The New Masters)
ST2_00, // SoundTracker 2.0, 2.1, 2.2 (Unknown/D.O.C.)
};
@ -1304,8 +1344,6 @@ struct M15FileHeaders
MPT_BINARY_STRUCT(M15FileHeaders, 20 + 15 * 30 + 130)
typedef std::array<uint8, 4> M15PatternData[64][4];
static bool ValidateHeader(const M15FileHeaders &fileHeaders)
{
@ -1372,39 +1410,11 @@ static bool ValidateHeader(const M15FileHeaders &fileHeaders)
}
static uint32 CountIllegalM15PatternBytes(const M15PatternData &patternData)
{
uint32 illegalBytes = 0;
for(uint8 row = 0; row < 64; ++row)
{
for(uint8 channel = 0; channel < 4; ++channel)
{
if(patternData[row][channel][0] & 0xF0u)
{
illegalBytes++;
}
}
}
return illegalBytes;
}
template <typename TFileReader>
static bool ValidateFirstM15Pattern(TFileReader &file)
{
M15PatternData patternData;
if(!file.ReadArray(patternData))
{
return false;
}
file.SkipBack(sizeof(patternData));
uint32 invalidBytes = CountIllegalM15PatternBytes(patternData);
// [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much]
if(invalidBytes > 512 / 64 * 2)
{
return false;
}
return true;
// threshold is chosen as: [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much]
return ValidateMODPatternData(file, 512 / 64 * 2, false);
}
@ -1419,7 +1429,7 @@ CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderM15(MemoryFileReader file, co
{
return ProbeFailure;
}
if(!file.CanRead(sizeof(M15PatternData)))
if(!file.CanRead(sizeof(MODPatternData)))
{
return ProbeWantMoreData;
}
@ -1456,7 +1466,7 @@ bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
InitializeGlobals(MOD_TYPE_MOD);
m_playBehaviour.reset(kMODOneShotLoops);
m_playBehaviour.set(kMODIgnorePanning);
m_playBehaviour.set(kMODSampleSwap); // untested
m_playBehaviour.set(kMODSampleSwap); // untested
m_nChannels = 4;
STVersions minVersion = UST1_00;
@ -1496,7 +1506,7 @@ bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(fileHeader);
ReadOrderFromArray(Order(), fileHeader.orderList);
PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), fileHeader.numOrders, totalSampleLen, m_nChannels, false);
PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), fileHeader.numOrders, totalSampleLen, m_nChannels);
// Most likely just a file with lots of NULs at the start
if(fileHeader.restartPos == 0 && fileHeader.numOrders == 0 && numPatterns <= 1)
@ -1552,11 +1562,11 @@ bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
bool patternInUse = std::find(Order().cbegin(), Order().cend(), pat) != Order().cend();
uint8 numDxx = 0;
uint8 emptyCmds = 0;
M15PatternData patternData;
MODPatternData patternData;
file.ReadArray(patternData);
if(patternInUse)
{
illegalBytes += CountIllegalM15PatternBytes(patternData);
illegalBytes += CountMalformedMODPatternData(patternData, false);
// Reject files that contain a lot of illegal pattern data.
// STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3)
// has one illegal byte, so we only reject after an arbitrary threshold has been passed.
@ -1655,7 +1665,7 @@ bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
Patterns.ResizeArray(numPatterns);
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{
M15PatternData patternData;
MODPatternData patternData;
file.ReadArray(patternData);
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
@ -1871,7 +1881,7 @@ bool CSoundFile::ReadICE(FileReader &file, ModLoadingFlags loadFlags)
InitializeGlobals(MOD_TYPE_MOD);
m_playBehaviour.reset(kMODOneShotLoops);
m_playBehaviour.set(kMODIgnorePanning);
m_playBehaviour.set(kMODSampleSwap); // untested
m_playBehaviour.set(kMODSampleSwap); // untested
if(IsMagic(magic, "MTN\0"))
{

View File

@ -627,7 +627,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
m.param--;
if(m.param < loopList.size())
{
if(!loopList[m.param].looped && m_nSamples < MAX_SAMPLES - 1)
if(!loopList[m.param].looped && CanAddMoreSamples())
loopList[m.param].looped = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(loopList[m.param].looped);
}
@ -648,7 +648,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
m.vol = m.param;
}
// switch to non-looped version of sample and create it if needed
if(!nonLooped[m.instr - 1] && m_nSamples < MAX_SAMPLES - 1)
if(!nonLooped[m.instr - 1] && CanAddMoreSamples())
nonLooped[m.instr - 1] = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(nonLooped[m.instr - 1]);
}
@ -664,7 +664,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
m.param--;
if(m.param < loopList.size())
{
if(!loopList[m.param].nonLooped && m_nSamples < MAX_SAMPLES-1)
if(!loopList[m.param].nonLooped && CanAddMoreSamples())
loopList[m.param].nonLooped = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(loopList[m.param].nonLooped);
}

View File

@ -168,7 +168,7 @@ bool CSoundFile::ReadUAX(FileReader &file, ModLoadingFlags loadFlags)
FileReader fileChunk = chunk.ReadChunk(size);
if(GetNumSamples() < MAX_SAMPLES - 1)
if(CanAddMoreSamples())
{
// Read as sample
if(ReadSampleFromFile(GetNumSamples() + 1, fileChunk, true))

View File

@ -393,13 +393,14 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags)
m_modFormat.madeWithTracker = U_("UltraTracker ") + versions[fileHeader.version - '1'];
m_modFormat.charset = mpt::Charset::CP437;
m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT.
m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT.
// read "messageLength" lines, each containing 32 characters.
// Read "messageLength" lines, each containing 32 characters.
m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0);
m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint8());
if(GetNumSamples() >= MAX_SAMPLES)
if(SAMPLEINDEX numSamples = file.ReadUint8(); numSamples < MAX_SAMPLES)
m_nSamples = numSamples;
else
return false;
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
@ -423,12 +424,13 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags)
ReadOrderFromFile<uint8>(Order(), file, 256, 0xFF, 0xFE);
m_nChannels = file.ReadUint8() + 1;
PATTERNINDEX numPats = file.ReadUint8() + 1;
if(GetNumChannels() > MAX_BASECHANNELS)
if(CHANNELINDEX numChannels = file.ReadUint8() + 1u; numChannels <= MAX_BASECHANNELS)
m_nChannels = numChannels;
else
return false;
PATTERNINDEX numPats = file.ReadUint8() + 1;
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{
ChnSettings[chn].Reset();

View File

@ -43,7 +43,7 @@ void InstrumentEnvelope::Convert(MODTYPE fromType, MODTYPE toType)
}
// XM -> IT / MPTM: Shorten loop by one tick by inserting bogus point
if(nLoopEnd > nLoopStart && dwFlags[ENV_LOOP])
if(nLoopEnd > nLoopStart && dwFlags[ENV_LOOP] && nLoopEnd < size())
{
if(at(nLoopEnd).tick - 1 > at(nLoopEnd - 1).tick)
{

View File

@ -469,11 +469,10 @@ int32 ModSample::FrequencyToTranspose(uint32 freq)
void ModSample::FrequencyToTranspose()
{
int f2t = 0;
if(nC5Speed)
f2t = Clamp(FrequencyToTranspose(nC5Speed), -16384, 16383);
RelativeTone = static_cast<int8>(f2t / 128);
nFineTune = static_cast<int8>(f2t & 0x7F);
const int f2t = Clamp(FrequencyToTranspose(nC5Speed), -16384, 16383);
const auto fine = std::div(f2t, 128);
RelativeTone = static_cast<int8>(fine.quot);
nFineTune = static_cast<int8>(fine.rem);
}

View File

@ -207,6 +207,8 @@ bool ModSequence::IsValidPat(ORDERINDEX ord) const
ORDERINDEX ModSequence::FindOrder(PATTERNINDEX pat, ORDERINDEX startSearchAt, bool searchForward) const
{
const ORDERINDEX length = GetLength();
if(startSearchAt >= length)
return ORDERINDEX_INVALID;
ORDERINDEX ord = startSearchAt;
for(ORDERINDEX p = 0; p < length; p++)
{

View File

@ -26,8 +26,8 @@ class ModSequence: public std::vector<PATTERNINDEX>
friend class ModSequenceSet;
protected:
mpt::ustring m_name; // Sequence name.
CSoundFile &m_sndFile; // Associated CSoundFile.
mpt::ustring m_name; // Sequence name
CSoundFile &m_sndFile; // Associated CSoundFile
ORDERINDEX m_restartPos = 0; // Restart position when playback of this order ended
public:
@ -128,7 +128,7 @@ class ModSequenceSet
friend void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset);
protected:
std::vector<ModSequence> m_Sequences; // Array of sequences.
std::vector<ModSequence> m_Sequences; // Array of sequences
CSoundFile &m_sndFile;
SEQUENCEINDEX m_currentSeq = 0; // Index of current sequence.

View File

@ -31,11 +31,11 @@ OPENMPT_NAMESPACE_BEGIN
// Formats which have 7-bit (0...128) instead of 6-bit (0...64) global volume commands, or which are imported to this range (mostly formats which are converted to IT internally)
#ifdef MODPLUG_TRACKER
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_MT2)
static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_MT2;
#else
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_NONE)
static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_NONE;
#endif // MODPLUG_TRACKER
#define GLOBALVOL_7BIT_FORMATS (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM | MOD_TYPE_PTM | MOD_TYPE_MDL | MOD_TYPE_DTM | GLOBALVOL_7BIT_FORMATS_EXT)
static constexpr auto GLOBALVOL_7BIT_FORMATS = MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM | MOD_TYPE_PTM | MOD_TYPE_MDL | MOD_TYPE_DTM | GLOBALVOL_7BIT_FORMATS_EXT;
// Compensate frequency slide LUTs depending on whether we are handling periods or frequency - "up" and "down" in function name are seen from frequency perspective.
@ -244,7 +244,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
retval.startRow = target.startRow;
// Are we trying to reach a certain pattern position?
const bool hasSearchTarget = target.mode != GetLengthTarget::NoTarget;
const bool hasSearchTarget = target.mode != GetLengthTarget::NoTarget && target.mode != GetLengthTarget::GetAllSubsongs;
const bool adjustSamplePos = (adjustMode & eAdjustSamplePositions) == eAdjustSamplePositions;
SEQUENCEINDEX sequence = target.sequence;
@ -300,14 +300,6 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
for (;;)
{
// Time target reached.
if(target.mode == GetLengthTarget::SeekSeconds && memory.elapsedTime >= target.time)
{
retval.targetReached = true;
break;
}
uint32 rowDelay = 0, tickDelay = 0;
playState.m_nRow = playState.m_nNextRow;
playState.m_nCurrentOrder = playState.m_nNextOrder;
@ -322,6 +314,13 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
playState.m_nCurrentOrder = ++playState.m_nNextOrder;
}
// Time target reached.
if(target.mode == GetLengthTarget::SeekSeconds && memory.elapsedTime >= target.time)
{
retval.targetReached = true;
break;
}
// Check if pattern is valid
playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
bool positionJumpOnThisRow = false, positionJumpRightOfPatternLoop = false;
@ -352,7 +351,12 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
playState.m_nNextOrder = playState.m_nCurrentOrder;
if((!Patterns.IsValidPat(playState.m_nPattern)) && visitedRows.IsVisited(playState.m_nCurrentOrder, 0, true))
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
if(!hasSearchTarget)
{
retval.lastOrder = playState.m_nCurrentOrder;
retval.lastRow = 0;
}
if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
// We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break;
@ -384,7 +388,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
// If there isn't even a tune, we should probably stop here.
if(playState.m_nCurrentOrder == orderList.GetRestartPos())
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
// We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break;
@ -416,7 +420,12 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
if(visitedRows.IsVisited(playState.m_nCurrentOrder, playState.m_nRow, true))
{
if(!hasSearchTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
if(!hasSearchTarget)
{
retval.lastOrder = playState.m_nCurrentOrder;
retval.lastRow = playState.m_nRow;
}
if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true))
{
// We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break;
@ -455,6 +464,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
}
// For various effects, we need to know first how many ticks there are in this row.
uint32 rowDelay = 0, tickDelay = 0;
const ModCommand *p = Patterns[playState.m_nPattern].GetpModCommand(playState.m_nRow, 0);
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++)
{
@ -1033,7 +1043,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
SampleOffset(chn, offset);
} else if(m.command == CMD_OFFSETPERCENTAGE)
{
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, m.param, 255));
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, m.param, 256));
} else if(m.command == CMD_REVERSEOFFSET && chn.pModSample != nullptr)
{
memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
@ -1224,14 +1234,15 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
startTimes[start] = std::lcm(startTimes[start], 1 + (param & 0x0F));
}
}
for(const auto &i : startTimes)
for(const auto &[startTime, loopCount] : startTimes)
{
memory.elapsedTime += (memory.elapsedTime - i.first) * (double)(i.second - 1);
memory.elapsedTime += (memory.elapsedTime - startTime) * (loopCount - 1);
//memory.elapsedBeats += 1.0 / playState.m_nCurrentRowsPerBeat;
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
{
if(memory.chnSettings[nChn].patLoop == i.first)
if(memory.chnSettings[nChn].patLoop == startTime)
{
playState.m_lTotalSampleCount += (playState.m_lTotalSampleCount - memory.chnSettings[nChn].patLoopSmp) * (i.second - 1);
playState.m_lTotalSampleCount += (playState.m_lTotalSampleCount - memory.chnSettings[nChn].patLoopSmp) * (loopCount - 1);
if(m_playBehaviour[kITPatternLoopTargetReset] || (GetType() == MOD_TYPE_S3M))
{
memory.chnSettings[nChn].patLoop = memory.elapsedTime;
@ -1270,7 +1281,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
}
}
if(retval.targetReached || target.mode == GetLengthTarget::NoTarget)
if(retval.targetReached)
{
retval.lastOrder = playState.m_nCurrentOrder;
retval.lastRow = playState.m_nRow;
@ -1344,11 +1355,11 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
{
Order.SetSequence(sequence);
}
visitedSongRows.MoveVisitedRowsFrom(visitedRows);
}
if(adjustMode & (eAdjust | eAdjustOnlyVisitedRows))
visitedSongRows.MoveVisitedRowsFrom(visitedRows);
return results;
}
@ -3281,7 +3292,7 @@ bool CSoundFile::ProcessEffects()
case CMD_OFFSETPERCENTAGE:
if(triggerNote)
{
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 255));
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 256));
}
break;
@ -5812,7 +5823,8 @@ TEMPO CSoundFile::ConvertST2Tempo(uint8 tempo)
static constexpr uint32 st2MixingRate = 23863; // Highest possible setting in ST2
// This underflows at tempo 06...0F, and the resulting tick lengths depend on the mixing rate.
int32 samplesPerTick = st2MixingRate / (49 - ((ST2TempoFactor[tempo >> 4u] * (tempo & 0x0F)) >> 4u));
// Note: ST2.3 uses the constant 50 below, earlier versions use 49 but they also play samples at a different speed.
int32 samplesPerTick = st2MixingRate / (50 - ((ST2TempoFactor[tempo >> 4u] * (tempo & 0x0F)) >> 4u));
if(samplesPerTick <= 0)
samplesPerTick += 65536;
return TEMPO().SetRaw(Util::muldivrfloor(st2MixingRate, 5 * TEMPO::fractFact, samplesPerTick * 2));
@ -6176,7 +6188,7 @@ PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority res
const ModChannel &channel = m_PlayState.Chn[nChn];
PLUGINDEX plugin;
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE]) || channel.dwFlags[CHN_NOFX])
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX])
{
plugin = 0;
} else

View File

@ -197,6 +197,8 @@ enum enmGetLengthResetMode
eAdjustOnSuccess = 0x02 | eAdjust,
// Same as previous option, but will also try to emulate sample playback so that voices from previous patterns will sound when continuing playback at the target position.
eAdjustSamplePositions = 0x04 | eAdjustOnSuccess,
// Only adjust the visited rows state
eAdjustOnlyVisitedRows = 0x08,
};
@ -707,6 +709,9 @@ public:
ORDERINDEX GetCurrentOrder() const { return m_PlayState.m_nCurrentOrder; }
CHANNELINDEX GetNumChannels() const { return m_nChannels; }
constexpr bool CanAddMoreSamples(SAMPLEINDEX amount = 1) const noexcept { return (amount < MAX_SAMPLES) && m_nSamples < (MAX_SAMPLES - amount); }
constexpr bool CanAddMoreInstruments(INSTRUMENTINDEX amount = 1) const noexcept { return (amount < MAX_INSTRUMENTS) && m_nInstruments < (MAX_INSTRUMENTS - amount); }
#ifndef NO_PLUGINS
IMixPlugin* GetInstrumentPlugin(INSTRUMENTINDEX instr) const;
#endif

View File

@ -608,8 +608,23 @@ bool CSoundFile::ProcessRow()
// Let's check again if this really is the end of the song.
// The visited rows vector might have been screwed up while editing...
// This is of course not possible during rendering to WAV, so we ignore that case.
GetLengthType t = GetLength(eNoAdjust).back();
if(IsRenderingToDisc() || (t.lastOrder == m_PlayState.m_nCurrentOrder && t.lastRow == m_PlayState.m_nRow))
bool isReallyAtEnd = false;
if(IsRenderingToDisc())
{
isReallyAtEnd = true;
} else
{
for(const auto &t : GetLength(eNoAdjust, GetLengthTarget(true)))
{
if(t.lastOrder == m_PlayState.m_nCurrentOrder && t.lastRow == m_PlayState.m_nRow)
{
isReallyAtEnd = true;
break;
}
}
}
if(isReallyAtEnd)
{
// This is really the song's end!
visitedSongRows.Initialize(true);
@ -617,7 +632,7 @@ bool CSoundFile::ProcessRow()
} else
{
// Ok, this is really dirty, but we have to update the visited rows vector...
GetLength(eAdjustOnSuccess, GetLengthTarget(m_PlayState.m_nCurrentOrder, m_PlayState.m_nRow));
GetLength(eAdjustOnlyVisitedRows, GetLengthTarget(m_PlayState.m_nCurrentOrder, m_PlayState.m_nRow));
}
#else
if(m_SongFlags[SONG_PLAYALLSONGS])

View File

@ -93,7 +93,7 @@ static constexpr ModFormatInfo modFormatInfo[] =
{ MOD_TYPE_STM, UL_("ScreamTracker 2"), "stm" },
{ MOD_TYPE_STP, UL_("Soundtracker Pro II"), "stp" },
{ MOD_TYPE_ULT, UL_("UltraTracker"), "ult" },
{ MOD_TYPE_MOD, UL_("Grave Composer"), "wow" },
{ MOD_TYPE_MOD, UL_("Mod's Grave"), "wow" },
// converted formats (no MODTYPE)
{ MOD_TYPE_NONE, UL_("General Digital Music"), "gdm" },
{ MOD_TYPE_NONE, UL_("Un4seen MO3"), "mo3" },

View File

@ -255,6 +255,7 @@ void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharse
cueChunk.ReadStruct(cuePoint);
sample.cues[i] = cuePoint.position;
}
std::fill(std::begin(sample.cues) + numPoints, std::end(sample.cues), MAX_SAMPLE_LENGTH);
}
// Read MPT extra info
@ -598,15 +599,24 @@ void WAVWriter::WriteLoopInformation(const ModSample &sample)
// Write a sample's cue points to the file.
void WAVWriter::WriteCueInformation(const ModSample &sample)
{
StartChunk(RIFFChunk::idcue_);
uint32 numMarkers = 0;
for(const auto cue : sample.cues)
{
Write(mpt::as_le(static_cast<uint32>(CountOf(sample.cues))));
if(cue < sample.nLength)
numMarkers++;
}
for(uint32 i = 0; i < CountOf(sample.cues); i++)
StartChunk(RIFFChunk::idcue_);
Write(mpt::as_le(numMarkers));
uint32 i = 0;
for(const auto cue : sample.cues)
{
WAVCuePoint cuePoint;
cuePoint.ConvertToWAV(i, sample.cues[i]);
Write(cuePoint);
if(cue < sample.nLength)
{
WAVCuePoint cuePoint;
cuePoint.ConvertToWAV(i++, cue);
Write(cuePoint);
}
}
}

View File

@ -302,16 +302,16 @@ struct AMEnvelope
struct EnvPoint
{
uint16le tick;
uint16le value;
int16le value;
};
uint16le flags;
uint8le numPoints; // actually, it's num. points - 1, and 0xFF if there is no envelope
uint8le numPoints; // actually, it's num. points - 1, and 0xFF if there is no envelope
uint8le sustainPoint;
uint8le loopStart;
uint8le loopEnd;
EnvPoint values[10];
uint16le fadeout; // why is this here? it's only needed for the volume envelope...
uint16le fadeout; // why is this here? it's only needed for the volume envelope...
// Convert envelope data to OpenMPT's internal format.
void ConvertToMPT(InstrumentEnvelope &mptEnv, EnvelopeType envType) const
@ -326,6 +326,23 @@ struct AMEnvelope
mptEnv.nLoopStart = loopStart;
mptEnv.nLoopEnd = loopEnd;
int32 scale = 0, offset = 0;
switch(envType)
{
case ENV_VOLUME: // 0....32767
default:
scale = 32767 / ENVELOPE_MAX;
break;
case ENV_PITCH: // -4096....4096
scale = 8192 / ENVELOPE_MAX;
offset = 4096;
break;
case ENV_PANNING: // -32768...32767
scale = 65536 / ENVELOPE_MAX;
offset = 32768;
break;
}
for(uint32 i = 0; i < mptEnv.size(); i++)
{
mptEnv[i].tick = values[i].tick >> 4;
@ -334,21 +351,9 @@ struct AMEnvelope
else if(mptEnv[i].tick < mptEnv[i - 1].tick)
mptEnv[i].tick = mptEnv[i - 1].tick + 1;
const uint16 val = values[i].value;
switch(envType)
{
case ENV_VOLUME: // 0....32767
default:
mptEnv[i].value = (uint8)((val + 1) >> 9);
break;
case ENV_PITCH: // -4096....4096
mptEnv[i].value = (uint8)((((int16)val) + 0x1001) >> 7);
break;
case ENV_PANNING: // -32768...32767
mptEnv[i].value = (uint8)((((int16)val) + 0x8001) >> 10);
break;
}
Limit(mptEnv[i].value, uint8(ENVELOPE_MIN), uint8(ENVELOPE_MAX));
int32 val = values[i].value + offset;
val = (val + scale / 2) / scale;
mptEnv[i].value = static_cast<EnvelopeNode::value_t>(std::clamp(val, int32(ENVELOPE_MIN), int32(ENVELOPE_MAX)));
}
mptEnv.dwFlags.set(ENV_ENABLED, (flags & AMFFEnvelope::envEnabled) != 0);
@ -825,7 +830,7 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags)
{
AMFFSampleHeader sampleHeader;
if(m_nSamples + 1 >= MAX_SAMPLES || !chunk.ReadStruct(sampleHeader))
if(!CanAddMoreSamples() || !chunk.ReadStruct(sampleHeader))
{
continue;
}
@ -887,7 +892,7 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags)
for(auto sampleChunk : sampleChunks)
{
if(sampleChunk.ReadUint32LE() != AMFFRiffChunk::idAS__ || m_nSamples + 1 >= MAX_SAMPLES)
if(sampleChunk.ReadUint32LE() != AMFFRiffChunk::idAS__ || !CanAddMoreSamples())
{
continue;
}

View File

@ -170,7 +170,7 @@ constexpr CModSpecifications xm_ =
false, // Doesn't have artist name
false, // Doesn't have default resampling
false, // Integer tempo
" 0123456789ABCDRFFTE???GHK??XPL????????????", // Supported Effects
" 0123456789ABCDRFFTE???GHK??XPL???????????W", // Supported Effects
" vpcdabuhlrg????", // Supported Volume Column commands
};
@ -218,7 +218,7 @@ constexpr CModSpecifications xmEx_ =
true, // Has artist name
false, // Doesn't have default resampling
false, // Integer tempo
" 0123456789ABCDRFFTE???GHK?YXPLZ\\?#????????", // Supported Effects
" 0123456789ABCDRFFTE???GHK?YXPLZ\\?#???????W", // Supported Effects
" vpcdabuhlrg????", // Supported Volume Column commands
};

View File

@ -112,15 +112,7 @@ bool CPatternContainer::IsPatternEmpty(const PATTERNINDEX nPat) const
void CPatternContainer::ResizeArray(const PATTERNINDEX newSize)
{
if(Size() <= newSize)
{
m_Patterns.resize(newSize, CPattern(*this));
} else
{
for(PATTERNINDEX i = Size(); i > newSize; i--)
Remove(i - 1);
m_Patterns.resize(newSize, CPattern(*this));
}
m_Patterns.resize(newSize, CPattern(*this));
}

View File

@ -2000,7 +2000,7 @@ static MPT_NOINLINE void TestMisc2()
VERIFY_EQUAL(mpt::crc32_ogg(std::string("123456789")), 0x89a1897fu);
// Check floating-point accuracy in TransposeToFrequency
int32 transposeToFrequency[] =
static constexpr int32 transposeToFrequency[] =
{
5, 5, 5, 5,
31, 32, 33, 34,
@ -2014,8 +2014,23 @@ static MPT_NOINLINE void TestMisc2()
int freqIndex = 0;
for(int32 transpose = -128; transpose < 128; transpose += 32)
{
for(int32 finetune = -128; finetune < 128; finetune += 64, freqIndex++)
VERIFY_EQUAL_EPS(transposeToFrequency[freqIndex], static_cast<int32>(ModSample::TransposeToFrequency(transpose, finetune)), 1);
{
const auto freq = ModSample::TransposeToFrequency(transpose, finetune);
VERIFY_EQUAL_EPS(transposeToFrequency[freqIndex], static_cast<int32>(freq), 1);
if(transpose >= -96)
{
// Verify transpose+finetune <-> frequency roundtrip
// (not for transpose = -128 because it would require fractional precision that we don't have here)
ModSample smp;
smp.nC5Speed = freq;
smp.FrequencyToTranspose();
smp.TransposeToFrequency();
VERIFY_EQUAL(freq, smp.nC5Speed);
}
}
}
{
ModSample smp;
@ -3043,7 +3058,7 @@ static void TestLoadXMFile(const CSoundFile &sndFile)
// Global Variables
VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module");
VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.at(0), 'O');
VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite");
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultTempo, TEMPO(139, 0));
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5);
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
@ -3243,7 +3258,7 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile)
// Global Variables
VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module_____________X");
VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.at(0), 'O');
VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite");
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultTempo, TEMPO(139, 999));
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5);
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
@ -3271,7 +3286,7 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile)
// Edit history
VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size() > 0, true);
const FileHistory &fh = sndFile.GetFileHistory().at(0);
const FileHistory &fh = sndFile.GetFileHistory().front();
VERIFY_EQUAL_NONCONT(fh.loadDate.tm_year, 111);
VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mon, 5);
VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mday, 14);
@ -4078,21 +4093,21 @@ static MPT_NOINLINE void TestEditing()
sndFile.GetSample(2).AllocateSample();
modDoc->ReArrangeSamples({ 2, SAMPLEINDEX_INVALID, 1 });
VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).HasSampleData(), true);
VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).filename, std::string("2"));
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], std::string("2"));
VERIFY_EQUAL_NONCONT(sndFile.GetSample(2).filename, std::string());
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], std::string());
VERIFY_EQUAL_NONCONT(sndFile.GetSample(3).filename, std::string("1"));
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], std::string("1"));
VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).filename, "2");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], "2");
VERIFY_EQUAL_NONCONT(sndFile.GetSample(2).filename, "");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], "");
VERIFY_EQUAL_NONCONT(sndFile.GetSample(3).filename, "1");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], "1");
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);
// Convert / rearrange instruments
modDoc->ConvertSamplesToInstruments();
modDoc->ReArrangeInstruments({ INSTRUMENTINDEX_INVALID, 2, 1, 3 });
VERIFY_EQUAL_NONCONT(sndFile.Instruments[1]->name, std::string());
VERIFY_EQUAL_NONCONT(sndFile.Instruments[2]->name, std::string());
VERIFY_EQUAL_NONCONT(sndFile.Instruments[3]->name, std::string("2"));
VERIFY_EQUAL_NONCONT(sndFile.Instruments[4]->name, std::string("1"));
VERIFY_EQUAL_NONCONT(sndFile.Instruments[1]->name, "");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[2]->name, "");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[3]->name, "2");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[4]->name, "1");
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 4);
modDoc->ConvertInstrumentsToSamples();
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);