Updated libopenmpt to version 0.5.13

CQTexperiment
Christopher Snowhill 2021-11-14 21:34:08 -08:00
parent b53d396a10
commit 4344f358c2
17 changed files with 370 additions and 299 deletions

View File

@ -1,4 +1,4 @@
MPT_SVNVERSION=15759 MPT_SVNVERSION=15956
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.12 MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.13
MPT_SVNDATE=2021-10-04T13:48:02.912129Z MPT_SVNDATE=2021-11-14T17:01:47.266406Z

View File

@ -1,10 +1,10 @@
#pragma once #pragma once
#define OPENMPT_VERSION_SVNVERSION "15759" #define OPENMPT_VERSION_SVNVERSION "15956"
#define OPENMPT_VERSION_REVISION 15759 #define OPENMPT_VERSION_REVISION 15956
#define OPENMPT_VERSION_DIRTY 0 #define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0 #define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.12" #define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.13"
#define OPENMPT_VERSION_DATE "2021-10-04T13:48:02.912129Z" #define OPENMPT_VERSION_DATE "2021-11-14T17:01:47.266406Z"
#define OPENMPT_VERSION_IS_PACKAGE 1 #define OPENMPT_VERSION_IS_PACKAGE 1

View File

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

View File

@ -5,6 +5,22 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This For fully detailed change log, please see the source repository directly. This
is just a high-level summary. is just a high-level summary.
### libopenmpt 0.5.13 (2021-11-14)
* [**Bug**] Fixed various undefined behaviour found with ubsan.
* IMF: Change envelope interpretation to be more like in XM instead of IT and
tighten header validation.
* MED: Some samples had a ping-pong loop when there should be no loop at all.
* MT2: Ignore incorrect drums chunk size in early MT2 files
(fixes e.g. "A little Rock" by Csumi).
* MT2: Work around initial master volume of 0 used in some files that apply a
fade-in a the song start using track automation that would stay silent
forever otherwise (track automation is currently not supported).
* OKT: Apply portamento on every tick.
* mpg123: Update to v1.29.2 (2021-10-23).
### libopenmpt 0.5.12 (2021-10-04) ### libopenmpt 0.5.12 (2021-10-04)
* [**Sec**] Possible crash when loading malformed MDL files. (r15603) * [**Sec**] Possible crash when loading malformed MDL files. (r15603)
@ -24,6 +40,8 @@ is just a high-level summary.
* in_openmpt: Song metadata is no longer reverted when viewing file info. * in_openmpt: Song metadata is no longer reverted when viewing file info.
* mpg123: Update to v1.29.0 (2021-09-06).
### libopenmpt 0.5.11 (2021-08-22) ### libopenmpt 0.5.11 (2021-08-22)
* [**Sec**] Possible crash with malformed modules when trying to access * [**Sec**] Possible crash with malformed modules when trying to access

View File

@ -88,6 +88,8 @@ public:
static std::string format_exception( const char * const function ) { static std::string format_exception( const char * const function ) {
std::string err; std::string err;
try { try {
// cppcheck false-positive
// cppcheck-suppress rethrowNoCurrentException
throw; throw;
} catch ( const openmpt::exception & e ) { } catch ( const openmpt::exception & e ) {
err += function; err += function;
@ -131,6 +133,8 @@ static int error_from_exception( const char * * error_message ) {
} }
} }
try { try {
// cppcheck false-positive
// cppcheck-suppress rethrowNoCurrentException
throw; throw;
} catch ( const std::bad_alloc & e ) { } catch ( const std::bad_alloc & e ) {

View File

@ -1806,7 +1806,7 @@ double module_impl::ctl_get_floatingpoint( std::string_view ctl, bool throw_if_u
} }
return m_sndFile->m_nFreqFactor / 65536.0; return m_sndFile->m_nFreqFactor / 65536.0;
} else if ( ctl == "render.opl.volume_factor" ) { } else if ( ctl == "render.opl.volume_factor" ) {
return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ); return static_cast<double>( m_sndFile->m_OPLVolumeFactor ) / static_cast<double>( CSoundFile::m_OPLVolumeFactorScale );
} else { } else {
MPT_ASSERT_NOTREACHED(); MPT_ASSERT_NOTREACHED();
return 0.0; return 0.0;
@ -2045,7 +2045,7 @@ void module_impl::ctl_set_floatingpoint( std::string_view ctl, double value, boo
m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor ); m_sndFile->m_nFreqFactor = mpt::saturate_round<uint32_t>( 65536.0 * factor );
m_sndFile->RecalculateSamplesPerTick(); m_sndFile->RecalculateSamplesPerTick();
} else if ( ctl == "render.opl.volume_factor" ) { } else if ( ctl == "render.opl.volume_factor" ) {
m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( m_sndFile->m_OPLVolumeFactorScale ) ); m_sndFile->m_OPLVolumeFactor = mpt::saturate_round<int32>( value * static_cast<double>( CSoundFile::m_OPLVolumeFactorScale ) );
} else { } else {
MPT_ASSERT_NOTREACHED(); MPT_ASSERT_NOTREACHED();
} }

View File

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

View File

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

View File

@ -46,6 +46,7 @@ static const char * const license =
#include <limits> #include <limits>
#include <locale> #include <locale>
#include <map> #include <map>
#include <memory>
#include <random> #include <random>
#include <set> #include <set>
#include <sstream> #include <sstream>

View File

@ -42,7 +42,6 @@ enum RegionFlags
DLSREGION_SAMPLELOOP = 0x40, DLSREGION_SAMPLELOOP = 0x40,
DLSREGION_SELFNONEXCLUSIVE = 0x80, DLSREGION_SELFNONEXCLUSIVE = 0x80,
DLSREGION_SUSTAINLOOP = 0x100, DLSREGION_SUSTAINLOOP = 0x100,
DLSREGION_ISGLOBAL = 0x200,
}; };
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -376,7 +375,7 @@ enum SF2Generators : uint16
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// SF2 Structures Definitions // SF2 Structures Definitions
struct SFPRESETHEADER struct SFPresetHeader
{ {
char achPresetName[20]; char achPresetName[20];
uint16le wPreset; uint16le wPreset;
@ -387,49 +386,49 @@ struct SFPRESETHEADER
uint32le dwMorphology; uint32le dwMorphology;
}; };
MPT_BINARY_STRUCT(SFPRESETHEADER, 38) MPT_BINARY_STRUCT(SFPresetHeader, 38)
struct SFPRESETBAG struct SFPresetBag
{ {
uint16le wGenNdx; uint16le wGenNdx;
uint16le wModNdx; uint16le wModNdx;
}; };
MPT_BINARY_STRUCT(SFPRESETBAG, 4) MPT_BINARY_STRUCT(SFPresetBag, 4)
struct SFGENLIST struct SFGenList
{ {
uint16le sfGenOper; uint16le sfGenOper;
uint16le genAmount; uint16le genAmount;
}; };
MPT_BINARY_STRUCT(SFGENLIST, 4) MPT_BINARY_STRUCT(SFGenList, 4)
struct SFINST struct SFInst
{ {
char achInstName[20]; char achInstName[20];
uint16le wInstBagNdx; uint16le wInstBagNdx;
}; };
MPT_BINARY_STRUCT(SFINST, 22) MPT_BINARY_STRUCT(SFInst, 22)
struct SFINSTBAG struct SFInstBag
{ {
uint16le wGenNdx; uint16le wGenNdx;
uint16le wModNdx; uint16le wModNdx;
}; };
MPT_BINARY_STRUCT(SFINSTBAG, 4) MPT_BINARY_STRUCT(SFInstBag, 4)
struct SFINSTGENLIST struct SFInstGenList
{ {
uint16le sfGenOper; uint16le sfGenOper;
uint16le genAmount; uint16le genAmount;
}; };
MPT_BINARY_STRUCT(SFINSTGENLIST, 4) MPT_BINARY_STRUCT(SFInstGenList, 4)
struct SFSAMPLE struct SFSample
{ {
char achSampleName[20]; char achSampleName[20];
uint32le dwStart; uint32le dwStart;
@ -443,7 +442,7 @@ struct SFSAMPLE
uint16le sfSampleType; uint16le sfSampleType;
}; };
MPT_BINARY_STRUCT(SFSAMPLE, 46) MPT_BINARY_STRUCT(SFSample, 46)
// End of structures definitions // End of structures definitions
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
@ -581,11 +580,17 @@ bool CDLSBank::IsDLSBank(const mpt::PathString &filename)
const DLSINSTRUMENT *CDLSBank::FindInstrument(bool isDrum, uint32 bank, uint32 program, uint32 key, uint32 *pInsNo) const const DLSINSTRUMENT *CDLSBank::FindInstrument(bool isDrum, uint32 bank, uint32 program, uint32 key, uint32 *pInsNo) const
{ {
if(m_Instruments.empty()) // This helps finding the "more correct" instrument if we search for an instrument in any bank, and the higher-bank instruments appear first in the file
return nullptr; // Fixes issues when loading GeneralUser GS into OpenMPT's MIDI library.
for (uint32 iIns = 0; iIns < m_Instruments.size(); iIns++) std::vector<std::reference_wrapper<const DLSINSTRUMENT>> sortedInstr{m_Instruments.begin(), m_Instruments.end()};
if(bank >= 0x4000 || program >= 0x80)
{
std::sort(sortedInstr.begin(), sortedInstr.end(), [](const DLSINSTRUMENT &l, const DLSINSTRUMENT &r)
{ return std::tie(l.ulBank, l.ulInstrument) < std::tie(r.ulBank, r.ulInstrument); });
}
for(const DLSINSTRUMENT &dlsIns : sortedInstr)
{ {
const DLSINSTRUMENT &dlsIns = m_Instruments[iIns];
uint32 insbank = ((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F); uint32 insbank = ((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F);
if((bank >= 0x4000) || (insbank == bank)) if((bank >= 0x4000) || (insbank == bank))
{ {
@ -595,16 +600,16 @@ const DLSINSTRUMENT *CDLSBank::FindInstrument(bool isDrum, uint32 bank, uint32 p
{ {
for(const auto &region : dlsIns.Regions) for(const auto &region : dlsIns.Regions)
{ {
if(region.nWaveLink == Util::MaxValueOfType(region.nWaveLink)) if(region.IsDummy())
continue;
if(region.fuOptions & DLSREGION_ISGLOBAL)
continue; continue;
if((!key || key >= 0x80) if((!key || key >= 0x80)
|| (key >= region.uKeyMin && key <= region.uKeyMax)) || (key >= region.uKeyMin && key <= region.uKeyMax))
{ {
if(pInsNo) if(pInsNo)
*pInsNo = iIns; *pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns));
// cppcheck false-positive
// cppcheck-suppress returnDanglingLifetime
return &dlsIns; return &dlsIns;
} }
} }
@ -614,7 +619,9 @@ const DLSINSTRUMENT *CDLSBank::FindInstrument(bool isDrum, uint32 bank, uint32 p
if((program >= 0x80) || (program == (dlsIns.ulInstrument & 0x7F))) if((program >= 0x80) || (program == (dlsIns.ulInstrument & 0x7F)))
{ {
if(pInsNo) if(pInsNo)
*pInsNo = iIns; *pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns));
// cppcheck false-positive
// cppcheck-suppress returnDanglingLifetime
return &dlsIns; return &dlsIns;
} }
} }
@ -877,7 +884,7 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
case IFFID_phdr: case IFFID_phdr:
if(m_Instruments.empty()) if(m_Instruments.empty())
{ {
uint32 numIns = static_cast<uint32>(chunk.GetLength() / sizeof(SFPRESETHEADER)); uint32 numIns = static_cast<uint32>(chunk.GetLength() / sizeof(SFPresetHeader));
if(numIns <= 1) if(numIns <= 1)
break; break;
// The terminal sfPresetHeader record should never be accessed, and exists only to provide a terminal wPresetBagNdx with which to determine the number of zones in the last preset. // The terminal sfPresetHeader record should never be accessed, and exists only to provide a terminal wPresetBagNdx with which to determine the number of zones in the last preset.
@ -887,9 +894,9 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
#ifdef DLSBANK_LOG #ifdef DLSBANK_LOG
MPT_LOG(LogDebug, "DLSBank", mpt::format(U_("phdr: %1 instruments"))(m_Instruments.size())); MPT_LOG(LogDebug, "DLSBank", mpt::format(U_("phdr: %1 instruments"))(m_Instruments.size()));
#endif #endif
SFPRESETHEADER psfh; SFPresetHeader psfh;
chunk.ReadStruct(psfh); chunk.ReadStruct(psfh);
for (auto &dlsIns : m_Instruments) for(auto &dlsIns : m_Instruments)
{ {
mpt::String::WriteAutoBuf(dlsIns.szName) = mpt::String::ReadAutoBuf(psfh.achPresetName); mpt::String::WriteAutoBuf(dlsIns.szName) = mpt::String::ReadAutoBuf(psfh.achPresetName);
dlsIns.ulInstrument = psfh.wPreset & 0x7F; dlsIns.ulInstrument = psfh.wPreset & 0x7F;
@ -903,7 +910,7 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
break; break;
case IFFID_pbag: case IFFID_pbag:
if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFPRESETBAG))) if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFPresetBag)))
{ {
sf2info.presetBags = chunk.GetChunk(chunk.BytesLeft()); sf2info.presetBags = chunk.GetChunk(chunk.BytesLeft());
} }
@ -913,7 +920,7 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
break; break;
case IFFID_pgen: case IFFID_pgen:
if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFGENLIST))) if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFGenList)))
{ {
sf2info.presetGens = chunk.GetChunk(chunk.BytesLeft()); sf2info.presetGens = chunk.GetChunk(chunk.BytesLeft());
} }
@ -923,21 +930,21 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
break; break;
case IFFID_inst: case IFFID_inst:
if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFINST))) if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInst)))
{ {
sf2info.insts = chunk.GetChunk(chunk.BytesLeft()); sf2info.insts = chunk.GetChunk(chunk.BytesLeft());
} }
break; break;
case IFFID_ibag: case IFFID_ibag:
if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFINSTBAG))) if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInstBag)))
{ {
sf2info.instBags = chunk.GetChunk(chunk.BytesLeft()); sf2info.instBags = chunk.GetChunk(chunk.BytesLeft());
} }
break; break;
case IFFID_igen: case IFFID_igen:
if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFINSTGENLIST))) if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInstGenList)))
{ {
sf2info.instGens = chunk.GetChunk(chunk.BytesLeft()); sf2info.instGens = chunk.GetChunk(chunk.BytesLeft());
} }
@ -946,7 +953,7 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
case IFFID_shdr: case IFFID_shdr:
if (m_SamplesEx.empty()) if (m_SamplesEx.empty())
{ {
uint32 numSmp = static_cast<uint32>(chunk.GetLength() / sizeof(SFSAMPLE)); uint32 numSmp = static_cast<uint32>(chunk.GetLength() / sizeof(SFSample));
if (numSmp < 1) break; if (numSmp < 1) break;
m_SamplesEx.resize(numSmp); m_SamplesEx.resize(numSmp);
m_WaveForms.resize(numSmp); m_WaveForms.resize(numSmp);
@ -956,7 +963,7 @@ bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &heade
for (uint32 i = 0; i < numSmp; i++) for (uint32 i = 0; i < numSmp; i++)
{ {
SFSAMPLE p; SFSample p;
chunk.ReadStruct(p); chunk.ReadStruct(p);
DLSSAMPLEEX &dlsSmp = m_SamplesEx[i]; DLSSAMPLEEX &dlsSmp = m_SamplesEx[i];
mpt::String::WriteAutoBuf(dlsSmp.szName) = mpt::String::ReadAutoBuf(p.achSampleName); mpt::String::WriteAutoBuf(dlsSmp.szName) = mpt::String::ReadAutoBuf(p.achSampleName);
@ -1006,16 +1013,16 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
if (m_Instruments.empty() || m_SamplesEx.empty()) if (m_Instruments.empty() || m_SamplesEx.empty())
return false; return false;
const uint32 numPresetBags = static_cast<uint32>(sf2info.presetBags.GetLength() / sizeof(SFPRESETBAG)); const uint32 numInsts = static_cast<uint32>(sf2info.insts.GetLength() / sizeof(SFInst));
const uint32 numPresetGens = static_cast<uint32>(sf2info.presetGens.GetLength() / sizeof(SFGENLIST)); const uint32 numInstBags = static_cast<uint32>(sf2info.instBags.GetLength() / sizeof(SFInstBag));
const uint32 numInsts = static_cast<uint32>(sf2info.insts.GetLength() / sizeof(SFINST));
const uint32 numInstBags = static_cast<uint32>(sf2info.instBags.GetLength() / sizeof(SFINSTBAG));
const uint32 numInstGens = static_cast<uint32>(sf2info.instGens.GetLength() / sizeof(SFINSTGENLIST));
for (auto &dlsIns : m_Instruments) std::vector<std::pair<uint16, uint16>> instruments; // instrument, key range
std::vector<SFGenList> generators;
std::vector<SFInstGenList> instrGenerators;
for(auto &dlsIns : m_Instruments)
{ {
instruments.clear();
DLSENVELOPE dlsEnv; DLSENVELOPE dlsEnv;
std::vector<uint32> instruments;
int32 instrAttenuation = 0; int32 instrAttenuation = 0;
// Default Envelope Values // Default Envelope Values
dlsEnv.wVolAttack = 0; dlsEnv.wVolAttack = 0;
@ -1024,21 +1031,21 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
dlsEnv.nVolSustainLevel = 128; dlsEnv.nVolSustainLevel = 128;
dlsEnv.nDefPan = 128; dlsEnv.nDefPan = 128;
// Load Preset Bags // Load Preset Bags
sf2info.presetBags.Seek(dlsIns.wPresetBagNdx * sizeof(SFPRESETBAG)); sf2info.presetBags.Seek(dlsIns.wPresetBagNdx * sizeof(SFPresetBag));
for (uint32 ipbagcnt=0; ipbagcnt<(uint32)dlsIns.wPresetBagNum; ipbagcnt++) for(uint32 ipbagcnt = 0; ipbagcnt < dlsIns.wPresetBagNum; ipbagcnt++)
{ {
// Load generators for each preset bag // Load generators for each preset bag
SFPRESETBAG bag[2]; SFPresetBag bag[2];
if(!sf2info.presetBags.ReadArray(bag)) if(!sf2info.presetBags.ReadArray(bag))
break; break;
sf2info.presetBags.SkipBack(sizeof(SFPRESETBAG)); sf2info.presetBags.SkipBack(sizeof(SFPresetBag));
sf2info.presetGens.Seek(bag[0].wGenNdx * sizeof(SFGENLIST)); sf2info.presetGens.Seek(bag[0].wGenNdx * sizeof(SFGenList));
for (uint32 ipgenndx = bag[0].wGenNdx; ipgenndx < bag[1].wGenNdx; ipgenndx++) uint16 keyRange = 0xFFFF;
if(!sf2info.presetGens.ReadVector(generators, bag[1].wGenNdx - bag[0].wGenNdx))
continue;
for(const auto &gen : generators)
{ {
SFGENLIST gen;
if(!sf2info.presetGens.ReadStruct(gen))
break;
switch(gen.sfGenOper) switch(gen.sfGenOper)
{ {
case SF2_GEN_ATTACKVOLENV: case SF2_GEN_ATTACKVOLENV:
@ -1058,8 +1065,12 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
dlsEnv.wVolRelease = SF2TimeToDLS(gen.genAmount); dlsEnv.wVolRelease = SF2TimeToDLS(gen.genAmount);
break; break;
case SF2_GEN_INSTRUMENT: case SF2_GEN_INSTRUMENT:
if(std::find(instruments.begin(), instruments.end(), gen.genAmount) == instruments.end()) if(const auto instr = std::make_pair(gen.genAmount.get(), keyRange); std::find(instruments.begin(), instruments.end(), instr) == instruments.end())
instruments.push_back(gen.genAmount); instruments.push_back(instr);
keyRange = 0xFFFF;
break;
case SF2_GEN_KEYRANGE:
keyRange = gen.genAmount;
break; break;
case SF2_GEN_ATTENUATION: case SF2_GEN_ATTENUATION:
instrAttenuation = -static_cast<int16>(gen.genAmount); instrAttenuation = -static_cast<int16>(gen.genAmount);
@ -1081,22 +1092,35 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
} }
// Load Instrument Bags // Load Instrument Bags
dlsIns.Regions.clear(); dlsIns.Regions.clear();
for(const auto nInstrNdx : instruments) for(const auto [nInstrNdx, keyRange] : instruments)
{ {
if(nInstrNdx >= numInsts) if(nInstrNdx >= numInsts)
continue; continue;
sf2info.insts.Seek(nInstrNdx * sizeof(SFINST)); sf2info.insts.Seek(nInstrNdx * sizeof(SFInst));
SFINST insts[2]; SFInst insts[2];
sf2info.insts.ReadArray(insts); sf2info.insts.ReadArray(insts);
const auto startRegion = static_cast<uint32>(dlsIns.Regions.size()); const uint32 numRegions = insts[1].wInstBagNdx - insts[0].wInstBagNdx;
const auto endRegion = startRegion + insts[1].wInstBagNdx - insts[0].wInstBagNdx; dlsIns.Regions.reserve(dlsIns.Regions.size() + numRegions);
dlsIns.Regions.resize(endRegion);
//Log("\nIns %3d, %2d regions:\n", nIns, pSmp->nRegions); //Log("\nIns %3d, %2d regions:\n", nIns, pSmp->nRegions);
DLSREGION *pRgn = &dlsIns.Regions[startRegion]; DLSREGION globalZone{};
bool hasGlobalZone = false; globalZone.uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note
for(uint32 nRgn = startRegion; nRgn < endRegion; nRgn++, pRgn++) globalZone.tuning = 100;
globalZone.sFineTune = 0;
globalZone.nWaveLink = Util::MaxValueOfType(globalZone.nWaveLink);
if(keyRange != 0xFFFF)
{ {
uint32 ibagcnt = insts[0].wInstBagNdx + nRgn - startRegion; globalZone.uKeyMin = static_cast<uint8>(keyRange & 0xFF);
globalZone.uKeyMax = static_cast<uint8>(keyRange >> 8);
if(globalZone.uKeyMin > globalZone.uKeyMax)
std::swap(globalZone.uKeyMin, globalZone.uKeyMax);
} else
{
globalZone.uKeyMin = 0;
globalZone.uKeyMax = 127;
}
for(uint32 nRgn = 0; nRgn < numRegions; nRgn++)
{
uint32 ibagcnt = insts[0].wInstBagNdx + nRgn;
if(ibagcnt >= numInstBags) if(ibagcnt >= numInstBags)
break; break;
// Create a new envelope for drums // Create a new envelope for drums
@ -1105,42 +1129,42 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
{ {
pDlsEnv = &m_Envelopes[dlsIns.nMelodicEnv - 1]; pDlsEnv = &m_Envelopes[dlsIns.nMelodicEnv - 1];
} }
DLSREGION rgn = globalZone;
// Region Default Values // Region Default Values
int32 regionAttn = 0; int32 regionAttn = 0;
pRgn->uKeyMin = 0;
pRgn->uKeyMax = 127;
pRgn->uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note
pRgn->tuning = 100;
pRgn->sFineTune = 0;
pRgn->nWaveLink = Util::MaxValueOfType(pRgn->nWaveLink);
if(hasGlobalZone)
*pRgn = dlsIns.Regions[startRegion];
// Load Generators // Load Generators
sf2info.instBags.Seek(ibagcnt * sizeof(SFINSTBAG)); sf2info.instBags.Seek(ibagcnt * sizeof(SFInstBag));
SFINSTBAG bags[2]; SFInstBag bags[2];
sf2info.instBags.ReadArray(bags); sf2info.instBags.ReadArray(bags);
sf2info.instGens.Seek(bags[0].wGenNdx * sizeof(SFINSTGENLIST)); sf2info.instGens.Seek(bags[0].wGenNdx * sizeof(SFInstGenList));
uint16 lastOp = SF2_GEN_SAMPLEID; uint16 lastOp = SF2_GEN_SAMPLEID;
int32 loopStart = 0, loopEnd = 0; int32 loopStart = 0, loopEnd = 0;
for(uint32 igenndx = bags[0].wGenNdx; igenndx < bags[1].wGenNdx; igenndx++) if(!sf2info.instGens.ReadVector(instrGenerators, bags[1].wGenNdx - bags[0].wGenNdx))
break;
for(const auto &gen : instrGenerators)
{ {
if(igenndx >= numInstGens)
break;
SFINSTGENLIST gen;
sf2info.instGens.ReadStruct(gen);
uint16 value = gen.genAmount; uint16 value = gen.genAmount;
lastOp = gen.sfGenOper; lastOp = gen.sfGenOper;
switch(gen.sfGenOper) switch(gen.sfGenOper)
{ {
case SF2_GEN_KEYRANGE: case SF2_GEN_KEYRANGE:
pRgn->uKeyMin = (uint8)(value & 0xFF); {
pRgn->uKeyMax = (uint8)(value >> 8); uint8 keyMin = static_cast<uint8>(value & 0xFF);
if(pRgn->uKeyMin > pRgn->uKeyMax) uint8 keyMax = static_cast<uint8>(value >> 8);
std::swap(pRgn->uKeyMin, pRgn->uKeyMax); if(keyMin > keyMax)
std::swap(keyMin, keyMax);
rgn.uKeyMin = std::max(rgn.uKeyMin, keyMin);
rgn.uKeyMax = std::min(rgn.uKeyMax, keyMax);
// There was no overlap between instrument region and preset region - skip it
if(rgn.uKeyMin > rgn.uKeyMax)
rgn.uKeyMin = rgn.uKeyMax = 0xFF;
}
break; break;
case SF2_GEN_UNITYNOTE: case SF2_GEN_UNITYNOTE:
if (value < 128) pRgn->uUnityNote = (uint8)value; if (value < 128) rgn.uUnityNote = (uint8)value;
break; break;
case SF2_GEN_ATTACKVOLENV: case SF2_GEN_ATTACKVOLENV:
pDlsEnv->wVolAttack = SF2TimeToDLS(gen.genAmount); pDlsEnv->wVolAttack = SF2TimeToDLS(gen.genAmount);
@ -1162,7 +1186,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
{ {
int32 pan = static_cast<int16>(value); int32 pan = static_cast<int16>(value);
pan = std::clamp(Util::muldivr(pan + 500, 256, 1000), 0, 256); pan = std::clamp(Util::muldivr(pan + 500, 256, 1000), 0, 256);
pRgn->panning = static_cast<int16>(pan); rgn.panning = static_cast<int16>(pan);
pDlsEnv->nDefPan = mpt::saturate_cast<uint8>(pan); pDlsEnv->nDefPan = mpt::saturate_cast<uint8>(pan);
} }
break; break;
@ -1172,33 +1196,33 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
case SF2_GEN_SAMPLEID: case SF2_GEN_SAMPLEID:
if (value < m_SamplesEx.size()) if (value < m_SamplesEx.size())
{ {
pRgn->nWaveLink = value; rgn.nWaveLink = value;
pRgn->ulLoopStart = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwStartloop + loopStart); rgn.ulLoopStart = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwStartloop + loopStart);
pRgn->ulLoopEnd = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwEndloop + loopEnd); rgn.ulLoopEnd = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwEndloop + loopEnd);
} }
break; break;
case SF2_GEN_SAMPLEMODES: case SF2_GEN_SAMPLEMODES:
value &= 3; value &= 3;
pRgn->fuOptions &= uint16(~(DLSREGION_SAMPLELOOP|DLSREGION_PINGPONGLOOP|DLSREGION_SUSTAINLOOP)); rgn.fuOptions &= uint16(~(DLSREGION_SAMPLELOOP|DLSREGION_PINGPONGLOOP|DLSREGION_SUSTAINLOOP));
if(value == 1) if(value == 1)
pRgn->fuOptions |= DLSREGION_SAMPLELOOP; rgn.fuOptions |= DLSREGION_SAMPLELOOP;
else if(value == 2) else if(value == 2)
pRgn->fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_PINGPONGLOOP; rgn.fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_PINGPONGLOOP;
else if(value == 3) else if(value == 3)
pRgn->fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_SUSTAINLOOP; rgn.fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_SUSTAINLOOP;
pRgn->fuOptions |= DLSREGION_OVERRIDEWSMP; rgn.fuOptions |= DLSREGION_OVERRIDEWSMP;
break; break;
case SF2_GEN_KEYGROUP: case SF2_GEN_KEYGROUP:
pRgn->fuOptions |= (value & DLSREGION_KEYGROUPMASK); rgn.fuOptions |= (value & DLSREGION_KEYGROUPMASK);
break; break;
case SF2_GEN_COARSETUNE: case SF2_GEN_COARSETUNE:
pRgn->sFineTune += static_cast<int16>(value) * 128; rgn.sFineTune += static_cast<int16>(value) * 128;
break; break;
case SF2_GEN_FINETUNE: case SF2_GEN_FINETUNE:
pRgn->sFineTune += static_cast<int16>(Util::muldiv(static_cast<int8>(value), 128, 100)); rgn.sFineTune += static_cast<int16>(Util::muldiv(static_cast<int8>(value), 128, 100));
break; break;
case SF2_GEN_SCALE_TUNING: case SF2_GEN_SCALE_TUNING:
pRgn->tuning = mpt::saturate_cast<uint8>(value); rgn.tuning = mpt::saturate_cast<uint8>(value);
break; break;
case SF2_GEN_START_LOOP_FINE: case SF2_GEN_START_LOOP_FINE:
loopStart += static_cast<int16>(value); loopStart += static_cast<int16>(value);
@ -1216,14 +1240,14 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
// Log(" gen=%d value=%04X\n", pgen->sfGenOper, pgen->genAmount); // Log(" gen=%d value=%04X\n", pgen->sfGenOper, pgen->genAmount);
} }
} }
if(lastOp != SF2_GEN_SAMPLEID && nRgn == startRegion)
{
hasGlobalZone = true;
pRgn->fuOptions |= DLSREGION_ISGLOBAL;
}
int32 linearVol = DLS32BitRelativeGainToLinear(((instrAttenuation + regionAttn) * 65536) / 10) / 256; int32 linearVol = DLS32BitRelativeGainToLinear(((instrAttenuation + regionAttn) * 65536) / 10) / 256;
Limit(linearVol, 16, 256); Limit(linearVol, 16, 256);
pRgn->usVolume = static_cast<uint16>(linearVol); rgn.usVolume = static_cast<uint16>(linearVol);
if(lastOp != SF2_GEN_SAMPLEID && nRgn == 0)
globalZone = rgn;
else if(!rgn.IsDummy())
dlsIns.Regions.push_back(rgn);
//Log("\n"); //Log("\n");
} }
} }
@ -1399,10 +1423,10 @@ bool CDLSBank::Open(FileReader file)
uint32 subID = listChunk.ReadUint32LE(); uint32 subID = listChunk.ReadUint32LE();
if ((subID == IFFID_ins) && (nInsDef < m_Instruments.size())) if ((subID == IFFID_ins) && (nInsDef < m_Instruments.size()))
{ {
DLSINSTRUMENT *pDlsIns = &m_Instruments[nInsDef]; DLSINSTRUMENT &dlsIns = m_Instruments[nInsDef];
//Log("Instrument %d:\n", nInsDef); //Log("Instrument %d:\n", nInsDef);
pDlsIns->Regions.push_back({}); dlsIns.Regions.push_back({});
UpdateInstrumentDefinition(pDlsIns, subData); UpdateInstrumentDefinition(&dlsIns, subData);
nInsDef++; nInsDef++;
} }
} else } else
@ -1483,7 +1507,7 @@ bool CDLSBank::Open(FileReader file)
} }
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
// Extracts the WaveForms from a DLS bank // Extracts the Waveforms from a DLS/SF2 bank
uint32 CDLSBank::GetRegionFromKey(uint32 nIns, uint32 nKey) const uint32 CDLSBank::GetRegionFromKey(uint32 nIns, uint32 nKey) const
{ {
@ -1492,9 +1516,10 @@ uint32 CDLSBank::GetRegionFromKey(uint32 nIns, uint32 nKey) const
const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; const DLSINSTRUMENT &dlsIns = m_Instruments[nIns];
for(uint32 rgn = 0; rgn < static_cast<uint32>(dlsIns.Regions.size()); rgn++) for(uint32 rgn = 0; rgn < static_cast<uint32>(dlsIns.Regions.size()); rgn++)
{ {
if(nKey < dlsIns.Regions[rgn].uKeyMin || nKey > dlsIns.Regions[rgn].uKeyMax) const auto &region = dlsIns.Regions[rgn];
if(nKey < region.uKeyMin || nKey > region.uKeyMax)
continue; continue;
if(dlsIns.Regions[rgn].fuOptions & DLSREGION_ISGLOBAL) if(region.nWaveLink == Util::MaxValueOfType(region.nWaveLink))
continue; continue;
return rgn; return rgn;
} }
@ -1590,8 +1615,8 @@ bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nI
if(nIns >= m_Instruments.size()) if(nIns >= m_Instruments.size())
return false; return false;
const DLSINSTRUMENT *pDlsIns = &m_Instruments[nIns]; const DLSINSTRUMENT &dlsIns = m_Instruments[nIns];
if(nRgn >= pDlsIns->Regions.size()) if(nRgn >= dlsIns.Regions.size())
return false; return false;
if(!ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen)) if(!ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen))
return false; return false;
@ -1603,7 +1628,7 @@ bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nI
if (m_nType & SOUNDBANK_TYPE_SF2) if (m_nType & SOUNDBANK_TYPE_SF2)
{ {
sndFile.DestroySample(nSample); sndFile.DestroySample(nSample);
uint32 nWaveLink = pDlsIns->Regions[nRgn].nWaveLink; uint32 nWaveLink = dlsIns.Regions[nRgn].nWaveLink;
ModSample &sample = sndFile.GetSample(nSample); ModSample &sample = sndFile.GetSample(nSample);
if (sndFile.m_nSamples < nSample) sndFile.m_nSamples = nSample; if (sndFile.m_nSamples < nSample) sndFile.m_nSamples = nSample;
if (nWaveLink < m_SamplesEx.size()) if (nWaveLink < m_SamplesEx.size())
@ -1614,15 +1639,15 @@ bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nI
#endif #endif
sample.Initialize(); sample.Initialize();
sample.nLength = dwLen / 2; sample.nLength = dwLen / 2;
sample.nLoopStart = pDlsIns->Regions[nRgn].ulLoopStart; sample.nLoopStart = dlsIns.Regions[nRgn].ulLoopStart;
sample.nLoopEnd = pDlsIns->Regions[nRgn].ulLoopEnd; sample.nLoopEnd = dlsIns.Regions[nRgn].ulLoopEnd;
sample.nC5Speed = p.dwSampleRate; sample.nC5Speed = p.dwSampleRate;
sample.RelativeTone = p.byOriginalPitch; sample.RelativeTone = p.byOriginalPitch;
sample.nFineTune = p.chPitchCorrection; sample.nFineTune = p.chPitchCorrection;
if (p.szName[0]) if (p.szName[0])
sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(p.szName); sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(p.szName);
else if(pDlsIns->szName[0]) else if(dlsIns.szName[0])
sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(pDlsIns->szName); sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(dlsIns.szName);
FileReader chunk(mpt::as_span(pWaveForm.data(), dwLen)); FileReader chunk(mpt::as_span(pWaveForm.data(), dwLen));
SampleIO( SampleIO(
@ -1637,13 +1662,13 @@ bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nI
{ {
FileReader file(mpt::as_span(pWaveForm.data(), dwLen)); FileReader file(mpt::as_span(pWaveForm.data(), dwLen));
hasWaveform = sndFile.ReadWAVSample(nSample, file, false, &wsmpChunk); hasWaveform = sndFile.ReadWAVSample(nSample, file, false, &wsmpChunk);
if(pDlsIns->szName[0]) if(dlsIns.szName[0])
sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(pDlsIns->szName); sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(dlsIns.szName);
} }
if (hasWaveform) if (hasWaveform)
{ {
ModSample &sample = sndFile.GetSample(nSample); ModSample &sample = sndFile.GetSample(nSample);
const DLSREGION &rgn = pDlsIns->Regions[nRgn]; const DLSREGION &rgn = dlsIns.Regions[nRgn];
sample.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); sample.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
if (rgn.fuOptions & DLSREGION_SAMPLELOOP) sample.uFlags.set(CHN_LOOP); if (rgn.fuOptions & DLSREGION_SAMPLELOOP) sample.uFlags.set(CHN_LOOP);
if (rgn.fuOptions & DLSREGION_SUSTAINLOOP) sample.uFlags.set(CHN_SUSTAINLOOP); if (rgn.fuOptions & DLSREGION_SUSTAINLOOP) sample.uFlags.set(CHN_SUSTAINLOOP);
@ -1725,35 +1750,34 @@ static uint16 ScaleEnvelope(uint32 time, float tempoScale)
bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, uint32 nIns, uint32 nDrumRgn) const bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, uint32 nIns, uint32 nDrumRgn) const
{ {
uint32 nRgnMin, nRgnMax, nEnv; uint32 minRegion, maxRegion, nEnv;
if (nIns >= m_Instruments.size()) return false; if (nIns >= m_Instruments.size())
const DLSINSTRUMENT *pDlsIns = &m_Instruments[nIns]; return false;
std::vector<SAMPLEINDEX> RgnToSmp(pDlsIns->Regions.size()); const DLSINSTRUMENT &dlsIns = m_Instruments[nIns];
if (pDlsIns->ulBank & F_INSTRUMENT_DRUMS) const bool isDrum = (dlsIns.ulBank & F_INSTRUMENT_DRUMS);
if(isDrum)
{ {
if(nDrumRgn >= pDlsIns->Regions.size()) if(nDrumRgn >= dlsIns.Regions.size())
return false; return false;
nRgnMin = nDrumRgn; minRegion = nDrumRgn;
nRgnMax = nDrumRgn+1; maxRegion = nDrumRgn + 1;
nEnv = pDlsIns->Regions[nDrumRgn].uPercEnv; nEnv = dlsIns.Regions[nDrumRgn].uPercEnv;
} else } else
{ {
if(pDlsIns->Regions.empty()) if(dlsIns.Regions.empty())
return false; return false;
nRgnMin = 0; minRegion = 0;
nRgnMax = static_cast<uint32>(pDlsIns->Regions.size()); maxRegion = static_cast<uint32>(dlsIns.Regions.size());
nEnv = pDlsIns->nMelodicEnv; nEnv = dlsIns.nMelodicEnv;
} }
if(nRgnMin == 0 && (pDlsIns->Regions[0].fuOptions & DLSREGION_ISGLOBAL))
nRgnMin++;
#ifdef DLSINSTR_LOG #ifdef DLSINSTR_LOG
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_("DLS Instrument #%1: %2"))(nIns, mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(pDlsIns->szName)))); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_("DLS Instrument #%1: %2"))(nIns, mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(dlsIns.szName))));
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Bank=0x%1 Instrument=0x%2"))(mpt::ufmt::HEX0<4>(pDlsIns->ulBank), mpt::ufmt::HEX0<4>(pDlsIns->ulInstrument))); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Bank=0x%1 Instrument=0x%2"))(mpt::ufmt::HEX0<4>(dlsIns.ulBank), mpt::ufmt::HEX0<4>(dlsIns.ulInstrument)));
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" %1 regions, nMelodicEnv=%2"))(pDlsIns->Regions.size(), pDlsIns->nMelodicEnv)); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" %1 regions, nMelodicEnv=%2"))(dlsIns.Regions.size(), dlsIns.nMelodicEnv));
for (uint32 iDbg=0; iDbg<pDlsIns->Regions.size(); iDbg++) for (uint32 iDbg=0; iDbg<dlsIns.Regions.size(); iDbg++)
{ {
const DLSREGION *prgn = &pDlsIns->Regions[iDbg]; const DLSREGION *prgn = &dlsIns.Regions[iDbg];
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Region %1:"))(iDbg)); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Region %1:"))(iDbg));
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" WaveLink = %1 (loop [%2, %3])"))(prgn->nWaveLink, prgn->ulLoopStart, prgn->ulLoopEnd)); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" WaveLink = %1 (loop [%2, %3])"))(prgn->nWaveLink, prgn->ulLoopStart, prgn->ulLoopEnd));
MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Key Range: [%1, %2]"))(prgn->uKeyMin, prgn->uKeyMax)); MPT_LOG(LogDebug, "DLSINSTR", mpt::format(U_(" Key Range: [%1, %2]"))(prgn->uKeyMin, prgn->uKeyMax));
@ -1768,57 +1792,57 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
return false; return false;
} }
if (sndFile.Instruments[nInstr]) if(sndFile.Instruments[nInstr])
{ {
sndFile.DestroyInstrument(nInstr, deleteAssociatedSamples); sndFile.DestroyInstrument(nInstr, deleteAssociatedSamples);
} }
// Initializes Instrument // Initializes Instrument
if (pDlsIns->ulBank & F_INSTRUMENT_DRUMS) if(isDrum)
{ {
uint32 key = pDlsIns->Regions[nDrumRgn].uKeyMin; uint32 key = dlsIns.Regions[nDrumRgn].uKeyMin;
if((key >= 24) && (key <= 84)) if((key >= 24) && (key <= 84))
{ {
std::string s = szMidiPercussionNames[key-24]; std::string s = szMidiPercussionNames[key-24];
if(!mpt::String::ReadAutoBuf(pDlsIns->szName).empty()) if(!mpt::String::ReadAutoBuf(dlsIns.szName).empty())
{ {
s += mpt::format(" (%1)")(mpt::String::RTrim<std::string>(mpt::String::ReadAutoBuf(pDlsIns->szName))); s += mpt::format(" (%1)")(mpt::String::RTrim<std::string>(mpt::String::ReadAutoBuf(dlsIns.szName)));
} }
pIns->name = s; pIns->name = s;
} else } else
{ {
pIns->name = mpt::String::ReadAutoBuf(pDlsIns->szName); pIns->name = mpt::String::ReadAutoBuf(dlsIns.szName);
} }
} else } else
{ {
pIns->name = mpt::String::ReadAutoBuf(pDlsIns->szName); pIns->name = mpt::String::ReadAutoBuf(dlsIns.szName);
} }
int nTranspose = 0; int transpose = 0;
if(pDlsIns->ulBank & F_INSTRUMENT_DRUMS) if(isDrum)
{ {
for(uint32 iNoteMap = 0; iNoteMap < NOTE_MAX; iNoteMap++) for(uint32 iNoteMap = 0; iNoteMap < NOTE_MAX; iNoteMap++)
{ {
if(sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MID | MOD_TYPE_MPT)) if(sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MID | MOD_TYPE_MPT))
{ {
// Format has instrument note mapping // Format has instrument note mapping
if(pDlsIns->Regions[nDrumRgn].tuning == 0) if(dlsIns.Regions[nDrumRgn].tuning == 0)
pIns->NoteMap[iNoteMap] = NOTE_MIDDLEC; pIns->NoteMap[iNoteMap] = NOTE_MIDDLEC;
else if(iNoteMap < pDlsIns->Regions[nDrumRgn].uKeyMin) else if (iNoteMap < dlsIns.Regions[nDrumRgn].uKeyMin)
pIns->NoteMap[iNoteMap] = (uint8)(pDlsIns->Regions[nDrumRgn].uKeyMin + NOTE_MIN); pIns->NoteMap[iNoteMap] = (uint8)(dlsIns.Regions[nDrumRgn].uKeyMin + NOTE_MIN);
else if(iNoteMap > pDlsIns->Regions[nDrumRgn].uKeyMax) else if(iNoteMap > dlsIns.Regions[nDrumRgn].uKeyMax)
pIns->NoteMap[iNoteMap] = (uint8)(pDlsIns->Regions[nDrumRgn].uKeyMax + NOTE_MIN); pIns->NoteMap[iNoteMap] = (uint8)(dlsIns.Regions[nDrumRgn].uKeyMax + NOTE_MIN);
} else } else
{ {
if(iNoteMap == pDlsIns->Regions[nDrumRgn].uKeyMin) if(iNoteMap == dlsIns.Regions[nDrumRgn].uKeyMin)
{ {
nTranspose = (pDlsIns->Regions[nDrumRgn].uKeyMin + (pDlsIns->Regions[nDrumRgn].uKeyMax - pDlsIns->Regions[nDrumRgn].uKeyMin) / 2) - 60; transpose = (dlsIns.Regions[nDrumRgn].uKeyMin + (dlsIns.Regions[nDrumRgn].uKeyMax - dlsIns.Regions[nDrumRgn].uKeyMin) / 2) - 60;
} }
} }
} }
} }
pIns->nFadeOut = 1024; pIns->nFadeOut = 1024;
pIns->nMidiProgram = (uint8)(pDlsIns->ulInstrument & 0x7F) + 1; pIns->nMidiProgram = (uint8)(dlsIns.ulInstrument & 0x7F) + 1;
pIns->nMidiChannel = (uint8)((pDlsIns->ulBank & F_INSTRUMENT_DRUMS) ? 10 : 0); pIns->nMidiChannel = (uint8)(isDrum ? 10 : 0);
pIns->wMidiBank = (uint16)(((pDlsIns->ulBank & 0x7F00) >> 1) | (pDlsIns->ulBank & 0x7F)); pIns->wMidiBank = (uint16)(((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F));
pIns->nNNA = NNA_NOTEOFF; pIns->nNNA = NNA_NOTEOFF;
pIns->nDCT = DCT_NOTE; pIns->nDCT = DCT_NOTE;
pIns->nDNA = DNA_NOTEFADE; pIns->nDNA = DNA_NOTEFADE;
@ -1826,31 +1850,38 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
uint32 nLoadedSmp = 0; uint32 nLoadedSmp = 0;
SAMPLEINDEX nextSample = 0; SAMPLEINDEX nextSample = 0;
// Extract Samples // Extract Samples
for (uint32 nRgn=nRgnMin; nRgn<nRgnMax; nRgn++) std::vector<SAMPLEINDEX> RgnToSmp(dlsIns.Regions.size());
std::set<uint16> extractedSamples;
for(uint32 nRgn = minRegion; nRgn < maxRegion; nRgn++)
{ {
bool duplicateRegion = false; bool duplicateRegion = false;
SAMPLEINDEX nSmp = 0; SAMPLEINDEX nSmp = 0;
const DLSREGION *pRgn = &pDlsIns->Regions[nRgn]; const DLSREGION &rgn = dlsIns.Regions[nRgn];
if(rgn.IsDummy())
continue;
// Elimitate Duplicate Regions // Elimitate Duplicate Regions
uint32 iDup; uint32 dupRegion;
for (iDup=nRgnMin; iDup<nRgn; iDup++) for(dupRegion = minRegion; dupRegion < nRgn; dupRegion++)
{ {
const DLSREGION *pRgn2 = &pDlsIns->Regions[iDup]; const DLSREGION &rgn2 = dlsIns.Regions[dupRegion];
if (((pRgn2->nWaveLink == pRgn->nWaveLink) if(RgnToSmp[dupRegion] == 0 || rgn2.IsDummy())
&& (pRgn2->ulLoopEnd == pRgn->ulLoopEnd) continue;
&& (pRgn2->ulLoopStart == pRgn->ulLoopStart)) // No need to extract the same sample data twice
|| ((pRgn2->uKeyMin == pRgn->uKeyMin) const bool sameSample = (rgn2.nWaveLink == rgn.nWaveLink) && (rgn2.ulLoopEnd == rgn.ulLoopEnd) && (rgn2.ulLoopStart == rgn.ulLoopStart) && extractedSamples.count(rgn.nWaveLink);
&& (pRgn2->uKeyMax == pRgn->uKeyMax))) // Candidate for stereo sample creation
const bool sameKeyRange = (rgn2.uKeyMin == rgn.uKeyMin) && (rgn2.uKeyMax == rgn.uKeyMax);
if(sameSample || sameKeyRange)
{ {
duplicateRegion = true; duplicateRegion = true;
nSmp = RgnToSmp[iDup]; if(!sameKeyRange)
nSmp = RgnToSmp[dupRegion];
break; break;
} }
} }
// Create a new sample // Create a new sample
if (!duplicateRegion) if (!duplicateRegion)
{ {
uint32 nmaxsmp = (m_nType & MOD_TYPE_XM) ? 16 : 32; uint32 nmaxsmp = (m_nType & MOD_TYPE_XM) ? 16 : (NOTE_MAX - NOTE_MIN + 1);
if (nLoadedSmp >= nmaxsmp) if (nLoadedSmp >= nmaxsmp)
{ {
nSmp = RgnToSmp[nRgn - 1]; nSmp = RgnToSmp[nRgn - 1];
@ -1866,40 +1897,41 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
RgnToSmp[nRgn] = nSmp; RgnToSmp[nRgn] = nSmp;
// Map all notes to the right sample // Map all notes to the right sample
if (nSmp) if(nSmp)
{ {
for (uint32 iKey=0; iKey<NOTE_MAX; iKey++) for(uint8 key = 0; key < NOTE_MAX; key++)
{ {
if ((nRgn == nRgnMin) || ((iKey >= pRgn->uKeyMin) && (iKey <= pRgn->uKeyMax))) if(isDrum || (key >= rgn.uKeyMin && key <= rgn.uKeyMax))
{ {
pIns->Keyboard[iKey] = nSmp; pIns->Keyboard[key] = nSmp;
} }
} }
// Load the sample // Load the sample
if(!duplicateRegion || !sndFile.GetSample(nSmp).HasSampleData()) if(!duplicateRegion || !sndFile.GetSample(nSmp).HasSampleData())
{ {
ExtractSample(sndFile, nSmp, nIns, nRgn, nTranspose); ExtractSample(sndFile, nSmp, nIns, nRgn, transpose);
} else if(sndFile.GetSample(nSmp).GetNumChannels() == 1) extractedSamples.insert(rgn.nWaveLink);
}
} else if(duplicateRegion && sndFile.GetSample(nSmp).GetNumChannels() == 1)
{
// Try to combine stereo samples
const uint16 pan1 = GetPanning(nIns, nRgn), pan2 = GetPanning(nIns, dupRegion);
if((pan1 < 16 && pan2 >= 240) || (pan2 < 16 && pan1 >= 240))
{ {
// Try to combine stereo samples ModSample &sample = sndFile.GetSample(nSmp);
const uint16 pan1 = GetPanning(nIns, nRgn), pan2 = GetPanning(nIns, iDup); ctrlSmp::ConvertToStereo(sample, sndFile);
if((pan1 < 16 && pan2 >= 240) || (pan2 < 16 && pan1 >= 240)) std::vector<uint8> pWaveForm;
uint32 dwLen = 0;
if(ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen) && dwLen >= sample.GetSampleSizeInBytes() / 2)
{ {
ModSample &sample = sndFile.GetSample(nSmp); SmpLength len = sample.nLength;
ctrlSmp::ConvertToStereo(sample, sndFile); const int16 *src = reinterpret_cast<int16 *>(pWaveForm.data());
std::vector<uint8> pWaveForm; int16 *dst = sample.sample16() + ((pan1 == 0) ? 0 : 1);
uint32 dwLen = 0; while(len--)
if(ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen) && dwLen >= sample.GetSampleSizeInBytes() / 2)
{ {
SmpLength len = sample.nLength; *dst = *src;
const int16 *src = reinterpret_cast<int16 *>(pWaveForm.data()); src++;
int16 *dst = sample.sample16() + ((pan1 == 0) ? 0 : 1); dst += 2;
while(len--)
{
*dst = *src;
src++;
dst += 2;
}
} }
} }
} }
@ -1910,27 +1942,26 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
if(sndFile.m_nTempoMode == tempoModeModern) if(sndFile.m_nTempoMode == tempoModeModern)
{ {
uint32 ticksPerBeat = sndFile.m_nDefaultRowsPerBeat * sndFile.m_nDefaultSpeed; uint32 ticksPerBeat = sndFile.m_nDefaultRowsPerBeat * sndFile.m_nDefaultSpeed;
if(ticksPerBeat == 0) if(ticksPerBeat != 0)
ticksPerBeat = 24; tempoScale = ticksPerBeat / 24.0f;
tempoScale = ticksPerBeat / 24.0f;
} }
// Initializes Envelope // Initializes Envelope
if ((nEnv) && (nEnv <= m_Envelopes.size())) if ((nEnv) && (nEnv <= m_Envelopes.size()))
{ {
const DLSENVELOPE *part = &m_Envelopes[nEnv-1]; const DLSENVELOPE &part = m_Envelopes[nEnv - 1];
// Volume Envelope // Volume Envelope
if ((part->wVolAttack) || (part->wVolDecay < 20*50) || (part->nVolSustainLevel) || (part->wVolRelease < 20*50)) if ((part.wVolAttack) || (part.wVolDecay < 20*50) || (part.nVolSustainLevel) || (part.wVolRelease < 20*50))
{ {
pIns->VolEnv.dwFlags.set(ENV_ENABLED); pIns->VolEnv.dwFlags.set(ENV_ENABLED);
// Delay section // Delay section
// -> DLS level 2 // -> DLS level 2
// Attack section // Attack section
pIns->VolEnv.clear(); pIns->VolEnv.clear();
if (part->wVolAttack) if (part.wVolAttack)
{ {
pIns->VolEnv.push_back(0, (uint8)(ENVELOPE_MAX / (part->wVolAttack / 2 + 2) + 8)); // /----- pIns->VolEnv.push_back(0, (uint8)(ENVELOPE_MAX / (part.wVolAttack / 2 + 2) + 8)); // /-----
pIns->VolEnv.push_back(ScaleEnvelope(part->wVolAttack, tempoScale), ENVELOPE_MAX); // | pIns->VolEnv.push_back(ScaleEnvelope(part.wVolAttack, tempoScale), ENVELOPE_MAX); // |
} else } else
{ {
pIns->VolEnv.push_back(0, ENVELOPE_MAX); pIns->VolEnv.push_back(0, ENVELOPE_MAX);
@ -1938,24 +1969,24 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
// Hold section // Hold section
// -> DLS Level 2 // -> DLS Level 2
// Sustain Level // Sustain Level
if (part->nVolSustainLevel > 0) if (part.nVolSustainLevel > 0)
{ {
if (part->nVolSustainLevel < 128) if (part.nVolSustainLevel < 128)
{ {
uint16 lStartTime = pIns->VolEnv.back().tick; uint16 lStartTime = pIns->VolEnv.back().tick;
int32 lSusLevel = - DLS32BitRelativeLinearToGain(part->nVolSustainLevel << 9) / 65536; int32 lSusLevel = - DLS32BitRelativeLinearToGain(part.nVolSustainLevel << 9) / 65536;
int32 lDecayTime = 1; int32 lDecayTime = 1;
if (lSusLevel > 0) if (lSusLevel > 0)
{ {
lDecayTime = (lSusLevel * (int32)part->wVolDecay) / 960; lDecayTime = (lSusLevel * (int32)part.wVolDecay) / 960;
for (uint32 i=0; i<7; i++) for (uint32 i=0; i<7; i++)
{ {
int32 lFactor = 128 - (1 << i); int32 lFactor = 128 - (1 << i);
if (lFactor <= part->nVolSustainLevel) break; if (lFactor <= part.nVolSustainLevel) break;
int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 9) / 65536; int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 9) / 65536;
if (lev > 0) if (lev > 0)
{ {
int32 ltime = (lev * (int32)part->wVolDecay) / 960; int32 ltime = (lev * (int32)part.wVolDecay) / 960;
if ((ltime > 1) && (ltime < lDecayTime)) if ((ltime > 1) && (ltime < lDecayTime))
{ {
uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale); uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale);
@ -1971,7 +2002,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
uint16 decayEnd = lStartTime + ScaleEnvelope(lDecayTime, tempoScale); uint16 decayEnd = lStartTime + ScaleEnvelope(lDecayTime, tempoScale);
if (decayEnd > pIns->VolEnv.back().tick) if (decayEnd > pIns->VolEnv.back().tick)
{ {
pIns->VolEnv.push_back(decayEnd, (uint8)((part->nVolSustainLevel+1) / 2)); pIns->VolEnv.push_back(decayEnd, (uint8)((part.nVolSustainLevel+1) / 2));
} }
} }
pIns->VolEnv.dwFlags.set(ENV_SUSTAIN); pIns->VolEnv.dwFlags.set(ENV_SUSTAIN);
@ -1982,9 +2013,9 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
} }
pIns->VolEnv.nSustainStart = pIns->VolEnv.nSustainEnd = (uint8)(pIns->VolEnv.size() - 1); pIns->VolEnv.nSustainStart = pIns->VolEnv.nSustainEnd = (uint8)(pIns->VolEnv.size() - 1);
// Release section // Release section
if ((part->wVolRelease) && (pIns->VolEnv.back().value > 1)) if ((part.wVolRelease) && (pIns->VolEnv.back().value > 1))
{ {
int32 lReleaseTime = part->wVolRelease; int32 lReleaseTime = part.wVolRelease;
uint16 lStartTime = pIns->VolEnv.back().tick; uint16 lStartTime = pIns->VolEnv.back().tick;
int32 lStartFactor = pIns->VolEnv.back().value; int32 lStartFactor = pIns->VolEnv.back().value;
int32 lSusLevel = - DLS32BitRelativeLinearToGain(lStartFactor << 10) / 65536; int32 lSusLevel = - DLS32BitRelativeLinearToGain(lStartFactor << 10) / 65536;
@ -1999,7 +2030,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 10) / 65536; int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 10) / 65536;
if (lev > 0) if (lev > 0)
{ {
int32 ltime = (((int32)part->wVolRelease * lev) / 960) - lDecayEndTime; int32 ltime = (((int32)part.wVolRelease * lev) / 960) - lDecayEndTime;
if ((ltime > 1) && (ltime < lReleaseTime)) if ((ltime > 1) && (ltime < lReleaseTime))
{ {
uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale); uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale);
@ -2023,7 +2054,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
} }
} }
} }
if (pDlsIns->ulBank & F_INSTRUMENT_DRUMS) if(isDrum)
{ {
// Create a default envelope for drums // Create a default envelope for drums
pIns->VolEnv.dwFlags.reset(ENV_SUSTAIN); pIns->VolEnv.dwFlags.reset(ENV_SUSTAIN);

View File

@ -37,6 +37,8 @@ struct DLSREGION
uint8 uKeyMax; uint8 uKeyMax;
uint8 uUnityNote; uint8 uUnityNote;
uint8 tuning = 100; uint8 tuning = 100;
constexpr bool IsDummy() const noexcept { return uKeyMin == 0xFF || nWaveLink == Util::MaxValueOfType(nWaveLink); }
}; };
struct DLSENVELOPE struct DLSENVELOPE

View File

@ -16,11 +16,11 @@ OPENMPT_NAMESPACE_BEGIN
struct IMFChannel struct IMFChannel
{ {
char name[12]; // Channel name (ASCIIZ-String, max 11 chars) char name[12]; // Channel name (ASCIIZ-String, max 11 chars)
uint8 chorus; // Default chorus uint8 chorus; // Default chorus
uint8 reverb; // Default reverb uint8 reverb; // Default reverb
uint8 panning; // Pan positions 00-FF uint8 panning; // Pan positions 00-FF
uint8 status; // Channel status: 0 = enabled, 1 = mute, 2 = disabled (ignore effects!) uint8 status; // Channel status: 0 = enabled, 1 = mute, 2 = disabled (ignore effects!)
}; };
MPT_BINARY_STRUCT(IMFChannel, 16) MPT_BINARY_STRUCT(IMFChannel, 16)
@ -32,19 +32,19 @@ struct IMFFileHeader
linearSlides = 0x01, linearSlides = 0x01,
}; };
char title[32]; // Songname (ASCIIZ-String, max. 31 chars) char title[32]; // Songname (ASCIIZ-String, max. 31 chars)
uint16le ordNum; // Number of orders saved uint16le ordNum; // Number of orders saved
uint16le patNum; // Number of patterns saved uint16le patNum; // Number of patterns saved
uint16le insNum; // Number of instruments saved uint16le insNum; // Number of instruments saved
uint16le flags; // See SongFlags uint16le flags; // See SongFlags
uint8le unused1[8]; uint8le unused1[8];
uint8le tempo; // Default tempo (Axx, 1...255) uint8le tempo; // Default tempo (Axx, 1...255)
uint8le bpm; // Default beats per minute (BPM) (Txx, 32...255) uint8le bpm; // Default beats per minute (BPM) (Txx, 32...255)
uint8le master; // Default master volume (Vxx, 0...64) uint8le master; // Default master volume (Vxx, 0...64)
uint8le amp; // Amplification factor (mixing volume, 4...127) uint8le amp; // Amplification factor (mixing volume, 4...127)
uint8le unused2[8]; uint8le unused2[8];
char im10[4]; // 'IM10' char im10[4]; // 'IM10'
IMFChannel channels[32]; // Channel settings IMFChannel channels[32]; // Channel settings
}; };
MPT_BINARY_STRUCT(IMFFileHeader, 576) MPT_BINARY_STRUCT(IMFFileHeader, 576)
@ -53,16 +53,16 @@ struct IMFEnvelope
{ {
enum EnvFlags enum EnvFlags
{ {
envEnabled = 0x01, envEnabled = 0x01,
envSustain = 0x02, envSustain = 0x02,
envLoop = 0x04, envLoop = 0x04,
}; };
uint8 points; // Number of envelope points uint8 points; // Number of envelope points
uint8 sustain; // Envelope sustain point uint8 sustain; // Envelope sustain point
uint8 loopStart; // Envelope loop start point uint8 loopStart; // Envelope loop start point
uint8 loopEnd; // Envelope loop end point uint8 loopEnd; // Envelope loop end point
uint8 flags; // See EnvFlags uint8 flags; // See EnvFlags
uint8 unused[3]; uint8 unused[3];
}; };
@ -80,19 +80,19 @@ struct IMFInstrument
{ {
enum EnvTypes enum EnvTypes
{ {
volEnv = 0, volEnv = 0,
panEnv = 1, panEnv = 1,
filterEnv = 2, filterEnv = 2,
}; };
char name[32]; // Inst. name (ASCIIZ-String, max. 31 chars) char name[32]; // Inst. name (ASCIIZ-String, max. 31 chars)
uint8le map[120]; // Multisample settings uint8le map[120]; // Multisample settings
uint8le unused[8]; uint8le unused[8];
IMFEnvNode nodes[3][16]; IMFEnvNode nodes[3][16];
IMFEnvelope env[3]; IMFEnvelope env[3];
uint16le fadeout; // Fadeout rate (0...0FFFH) uint16le fadeout; // Fadeout rate (0...0FFFH)
uint16le smpNum; // Number of samples in instrument uint16le smpNum; // Number of samples in instrument
char ii10[4]; // 'II10' char ii10[4]; // 'II10' (not verified by Orpheus)
void ConvertEnvelope(InstrumentEnvelope &mptEnv, EnvTypes e) const void ConvertEnvelope(InstrumentEnvelope &mptEnv, EnvTypes e) const
{ {
@ -114,6 +114,7 @@ struct IMFInstrument
minTick++; minTick++;
mptEnv[n].value = static_cast<uint8>(std::min(nodes[e][n].value >> shift, ENVELOPE_MAX)); mptEnv[n].value = static_cast<uint8>(std::min(nodes[e][n].value >> shift, ENVELOPE_MAX));
} }
mptEnv.Convert(MOD_TYPE_XM, MOD_TYPE_IT);
} }
// Convert an IMFInstrument to OpenMPT's internal instrument representation. // Convert an IMFInstrument to OpenMPT's internal instrument representation.
@ -155,20 +156,20 @@ struct IMFSample
smpPanning = 0x08, smpPanning = 0x08,
}; };
char filename[13]; // Sample filename (12345678.ABC) */ char filename[13]; // Sample filename (12345678.ABC) */
uint8le unused1[3]; uint8le unused1[3];
uint32le length; // Length (in bytes) uint32le length; // Length (in bytes)
uint32le loopStart; // Loop start (in bytes) uint32le loopStart; // Loop start (in bytes)
uint32le loopEnd; // Loop end (in bytes) uint32le loopEnd; // Loop end (in bytes)
uint32le c5Speed; // Samplerate uint32le c5Speed; // Samplerate
uint8le volume; // Default volume (0...64) uint8le volume; // Default volume (0...64)
uint8le panning; // Default pan (0...255) uint8le panning; // Default pan (0...255)
uint8le unused2[14]; uint8le unused2[14];
uint8le flags; // Sample flags uint8le flags; // Sample flags
uint8le unused3[5]; uint8le unused3[5];
uint16le ems; // Reserved for internal usage uint16le ems; // Reserved for internal usage
uint32le dram; // Reserved for internal usage uint32le dram; // Reserved for internal usage
char is10[4]; // 'IS10' char is10[4]; // 'IS10'
// Convert an IMFSample to OpenMPT's internal sample representation. // Convert an IMFSample to OpenMPT's internal sample representation.
void ConvertToMPT(ModSample &mptSmp) const void ConvertToMPT(ModSample &mptSmp) const
@ -255,7 +256,7 @@ static void ImportIMFEffect(ModCommand &m)
{ {
uint8 n; uint8 n;
// fix some of them // fix some of them
switch (m.command) switch(m.command)
{ {
case 0xE: // fine volslide case 0xE: // fine volslide
// hackaround to get almost-right behavior for fine slides (i think!) // hackaround to get almost-right behavior for fine slides (i think!)
@ -278,7 +279,7 @@ static void ImportIMFEffect(ModCommand &m)
case 0x15: // fine slide down case 0x15: // fine slide down
// this is about as close as we can do... // this is about as close as we can do...
if(m.param >> 4) if(m.param >> 4)
m.param = 0xF0 | std::min(static_cast<uint8>(m.param >> 4), uint8(0x0F)); m.param = 0xF0 | (m.param >> 4);
else else
m.param |= 0xE0; m.param |= 0xE0;
break; break;
@ -353,8 +354,12 @@ static void ImportIMFEffect(ModCommand &m)
static bool ValidateHeader(const IMFFileHeader &fileHeader) static bool ValidateHeader(const IMFFileHeader &fileHeader)
{ {
if(std::memcmp(fileHeader.im10, "IM10", 4) if(std::memcmp(fileHeader.im10, "IM10", 4)
|| fileHeader.ordNum > 256 || fileHeader.ordNum > 256
|| fileHeader.insNum >= MAX_INSTRUMENTS) || fileHeader.insNum >= MAX_INSTRUMENTS
|| fileHeader.bpm < 32
|| fileHeader.master > 64
|| fileHeader.amp < 4
|| fileHeader.amp > 127)
{ {
return false; return false;
} }
@ -480,8 +485,8 @@ bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags)
m_SongFlags.set(SONG_LINEARSLIDES, fileHeader.flags & IMFFileHeader::linearSlides); m_SongFlags.set(SONG_LINEARSLIDES, fileHeader.flags & IMFFileHeader::linearSlides);
m_nDefaultSpeed = fileHeader.tempo; m_nDefaultSpeed = fileHeader.tempo;
m_nDefaultTempo.Set(fileHeader.bpm); m_nDefaultTempo.Set(fileHeader.bpm);
m_nDefaultGlobalVolume = Clamp<uint8, uint8>(fileHeader.master, 0, 64) * 4; m_nDefaultGlobalVolume = fileHeader.master * 4u;
m_nSamplePreAmp = Clamp<uint8, uint8>(fileHeader.amp, 4, 127); m_nSamplePreAmp = fileHeader.amp;
m_nInstruments = fileHeader.insNum; m_nInstruments = fileHeader.insNum;
m_nSamples = 0; // Will be incremented later m_nSamples = 0; // Will be incremented later

View File

@ -1011,10 +1011,8 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
} }
if(size > offsetof(MMDInstrExt, instrFlags)) if(size > offsetof(MMDInstrExt, instrFlags))
{ {
if(instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) sample.uFlags.set(CHN_LOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) != 0);
sample.uFlags.set(CHN_LOOP); sample.uFlags.set(CHN_PINGPONGLOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG) != 0);
if(instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG)
sample.uFlags.set(CHN_LOOP | CHN_PINGPONGLOOP);
if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED) if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED)
sample.nGlobalVol = 0; sample.nGlobalVol = 0;
} }

View File

@ -1456,7 +1456,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
// Note: Every Ogg stream has a unique serial number. // Note: Every Ogg stream has a unique serial number.
// stb_vorbis (currently) ignores this serial number so we can just stitch // stb_vorbis (currently) ignores this serial number so we can just stitch
// together our sample without adjusting the shared header's serial number. // together our sample without adjusting the shared header's serial number.
const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples; const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples && sampleChunk.headerSize > 0;
#if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE)

View File

@ -449,6 +449,8 @@ struct AMInstrument
pitchEnv.dwFlags.set(ENV_ENABLED); pitchEnv.dwFlags.set(ENV_ENABLED);
pitchEnv.reserve(2); pitchEnv.reserve(2);
pitchEnv.push_back(0, ENVELOPE_MID); pitchEnv.push_back(0, ENVELOPE_MID);
// cppcheck false-positive
// cppcheck-suppress zerodiv
pitchEnv.push_back(static_cast<EnvelopeNode::tick_t>(1024 / abs(pitchFall)), pitchFall > 0 ? ENVELOPE_MIN : ENVELOPE_MAX); pitchEnv.push_back(static_cast<EnvelopeNode::tick_t>(1024 / abs(pitchFall)), pitchFall > 0 ? ENVELOPE_MIN : ENVELOPE_MAX);
} }
} }

View File

@ -477,11 +477,13 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags)
ReadOrderFromArray(Order(), orders, fileHeader.numOrders); ReadOrderFromArray(Order(), orders, fileHeader.numOrders);
Order().SetRestartPos(fileHeader.restartPos); Order().SetRestartPos(fileHeader.restartPos);
FileReader drumData = file.ReadChunk(file.ReadUint16LE()); // This value is supposed to be the size of the drums data, but in old MT2.0 files it's 8 bytes too small.
// MadTracker itself unconditionally reads 274 bytes here if the value is != 0, so we do the same.
const bool hasDrumChannels = file.ReadUint16LE() != 0;
FileReader drumData = file.ReadChunk(hasDrumChannels ? sizeof(MT2DrumsData) : 0);
FileReader extraData = file.ReadChunk(file.ReadUint32LE()); FileReader extraData = file.ReadChunk(file.ReadUint32LE());
const CHANNELINDEX channelsWithoutDrums = m_nChannels; const CHANNELINDEX channelsWithoutDrums = m_nChannels;
const bool hasDrumChannels = drumData.CanRead(sizeof(MT2DrumsData));
static_assert(MAX_BASECHANNELS >= 64 + 8); static_assert(MAX_BASECHANNELS >= 64 + 8);
if(hasDrumChannels) if(hasDrumChannels)
{ {
@ -604,7 +606,10 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags)
break; break;
case MagicLE("TRKS"): case MagicLE("TRKS"):
m_nSamplePreAmp = chunk.ReadUint16LE() / 256u; // 131072 is 0dB... I think (that's how MTIOModule_MT2.cpp reads) m_nSamplePreAmp = chunk.ReadUint16LE() / 256u; // 131072 is 0dB... I think (that's how MTIOModule_MT2.cpp reads)
// Dirty workaround for modules that use track automation for a fade-in at the song start (e.g. Rock.mt2)
if(!m_nSamplePreAmp)
m_nSamplePreAmp = 48;
m_nVSTiVolume = m_nSamplePreAmp / 2u; m_nVSTiVolume = m_nSamplePreAmp / 2u;
for(CHANNELINDEX c = 0; c < GetNumChannels(); c++) for(CHANNELINDEX c = 0; c < GetNumChannels(); c++)
{ {
@ -736,11 +741,11 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags)
chunk.ReadRaw(mixPlug.pluginData.data() + 4, vstHeader.n); chunk.ReadRaw(mixPlug.pluginData.data() + 4, vstHeader.n);
} else } else
{ {
float32 *f = reinterpret_cast<float32 *>(mixPlug.pluginData.data()); auto memFile = std::make_pair(mpt::as_span(mixPlug.pluginData), mpt::IO::Offset(0));
*(f++) = 0; // Plugin data type mpt::IO::WriteIntLE<uint32>(memFile, 0); // Plugin data type
for(uint32 param = 0; param < vstHeader.n; param++, f++) for(uint32 param = 0; param < vstHeader.n; param++)
{ {
*f = chunk.ReadFloatLE(); mpt::IO::Write(memFile, IEEE754binary32LE{chunk.ReadFloatLE()});
} }
} }
} else } else

View File

@ -1048,7 +1048,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
startTick = playState.m_nMusicSpeed - 1; startTick = playState.m_nMusicSpeed - 1;
} else if(m.volcmd == VOLCMD_OFFSET) } else if(m.volcmd == VOLCMD_OFFSET)
{ {
if(m.vol <= CountOf(chn.pModSample->cues) && chn.pModSample != nullptr) if(chn.pModSample != nullptr && m.vol <= CountOf(chn.pModSample->cues))
{ {
SmpLength offset; SmpLength offset;
if(m.vol == 0) if(m.vol == 0)
@ -1965,7 +1965,7 @@ void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetE
// ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end) // ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end)
if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = chn.nLength = pSmp->nLength; if(m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) chn.nLoopEnd = chn.nLength = pSmp->nLength;
if(chn.dwFlags[CHN_REVERSE]) if(chn.dwFlags[CHN_REVERSE] && chn.nLength > 0)
{ {
chn.dwFlags.set(CHN_PINGPONGFLAG); chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.position.SetInt(chn.nLength - 1); chn.position.SetInt(chn.nLength - 1);
@ -3838,7 +3838,7 @@ void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const
// Regular Slide // Regular Slide
if(!chn.isFirstTick if(!chn.isFirstTick
|| (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1])
|| GetType() == MOD_TYPE_669 || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT))
|| (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES])) || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]))
{ {
DoFreqSlide(chn, -int(param) * 4); DoFreqSlide(chn, -int(param) * 4);
@ -3906,7 +3906,7 @@ void CSoundFile::PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, cons
if(!chn.isFirstTick if(!chn.isFirstTick
|| (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1])
|| GetType() == MOD_TYPE_669 || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT))
|| (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES])) || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]))
{ {
DoFreqSlide(chn, int(param) * 4); DoFreqSlide(chn, int(param) * 4);
@ -4963,19 +4963,20 @@ void CSoundFile::InvertLoop(ModChannel &chn)
// Process a MIDI Macro. // Process a MIDI Macro.
// Parameters: // Parameters:
// [in] nChn: Mod channel to apply macro on // nChn: Mod channel to apply macro on
// [in] isSmooth: If true, internal macros are interpolated between two rows // isSmooth: If true, internal macros are interpolated between two rows
// [in] macro: Actual MIDI Macro string // macro: Actual MIDI Macro string
// [in] param: Parameter for parametric macros (Z00 - Z7F) // param: Parameter for parametric macros (Z00 - Z7F)
// [in] plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected) // plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected)
void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin) void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin)
{ {
ModChannel &chn = m_PlayState.Chn[nChn]; ModChannel &chn = m_PlayState.Chn[nChn];
const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr; const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr;
uint8 out[MACRO_LENGTH]; uint8 out[MACRO_LENGTH];
uint32 outPos = 0; // output buffer position, which also equals the number of complete bytes uint32 outPos = 0; // output buffer position, which also equals the number of complete bytes
const uint8 lastZxxParam = chn.lastZxxParam; const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string
uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message
bool firstNibble = true; bool firstNibble = true;
for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++) for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++)
@ -5088,8 +5089,12 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
// Interpolation for external MIDI messages - interpolation for internal messages // Interpolation for external MIDI messages - interpolation for internal messages
// is handled separately to allow for more than 7-bit granularity where it's possible // is handled separately to allow for more than 7-bit granularity where it's possible
data = static_cast<uint8>(CalculateSmoothParamChange(lastZxxParam, data)); data = static_cast<uint8>(CalculateSmoothParamChange(lastZxxParam, data));
chn.lastZxxParam = data;
updateZxxParam = 0x80;
} else if(updateZxxParam == 0xFF)
{
updateZxxParam = data;
} }
chn.lastZxxParam = data;
} else if(macro[pos] == 's') } else if(macro[pos] == 's')
{ {
// SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience) // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience)
@ -5137,6 +5142,8 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
// Finish current byte // Finish current byte
outPos++; outPos++;
} }
if(updateZxxParam < 0x80)
chn.lastZxxParam = updateZxxParam;
// Macro string has been parsed and translated, now send the message(s)... // Macro string has been parsed and translated, now send the message(s)...
uint32 sendPos = 0; uint32 sendPos = 0;
@ -5384,11 +5391,9 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
#endif // NO_PLUGINS #endif // NO_PLUGINS
return macroLen; return macroLen;
} }
return 0; return 0;
} }
@ -5504,7 +5509,7 @@ void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const
// //
void CSoundFile::ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const void CSoundFile::ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const
{ {
if(chn.pModSample != nullptr) if(chn.pModSample != nullptr && chn.nLength > 0)
{ {
chn.dwFlags.set(CHN_PINGPONGFLAG); chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.dwFlags.reset(CHN_LOOP); chn.dwFlags.reset(CHN_LOOP);