diff --git a/Frameworks/OpenMPT/OpenMPT/build/dist.mk b/Frameworks/OpenMPT/OpenMPT/build/dist.mk index fe859472f..2d038bdac 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/dist.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/dist.mk @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h index 73789667e..ffe4e8be4 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h +++ b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h index 20772bc2c..e6c3118a2 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h +++ b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h @@ -563,10 +563,7 @@ namespace FileReader static_assert(mpt::is_binary_safe::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::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(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(mpt::bit_width(target)); // Bits used in the most significant byte while(readPos < avail && (b & 0x80) != 0) { diff --git a/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.cpp b/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.cpp index 48d223a51..0c82bf4e1 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.cpp @@ -388,8 +388,11 @@ void SsbWrite::OnWroteItem(const ID &id, const Postype& posBeforeWrite) { const Offtype nRawEntrySize = oStrm.tellp() - posBeforeWrite; - if (nRawEntrySize < 0 || static_cast(nRawEntrySize) > std::numeric_limits::max()) - { AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE); return; } + MPT_MAYBE_CONSTANT_IF(nRawEntrySize < 0 || static_cast(nRawEntrySize) > std::numeric_limits::max()) + { + AddWriteNote(SNW_INSUFFICIENT_DATASIZETYPE); + return; + } if(GetFlag(RwfRMapHasSize) && (nRawEntrySize < 0 || static_cast(nRawEntrySize) > (std::numeric_limits::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(rawEndOfHdrData) > std::numeric_limits::max()) - { AddReadNote(SNR_INSUFFICIENT_RPOSTYPE); return; } + MPT_MAYBE_CONSTANT_IF(rawEndOfHdrData < 0 || static_cast(rawEndOfHdrData) > std::numeric_limits::max()) + { + AddReadNote(SNR_INSUFFICIENT_RPOSTYPE); + return; + } m_rposEndofHdrData = static_cast(rawEndOfHdrData); m_rposMapBegin = (GetFlag(RwfRwHasMap)) ? static_cast(tempU64) : m_rposEndofHdrData; diff --git a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h index 0a66669a7..9e0e119c1 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h +++ b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/get-afl.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/get-afl.sh index 7b93d4c33..5cd7ea690 100755 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/get-afl.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/get-afl.sh @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md index 29103f028..d8e3b424a 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp index 07f3d079e..fff8a59fb 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp @@ -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 ); diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp index 6f52ec5a6..956354b72 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp @@ -126,7 +126,7 @@ std::vector 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 ); } diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp index 3968625e6..74f407a31 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp @@ -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( subsong->sequence ), static_cast( subsong->start_order ), static_cast( subsong->start_row ) ) ).back(); + GetLengthType t = m_sndFile->GetLength( m_ctl_seek_sync_samples ? eAdjustSamplePositions : eAdjust, GetLengthTarget( seconds ).StartPos( static_cast( subsong->sequence ), static_cast( subsong->start_order ), static_cast( 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( subsong->sequence ), static_cast( subsong->start_order ), static_cast( 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 module_impl::get_subsong_names() const { std::vector retval; std::unique_ptr subsongs_temp = has_subsongs_inited() ? std::unique_ptr() : std::make_unique( 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( subsong.sequence ) ).GetName() ) ); + const auto & order = m_sndFile->Order( static_cast( 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( subsong.start_order ) ) ) + retval.back() = mpt::ToCharset( mpt::Charset::UTF8, m_sndFile->GetCharsetInternal(), m_sndFile->Patterns[ order[ subsong.start_order ] ].GetName() ); + } } return retval; } diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h index 1e9407adc..5045bd800 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h @@ -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 */ diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk index b062362ec..4c3613743 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp index 55d22a73e..f09c1545c 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp index cf6ce188a..5b7ee2e9f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp @@ -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++; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp index 82f69a15b..ac1e88940 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp @@ -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(song); } MMD2Song GetMMD2Song() const { static_assert(sizeof(MMD2Song) == sizeof(song)); - MMD2Song result; - std::memcpy(&result, song, sizeof(result)); - return result; + return mpt::bit_cast(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 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(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(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(MAX_SEQUENCES, fileHeader.expDataOffset ? fileHeader.extraSongs + 1 : 1); - file.Rewind(); PATTERNINDEX basePattern = 0; for(SEQUENCEINDEX song = 0; song < numSongs; song++) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp index 7e7deef7c..4c3fe140a 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp @@ -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; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp index 27dfbf000..174768784 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp @@ -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; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp index 37ae97172..f909e9b60 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp @@ -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 ¶m, 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 ¶m, 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, 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(vibAmp * 2); sample.nVibRate = static_cast(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 +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(); - 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(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(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 +template 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 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 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")) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp index 0b0a17582..fb4b38606 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp @@ -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(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(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(loopList[m.param].nonLooped); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp index 8a5fa53c1..ea55d36f3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp @@ -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)) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp index 07cdaed37..2a76aeb2c 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp @@ -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(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(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(); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp index 887b79993..6260ea5ed 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp @@ -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) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp index 380b9d325..e639f9ca1 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp @@ -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(f2t / 128); - nFineTune = static_cast(f2t & 0x7F); + const int f2t = Clamp(FrequencyToTranspose(nC5Speed), -16384, 16383); + const auto fine = std::div(f2t, 128); + RelativeTone = static_cast(fine.quot); + nFineTune = static_cast(fine.rem); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp index 69ee1f279..b7638a21d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp @@ -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++) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h index cca8d623b..48f8aedb0 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h @@ -26,8 +26,8 @@ class ModSequence: public std::vector 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 m_Sequences; // Array of sequences. + std::vector m_Sequences; // Array of sequences CSoundFile &m_sndFile; SEQUENCEINDEX m_currentSeq = 0; // Index of current sequence. diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp index eee08c407..d389e14b3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp @@ -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 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 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 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 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 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 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 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 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 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 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 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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h index a5b12c99b..739b013cf 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp index cbf988c29..e41b46ea4 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp @@ -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]) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp index 5398a4de4..331a8f843 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp @@ -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" }, diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp index d8bf338e5..842925fb3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp @@ -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(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); + } } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp index 46f8b01d2..5c90e6f09 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp @@ -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(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; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp index 959f165e9..5fe972a65 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp @@ -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 }; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp index ff69ca0f4..fe1ff17ad 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp @@ -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)); } diff --git a/Frameworks/OpenMPT/OpenMPT/test/test.cpp b/Frameworks/OpenMPT/OpenMPT/test/test.cpp index 2c331256f..f7014d89b 100644 --- a/Frameworks/OpenMPT/OpenMPT/test/test.cpp +++ b/Frameworks/OpenMPT/OpenMPT/test/test.cpp @@ -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(ModSample::TransposeToFrequency(transpose, finetune)), 1); + { + const auto freq = ModSample::TransposeToFrequency(transpose, finetune); + VERIFY_EQUAL_EPS(transposeToFrequency[freqIndex], static_cast(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);