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_SVNVERSION=13775
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.2 MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.3
MPT_SVNDATE=2020-08-30T13:42:32.941871Z MPT_SVNDATE=2020-10-25T14:02:16.624929Z

View File

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

View File

@ -563,10 +563,7 @@ namespace FileReader
static_assert(mpt::is_binary_safe<T>::value); static_assert(mpt::is_binary_safe<T>::value);
if(f.CanRead(sizeof(destArray))) if(f.CanRead(sizeof(destArray)))
{ {
for(auto &element : destArray) f.ReadRaw(mpt::as_raw_memory(destArray));
{
Read(f, element);
}
return true; return true;
} else } else
{ {
@ -584,10 +581,7 @@ namespace FileReader
static_assert(mpt::is_binary_safe<T>::value); static_assert(mpt::is_binary_safe<T>::value);
if(f.CanRead(sizeof(destArray))) if(f.CanRead(sizeof(destArray)))
{ {
for(auto &element : destArray) f.ReadRaw(mpt::as_raw_memory(destArray));
{
Read(f, element);
}
return true; return true;
} else } else
{ {
@ -606,10 +600,7 @@ namespace FileReader
destVector.resize(destSize); destVector.resize(destSize);
if(f.CanRead(sizeof(T) * destSize)) if(f.CanRead(sizeof(T) * destSize))
{ {
for(auto &element : destVector) f.ReadRaw(mpt::as_raw_memory(destVector));
{
Read(f, element);
}
return true; return true;
} else } else
{ {
@ -679,18 +670,9 @@ namespace FileReader
typename TFileCursor::off_t avail = f.GetRaw(bytes, sizeof(bytes)); typename TFileCursor::off_t avail = f.GetRaw(bytes, sizeof(bytes));
typename TFileCursor::off_t readPos = 1; typename TFileCursor::off_t readPos = 1;
size_t writtenBits = 0;
uint8 b = mpt::byte_cast<uint8>(bytes[0]); uint8 b = mpt::byte_cast<uint8>(bytes[0]);
target = (b & 0x7F); target = (b & 0x7F);
size_t writtenBits = static_cast<size_t>(mpt::bit_width(target)); // Bits used in the most significant byte
// 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;
}
}
while(readPos < avail && (b & 0x80) != 0) 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; const Offtype nRawEntrySize = oStrm.tellp() - posBeforeWrite;
if (nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > std::numeric_limits<DataSize>::max()) MPT_MAYBE_CONSTANT_IF(nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > std::numeric_limits<DataSize>::max())
{ AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE); return; } {
AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE);
return;
}
if(GetFlag(RwfRMapHasSize) && (nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > (std::numeric_limits<DataSize>::max() >> 2))) if(GetFlag(RwfRMapHasSize) && (nRawEntrySize < 0 || static_cast<uint64>(nRawEntrySize) > (std::numeric_limits<DataSize>::max() >> 2)))
{ AddWriteNote(SNW_DATASIZETYPE_OVERFLOW); return; } { 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; const Offtype rawEndOfHdrData = iStrm.tellg() - m_posStart;
if (rawEndOfHdrData < 0 || static_cast<uint64>(rawEndOfHdrData) > std::numeric_limits<RposType>::max()) MPT_MAYBE_CONSTANT_IF(rawEndOfHdrData < 0 || static_cast<uint64>(rawEndOfHdrData) > std::numeric_limits<RposType>::max())
{ AddReadNote(SNR_INSUFFICIENT_RPOSTYPE); return; } {
AddReadNote(SNR_INSUFFICIENT_RPOSTYPE);
return;
}
m_rposEndofHdrData = static_cast<RposType>(rawEndOfHdrData); m_rposEndofHdrData = static_cast<RposType>(rawEndOfHdrData);
m_rposMapBegin = (GetFlag(RwfRwHasMap)) ? static_cast<RposType>(tempU64) : m_rposEndofHdrData; 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. // Version definitions. The only thing that needs to be changed when changing version number.
#define VER_MAJORMAJOR 1 #define VER_MAJORMAJOR 1
#define VER_MAJOR 29 #define VER_MAJOR 29
#define VER_MINOR 03 #define VER_MINOR 05
#define VER_MINORMINOR 00 #define VER_MINORMINOR 00
OPENMPT_NAMESPACE_END OPENMPT_NAMESPACE_END

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "${0%/*}" 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_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 rm $AFL_FILENAME
wget $AFL_URL || exit wget $AFL_URL || exit

View File

@ -5,6 +5,31 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This For fully detailed change log, please see the source repository directly. This
is just a high-level summary. 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) ### libopenmpt 0.5.2 (2020-08-30)
* [**Change**] `Makefile` `CONFIG=emscripten` now supports * [**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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_current_speed( speed ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_current_tempo( tempo ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_tempo_factor( factor ); mod_ext->impl->set_tempo_factor( factor );
@ -1597,7 +1597,7 @@ int set_tempo_factor( openmpt_module_ext * mod_ext, double factor ) {
} }
return 0; return 0;
} }
double get_tempo_factor( openmpt_module_ext * mod_ext ) { static double get_tempo_factor( openmpt_module_ext * mod_ext ) {
try { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_tempo_factor(); return mod_ext->impl->get_tempo_factor();
@ -1606,7 +1606,7 @@ double get_tempo_factor( openmpt_module_ext * mod_ext ) {
} }
return 0.0; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_pitch_factor( factor ); mod_ext->impl->set_pitch_factor( factor );
@ -1616,7 +1616,7 @@ int set_pitch_factor( openmpt_module_ext * mod_ext, double factor ) {
} }
return 0; return 0;
} }
double get_pitch_factor( openmpt_module_ext * mod_ext ) { static double get_pitch_factor( openmpt_module_ext * mod_ext ) {
try { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_pitch_factor(); return mod_ext->impl->get_pitch_factor();
@ -1625,7 +1625,7 @@ double get_pitch_factor( openmpt_module_ext * mod_ext ) {
} }
return 0.0; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_global_volume( volume ); mod_ext->impl->set_global_volume( volume );
@ -1635,7 +1635,7 @@ int set_global_volume( openmpt_module_ext * mod_ext, double volume ) {
} }
return 0; return 0;
} }
double get_global_volume( openmpt_module_ext * mod_ext ) { static double get_global_volume( openmpt_module_ext * mod_ext ) {
try { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_global_volume(); return mod_ext->impl->get_global_volume();
@ -1644,7 +1644,7 @@ double get_global_volume( openmpt_module_ext * mod_ext ) {
} }
return 0.0; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_channel_volume( channel, volume ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_channel_volume( channel ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_channel_mute_status( channel, mute ? true : false ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_channel_mute_status( channel ) ? 1 : 0; 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->set_instrument_mute_status( instrument, mute ? true : false ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->get_instrument_mute_status( instrument ) ? 1 : 0; 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
return mod_ext->impl->play_note( instrument, note, volume, panning ); 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; 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 { try {
openmpt::interface::check_soundfile( mod_ext ); openmpt::interface::check_soundfile( mod_ext );
mod_ext->impl->stop_note( channel ); 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 ) { bool is_extension_supported( const std::string & extension ) {
return openmpt::module_impl::is_extension_supported( 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 ); return openmpt::module_impl::is_extension_supported( extension );
} }

View File

@ -1106,11 +1106,11 @@ double module_impl::set_position_seconds( double seconds ) {
} else { } else {
subsong = &subsongs[m_current_subsong]; 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->m_PlayState.m_nCurrentOrder = t.lastOrder;
m_sndFile->SetCurrentOrder( t.lastOrder ); m_sndFile->SetCurrentOrder( t.lastOrder );
m_sndFile->m_PlayState.m_nNextRow = t.lastRow; 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; return m_currentPositionSeconds;
} }
double module_impl::set_position_order_row( std::int32_t order, std::int32_t row ) { 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::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() ); 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; const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp;
retval.reserve( subsongs.size() );
for ( const auto & subsong : subsongs ) { 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; return retval;
} }

View File

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

View File

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

View File

@ -190,7 +190,7 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags)
continue; continue;
} }
const ModCommand::COMMAND effTrans[] = static constexpr ModCommand::COMMAND effTrans[] =
{ {
CMD_PORTAMENTOUP, // Slide up (param * 80) Hz on every tick CMD_PORTAMENTOUP, // Slide up (param * 80) Hz on every tick
CMD_PORTAMENTODOWN, // Slide down (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++; patNum++;
} else if(!memcmp(chunkHeader.magic, "INST", 4) && GetNumSamples() < SAMPLEINDEX(MAX_SAMPLES - 1)) } else if(!memcmp(chunkHeader.magic, "INST", 4) && CanAddMoreSamples())
{ {
// Read sample // Read sample
m_nSamples++; m_nSamples++;

View File

@ -118,16 +118,12 @@ struct MMDSong
MMD0Song GetMMD0Song() const MMD0Song GetMMD0Song() const
{ {
static_assert(sizeof(MMD0Song) == sizeof(song)); static_assert(sizeof(MMD0Song) == sizeof(song));
MMD0Song result; return mpt::bit_cast<MMD0Song>(song);
std::memcpy(&result, song, sizeof(result));
return result;
} }
MMD2Song GetMMD2Song() const MMD2Song GetMMD2Song() const
{ {
static_assert(sizeof(MMD2Song) == sizeof(song)); static_assert(sizeof(MMD2Song) == sizeof(song));
MMD2Song result; return mpt::bit_cast<MMD2Song>(song);
std::memcpy(&result, song, sizeof(result));
return result;
} }
uint16be defaultTempo; uint16be defaultTempo;
int8be playTranspose; // The global play transpose value for current song 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; MMD0FileHeader fileHeader;
MMD0Exp expData; MMD0Exp expData;
MMDSong songHeader; MMDSong songHeader;
file.Rewind(); file.Rewind();
uint32 songOffset = 0;
MEDReadNextSong(file, fileHeader, expData, songHeader); 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; CHANNELINDEX numChannels = 4;
// Scan patterns for max number of channels // Scan patterns for max number of channels
for(SEQUENCEINDEX song = 0; song < numSongs; song++) for(SEQUENCEINDEX song = 0; song < numSongs; song++)
{ {
const PATTERNINDEX numPatterns = songHeader.numBlocks; const PATTERNINDEX numPatterns = songHeader.numBlocks;
if(songHeader.numSamples > 63 || numPatterns > 0x7FFF) if(songHeader.numSamples > 63 || numPatterns > 0x7FFF)
return 0; return {};
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) 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())); 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; break;
}
songOffset = expData.nextModOffset;
MEDReadNextSong(file, fileHeader, expData, songHeader); MEDReadNextSong(file, fileHeader, expData, songHeader);
} }
return numChannels; return {numChannels, numSongs};
} }
@ -680,9 +681,10 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(expData); file.ReadStruct(expData);
} }
m_nChannels = MEDScanNumChannels(file, version); const auto [numChannels, numSongs] = MEDScanNumChannels(file, version);
if(m_nChannels < 1 || m_nChannels > MAX_BASECHANNELS) if(numChannels < 1 || numChannels > MAX_BASECHANNELS)
return false; return false;
m_nChannels = numChannels;
// Start with the instruments, as those are shared between songs // 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(); file.Rewind();
PATTERNINDEX basePattern = 0; PATTERNINDEX basePattern = 0;
for(SEQUENCEINDEX song = 0; song < numSongs; song++) for(SEQUENCEINDEX song = 0; song < numSongs; song++)

View File

@ -292,7 +292,8 @@ uint32 CSoundFile::MapMidiInstrument(uint8 program, uint16 bank, uint8 midiChann
if (program + 1 == p->nMidiProgram && bank + 1 == p->wMidiBank && p->nMidiDrumKey == 0) return i; 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); pIns = AllocateInstrument(m_nInstruments + 1);
if(pIns == nullptr) if(pIns == nullptr)
@ -347,7 +348,7 @@ struct MThd
uint16be division; // Delta timing value: positive = units/beat; negative = smpte compatible units 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; 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_nDefaultSpeed = fileHeader.defaultSpeed ? fileHeader.defaultSpeed : 6;
m_nDefaultTempo.Set(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125, 0); m_nDefaultTempo.Set(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125, 0);
mpt::ustring originalFormatType;
mpt::ustring originalFormatName;
if(fileHeader.flags & MO3FileHeader::isIT) if(fileHeader.flags & MO3FileHeader::isIT)
{
SetType(MOD_TYPE_IT); SetType(MOD_TYPE_IT);
originalFormatType = U_("it"); else if(fileHeader.flags & MO3FileHeader::isS3M)
originalFormatName = U_("Impulse Tracker");
} else if(fileHeader.flags & MO3FileHeader::isS3M)
{
SetType(MOD_TYPE_S3M); SetType(MOD_TYPE_S3M);
originalFormatType = U_("s3m"); else if(fileHeader.flags & MO3FileHeader::isMOD)
originalFormatName = U_("ScreamTracker 3");
} else if(fileHeader.flags & MO3FileHeader::isMOD)
{
SetType(MOD_TYPE_MOD); SetType(MOD_TYPE_MOD);
originalFormatType = U_("mod"); else if(fileHeader.flags & MO3FileHeader::isMTM)
originalFormatName = U_("Generic MOD");
} else if(fileHeader.flags & MO3FileHeader::isMTM)
{
SetType(MOD_TYPE_MTM); SetType(MOD_TYPE_MTM);
originalFormatType = U_("mtm"); else
originalFormatName = U_("MultiTracker");
} else
{
SetType(MOD_TYPE_XM); SetType(MOD_TYPE_XM);
originalFormatType = U_("xm");
originalFormatName = U_("FastTracker 2");
}
if(fileHeader.flags & MO3FileHeader::linearSlides) if(fileHeader.flags & MO3FileHeader::linearSlides)
m_SongFlags.set(SONG_LINEARSLIDES); m_SongFlags.set(SONG_LINEARSLIDES);
@ -1828,7 +1810,6 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
mpt::ustring madeWithTracker; mpt::ustring madeWithTracker;
uint16 cwtv = 0; uint16 cwtv = 0;
uint16 cmwt = 0; uint16 cmwt = 0;
MPT_UNUSED_VARIABLE(cmwt);
while(musicChunk.CanRead(8)) while(musicChunk.CanRead(8))
{ {
uint32 id = musicChunk.ReadUint32LE(); uint32 id = musicChunk.ReadUint32LE();
@ -1926,18 +1907,20 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
if(fileHeader.flags & MO3FileHeader::modplugMode) if(fileHeader.flags & MO3FileHeader::modplugMode)
{ {
// Apply some old ModPlug (mis-)behaviour // Apply some old ModPlug (mis-)behaviour
if(!m_dwLastSavedWithVersion)
{
// 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++) for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++)
{ {
if(ModInstrument *ins = Instruments[i]) if(ModInstrument *ins = Instruments[i])
{ {
// Fix pitch / filter envelope being shortened by one tick // Fix pitch / filter envelope being shortened by one tick (for files before v1.20)
if(m_dwLastSavedWithVersion < MPT_V("1.20.00.00"))
ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType()); ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType());
// Fix excessive pan swing range // Fix excessive pan swing range (for files before v1.26)
if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00"))
ins->nPanSwing = (ins->nPanSwing + 3) / 4u; ins->nPanSwing = (ins->nPanSwing + 3) / 4u;
} }
} }
}
if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00"))
{ {
m_playBehaviour.reset(kITOffset); m_playBehaviour.reset(kITOffset);
@ -1956,8 +1939,39 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
m_modFormat.formatName = mpt::format(U_("Un4seen MO3 v%1"))(version); m_modFormat.formatName = mpt::format(U_("Un4seen MO3 v%1"))(version);
m_modFormat.type = U_("mo3"); 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); m_modFormat.madeWithTracker = std::move(madeWithTracker);
if(m_dwLastSavedWithVersion) if(m_dwLastSavedWithVersion)
m_modFormat.charset = mpt::Charset::Windows1252; m_modFormat.charset = mpt::Charset::Windows1252;

View File

@ -61,6 +61,7 @@ void CSoundFile::ConvertModCommand(ModCommand &m)
case 'P' - 55: m.command = CMD_PANNINGSLIDE; break; case 'P' - 55: m.command = CMD_PANNINGSLIDE; break;
case 'R' - 55: m.command = CMD_RETRIG; break; case 'R' - 55: m.command = CMD_RETRIG; break;
case 'T' - 55: m.command = CMD_TREMOR; 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 'X' - 55: m.command = CMD_XFINEPORTAUPDOWN; break;
case 'Y' - 55: m.command = CMD_PANBRELLO; break; //34 case 'Y' - 55: m.command = CMD_PANBRELLO; break; //34
case 'Z' - 55: m.command = CMD_MIDI; break; //35 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_PANNINGSLIDE: command = 'P' - 55; break;
case CMD_RETRIG: command = 'R' - 55; break; case CMD_RETRIG: command = 'R' - 55; break;
case CMD_TREMOR: command = 'T' - 55; break; case CMD_TREMOR: command = 'T' - 55; break;
case CMD_DUMMY: command = 'W' - 55; break;
case CMD_XFINEPORTAUPDOWN: command = 'X' - 55; 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. 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... param = 0; // Don't set command to 0 to indicate that there *was* some X command here...
@ -342,6 +344,9 @@ struct MODSampleHeader
MPT_BINARY_STRUCT(MODSampleHeader, 30) 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 // Synthesized StarTrekker instruments
struct AMInstrument struct AMInstrument
{ {
@ -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. // 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 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) 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 patternStartOffset = file.GetPosition();
const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset; 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. // Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files.
// WOW files should 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; numChannels = 8;
} else if(numPatterns != officialPatterns && numChannels == 4 && !checkForWOW) file.Seek(patternStartOffset);
} else if(numPatterns != officialPatterns && numChannels == 4 && !wowSampleLen)
{ {
// Fix SoundTracker modules where "hidden" patterns should be ignored. // Fix SoundTracker modules where "hidden" patterns should be ignored.
// razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b) // 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. // 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. // 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); file.Seek(patternStartOffset + officialPatterns * 1024);
int illegalBytes = 0; if(!ValidateMODPatternData(file, 64, true))
for(int i = 0; i < 256; i++)
{
const auto data = file.ReadArray<uint8, 4>();
if(data[0] & 0xE0)
{
illegalBytes++;
if(illegalBytes > 64)
{
numPatterns = officialPatterns; numPatterns = officialPatterns;
break;
}
}
}
file.Seek(patternStartOffset); file.Seek(patternStartOffset);
} }
@ -724,7 +749,7 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result)
result.madeWithTracker = UL_("Generic MOD-compatible Tracker"); result.madeWithTracker = UL_("Generic MOD-compatible Tracker");
result.isGenericMultiChannel = true; result.isGenericMultiChannel = true;
result.numChannels = magic[0] - '0'; result.numChannels = magic[0] - '0';
} else if(magic[0] >= '1' && magic[0] <= '9' && magic[1]>='0' && magic[1] <= '9' } else if(magic[0] >= '1' && magic[0] <= '9' && magic[1] >= '0' && magic[1] <= '9'
&& (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2))) && (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2)))
{ {
// xxCN / xxCH - Many trackers // xxCN / xxCH - Many trackers
@ -819,13 +844,14 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
const bool isMdKd = IsMagic(magic, "M.K."); const bool isMdKd = IsMagic(magic, "M.K.");
// Adjust finetune values for modules saved with "His Master's Noisetracker" // Adjust finetune values for modules saved with "His Master's Noisetracker"
const bool isHMNT = IsMagic(magic, "M&K!") || IsMagic(magic, "FEST"); const bool isHMNT = IsMagic(magic, "M&K!") || IsMagic(magic, "FEST");
bool maybeWOW = isMdKd;
// Reading song title // Reading song title
file.Seek(0); file.Seek(0);
file.ReadString<mpt::String::spacePadded>(m_songName, 20); file.ReadString<mpt::String::spacePadded>(m_songName, 20);
// Load Sample Headers // Load Sample Headers
SmpLength totalSampleLen = 0; SmpLength totalSampleLen = 0, wowSampleLen = 0;
m_nSamples = 31; m_nSamples = 31;
uint32 invalidBytes = 0; uint32 invalidBytes = 0;
for(SAMPLEINDEX smp = 1; smp <= 31; smp++) for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
@ -835,15 +861,22 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
totalSampleLen += Samples[smp].nLength; totalSampleLen += Samples[smp].nLength;
if(isHMNT) if(isHMNT)
{
Samples[smp].nFineTune = -static_cast<int8>(sampleHeader.finetune << 3); Samples[smp].nFineTune = -static_cast<int8>(sampleHeader.finetune << 3);
} else if(Samples[smp].nLength > 65535) else if(Samples[smp].nLength > 65535)
{
isNoiseTracker = false; isNoiseTracker = false;
}
if(sampleHeader.length && !sampleHeader.loopLength) if(sampleHeader.length && !sampleHeader.loopLength)
{
hasRepLen0 = true; 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. // 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.ReadStruct(fileHeader);
file.Skip(4); // Magic bytes (we already parsed these) file.Skip(4); // Magic bytes (we already parsed these)
if(fileHeader.restartPos > 0)
maybeWOW = false;
if(!maybeWOW)
wowSampleLen = 0;
ReadOrderFromArray(Order(), fileHeader.orderList); ReadOrderFromArray(Order(), fileHeader.orderList);
ORDERINDEX realOrders = fileHeader.numOrders; 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) // Get number of patterns (including some order list sanity checks)
PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, isMdKd); PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, wowSampleLen);
if(isMdKd && GetNumChannels() == 8) 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"); modMagicResult.madeWithTracker = UL_("Mod's Grave");
isGenericMultiChannel = true;
} }
if(isFLT8) if(isFLT8)
@ -1086,7 +1125,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
lastInstrument[chn] = m.instr; 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(kMODOutOfRangeNoteDelay);
m_playBehaviour.set(kMODTempoOnSecondTick); m_playBehaviour.set(kMODTempoOnSecondTick);
// Arbitrary threshold for deciding that 8xx effects are only used as sync markers // Arbitrary threshold for deciding that 8xx effects are only used as sync markers
if(maxPanning < 0x20) if(maxPanning < 0x30)
{ {
m_playBehaviour.set(kMODIgnorePanning); m_playBehaviour.set(kMODIgnorePanning);
if(fileHeader.restartPos != 0x7F) 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) // 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]) static uint32 CountInvalidChars(const char (&name)[N])
{ {
uint32 invalidChars = 0; uint32 invalidChars = 0;
@ -1304,8 +1344,6 @@ struct M15FileHeaders
MPT_BINARY_STRUCT(M15FileHeaders, 20 + 15 * 30 + 130) MPT_BINARY_STRUCT(M15FileHeaders, 20 + 15 * 30 + 130)
typedef std::array<uint8, 4> M15PatternData[64][4];
static bool ValidateHeader(const M15FileHeaders &fileHeaders) 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> template <typename TFileReader>
static bool ValidateFirstM15Pattern(TFileReader &file) static bool ValidateFirstM15Pattern(TFileReader &file)
{ {
M15PatternData patternData; // threshold is chosen as: [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much]
if(!file.ReadArray(patternData)) return ValidateMODPatternData(file, 512 / 64 * 2, false);
{
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;
} }
@ -1419,7 +1429,7 @@ CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderM15(MemoryFileReader file, co
{ {
return ProbeFailure; return ProbeFailure;
} }
if(!file.CanRead(sizeof(M15PatternData))) if(!file.CanRead(sizeof(MODPatternData)))
{ {
return ProbeWantMoreData; return ProbeWantMoreData;
} }
@ -1496,7 +1506,7 @@ bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(fileHeader); file.ReadStruct(fileHeader);
ReadOrderFromArray(Order(), fileHeader.orderList); 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 // Most likely just a file with lots of NULs at the start
if(fileHeader.restartPos == 0 && fileHeader.numOrders == 0 && numPatterns <= 1) 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(); bool patternInUse = std::find(Order().cbegin(), Order().cend(), pat) != Order().cend();
uint8 numDxx = 0; uint8 numDxx = 0;
uint8 emptyCmds = 0; uint8 emptyCmds = 0;
M15PatternData patternData; MODPatternData patternData;
file.ReadArray(patternData); file.ReadArray(patternData);
if(patternInUse) if(patternInUse)
{ {
illegalBytes += CountIllegalM15PatternBytes(patternData); illegalBytes += CountMalformedMODPatternData(patternData, false);
// Reject files that contain a lot of illegal pattern data. // Reject files that contain a lot of illegal pattern data.
// STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3) // STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3)
// has one illegal byte, so we only reject after an arbitrary threshold has been passed. // 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); Patterns.ResizeArray(numPatterns);
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{ {
M15PatternData patternData; MODPatternData patternData;
file.ReadArray(patternData); file.ReadArray(patternData);
if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))

View File

@ -627,7 +627,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
m.param--; m.param--;
if(m.param < loopList.size()) 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; loopList[m.param].looped = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(loopList[m.param].looped); 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; m.vol = m.param;
} }
// switch to non-looped version of sample and create it if needed // 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; nonLooped[m.instr - 1] = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(nonLooped[m.instr - 1]); m.instr = static_cast<ModCommand::INSTR>(nonLooped[m.instr - 1]);
} }
@ -664,7 +664,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags)
m.param--; m.param--;
if(m.param < loopList.size()) 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; loopList[m.param].nonLooped = ++m_nSamples;
m.instr = static_cast<ModCommand::INSTR>(loopList[m.param].nonLooped); 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); FileReader fileChunk = chunk.ReadChunk(size);
if(GetNumSamples() < MAX_SAMPLES - 1) if(CanAddMoreSamples())
{ {
// Read as sample // Read as sample
if(ReadSampleFromFile(GetNumSamples() + 1, fileChunk, true)) if(ReadSampleFromFile(GetNumSamples() + 1, fileChunk, true))

View File

@ -395,11 +395,12 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags)
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_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0);
m_nSamples = static_cast<SAMPLEINDEX>(file.ReadUint8()); if(SAMPLEINDEX numSamples = file.ReadUint8(); numSamples < MAX_SAMPLES)
if(GetNumSamples() >= MAX_SAMPLES) m_nSamples = numSamples;
else
return false; return false;
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++) 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); ReadOrderFromFile<uint8>(Order(), file, 256, 0xFF, 0xFE);
m_nChannels = file.ReadUint8() + 1; if(CHANNELINDEX numChannels = file.ReadUint8() + 1u; numChannels <= MAX_BASECHANNELS)
PATTERNINDEX numPats = file.ReadUint8() + 1; m_nChannels = numChannels;
else
if(GetNumChannels() > MAX_BASECHANNELS)
return false; return false;
PATTERNINDEX numPats = file.ReadUint8() + 1;
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{ {
ChnSettings[chn].Reset(); 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 // 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) if(at(nLoopEnd).tick - 1 > at(nLoopEnd - 1).tick)
{ {

View File

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

View File

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

View File

@ -26,8 +26,8 @@ class ModSequence: public std::vector<PATTERNINDEX>
friend class ModSequenceSet; friend class ModSequenceSet;
protected: protected:
mpt::ustring m_name; // Sequence name. mpt::ustring m_name; // Sequence name
CSoundFile &m_sndFile; // Associated CSoundFile. CSoundFile &m_sndFile; // Associated CSoundFile
ORDERINDEX m_restartPos = 0; // Restart position when playback of this order ended ORDERINDEX m_restartPos = 0; // Restart position when playback of this order ended
public: public:
@ -128,7 +128,7 @@ class ModSequenceSet
friend void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset); friend void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mpt::Charset defaultCharset);
protected: protected:
std::vector<ModSequence> m_Sequences; // Array of sequences. std::vector<ModSequence> m_Sequences; // Array of sequences
CSoundFile &m_sndFile; CSoundFile &m_sndFile;
SEQUENCEINDEX m_currentSeq = 0; // Index of current sequence. 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) // 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 #ifdef MODPLUG_TRACKER
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_MT2) static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_MT2;
#else #else
#define GLOBALVOL_7BIT_FORMATS_EXT (MOD_TYPE_NONE) static constexpr auto GLOBALVOL_7BIT_FORMATS_EXT = MOD_TYPE_NONE;
#endif // MODPLUG_TRACKER #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. // 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; retval.startRow = target.startRow;
// Are we trying to reach a certain pattern position? // 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; const bool adjustSamplePos = (adjustMode & eAdjustSamplePositions) == eAdjustSamplePositions;
SEQUENCEINDEX sequence = target.sequence; SEQUENCEINDEX sequence = target.sequence;
@ -300,14 +300,6 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
for (;;) 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_nRow = playState.m_nNextRow;
playState.m_nCurrentOrder = playState.m_nNextOrder; playState.m_nCurrentOrder = playState.m_nNextOrder;
@ -322,6 +314,13 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
playState.m_nCurrentOrder = ++playState.m_nNextOrder; 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 // Check if pattern is valid
playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex(); playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex();
bool positionJumpOnThisRow = false, positionJumpRightOfPatternLoop = false; bool positionJumpOnThisRow = false, positionJumpRightOfPatternLoop = false;
@ -352,7 +351,12 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
playState.m_nNextOrder = playState.m_nCurrentOrder; playState.m_nNextOrder = playState.m_nCurrentOrder;
if((!Patterns.IsValidPat(playState.m_nPattern)) && visitedRows.IsVisited(playState.m_nCurrentOrder, 0, true)) 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. // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break; 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 there isn't even a tune, we should probably stop here.
if(playState.m_nCurrentOrder == orderList.GetRestartPos()) 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. // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break; break;
@ -416,7 +420,12 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
if(visitedRows.IsVisited(playState.m_nCurrentOrder, playState.m_nRow, true)) 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. // We aren't searching for a specific row, or we couldn't find any more unvisited rows.
break; 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. // 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); const ModCommand *p = Patterns[playState.m_nPattern].GetpModCommand(playState.m_nRow, 0);
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++) for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++)
{ {
@ -1033,7 +1043,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
SampleOffset(chn, offset); SampleOffset(chn, offset);
} else if(m.command == CMD_OFFSETPERCENTAGE) } 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) } else if(m.command == CMD_REVERSEOFFSET && chn.pModSample != nullptr)
{ {
memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far 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)); 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++) 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)) if(m_playBehaviour[kITPatternLoopTargetReset] || (GetType() == MOD_TYPE_S3M))
{ {
memory.chnSettings[nChn].patLoop = memory.elapsedTime; 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.lastOrder = playState.m_nCurrentOrder;
retval.lastRow = playState.m_nRow; retval.lastRow = playState.m_nRow;
@ -1344,11 +1355,11 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
{ {
Order.SetSequence(sequence); Order.SetSequence(sequence);
} }
visitedSongRows.MoveVisitedRowsFrom(visitedRows);
} }
if(adjustMode & (eAdjust | eAdjustOnlyVisitedRows))
visitedSongRows.MoveVisitedRowsFrom(visitedRows);
return results; return results;
} }
@ -3281,7 +3292,7 @@ bool CSoundFile::ProcessEffects()
case CMD_OFFSETPERCENTAGE: case CMD_OFFSETPERCENTAGE:
if(triggerNote) if(triggerNote)
{ {
SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 255)); SampleOffset(chn, Util::muldiv_unsigned(chn.nLength, param, 256));
} }
break; break;
@ -5812,7 +5823,8 @@ TEMPO CSoundFile::ConvertST2Tempo(uint8 tempo)
static constexpr uint32 st2MixingRate = 23863; // Highest possible setting in ST2 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. // 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) if(samplesPerTick <= 0)
samplesPerTick += 65536; samplesPerTick += 65536;
return TEMPO().SetRaw(Util::muldivrfloor(st2MixingRate, 5 * TEMPO::fractFact, samplesPerTick * 2)); 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]; const ModChannel &channel = m_PlayState.Chn[nChn];
PLUGINDEX plugin; 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; plugin = 0;
} else } else

View File

@ -197,6 +197,8 @@ enum enmGetLengthResetMode
eAdjustOnSuccess = 0x02 | eAdjust, 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. // 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, eAdjustSamplePositions = 0x04 | eAdjustOnSuccess,
// Only adjust the visited rows state
eAdjustOnlyVisitedRows = 0x08,
}; };
@ -707,6 +709,9 @@ public:
ORDERINDEX GetCurrentOrder() const { return m_PlayState.m_nCurrentOrder; } ORDERINDEX GetCurrentOrder() const { return m_PlayState.m_nCurrentOrder; }
CHANNELINDEX GetNumChannels() const { return m_nChannels; } 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 #ifndef NO_PLUGINS
IMixPlugin* GetInstrumentPlugin(INSTRUMENTINDEX instr) const; IMixPlugin* GetInstrumentPlugin(INSTRUMENTINDEX instr) const;
#endif #endif

View File

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

View File

@ -93,7 +93,7 @@ static constexpr ModFormatInfo modFormatInfo[] =
{ MOD_TYPE_STM, UL_("ScreamTracker 2"), "stm" }, { MOD_TYPE_STM, UL_("ScreamTracker 2"), "stm" },
{ MOD_TYPE_STP, UL_("Soundtracker Pro II"), "stp" }, { MOD_TYPE_STP, UL_("Soundtracker Pro II"), "stp" },
{ MOD_TYPE_ULT, UL_("UltraTracker"), "ult" }, { 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) // converted formats (no MODTYPE)
{ MOD_TYPE_NONE, UL_("General Digital Music"), "gdm" }, { MOD_TYPE_NONE, UL_("General Digital Music"), "gdm" },
{ MOD_TYPE_NONE, UL_("Un4seen MO3"), "mo3" }, { MOD_TYPE_NONE, UL_("Un4seen MO3"), "mo3" },

View File

@ -255,6 +255,7 @@ void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharse
cueChunk.ReadStruct(cuePoint); cueChunk.ReadStruct(cuePoint);
sample.cues[i] = cuePoint.position; sample.cues[i] = cuePoint.position;
} }
std::fill(std::begin(sample.cues) + numPoints, std::end(sample.cues), MAX_SAMPLE_LENGTH);
} }
// Read MPT extra info // Read MPT extra info
@ -598,16 +599,25 @@ void WAVWriter::WriteLoopInformation(const ModSample &sample)
// Write a sample's cue points to the file. // Write a sample's cue points to the file.
void WAVWriter::WriteCueInformation(const ModSample &sample) 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)
{
if(cue < sample.nLength)
{ {
WAVCuePoint cuePoint; WAVCuePoint cuePoint;
cuePoint.ConvertToWAV(i, sample.cues[i]); cuePoint.ConvertToWAV(i++, cue);
Write(cuePoint); Write(cuePoint);
} }
}
} }

View File

@ -302,7 +302,7 @@ struct AMEnvelope
struct EnvPoint struct EnvPoint
{ {
uint16le tick; uint16le tick;
uint16le value; int16le value;
}; };
uint16le flags; uint16le flags;
@ -326,6 +326,23 @@ struct AMEnvelope
mptEnv.nLoopStart = loopStart; mptEnv.nLoopStart = loopStart;
mptEnv.nLoopEnd = loopEnd; 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++) for(uint32 i = 0; i < mptEnv.size(); i++)
{ {
mptEnv[i].tick = values[i].tick >> 4; mptEnv[i].tick = values[i].tick >> 4;
@ -334,21 +351,9 @@ struct AMEnvelope
else if(mptEnv[i].tick < mptEnv[i - 1].tick) else if(mptEnv[i].tick < mptEnv[i - 1].tick)
mptEnv[i].tick = mptEnv[i - 1].tick + 1; mptEnv[i].tick = mptEnv[i - 1].tick + 1;
const uint16 val = values[i].value; int32 val = values[i].value + offset;
switch(envType) val = (val + scale / 2) / scale;
{ mptEnv[i].value = static_cast<EnvelopeNode::value_t>(std::clamp(val, int32(ENVELOPE_MIN), int32(ENVELOPE_MAX)));
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));
} }
mptEnv.dwFlags.set(ENV_ENABLED, (flags & AMFFEnvelope::envEnabled) != 0); mptEnv.dwFlags.set(ENV_ENABLED, (flags & AMFFEnvelope::envEnabled) != 0);
@ -825,7 +830,7 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags)
{ {
AMFFSampleHeader sampleHeader; AMFFSampleHeader sampleHeader;
if(m_nSamples + 1 >= MAX_SAMPLES || !chunk.ReadStruct(sampleHeader)) if(!CanAddMoreSamples() || !chunk.ReadStruct(sampleHeader))
{ {
continue; continue;
} }
@ -887,7 +892,7 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags)
for(auto sampleChunk : sampleChunks) for(auto sampleChunk : sampleChunks)
{ {
if(sampleChunk.ReadUint32LE() != AMFFRiffChunk::idAS__ || m_nSamples + 1 >= MAX_SAMPLES) if(sampleChunk.ReadUint32LE() != AMFFRiffChunk::idAS__ || !CanAddMoreSamples())
{ {
continue; continue;
} }

View File

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

View File

@ -112,15 +112,7 @@ bool CPatternContainer::IsPatternEmpty(const PATTERNINDEX nPat) const
void CPatternContainer::ResizeArray(const PATTERNINDEX newSize) void CPatternContainer::ResizeArray(const PATTERNINDEX newSize)
{ {
if(Size() <= newSize)
{
m_Patterns.resize(newSize, CPattern(*this)); m_Patterns.resize(newSize, CPattern(*this));
} else
{
for(PATTERNINDEX i = Size(); i > newSize; i--)
Remove(i - 1);
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); VERIFY_EQUAL(mpt::crc32_ogg(std::string("123456789")), 0x89a1897fu);
// Check floating-point accuracy in TransposeToFrequency // Check floating-point accuracy in TransposeToFrequency
int32 transposeToFrequency[] = static constexpr int32 transposeToFrequency[] =
{ {
5, 5, 5, 5, 5, 5, 5, 5,
31, 32, 33, 34, 31, 32, 33, 34,
@ -2014,8 +2014,23 @@ static MPT_NOINLINE void TestMisc2()
int freqIndex = 0; int freqIndex = 0;
for(int32 transpose = -128; transpose < 128; transpose += 32) for(int32 transpose = -128; transpose < 128; transpose += 32)
{
for(int32 finetune = -128; finetune < 128; finetune += 64, freqIndex++) 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; ModSample smp;
@ -3043,7 +3058,7 @@ static void TestLoadXMFile(const CSoundFile &sndFile)
// Global Variables // Global Variables
VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module"); 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_nDefaultTempo, TEMPO(139, 0));
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5);
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
@ -3243,7 +3258,7 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile)
// Global Variables // Global Variables
VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module_____________X"); 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_nDefaultTempo, TEMPO(139, 999));
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5);
VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128);
@ -3271,7 +3286,7 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile)
// Edit history // Edit history
VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size() > 0, true); 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_year, 111);
VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mon, 5); VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mon, 5);
VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mday, 14); VERIFY_EQUAL_NONCONT(fh.loadDate.tm_mday, 14);
@ -4078,21 +4093,21 @@ static MPT_NOINLINE void TestEditing()
sndFile.GetSample(2).AllocateSample(); sndFile.GetSample(2).AllocateSample();
modDoc->ReArrangeSamples({ 2, SAMPLEINDEX_INVALID, 1 }); modDoc->ReArrangeSamples({ 2, SAMPLEINDEX_INVALID, 1 });
VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).HasSampleData(), true); VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).HasSampleData(), true);
VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).filename, std::string("2")); VERIFY_EQUAL_NONCONT(sndFile.GetSample(1).filename, "2");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], std::string("2")); VERIFY_EQUAL_NONCONT(sndFile.m_szNames[1], "2");
VERIFY_EQUAL_NONCONT(sndFile.GetSample(2).filename, std::string()); VERIFY_EQUAL_NONCONT(sndFile.GetSample(2).filename, "");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], std::string()); VERIFY_EQUAL_NONCONT(sndFile.m_szNames[2], "");
VERIFY_EQUAL_NONCONT(sndFile.GetSample(3).filename, std::string("1")); VERIFY_EQUAL_NONCONT(sndFile.GetSample(3).filename, "1");
VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], std::string("1")); VERIFY_EQUAL_NONCONT(sndFile.m_szNames[3], "1");
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);
// Convert / rearrange instruments // Convert / rearrange instruments
modDoc->ConvertSamplesToInstruments(); modDoc->ConvertSamplesToInstruments();
modDoc->ReArrangeInstruments({ INSTRUMENTINDEX_INVALID, 2, 1, 3 }); modDoc->ReArrangeInstruments({ INSTRUMENTINDEX_INVALID, 2, 1, 3 });
VERIFY_EQUAL_NONCONT(sndFile.Instruments[1]->name, std::string()); VERIFY_EQUAL_NONCONT(sndFile.Instruments[1]->name, "");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[2]->name, std::string()); VERIFY_EQUAL_NONCONT(sndFile.Instruments[2]->name, "");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[3]->name, std::string("2")); VERIFY_EQUAL_NONCONT(sndFile.Instruments[3]->name, "2");
VERIFY_EQUAL_NONCONT(sndFile.Instruments[4]->name, std::string("1")); VERIFY_EQUAL_NONCONT(sndFile.Instruments[4]->name, "1");
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 4); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 4);
modDoc->ConvertInstrumentsToSamples(); modDoc->ConvertInstrumentsToSamples();
VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(37, 4)->instr, 3);