Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libopenmpt for openSUSE:Factory checked in at 2026-08-25 13:18:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libopenmpt (Old) and /work/SRC/openSUSE:Factory/.libopenmpt.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libopenmpt" Tue Aug 25 13:18:14 2026 rev:60 rq:1373139 version:0.8.9 Changes: -------- --- /work/SRC/openSUSE:Factory/libopenmpt/libopenmpt.changes 2026-05-20 16:48:18.225633492 +0200 +++ /work/SRC/openSUSE:Factory/.libopenmpt.new.1258/libopenmpt.changes 2026-08-25 13:18:19.515227557 +0200 @@ -1,0 +2,22 @@ +Sat Aug 22 20:03:14 UTC 2026 - Mia Herkt <[email protected]> + +- Update to version 0.8.9: + * [Sec] Possible heap out-of-bounds write when loading SymMOD + files containing WAV IMA ADPCM samples. + See also https://github.com/OpenMPT/openmpt/security/advisories/GHSA-fxf7-wc37-p2cx + * [Sec] Possible heap out-of-bounds read when loading custom + tunings from MPTM files. +- Charges in version 0.8.8: + * IT: Due to an Impulse Tracker bug in Compatible Gxx mode, + Envelope Carry may not resume the envelope from the correct + position when there is both an instrument number and tone + portamento next to a note. + * XM: NitroTracker ignores instrument numbers where there is no + note next to them, so they are no longer imported. Fixes + various NitroTracker-made XMs such as notominous-a19.xm. + * STK: Loosen heuristics a bit to allow STK.CRB-GreatMuzaxs6 to + load. + * GT2: Loading file versions 6 and later was broken since + libopenmpt 0.8.0. + +------------------------------------------------------------------- Old: ---- libopenmpt-0.8.7+release.autotools.tar.gz New: ---- libopenmpt-0.8.9+release.autotools.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libopenmpt.spec ++++++ --- /var/tmp/diff_new_pack.s5gf0J/_old 2026-08-25 13:18:20.234252890 +0200 +++ /var/tmp/diff_new_pack.s5gf0J/_new 2026-08-25 13:18:20.236252960 +0200 @@ -21,7 +21,7 @@ %define libopenmpt_modplug_version 0.8.9.0 Name: libopenmpt -Version: 0.8.7 +Version: 0.8.9 Release: 0 Summary: C++ and C library to decode tracker music files License: BSD-3-Clause ++++++ libopenmpt-0.8.7+release.autotools.tar.gz -> libopenmpt-0.8.9+release.autotools.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/common/versionNumber.h new/libopenmpt-0.8.9+release.autotools/common/versionNumber.h --- old/libopenmpt-0.8.7+release.autotools/common/versionNumber.h 2026-05-17 12:54:41.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/common/versionNumber.h 2026-08-18 23:11:45.000000000 +0200 @@ -15,5 +15,5 @@ // Version definitions. The only thing that needs to be changed when changing version number. #define VER_MAJORMAJOR 1 #define VER_MAJOR 32 -#define VER_MINOR 10 -#define VER_MINORMINOR 00 +#define VER_MINOR 11 +#define VER_MINORMINOR 02 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/configure new/libopenmpt-0.8.9+release.autotools/configure --- old/libopenmpt-0.8.7+release.autotools/configure 2026-05-17 14:06:38.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/configure 2026-08-19 11:21:40.000000000 +0200 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for libopenmpt 0.8.7+release.autotools. +# Generated by GNU Autoconf 2.71 for libopenmpt 0.8.9+release.autotools. # # Report bugs to <https://bugs.openmpt.org/>. # @@ -621,8 +621,8 @@ # Identity of this package. PACKAGE_NAME='libopenmpt' PACKAGE_TARNAME='libopenmpt' -PACKAGE_VERSION='0.8.7+release.autotools' -PACKAGE_STRING='libopenmpt 0.8.7+release.autotools' +PACKAGE_VERSION='0.8.9+release.autotools' +PACKAGE_STRING='libopenmpt 0.8.9+release.autotools' PACKAGE_BUGREPORT='https://bugs.openmpt.org/' PACKAGE_URL='https://lib.openmpt.org/' @@ -1514,7 +1514,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures libopenmpt 0.8.7+release.autotools to adapt to many kinds of systems. +\`configure' configures libopenmpt 0.8.9+release.autotools to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1585,7 +1585,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libopenmpt 0.8.7+release.autotools:";; + short | recursive ) echo "Configuration of libopenmpt 0.8.9+release.autotools:";; esac cat <<\_ACEOF @@ -1772,7 +1772,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libopenmpt configure 0.8.7+release.autotools +libopenmpt configure 0.8.9+release.autotools generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2152,7 +2152,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libopenmpt $as_me 0.8.7+release.autotools, which was +It was created by libopenmpt $as_me 0.8.9+release.autotools, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3648,7 +3648,7 @@ # Define the identity of the package. PACKAGE='libopenmpt' - VERSION='0.8.7+release.autotools' + VERSION='0.8.9+release.autotools' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -23188,13 +23188,13 @@ -printf "%s\n" "#define MPT_SVNURL \"https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.7\"" >>confdefs.h +printf "%s\n" "#define MPT_SVNURL \"https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.9\"" >>confdefs.h -printf "%s\n" "#define MPT_SVNVERSION \"25325\"" >>confdefs.h +printf "%s\n" "#define MPT_SVNVERSION \"25652\"" >>confdefs.h -printf "%s\n" "#define MPT_SVNDATE \"2026-05-17T11:08:12.701337Z\"" >>confdefs.h +printf "%s\n" "#define MPT_SVNDATE \"2026-08-19T07:15:57.388610Z\"" >>confdefs.h printf "%s\n" "#define MPT_PACKAGE true" >>confdefs.h @@ -27168,7 +27168,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by libopenmpt $as_me 0.8.7+release.autotools, which was +This file was extended by libopenmpt $as_me 0.8.9+release.autotools, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -27228,7 +27228,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -libopenmpt config.status 0.8.7+release.autotools +libopenmpt config.status 0.8.9+release.autotools configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/configure.ac new/libopenmpt-0.8.9+release.autotools/configure.ac --- old/libopenmpt-0.8.7+release.autotools/configure.ac 2026-05-17 14:06:09.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/configure.ac 2026-08-19 11:21:10.000000000 +0200 @@ -1,4 +1,4 @@ -AC_INIT([libopenmpt], [0.8.7+release.autotools], [https://bugs.openmpt.org/], [libopenmpt], [https://lib.openmpt.org/]) +AC_INIT([libopenmpt], [0.8.9+release.autotools], [https://bugs.openmpt.org/], [libopenmpt], [https://lib.openmpt.org/]) AC_PREREQ([2.69]) # we do want 2.70, but distributions are not ready yet #AC_PREREQ([2.70]) @@ -88,9 +88,9 @@ AC_SUBST([LIBOPENMPT_LTVER_REVISION]) AC_SUBST([LIBOPENMPT_LTVER_AGE]) -AC_DEFINE([MPT_SVNURL], ["https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.7"], [svn version]) -AC_DEFINE([MPT_SVNVERSION], ["25325"], [svn version]) -AC_DEFINE([MPT_SVNDATE], ["2026-05-17T11:08:12.701337Z"], [svn date]) +AC_DEFINE([MPT_SVNURL], ["https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.9"], [svn version]) +AC_DEFINE([MPT_SVNVERSION], ["25652"], [svn version]) +AC_DEFINE([MPT_SVNDATE], ["2026-08-19T07:15:57.388610Z"], [svn date]) AC_DEFINE([MPT_PACKAGE], [true], [is package]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/doc/libopenmpt/changelog.md new/libopenmpt-0.8.9+release.autotools/doc/libopenmpt/changelog.md --- old/libopenmpt-0.8.7+release.autotools/doc/libopenmpt/changelog.md 2026-05-17 13:08:10.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/doc/libopenmpt/changelog.md 2026-08-19 09:15:55.000000000 +0200 @@ -5,6 +5,30 @@ For fully detailed change log, please see the source repository directly. This is just a high-level summary. +### libopenmpt 0.8.9 (2026-08-19) + + * [**Sec**] Possible heap out-of-bounds write when loading SymMOD files + containing WAV IMA ADPCM samples (r25623, r25645). See also + <https://github.com/OpenMPT/openmpt/security/advisories/GHSA-fxf7-wc37-p2cx> + . Found by [Tristan Madani](https://github.com/TristanInSec). + * [**Sec**] Possible heap out-of-bounds read when loading custom tunings from + MPTM files (r25609, r25615). + +### libopenmpt 0.8.8 (2026-08-15) + + * IT: Due to an Impulse Tracker bug in Compatible Gxx mode, Envelope Carry may + not resume the envelope from the correct position when there is both an + instrument number and tone portamento next to a note. + * XM: NitroTracker ignores instrument numbers where there is no note next to + them, so they are no longer imported. Fixes various NitroTracker-made XMs + such as notominous-a19.xm. + * STK: Loosen heuristics a bit to allow STK.CRB-GreatMuzaxs6 to load. + * GT2: Loading file versions 6 and later was broken since libopenmpt 0.8.0. + + * mpg123: Update to v1.33.7 (2026-08-03). + * miniz: Update to v3.1.2 (2026-07-01). + * pugixml: Update to v1.16 (2026-06-16). + ### libopenmpt 0.8.7 (2026-05-17) * ULT: Sustain loops were stopped after a portamento. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_ext_impl.cpp new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_ext_impl.cpp --- old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_ext_impl.cpp 2024-11-19 19:26:05.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_ext_impl.cpp 2026-07-12 16:13:40.000000000 +0200 @@ -270,7 +270,7 @@ chn.nMasterChn = 0; // remove NNA association chn.nNewNote = chn.nLastNote = static_cast<std::uint8_t>(note); chn.ResetEnvelopes(); - m_sndFile->InstrumentChange(chn, instrument + 1); + m_sndFile->InstrumentChange(m_sndFile->m_PlayState, free_channel, instrument + 1); chn.nFadeOutVol = 0x10000; m_sndFile->NoteChange(chn, note, false, true, true); chn.nPan = mpt::saturate_round<std::int32_t>( OpenMPT::Clamp( panning * 128.0, -128.0, 128.0 ) + 128.0 ); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_version.h new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_version.h --- old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_version.h 2026-05-17 13:08:10.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_version.h 2026-08-19 09:15:55.000000000 +0200 @@ -21,7 +21,7 @@ /*! \brief libopenmpt minor version number */ #define OPENMPT_API_VERSION_MINOR 8 /*! \brief libopenmpt patch version number */ -#define OPENMPT_API_VERSION_PATCH 7 +#define OPENMPT_API_VERSION_PATCH 9 /*! \brief libopenmpt pre-release tag */ #define OPENMPT_API_VERSION_PREREL "" /*! \brief libopenmpt pre-release flag */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_version.mk new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_version.mk --- old/libopenmpt-0.8.7+release.autotools/libopenmpt/libopenmpt_version.mk 2026-05-17 13:08:10.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/libopenmpt/libopenmpt_version.mk 2026-08-19 09:15:55.000000000 +0200 @@ -1,8 +1,8 @@ LIBOPENMPT_VERSION_MAJOR=0 LIBOPENMPT_VERSION_MINOR=8 -LIBOPENMPT_VERSION_PATCH=7 +LIBOPENMPT_VERSION_PATCH=9 LIBOPENMPT_VERSION_PREREL= LIBOPENMPT_LTVER_CURRENT=5 -LIBOPENMPT_LTVER_REVISION=7 +LIBOPENMPT_LTVER_REVISION=9 LIBOPENMPT_LTVER_AGE=5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/man/openmpt123.1 new/libopenmpt-0.8.9+release.autotools/man/openmpt123.1 --- old/libopenmpt-0.8.7+release.autotools/man/openmpt123.1 2026-05-17 14:06:07.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/man/openmpt123.1 2026-08-19 11:21:08.000000000 +0200 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH OPENMPT123 "1" "May 2026" "openmpt123 v0.8.7" "User Commands" +.TH OPENMPT123 "1" "August 2026" "openmpt123 v0.8.9" "User Commands" .SH NAME openmpt123 - command line module music player based on libopenmpt .SH SYNOPSIS .B openmpt123 [\fI\,options\/\fR] [\fI\,--\/\fR] \fI\,file1 \/\fR[\fI\,file2\/\fR] ... .SH DESCRIPTION -openmpt123 v0.8.7, libopenmpt 0.8.7+r25325 (OpenMPT 1.32.10.00 https://source.openmpt.org/svn/openmpt/tags/libopenmpt\-0.8.7@25325 (2026\-05\-17T11:08:12.701337Z) clean) +openmpt123 v0.8.9, libopenmpt 0.8.9+r25652 (OpenMPT 1.32.11.02\-r25652 https://source.openmpt.org/svn/openmpt/tags/libopenmpt\-0.8.9@25652 (2026\-08\-19T07:15:57.388610Z) clean) Copyright \(co 2013\-2026 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/> .PP openmpt123 plays module music files. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Load_gt2.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Load_gt2.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Load_gt2.cpp 2024-11-01 21:00:41.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Load_gt2.cpp 2026-08-01 11:19:11.000000000 +0200 @@ -1181,7 +1181,9 @@ return true; std::vector<uint16be> pannedTracks; - file.ReadVector(pannedTracks, fileHeader.numPannedTracks); + if(fileHeader.fileVersion <= 5) + file.ReadVector(pannedTracks, fileHeader.numPannedTracks); + file.Seek(fileHeader.headerSize); ChunkReader chunkFile(file); auto chunks = chunkFile.ReadChunksUntil<GT2Chunk>(1, GT2Chunk::idENDC); @@ -1230,7 +1232,6 @@ ChnSettings[chn].nPan = std::min(static_cast<uint16>(Util::muldivr_unsigned(pannedTracks[chn], 256, 4095)), uint16(256)); } } - file.Seek(fileHeader.headerSize); if(auto chunk = chunks.GetChunk(GT2Chunk::idSONG); chunk.CanRead(2)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Load_it.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Load_it.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Load_it.cpp 2025-09-09 19:24:42.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Load_it.cpp 2026-07-13 20:27:14.000000000 +0200 @@ -1317,6 +1317,7 @@ {SchismVersionFromDate<2024, 5, 27>::date, kITOffsetWithInstrNumber }, // https://github.com/schismtracker/schismtracker/commit/9237960d45079a54ad73f87bacfe5dd8ae82e273 {SchismVersionFromDate<2024, 10, 13>::date, kITDoublePortamentoSlides }, // https://github.com/schismtracker/schismtracker/commit/223e327d9448561931b8cac8a55180286b17276c {SchismVersionFromDate<2025, 1, 8>::date, kITCarryAfterNoteOff }, // https://github.com/schismtracker/schismtracker/commit/ff7a817df327c8f13d97b8c6546a9329f59edff8 + {SchismVersionFromDate<2026, 7, 13>::date, kITCompatGxxCarryPortaWithIns }, // https://github.com/schismtracker/schismtracker/commit/21bbd9a74df8266efe10c1c7c8aaccfc7512ede5 }; for(const auto &quirk : SchismQuirks) { @@ -1351,6 +1352,15 @@ else madeWithTracker = UL_("Unknown"); break; + case 0x0E: + if(fileHeader.cwtv == 0xEFFF) + madeWithTracker = UL_("rosetracker"); + else + madeWithTracker = UL_("Unknown"); + break; + default: + madeWithTracker = UL_("Unknown"); + break; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Load_mdl.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Load_mdl.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Load_mdl.cpp 2024-07-14 01:44:35.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Load_mdl.cpp 2026-08-16 22:27:21.000000000 +0200 @@ -644,7 +644,7 @@ if(sampleHeader.smpNum == 0 || sampleHeader.smpNum > GetNumSamples()) continue; - LimitMax(sampleHeader.lastNote, static_cast<uint8>(std::size(mptIns->Keyboard))); + LimitMax(sampleHeader.lastNote, static_cast<uint8>(std::size(mptIns->Keyboard) - 1)); for(uint8 n = firstNote; n <= sampleHeader.lastNote; n++) { mptIns->Keyboard[n] = sampleHeader.smpNum; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Load_stk.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Load_stk.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Load_stk.cpp 2024-11-27 23:18:46.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Load_stk.cpp 2026-07-12 17:23:25.000000000 +0200 @@ -52,7 +52,7 @@ SmpLength totalSampleLen = 0; uint8 allVolumes = 0; uint8 validNameCount = 0; - bool invalidNames = false; + uint8 invalidNames = 0; for(SAMPLEINDEX smp = 0; smp < 15; smp++) { @@ -66,7 +66,7 @@ if(const auto nameType = ClassifyName(sampleHeader.name); nameType == NameClassification::ValidASCII) validNameCount++; else if(nameType == NameClassification::Invalid) - invalidNames = true; + invalidNames++; // Sanity checks - invalid character count adjusted for ata.mod (MD5 937b79b54026fa73a1a4d3597c26eace, SHA1 3322ca62258adb9e0ae8e9afe6e0c29d39add874) // Sample length adjusted for romantic.stk which has a (valid) sample of length 72222 @@ -82,7 +82,8 @@ } // scramble_2.mod has a lot of garbage in the song title, but it has lots of properly-formatted sample names, so we consider those to be more important than the garbage bytes. - if(invalidCharsInTitle > 5 && (validNameCount < 4 || invalidNames)) + // STK.CRB-GreatMuzaxs6 also has a garbage title and first sample name, so we consider one invalid sample name to be okay. + if(invalidCharsInTitle > 5 && (validNameCount < 4 || invalidNames > 1)) return false; // Reject any files with no (or only silent) samples at all, as this might just be a random binary file (e.g. ID3 tags with tons of padding) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Load_xm.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Load_xm.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Load_xm.cpp 2025-01-01 00:28:24.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Load_xm.cpp 2026-07-14 18:20:44.000000000 +0200 @@ -239,6 +239,8 @@ // Read .XM patterns static void ReadXMPatterns(FileReader &file, const XMFileHeader &fileHeader, CSoundFile &sndFile) { + const bool isNitroTracker = !memcmp(fileHeader.trackerName, "NitroTracker", 13); + // Reading patterns sndFile.Patterns.ResizeArray(fileHeader.patterns); for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++) @@ -327,6 +329,10 @@ if(m.instr == 0xFF) { m.instr = 0; + } else if(isNitroTracker && m.instr && !m.IsNote()) + { + // NitroTracker ignores instrument numbers unless there's a note next to them + m.instr = 0; } if(vol >= 0x10 && vol <= 0x50) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/PlayState.h new/libopenmpt-0.8.9+release.autotools/soundlib/PlayState.h --- old/libopenmpt-0.8.7+release.autotools/soundlib/PlayState.h 2026-03-28 00:59:47.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/PlayState.h 2026-07-12 16:10:37.000000000 +0200 @@ -60,6 +60,8 @@ ORDERINDEX m_nSeqOverride = ORDERINDEX_INVALID; // Queued order to be processed next, regardless of what order would normally follow OrderTransitionMode m_seqOverrideMode = OrderTransitionMode::AtPatternEnd; + CHANNELINDEX m_lastMovedChannel = CHANNELINDEX_INVALID; // IT compat Gxx + carry + porta bug emulation + // Global volume public: int32 m_nGlobalVolume = MAX_GLOBAL_VOLUME; // Current global volume (0...MAX_GLOBAL_VOLUME) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/PlaybackTest.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/PlaybackTest.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/PlaybackTest.cpp 2025-02-16 08:40:28.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/PlaybackTest.cpp 2026-08-12 08:25:13.000000000 +0200 @@ -273,7 +273,6 @@ return result; } #if MPT_GCC_AT_LEAST(12, 0, 0) && MPT_GCC_BEFORE(13, 1, 0) -#pragma GCC diagnostic pop #pragma GCC pop_options #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/SampleFormatFLAC.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/SampleFormatFLAC.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/SampleFormatFLAC.cpp 2025-06-29 22:12:39.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/SampleFormatFLAC.cpp 2026-08-18 13:55:04.000000000 +0200 @@ -151,7 +151,6 @@ // Source bit depth const unsigned int bps = frame->header.bits_per_sample; - MPT_ASSERT((bps <= 8 && sample.GetElementarySampleSize() == 1) || (bps > 8 && sample.GetElementarySampleSize() == 2)); MPT_ASSERT(modChannels <= FLAC__stream_decoder_get_channels(decoder)); MPT_ASSERT(bps == FLAC__stream_decoder_get_bits_per_sample(decoder)); MPT_UNREFERENCED_PARAMETER(decoder); // decoder is unused if ASSERTs are compiled out @@ -159,22 +158,82 @@ // Do the sample conversion for(uint8 chn = 0; chn < modChannels; chn++) { - if(bps <= 8) + if(sample.GetElementarySampleSize() == 1) { int8 *sampleData8 = sample.sample8() + offset; - CopySample<SC::ConversionChain<SC::ConvertShift< int8, int32, 0>, SC::DecodeIdentity<int32> > >(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); - } else if(bps <= 16) + switch(bps) + { + case 1: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 7>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 2: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 6>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 3: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 5>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 4: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 4>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 5: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 3>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 6: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 2>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 7: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int8, int32, 1>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 8: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 0>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 9: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 1>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 10: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 2>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 11: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 3>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 12: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 4>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 13: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 5>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 14: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 6>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 15: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 7>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 16: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 8>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 17: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 9>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 18: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 10>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 19: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 11>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 20: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 12>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 21: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 13>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 22: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 14>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 23: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 15>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 24: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 16>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 25: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 17>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 26: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 18>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 27: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 19>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 28: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 20>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 29: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 21>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 30: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 22>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 31: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 23>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 32: CopySample<SC::ConversionChain<SC::ConvertShift<int8, int32, 24>, SC::DecodeIdentity<int32>>>(sampleData8 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + } + } else if(sample.GetElementarySampleSize() == 2) { int16 *sampleData16 = sample.sample16() + offset; - CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 0>, SC::DecodeIdentity<int32> > >(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); - } else if(bps <= 24) - { - int16 *sampleData16 = sample.sample16() + offset; - CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 8>, SC::DecodeIdentity<int32> > >(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); - } else if(bps <= 32) - { - int16 *sampleData16 = sample.sample16() + offset; - CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 16>, SC::DecodeIdentity<int32> > >(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); + switch(bps) + { + case 1: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 15>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 2: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 14>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 3: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 13>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 4: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 12>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 5: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 11>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 6: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 10>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 7: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 9>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 8: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 8>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 9: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 7>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 10: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 6>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 11: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 5>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 12: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 4>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 13: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 3>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 14: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 2>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 15: CopySample<SC::ConversionChain<SC::ConvertShiftUp<int16, int32, 1>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 16: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 0>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 17: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 1>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 18: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 2>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 19: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 3>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 20: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 4>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 21: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 5>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 22: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 6>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 23: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 7>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 24: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 8>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 25: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 9>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 26: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 10>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 27: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 11>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 28: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 12>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 29: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 13>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 30: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 14>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 31: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 15>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + case 32: CopySample<SC::ConversionChain<SC::ConvertShift<int16, int32, 16>, SC::DecodeIdentity<int32>>>(sampleData16 + chn, copySamples, modChannels, buffer[chn], srcSize, 1); break; + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/SampleFormats.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/SampleFormats.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/SampleFormats.cpp 2025-06-06 00:15:38.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/SampleFormats.cpp 2026-08-19 07:36:54.000000000 +0200 @@ -354,11 +354,34 @@ //////////////////////////////////////////////////////////////////////// // IMA ADPCM Support for WAV files +struct IMAADPCM_BlockHeader +{ + int16le value; + int8le index; + int8le reserved; +}; + +MPT_BINARY_STRUCT(IMAADPCM_BlockHeader, 4) + +struct IMAADPCM_Chunk +{ + uint8 data[4]; +}; + +MPT_BINARY_STRUCT(IMAADPCM_Chunk, 4) static bool IMAADPCMUnpack16(int16 *target, SmpLength sampleLen, FileReader file, uint16 blockAlign, uint32 numChannels) { - static constexpr int8 IMAIndexTab[8] = { -1, -1, -1, -1, 2, 4, 6, 8 }; - static constexpr int16 IMAUnpackTable[90] = + + // reference code from <https://www.cs.columbia.edu/~hgs/audio/dvi/IMA_ADPCM.pdf> + + // v reference code + static constexpr int8 indexTable[16] = + { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + static constexpr int16 stepsizeTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, @@ -371,60 +394,118 @@ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, - 32767, 0 + 32767 }; + // ^ reference code - if(target == nullptr || blockAlign < 4u * numChannels) + if(!target) + { return false; - - SmpLength samplePos = 0; + } + if(blockAlign < (sizeof(IMAADPCM_BlockHeader) * numChannels)) + { + return false; + } + // stricter rejection of bogus blockAlign + /* + if(((blockAlign - (sizeof(IMAADPCM_BlockHeader) * numChannels)) % (sizeof(IMAADPCM_Chunk) * numChannels)) != 0) + { + return false; + } + */ sampleLen *= numChannels; - while(file.CanRead(4u * numChannels) && samplePos < sampleLen) + static constexpr SmpLength samplesPerChunk = 8; + // round up expected chunks, assume zero data for short blocks + const uint16 chunksPerChannelPerBlock = static_cast<uint16>(mpt::align_up(blockAlign - (sizeof(IMAADPCM_BlockHeader) * numChannels), sizeof(IMAADPCM_Chunk) * numChannels) / (sizeof(IMAADPCM_Chunk) * numChannels)); + SmpLength samplePos = 0; + while(file.CanRead(sizeof(IMAADPCM_BlockHeader) * numChannels)) { FileReader block = file.ReadChunk(blockAlign); - FileReader::PinnedView blockView = block.GetPinnedView(); - const std::byte *data = blockView.data(); - const uint32 blockSize = static_cast<uint32>(blockView.size()); - - for(uint32 chn = 0; chn < numChannels; chn++) + FileReader blockChunks = block.GetChunkAt(sizeof(IMAADPCM_BlockHeader) * numChannels, chunksPerChannelPerBlock * numChannels * sizeof(IMAADPCM_Chunk)); + FileReader::PinnedView blockChunksView = blockChunks.GetPinnedView(); + MemoryFileReader chunks{blockChunksView.GetSpan()}; + for(uint32 channel = 0; channel < numChannels; ++channel) { - // Block header - int32 value = block.ReadInt16LE(); - int32 nIndex = block.ReadUint8(); - Limit(nIndex, 0, 89); - block.Skip(1); - - SmpLength smpPos = samplePos + chn; - uint32 dataPos = (numChannels + chn) * 4; - // Block data - while(smpPos <= (sampleLen - 8) && dataPos <= (blockSize - 4)) + block.Seek(sizeof(IMAADPCM_BlockHeader) * channel); + IMAADPCM_BlockHeader header; + block.ReadStruct(header); + + // v reference code + int32 predictedSample = header.value; + int32 index = header.index; + //int32 stepsize = 7; // wrong + // ^ reference code + index = std::clamp(index, static_cast<int32>(0), static_cast<int32>(88)); + int32 stepsize = stepsizeTable[index]; + + // skip writing output after given sample length + if(samplePos < sampleLen) + { + target[samplePos] = static_cast<int16>(predictedSample); + } + samplePos += numChannels; + + for(uint16 chunk = 0; chunk < chunksPerChannelPerBlock; ++chunk) { - for(uint32 i = 0; i < 8; i++) + IMAADPCM_Chunk chunkdata; + // assume zero data for short blocks + if(chunks.Seek((sizeof(IMAADPCM_Chunk) * numChannels * chunk) + (channel * sizeof(IMAADPCM_Chunk)))) + { + chunks.ReadStructPartial(chunkdata); + } else { - uint8 delta = mpt::byte_cast<uint8>(data[dataPos]); - if(i & 1) + mpt::memclear(chunkdata); + } + // always decode the complete chunk, even if the output only has space for a partial chunk at the end of the sample + for(SmpLength chunkSample = 0; chunkSample < samplesPerChunk; ++chunkSample) + { + const int32 originalSample = (chunkdata.data[chunkSample >> 1u] >> ((chunkSample & 1u) * 4u)) & 0x0fu; + int32 newSample = predictedSample; + + // v reference code + int32 difference = 0; + if(originalSample & 4) { - delta >>= 4; - dataPos++; - } else + difference += stepsize; + } + if(originalSample & 2) + { + difference += stepsize >> 1; + } + if(originalSample & 1) + { + difference += stepsize >> 2; + } + difference += stepsize >> 3; + if(originalSample & 8) { - delta &= 0x0F; + difference = -difference; } - int32 v = IMAUnpackTable[nIndex] >> 3; - if (delta & 1) v += IMAUnpackTable[nIndex] >> 2; - if (delta & 2) v += IMAUnpackTable[nIndex] >> 1; - if (delta & 4) v += IMAUnpackTable[nIndex]; - if (delta & 8) value -= v; else value += v; - nIndex += IMAIndexTab[delta & 7]; - Limit(nIndex, 0, 88); - Limit(value, -32768, 32767); - target[smpPos] = static_cast<int16>(value); - smpPos += numChannels; + newSample += difference; + newSample = std::clamp(newSample, static_cast<int32>(-32768), static_cast<int32>(32767)); + // ^ reference code + + // skip writing output after given sample length + predictedSample = newSample; + if(samplePos < sampleLen) + { + target[samplePos] = static_cast<int16>(predictedSample); + } + + // v reference code + index += indexTable[originalSample]; + index = std::clamp(index, static_cast<int32>(0), static_cast<int32>(88)); + stepsize = stepsizeTable[index]; + // ^ reference code + + samplePos += numChannels; } - dataPos += (numChannels - 1) * 4u; } + samplePos -= ((samplesPerChunk * chunksPerChannelPerBlock) + 1) * numChannels; + samplePos += 1; } - samplePos += ((blockSize - (numChannels * 4u)) * 2u); + samplePos -= numChannels; + samplePos += ((samplesPerChunk * chunksPerChannelPerBlock) + 1) * numChannels; } return true; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Snd_defs.h new/libopenmpt-0.8.9+release.autotools/soundlib/Snd_defs.h --- old/libopenmpt-0.8.7+release.autotools/soundlib/Snd_defs.h 2025-08-31 17:10:41.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Snd_defs.h 2026-07-12 16:10:37.000000000 +0200 @@ -620,6 +620,7 @@ kITNoteCutWithPorta, // Note Cut (SCx) resets note frequency and interacts with tone portamento with row delay kITVolColNoSlidePropagation, // Don't propagate volume command c/d parameter to regular command D memory kITStoppedFilterEnvAtStart, // Stopped filter envelope is still applied even when its first tick has not been processed yet + kITCompatGxxCarryPortaWithIns, // Envelope Carry quirk in Compatible Gxx mode with portamento and instrument number // Add new play behaviours here. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Snd_fx.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Snd_fx.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Snd_fx.cpp 2026-02-10 22:32:08.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Snd_fx.cpp 2026-07-12 16:10:37.000000000 +0200 @@ -95,6 +95,7 @@ state->m_ppqPosFract = 0.0; state->m_ppqPosBeat = 0; state->m_nGlobalVolume = sndFile.m_nDefaultGlobalVolume; + state->m_lastMovedChannel = CHANNELINDEX_INVALID; state->m_globalScriptState.Initialize(sndFile); chnSettings.assign(sndFile.GetNumChannels(), {}); const auto muteFlag = CSoundFile::GetChannelMuteFlag(); @@ -1102,7 +1103,7 @@ chn.increment = GetChannelIncrement(chn, chn.nPeriod, 0).first; } int32 setPan = chn.nPan; - if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta); + if(chn.nNewIns != 0) InstrumentChange(playState, nChn, chn.nNewIns, porta); NoteChange(chn, m.note, porta); HandleNoteChangeFilter(chn); HandleDigiSamplePlayDirection(playState, nChn); @@ -1459,8 +1460,9 @@ // Effects // Change sample or instrument number. -void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bool bUpdVol, bool bResetEnv) const +void CSoundFile::InstrumentChange(PlayState &playState, CHANNELINDEX channel, uint32 instr, bool bPorta, bool bUpdVol, bool bResetEnv) const { + ModChannel &chn = playState.Chn[channel]; const ModInstrument *pIns = instr <= GetNumInstruments() ? Instruments[instr] : nullptr; const ModSample *pSmp = &Samples[instr <= GetNumSamples() ? instr : 0]; const auto oldInsVol = chn.nInsVol; @@ -1668,9 +1670,34 @@ chn.ResetEnvelopes(); } else { - if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset(); - if(!pIns->PanEnv.dwFlags[ENV_CARRY]) chn.PanEnv.Reset(); - if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) chn.PitchEnv.Reset(); + // IT Compatibility: In Compatible Gxx mode, Envelope Carry mode has a + // really terrible quirk where, if a note has both an instrument number + // and tone portamento, it does not resume envelopes where the previously + // playing note left off: + // Internally, IT keeps track of the last allocated NNA channel in a + // global variable, which is then used to get the old envelope information + // from. + // However in this particular case (porta + instrument number), the last + // NNA channel memory is not updated. Due to this, the 'last' channel may + // actually be some earlier, already stopped note from the same channel, or + // it might be a completely different pattern channel altogether. Nasty! + // Test case: CarryCompatGxxPortaWithIns.it + const bool compatGxxCarryReset = m_playBehaviour[kITCompatGxxCarryPortaWithIns] && bPorta && m_SongFlags[SONG_ITCOMPATGXX]; + const ModChannel *lastChn = playState.m_lastMovedChannel < playState.Chn.size() ? &playState.Chn[playState.m_lastMovedChannel] : nullptr; + if(!pIns->VolEnv.dwFlags[ENV_CARRY]) + chn.VolEnv.Reset(); + else if(compatGxxCarryReset) + chn.VolEnv.nEnvPosition = lastChn ? lastChn->VolEnv.nEnvPosition : 0; + + if(!pIns->PanEnv.dwFlags[ENV_CARRY]) + chn.PanEnv.Reset(); + else if(compatGxxCarryReset) + chn.PanEnv.nEnvPosition = lastChn ? lastChn->PanEnv.nEnvPosition : 0; + + if(!pIns->PitchEnv.dwFlags[ENV_CARRY]) + chn.PitchEnv.Reset(); + else if(compatGxxCarryReset) + chn.PitchEnv.nEnvPosition = lastChn ? lastChn->PitchEnv.nEnvPosition : 0; } } @@ -1997,7 +2024,7 @@ chn.nResSwing = chn.nCutSwing = 0; if(pIns) { - // IT Compatiblity: NNA is reset on every note change, not every instrument change (fixes spx-farspacedance.it). + // IT Compatibility: NNA is reset on every note change, not every instrument change (fixes spx-farspacedance.it). if(m_playBehaviour[kITNNAReset]) chn.nNNA = pIns->nNNA; if(!pIns->VolEnv.dwFlags[ENV_CARRY]) chn.VolEnv.Reset(); @@ -2333,6 +2360,8 @@ } const CHANNELINDEX nnaChn = GetNNAChannel(nChn); + if(m_PlayState.m_lastMovedChannel == nChn) + m_PlayState.m_lastMovedChannel = nnaChn; if(nnaChn == CHANNELINDEX_INVALID) return CHANNELINDEX_INVALID; ModChannel &chn = m_PlayState.Chn[nnaChn]; @@ -2485,11 +2514,15 @@ } } + m_PlayState.m_lastMovedChannel = CHANNELINDEX_INVALID; + // New Note Action if(!srcChn.IsSamplePlaying() && !applyNNAtoPlug) return CHANNELINDEX_INVALID; const CHANNELINDEX nnaChn = GetNNAChannel(nChn); + if(srcChn.pModInstrument == pIns) + m_PlayState.m_lastMovedChannel = nnaChn; #ifndef NO_PLUGINS if(applyNNAtoPlug) @@ -3087,7 +3120,7 @@ const ModSample *oldSample = chn.pModSample; //const ModInstrument *oldInstrument = chn.pModInstrument; - InstrumentChange(chn, instr, bPorta, true); + InstrumentChange(m_PlayState, nChn, instr, bPorta, true); if(!chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE] && chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl) { @@ -3152,7 +3185,7 @@ if(m_playBehaviour[kITEmptyNoteMapSlotIgnoreCell] && ModCommand::IsNote(previousNewNote)) chn.nNewNote = previousNewNote; - InstrumentChange(chn, chn.nNewIns, bPorta, chn.pModSample == nullptr && chn.pModInstrument == nullptr, !(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))); + InstrumentChange(m_PlayState, nChn, chn.nNewIns, bPorta, chn.pModSample == nullptr && chn.pModInstrument == nullptr, !(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))); chn.nNewNote = note; chn.swapSampleIndex = chn.nNewIns = 0; } @@ -5975,7 +6008,7 @@ { if(chn.rowCommand.instr && param < 0x100) { - InstrumentChange(chn, chn.rowCommand.instr, false, false); + InstrumentChange(m_PlayState, nChn, chn.rowCommand.instr, false, false); resetEnv = true; } if(param < 0x100) @@ -5986,7 +6019,7 @@ if(m_playBehaviour[kMODSampleSwap] && chn.rowCommand.instr) { auto oldFineTune = chn.nFineTune; - InstrumentChange(chn, chn.rowCommand.instr, false, false); + InstrumentChange(m_PlayState, nChn, chn.rowCommand.instr, false, false); chn.nFineTune = oldFineTune; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Sndfile.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Sndfile.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Sndfile.cpp 2025-11-15 15:03:20.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Sndfile.cpp 2026-07-12 16:10:37.000000000 +0200 @@ -937,6 +937,7 @@ m_PlayState.m_lTotalSampleCount = 0; m_PlayState.m_ppqPosFract = 0.0; m_PlayState.m_ppqPosBeat = 0; + m_PlayState.m_lastMovedChannel = CHANNELINDEX_INVALID; m_PlayState.m_globalScriptState.Initialize(*this); } @@ -1232,6 +1233,7 @@ playBehaviour.set(kITNoteCutWithPorta); playBehaviour.set(kITVolColNoSlidePropagation); playBehaviour.set(kITStoppedFilterEnvAtStart); + playBehaviour.set(kITCompatGxxCarryPortaWithIns); break; case MOD_TYPE_XM: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Sndfile.h new/libopenmpt-0.8.9+release.autotools/soundlib/Sndfile.h --- old/libopenmpt-0.8.7+release.autotools/soundlib/Sndfile.h 2025-07-12 23:07:46.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Sndfile.h 2026-07-12 16:10:37.000000000 +0200 @@ -1024,7 +1024,7 @@ CHANNELINDEX CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, bool forceCut); void StopOldNNA(ModChannel &chn, CHANNELINDEX channel); void NoteChange(ModChannel &chn, int note, bool bPorta = false, bool bResetEnv = true, bool bManual = false, CHANNELINDEX channelHint = CHANNELINDEX_INVALID) const; - void InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta = false, bool bUpdVol = true, bool bResetEnv = true) const; + void InstrumentChange(PlayState &playState, CHANNELINDEX channel, uint32 instr, bool bPorta = false, bool bUpdVol = true, bool bResetEnv = true) const; void ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *instr, const ModSample *smp) const; uint32 CalculateXParam(PATTERNINDEX pat, ROWINDEX row, CHANNELINDEX chn, uint32 *extendedRows = nullptr) const; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/Sndmix.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/Sndmix.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/Sndmix.cpp 2026-01-26 22:15:33.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/Sndmix.cpp 2026-07-12 16:10:37.000000000 +0200 @@ -2176,14 +2176,6 @@ for(CHANNELINDEX nChn = 0; nChn < m_PlayState.Chn.size(); nChn++) { ModChannel &chn = m_PlayState.Chn[nChn]; - // FT2 Compatibility: Prevent notes to be stopped after a fadeout. This way, a portamento effect can pick up a faded instrument which is long enough. - // This occurs for example in the bassline (channel 11) of jt_burn.xm. I hope this won't break anything else... - // I also suppose this could decrease mixing performance a bit, but hey, which CPU can't handle 32 muted channels these days... :-) - if(chn.dwFlags[CHN_NOTEFADE] && (!(chn.nFadeOutVol|chn.leftVol|chn.rightVol)) && !m_playBehaviour[kFT2ProcessSilentChannels]) - { - chn.nLength = 0; - chn.nROfs = chn.nLOfs = 0; - } // Increment age of NNA channels if(chn.nMasterChn && nChn < GetNumChannels() && chn.nnaChannelAge < Util::MaxValueOfType(chn.nnaChannelAge)) chn.nnaChannelAge++; @@ -2477,6 +2469,18 @@ IncrementEnvelopePositions(chn); } + // FT2 Compatibility: Prevent notes to be stopped after a fadeout. This way, a portamento effect can pick up a faded instrument which is long enough. + // This occurs for example in the bassline (channel 11) of jt_burn.xm. + // Test case: FadeoutPickup.xm + // NB: This check used to be right at the top of the loop, but was moved down here to match the channel stop behaviour + // of Impulse Tracker for the kITCompatGxxCarryPortaWithIns play behaviour. + // Test case: CarryCompatGxxPortaWithIns.it + if(chn.dwFlags[CHN_NOTEFADE] && (!(chn.nFadeOutVol | chn.leftVol | chn.rightVol)) && !m_playBehaviour[kFT2ProcessSilentChannels]) + { + chn.nLength = 0; + chn.nROfs = chn.nLOfs = 0; + } + // Volume ramping chn.dwFlags.set(CHN_VOLUMERAMP, (chn.nRealVolume | chn.rightVol | chn.leftVol) != 0 && !chn.dwFlags[CHN_ADLIB]); @@ -2601,6 +2605,12 @@ { m_PlayState.ChnMix[m_nMixChannels++] = nChn; } + + // Stop NNA channel processing if the channel is effectively muted. + // Required for Carry Envelope quirk to stop processing envelopes of affected channels. + // Test case: CarryCompatGxxPortaWithIns.it + if(nChn >= GetNumChannels() && !(chn.nVolume && chn.nGlobalVol && chn.nInsVol)) + chn.nLength = 0; } else { chn.rightVol = chn.leftVol = 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/UpgradeModule.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/UpgradeModule.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/UpgradeModule.cpp 2025-10-17 13:43:13.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/UpgradeModule.cpp 2026-07-12 16:10:37.000000000 +0200 @@ -606,6 +606,7 @@ { kITNoteCutWithPorta, MPT_V("1.32.01.02") }, { kITVolColNoSlidePropagation, MPT_V("1.32.02.03") }, { kITStoppedFilterEnvAtStart, MPT_V("1.32.03.04") }, + { kITCompatGxxCarryPortaWithIns, MPT_V("1.32.10.02") }, }; for(const auto &b : behaviours) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/WAVTools.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/WAVTools.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/WAVTools.cpp 2025-03-17 21:24:16.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/WAVTools.cpp 2026-08-18 23:03:51.000000000 +0200 @@ -107,6 +107,25 @@ // "fact" chunk should contain sample length of compressed samples. sampleLength = chunks.GetChunk(RIFFChunk::idfact).ReadUint32LE(); + if((formatInfo.format == WAVFormatChunk::fmtIMA_ADPCM) && (GetBlockAlign() > 0)) + { + FileReader::pos_type dataLength = sampleData.GetLength(); + std::size_t blocks = mpt::align_up<FileReader::pos_type>(dataLength, GetBlockAlign()) / GetBlockAlign(); + std::size_t framesPerBlock = ((GetBlockAlign() - (4 * formatInfo.numChannels)) * 2 / formatInfo.numChannels) + 1; + FileReader::pos_type expectedLengthFrames = blocks * framesPerBlock; + if(sampleLength == 0) + { + sampleLength = expectedLengthFrames; + } else if((sampleLength > 0) && (formatInfo.numChannels == 2)) + { + // libsndfile divides the sample length by 2 for stereo samples (it gets confused about frames vs samples) + if((sampleLength * 2) <= expectedLengthFrames) + { + sampleLength *= 2; + } + } + } + if((formatInfo.format != WAVFormatChunk::fmtIMA_ADPCM || sampleLength == 0) && GetSampleSize() != 0) { if((GetBlockAlign() == 0) || (GetBlockAlign() / GetNumChannels() >= 2 * GetSampleSize())) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/soundlib/tuning.cpp new/libopenmpt-0.8.9+release.autotools/soundlib/tuning.cpp --- old/libopenmpt-0.8.7+release.autotools/soundlib/tuning.cpp 2025-03-29 07:32:10.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/soundlib/tuning.cpp 2026-08-18 22:46:00.000000000 +0200 @@ -809,14 +809,17 @@ { // Convert old geometric to new groupgeometric because old geometric tunings // can have ratio(0) != 1.0, which would get lost when saving nowadays. - if(mpt::saturate_cast<NOTEINDEXTYPE>(m_RatioTable.size()) >= m_GroupSize - m_NoteMin) + NOTEINDEXTYPE baseNote = 0; + baseNote = std::clamp(baseNote, GetNoteRange().first, GetNoteRange().last); + if(IsValidNote(baseNote) && IsValidNote(mpt::saturate_cast<NOTEINDEXTYPE>(baseNote + static_cast<NOTEINDEXTYPE>(GetGroupSize()) - 1))) { std::vector<RATIOTYPE> ratios; - for(NOTEINDEXTYPE n = 0; n < m_GroupSize; ++n) + for(NOTEINDEXTYPE note = baseNote; note < m_GroupSize + baseNote; ++note) { - ratios.push_back(m_RatioTable[n - m_NoteMin]); + ratios.push_back(GetRatio(note)); } - CreateGroupGeometric(ratios, m_GroupRatio, GetNoteRange(), 0); + // ignore return value, keep as geometric if conversion fails + CreateGroupGeometric(ratios, m_GroupRatio, GetNoteRange(), baseNote); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/src/mpt/base/detect_libc.hpp new/libopenmpt-0.8.9+release.autotools/src/mpt/base/detect_libc.hpp --- old/libopenmpt-0.8.7+release.autotools/src/mpt/base/detect_libc.hpp 2024-12-31 09:57:00.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/src/mpt/base/detect_libc.hpp 2026-08-12 10:53:28.000000000 +0200 @@ -9,6 +9,16 @@ #include "mpt/base/detect_os.hpp" #include <cstddef> +#include <cstdint> +// Work-around MSYS2/CLANG64 bug: +// With Clang, including <stddef.h> includes only the generic +// Clang-provided header which does not result in trnansitively including +// the MinGW-specific version which would ultimately include <_mingw.h>. +// However, <_mingw.h> is required to properly identify the version of the +// C Runtime (i.e. if undefined, it defines _UCRT, __MSVCRT_VERSION__, or +// __CRTDLL__ and related macros, which are necessary for proper detection +// of the C library). +#include <cstdlib> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/src/mpt/base/math.hpp new/libopenmpt-0.8.9+release.autotools/src/mpt/base/math.hpp --- old/libopenmpt-0.8.7+release.autotools/src/mpt/base/math.hpp 2024-12-18 16:07:45.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/src/mpt/base/math.hpp 2026-05-20 18:32:04.000000000 +0200 @@ -13,6 +13,10 @@ #include <cmath> +#if MPT_OS_DJGPP +#include <math.h> +#endif // MPT_OS_DJGPP + namespace mpt { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/src/mpt/check/libc.hpp new/libopenmpt-0.8.9+release.autotools/src/mpt/check/libc.hpp --- old/libopenmpt-0.8.7+release.autotools/src/mpt/check/libc.hpp 2025-01-02 23:12:01.000000000 +0100 +++ new/libopenmpt-0.8.9+release.autotools/src/mpt/check/libc.hpp 2026-08-11 19:40:02.000000000 +0200 @@ -11,10 +11,14 @@ #ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_MTRT #if MPT_PLATFORM_MULTITHREADED #if MPT_LIBC_MINGW +#if MPT_LIBC_MS_UCRT +// nothing +#else /* MPT_LIBC_MS_MSVCRT || MPT_LIBC_MS_CRTDLL */ // MinGW only has `#define _MT` in header files instead of `#define _MT 1`. #if !defined(_MT) MPT_WARNING("C stdlib is not multi-threaded.") #endif +#endif #elif MPT_LIBC_MS #if defined(_MT) #if (_MT != 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/src/mpt/io_read/filedata_base.hpp new/libopenmpt-0.8.9+release.autotools/src/mpt/io_read/filedata_base.hpp --- old/libopenmpt-0.8.7+release.autotools/src/mpt/io_read/filedata_base.hpp 2024-07-24 11:11:45.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/src/mpt/io_read/filedata_base.hpp 2026-08-12 08:34:24.000000000 +0200 @@ -55,6 +55,14 @@ }; + +#if MPT_GCC_AT_LEAST(16, 0, 0) && MPT_GCC_BEFORE(17, 1, 0) +// Work-around confused GCC 16 optimizer which generates bogus -Warray-bounds in multiple call chains and member functions. +#pragma GCC push_options +#if defined(__OPTIMIZE__) +#pragma GCC optimize("O1") +#endif +#endif class FileDataWindow : public IFileData { private: std::shared_ptr<const IFileData> data; @@ -104,6 +112,9 @@ return std::min(length, dataLength - pos); } }; +#if MPT_GCC_AT_LEAST(16, 0, 0) && MPT_GCC_BEFORE(17, 1, 0) +#pragma GCC pop_options +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libopenmpt-0.8.7+release.autotools/src/openmpt/soundbase/SampleConvert.hpp new/libopenmpt-0.8.9+release.autotools/src/openmpt/soundbase/SampleConvert.hpp --- old/libopenmpt-0.8.7+release.autotools/src/openmpt/soundbase/SampleConvert.hpp 2026-04-25 08:54:48.000000000 +0200 +++ new/libopenmpt-0.8.9+release.autotools/src/openmpt/soundbase/SampleConvert.hpp 2026-05-20 17:12:41.000000000 +0200 @@ -172,7 +172,7 @@ using output_t = uint8; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= 128.0; return static_cast<uint8>(mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))) + 0x80); } @@ -253,7 +253,7 @@ using output_t = int8; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= 128.0; return mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))); } @@ -334,7 +334,7 @@ using output_t = int16; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= 32768.0; return mpt::saturate_cast<int16>(static_cast<int>(SC::fastround(val))); } @@ -415,7 +415,7 @@ using output_t = int24; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= 2147483648.0; return static_cast<int24>(mpt::rshift_signed(mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))), 8)); } @@ -496,7 +496,7 @@ using output_t = int32; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= 2147483648.0; return mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))); } @@ -577,7 +577,7 @@ using output_t = int64; MPT_FORCEINLINE output_t operator()(input_t val) { - val = std::clamp(val, -1.0, 1.0); + val = mpt::safe_clamp(val, -1.0, 1.0); val *= static_cast<double>(uint64(1) << 63); return mpt::saturate_trunc<int64>(SC::fastround(val)); }
