diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp | 2212 |
1 files changed, 2212 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp new file mode 100644 index 00000000..e5d7e52d --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/Dlsbank.cpp @@ -0,0 +1,2212 @@ +/* + * DLSBank.cpp + * ----------- + * Purpose: Sound bank loading. + * Notes : Supported sound bank types: DLS (including embedded DLS in MSS & RMI), SF2, SF3 / SF4 (modified SF2 with compressed samples) + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Sndfile.h" +#ifdef MODPLUG_TRACKER +#include "../mptrack/Mptrack.h" +#include "../common/mptFileIO.h" +#endif +#include "Dlsbank.h" +#include "Loaders.h" +#include "SampleCopy.h" +#include "../common/mptStringBuffer.h" +#include "../common/FileReader.h" +#include "openmpt/base/Endian.hpp" +#include "SampleIO.h" +#include "mpt/io/base.hpp" +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" + +OPENMPT_NAMESPACE_BEGIN + +#ifdef MODPLUG_TRACKER + +#ifdef MPT_ALL_LOGGING +#define DLSBANK_LOG +#define DLSINSTR_LOG +#endif + +#define F_RGN_OPTION_SELFNONEXCLUSIVE 0x0001 + +// Region Flags +enum RegionFlags +{ + DLSREGION_KEYGROUPMASK = 0x0F, + DLSREGION_OVERRIDEWSMP = 0x10, + DLSREGION_PINGPONGLOOP = 0x20, + DLSREGION_SAMPLELOOP = 0x40, + DLSREGION_SELFNONEXCLUSIVE = 0x80, + DLSREGION_SUSTAINLOOP = 0x100, +}; + +/////////////////////////////////////////////////////////////////////////// +// Articulation connection graph definitions + +enum ConnectionSource : uint16 +{ + // Generic Sources + CONN_SRC_NONE = 0x0000, + CONN_SRC_LFO = 0x0001, + CONN_SRC_KEYONVELOCITY = 0x0002, + CONN_SRC_KEYNUMBER = 0x0003, + CONN_SRC_EG1 = 0x0004, + CONN_SRC_EG2 = 0x0005, + CONN_SRC_PITCHWHEEL = 0x0006, + + CONN_SRC_POLYPRESSURE = 0x0007, + CONN_SRC_CHANNELPRESSURE = 0x0008, + CONN_SRC_VIBRATO = 0x0009, + + // Midi Controllers 0-127 + CONN_SRC_CC1 = 0x0081, + CONN_SRC_CC7 = 0x0087, + CONN_SRC_CC10 = 0x008a, + CONN_SRC_CC11 = 0x008b, + + CONN_SRC_CC91 = 0x00db, + CONN_SRC_CC93 = 0x00dd, + + CONN_SRC_RPN0 = 0x0100, + CONN_SRC_RPN1 = 0x0101, + CONN_SRC_RPN2 = 0x0102, +}; + +enum ConnectionDestination : uint16 +{ + // Generic Destinations + CONN_DST_NONE = 0x0000, + CONN_DST_ATTENUATION = 0x0001, + CONN_DST_RESERVED = 0x0002, + CONN_DST_PITCH = 0x0003, + CONN_DST_PAN = 0x0004, + + // LFO Destinations + CONN_DST_LFO_FREQUENCY = 0x0104, + CONN_DST_LFO_STARTDELAY = 0x0105, + + CONN_DST_KEYNUMBER = 0x0005, + + // EG1 Destinations + CONN_DST_EG1_ATTACKTIME = 0x0206, + CONN_DST_EG1_DECAYTIME = 0x0207, + CONN_DST_EG1_RESERVED = 0x0208, + CONN_DST_EG1_RELEASETIME = 0x0209, + CONN_DST_EG1_SUSTAINLEVEL = 0x020a, + + CONN_DST_EG1_DELAYTIME = 0x020b, + CONN_DST_EG1_HOLDTIME = 0x020c, + CONN_DST_EG1_SHUTDOWNTIME = 0x020d, + + // EG2 Destinations + CONN_DST_EG2_ATTACKTIME = 0x030a, + CONN_DST_EG2_DECAYTIME = 0x030b, + CONN_DST_EG2_RESERVED = 0x030c, + CONN_DST_EG2_RELEASETIME = 0x030d, + CONN_DST_EG2_SUSTAINLEVEL = 0x030e, + + CONN_DST_EG2_DELAYTIME = 0x030f, + CONN_DST_EG2_HOLDTIME = 0x0310, + + CONN_TRN_NONE = 0x0000, + CONN_TRN_CONCAVE = 0x0001, +}; + + +////////////////////////////////////////////////////////// +// Supported DLS1 Articulations + +// [4-bit transform][12-bit dest][8-bit control][8-bit source] = 32-bit ID +constexpr uint32 DLSArt(uint8 src, uint8 ctl, uint16 dst) +{ + return (dst << 16u) | (ctl << 8u) | src; +} + +enum DLSArt : uint32 +{ + // Vibrato / Tremolo + ART_LFO_FREQUENCY = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_LFO_FREQUENCY), + ART_LFO_STARTDELAY = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_LFO_STARTDELAY), + ART_LFO_ATTENUATION = DLSArt(CONN_SRC_LFO, CONN_SRC_NONE, CONN_DST_ATTENUATION), + ART_LFO_PITCH = DLSArt(CONN_SRC_LFO, CONN_SRC_NONE, CONN_DST_PITCH), + ART_LFO_MODWTOATTN = DLSArt(CONN_SRC_LFO, CONN_SRC_CC1, CONN_DST_ATTENUATION), + ART_LFO_MODWTOPITCH = DLSArt(CONN_SRC_LFO, CONN_SRC_CC1, CONN_DST_PITCH), + + // Volume Envelope + ART_VOL_EG_ATTACKTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_ATTACKTIME), + ART_VOL_EG_DECAYTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_DECAYTIME), + ART_VOL_EG_SUSTAINLEVEL = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_SUSTAINLEVEL), + ART_VOL_EG_RELEASETIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_RELEASETIME), + ART_VOL_EG_DELAYTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_DELAYTIME), + ART_VOL_EG_HOLDTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_HOLDTIME), + ART_VOL_EG_SHUTDOWNTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG1_SHUTDOWNTIME), + ART_VOL_EG_VELTOATTACK = DLSArt(CONN_SRC_KEYONVELOCITY, CONN_SRC_NONE, CONN_DST_EG1_ATTACKTIME), + ART_VOL_EG_KEYTODECAY = DLSArt(CONN_SRC_KEYNUMBER, CONN_SRC_NONE, CONN_DST_EG1_DECAYTIME), + + // Pitch Envelope + ART_PITCH_EG_ATTACKTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_ATTACKTIME), + ART_PITCH_EG_DECAYTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_DECAYTIME), + ART_PITCH_EG_SUSTAINLEVEL = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_SUSTAINLEVEL), + ART_PITCH_EG_RELEASETIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_RELEASETIME), + ART_PITCH_EG_VELTOATTACK = DLSArt(CONN_SRC_KEYONVELOCITY, CONN_SRC_NONE, CONN_DST_EG2_ATTACKTIME), + ART_PITCH_EG_KEYTODECAY = DLSArt(CONN_SRC_KEYNUMBER, CONN_SRC_NONE, CONN_DST_EG2_DECAYTIME), + + // Default Pan + ART_DEFAULTPAN = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_PAN), +}; + +////////////////////////////////////////////////////////// +// DLS IFF Chunk IDs + +enum IFFChunkID : uint32 +{ + // Standard IFF chunks IDs + IFFID_FORM = MagicLE("FORM"), + IFFID_RIFF = MagicLE("RIFF"), + IFFID_LIST = MagicLE("LIST"), + IFFID_INFO = MagicLE("INFO"), + + // IFF Info fields + IFFID_ICOP = MagicLE("ICOP"), + IFFID_INAM = MagicLE("INAM"), + IFFID_ICMT = MagicLE("ICMT"), + IFFID_IENG = MagicLE("IENG"), + IFFID_ISFT = MagicLE("ISFT"), + IFFID_ISBJ = MagicLE("ISBJ"), + + // Wave IFF chunks IDs + IFFID_wave = MagicLE("wave"), + IFFID_wsmp = MagicLE("wsmp"), + + IFFID_XDLS = MagicLE("XDLS"), + IFFID_DLS = MagicLE("DLS "), + IFFID_MLS = MagicLE("MLS "), + IFFID_RMID = MagicLE("RMID"), + IFFID_colh = MagicLE("colh"), + IFFID_ins = MagicLE("ins "), + IFFID_insh = MagicLE("insh"), + IFFID_ptbl = MagicLE("ptbl"), + IFFID_wvpl = MagicLE("wvpl"), + IFFID_rgn = MagicLE("rgn "), + IFFID_rgn2 = MagicLE("rgn2"), + IFFID_rgnh = MagicLE("rgnh"), + IFFID_wlnk = MagicLE("wlnk"), + IFFID_art1 = MagicLE("art1"), + IFFID_art2 = MagicLE("art2"), +}; + +////////////////////////////////////////////////////////// +// DLS Structures definitions + +struct IFFCHUNK +{ + uint32le id; + uint32le len; +}; + +MPT_BINARY_STRUCT(IFFCHUNK, 8) + +struct RIFFChunkID +{ + uint32le id_RIFF; + uint32le riff_len; + uint32le id_DLS; +}; + +MPT_BINARY_STRUCT(RIFFChunkID, 12) + +struct LISTChunk +{ + uint32le id; + uint32le len; + uint32le listid; +}; + +MPT_BINARY_STRUCT(LISTChunk, 12) + +struct DLSRgnRange +{ + uint16le usLow; + uint16le usHigh; +}; + +MPT_BINARY_STRUCT(DLSRgnRange, 4) + +struct VERSChunk +{ + uint32le id; + uint32le len; + uint16le version[4]; +}; + +MPT_BINARY_STRUCT(VERSChunk, 16) + +struct PTBLChunk +{ + uint32le cbSize; + uint32le cCues; +}; + +MPT_BINARY_STRUCT(PTBLChunk, 8) + +struct INSHChunk +{ + uint32le cRegions; + uint32le ulBank; + uint32le ulInstrument; +}; + +MPT_BINARY_STRUCT(INSHChunk, 12) + +struct RGNHChunk +{ + DLSRgnRange RangeKey; + DLSRgnRange RangeVelocity; + uint16le fusOptions; + uint16le usKeyGroup; +}; + +MPT_BINARY_STRUCT(RGNHChunk, 12) + +struct WLNKChunk +{ + uint16le fusOptions; + uint16le usPhaseGroup; + uint32le ulChannel; + uint32le ulTableIndex; +}; + +MPT_BINARY_STRUCT(WLNKChunk, 12) + +struct ART1Chunk +{ + uint32le cbSize; + uint32le cConnectionBlocks; +}; + +MPT_BINARY_STRUCT(ART1Chunk, 8) + +struct ConnectionBlock +{ + uint16le usSource; + uint16le usControl; + uint16le usDestination; + uint16le usTransform; + int32le lScale; +}; + +MPT_BINARY_STRUCT(ConnectionBlock, 12) + +struct WSMPChunk +{ + uint32le cbSize; + uint16le usUnityNote; + int16le sFineTune; + int32le lAttenuation; + uint32le fulOptions; + uint32le cSampleLoops; +}; + +MPT_BINARY_STRUCT(WSMPChunk, 20) + +struct WSMPSampleLoop +{ + uint32le cbSize; + uint32le ulLoopType; + uint32le ulLoopStart; + uint32le ulLoopLength; + +}; + +MPT_BINARY_STRUCT(WSMPSampleLoop, 16) + + +///////////////////////////////////////////////////////////////////// +// SF2 IFF Chunk IDs + +enum SF2ChunkID : uint32 +{ + IFFID_ifil = MagicLE("ifil"), + IFFID_sfbk = MagicLE("sfbk"), + IFFID_sfpk = MagicLE("sfpk"), // SF2Pack compressed soundfont + IFFID_sdta = MagicLE("sdta"), + IFFID_pdta = MagicLE("pdta"), + IFFID_phdr = MagicLE("phdr"), + IFFID_pbag = MagicLE("pbag"), + IFFID_pgen = MagicLE("pgen"), + IFFID_inst = MagicLE("inst"), + IFFID_ibag = MagicLE("ibag"), + IFFID_igen = MagicLE("igen"), + IFFID_shdr = MagicLE("shdr"), +}; + +/////////////////////////////////////////// +// SF2 Generators IDs + +enum SF2Generators : uint16 +{ + SF2_GEN_START_LOOP_FINE = 2, + SF2_GEN_END_LOOP_FINE = 3, + SF2_GEN_MODENVTOFILTERFC = 11, + SF2_GEN_PAN = 17, + SF2_GEN_DECAYMODENV = 28, + SF2_GEN_ATTACKVOLENV = 34, + SF2_GEN_HOLDVOLENV = 34, + SF2_GEN_DECAYVOLENV = 36, + SF2_GEN_SUSTAINVOLENV = 37, + SF2_GEN_RELEASEVOLENV = 38, + SF2_GEN_INSTRUMENT = 41, + SF2_GEN_KEYRANGE = 43, + SF2_GEN_START_LOOP_COARSE = 45, + SF2_GEN_ATTENUATION = 48, + SF2_GEN_END_LOOP_COARSE = 50, + SF2_GEN_COARSETUNE = 51, + SF2_GEN_FINETUNE = 52, + SF2_GEN_SAMPLEID = 53, + SF2_GEN_SAMPLEMODES = 54, + SF2_GEN_SCALE_TUNING = 56, + SF2_GEN_KEYGROUP = 57, + SF2_GEN_UNITYNOTE = 58, +}; + +///////////////////////////////////////////////////////////////////// +// SF2 Structures Definitions + +struct SFPresetHeader +{ + char achPresetName[20]; + uint16le wPreset; + uint16le wBank; + uint16le wPresetBagNdx; + uint32le dwLibrary; + uint32le dwGenre; + uint32le dwMorphology; +}; + +MPT_BINARY_STRUCT(SFPresetHeader, 38) + +struct SFPresetBag +{ + uint16le wGenNdx; + uint16le wModNdx; +}; + +MPT_BINARY_STRUCT(SFPresetBag, 4) + +struct SFGenList +{ + uint16le sfGenOper; + uint16le genAmount; +}; + +MPT_BINARY_STRUCT(SFGenList, 4) + +struct SFInst +{ + char achInstName[20]; + uint16le wInstBagNdx; +}; + +MPT_BINARY_STRUCT(SFInst, 22) + +struct SFInstBag +{ + uint16le wGenNdx; + uint16le wModNdx; +}; + +MPT_BINARY_STRUCT(SFInstBag, 4) + +struct SFInstGenList +{ + uint16le sfGenOper; + uint16le genAmount; +}; + +MPT_BINARY_STRUCT(SFInstGenList, 4) + +struct SFSample +{ + char achSampleName[20]; + uint32le dwStart; + uint32le dwEnd; + uint32le dwStartloop; + uint32le dwEndloop; + uint32le dwSampleRate; + uint8le byOriginalPitch; + int8le chPitchCorrection; + uint16le wSampleLink; + uint16le sfSampleType; +}; + +MPT_BINARY_STRUCT(SFSample, 46) + +// End of structures definitions +///////////////////////////////////////////////////////////////////// + + +struct SF2LoaderInfo +{ + FileReader presetBags; + FileReader presetGens; + FileReader insts; + FileReader instBags; + FileReader instGens; +}; + + +///////////////////////////////////////////////////////////////////// +// Unit conversion + +static uint8 DLSSustainLevelToLinear(int32 sustain) +{ + // 0.1% units + if(sustain >= 0) + { + int32 l = sustain / (1000 * 512); + if(l >= 0 && l <= 128) + return static_cast<uint8>(l); + } + return 128; +} + + +static uint8 SF2SustainLevelToLinear(int32 sustain) +{ + // 0.1% units + int32 l = 128 * (1000 - Clamp(sustain, 0, 1000)) / 1000; + return static_cast<uint8>(l); +} + + +int32 CDLSBank::DLS32BitTimeCentsToMilliseconds(int32 lTimeCents) +{ + // tc = log2(time[secs]) * 1200*65536 + // time[secs] = 2^(tc/(1200*65536)) + if ((uint32)lTimeCents == 0x80000000) return 0; + double fmsecs = 1000.0 * std::pow(2.0, ((double)lTimeCents)/(1200.0*65536.0)); + if (fmsecs < -32767) return -32767; + if (fmsecs > 32767) return 32767; + return (int32)fmsecs; +} + + +// 0dB = 0x10000 +int32 CDLSBank::DLS32BitRelativeGainToLinear(int32 lCentibels) +{ + // v = 10^(cb/(200*65536)) * V + return (int32)(65536.0 * std::pow(10.0, ((double)lCentibels)/(200*65536.0)) ); +} + + +int32 CDLSBank::DLS32BitRelativeLinearToGain(int32 lGain) +{ + // cb = log10(v/V) * 200 * 65536 + if (lGain <= 0) return -960 * 65536; + return (int32)(200 * 65536.0 * std::log10(((double)lGain) / 65536.0)); +} + + +int32 CDLSBank::DLSMidiVolumeToLinear(uint32 nMidiVolume) +{ + return (nMidiVolume * nMidiVolume << 16) / (127*127); +} + + +///////////////////////////////////////////////////////////////////// +// Implementation + +CDLSBank::CDLSBank() +{ + m_nMaxWaveLink = 0; + m_nType = SOUNDBANK_TYPE_INVALID; +} + + +bool CDLSBank::IsDLSBank(const mpt::PathString &filename) +{ + RIFFChunkID riff; + if(filename.empty()) return false; + mpt::ifstream f(filename, std::ios::binary); + if(!f) + { + return false; + } + MemsetZero(riff); + mpt::IO::Read(f, riff); + // Check for embedded DLS sections + if(riff.id_RIFF == IFFID_FORM) + { + // Miles Sound System + do + { + uint32 len = mpt::bit_cast<uint32be>(riff.riff_len); + if (len <= 4) break; + if (riff.id_DLS == IFFID_XDLS) + { + mpt::IO::Read(f, riff); + break; + } + if((len % 2u) != 0) + len++; + if (!mpt::IO::SeekRelative(f, len-4)) break; + } while (mpt::IO::Read(f, riff)); + } else if(riff.id_RIFF == IFFID_RIFF && riff.id_DLS == IFFID_RMID) + { + for (;;) + { + if(!mpt::IO::Read(f, riff)) + break; + if (riff.id_DLS == IFFID_DLS) + break; // found it + int len = riff.riff_len; + if((len % 2u) != 0) + len++; + if ((len <= 4) || !mpt::IO::SeekRelative(f, len-4)) break; + } + } + return ((riff.id_RIFF == IFFID_RIFF) + && ((riff.id_DLS == IFFID_DLS) || (riff.id_DLS == IFFID_MLS) || (riff.id_DLS == IFFID_sfbk)) + && (riff.riff_len >= 256)); +} + + +/////////////////////////////////////////////////////////////// +// Find an instrument based on the given parameters + +const DLSINSTRUMENT *CDLSBank::FindInstrument(bool isDrum, uint32 bank, uint32 program, uint32 key, uint32 *pInsNo) const +{ + // 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 + // Fixes issues when loading GeneralUser GS into OpenMPT's MIDI library. + 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) + { + uint32 insbank = ((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F); + if((bank >= 0x4000) || (insbank == bank)) + { + if(isDrum && (dlsIns.ulBank & F_INSTRUMENT_DRUMS)) + { + if((program >= 0x80) || (program == (dlsIns.ulInstrument & 0x7F))) + { + for(const auto ®ion : dlsIns.Regions) + { + if(region.IsDummy()) + continue; + + if((!key || key >= 0x80) + || (key >= region.uKeyMin && key <= region.uKeyMax)) + { + if(pInsNo) + *pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns)); + // cppcheck false-positive + // cppcheck-suppress returnDanglingLifetime + return &dlsIns; + } + } + } + } else if(!isDrum && !(dlsIns.ulBank & F_INSTRUMENT_DRUMS)) + { + if((program >= 0x80) || (program == (dlsIns.ulInstrument & 0x7F))) + { + if(pInsNo) + *pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns)); + // cppcheck false-positive + // cppcheck-suppress returnDanglingLifetime + return &dlsIns; + } + } + } + } + + return nullptr; +} + + +bool CDLSBank::FindAndExtract(CSoundFile &sndFile, const INSTRUMENTINDEX ins, const bool isDrum) const +{ + ModInstrument *pIns = sndFile.Instruments[ins]; + if(pIns == nullptr) + return false; + + uint32 dlsIns = 0, drumRgn = 0; + const uint32 program = (pIns->nMidiProgram != 0) ? pIns->nMidiProgram - 1 : 0; + const uint32 key = isDrum ? (pIns->nMidiDrumKey & 0x7F) : 0xFF; + if(FindInstrument(isDrum, (pIns->wMidiBank - 1) & 0x3FFF, program, key, &dlsIns) + || FindInstrument(isDrum, (pIns->wMidiBank - 1) & 0x3F80, program, key, &dlsIns) + || FindInstrument(isDrum, 0xFFFF, isDrum ? 0xFF : program, key, &dlsIns)) + { + if(key < 0x80) drumRgn = GetRegionFromKey(dlsIns, key); + if(ExtractInstrument(sndFile, ins, dlsIns, drumRgn)) + { + pIns = sndFile.Instruments[ins]; // Reset pointer because ExtractInstrument may delete the previous value. + if((key >= 24) && (key < 24 + std::size(szMidiPercussionNames))) + { + pIns->name = szMidiPercussionNames[key - 24]; + } + return true; + } + } + return false; +} + + +/////////////////////////////////////////////////////////////// +// Update DLS instrument definition from an IFF chunk + +bool CDLSBank::UpdateInstrumentDefinition(DLSINSTRUMENT *pDlsIns, FileReader chunk) +{ + IFFCHUNK header; + chunk.ReadStruct(header); + if(!header.len || !chunk.CanRead(header.len)) + return false; + if(header.id == IFFID_LIST) + { + uint32 listid = chunk.ReadUint32LE(); + while(chunk.CanRead(sizeof(IFFCHUNK))) + { + IFFCHUNK subHeader; + chunk.ReadStruct(subHeader); + chunk.SkipBack(sizeof(IFFCHUNK)); + FileReader subData = chunk.ReadChunk(subHeader.len + sizeof(IFFCHUNK)); + if(subHeader.len & 1) + { + chunk.Skip(1); + } + UpdateInstrumentDefinition(pDlsIns, subData); + } + switch(listid) + { + case IFFID_rgn: // Level 1 region + case IFFID_rgn2: // Level 2 region + pDlsIns->Regions.push_back({}); + break; + } + } else + { + switch(header.id) + { + case IFFID_insh: + { + INSHChunk insh; + chunk.ReadStruct(insh); + pDlsIns->ulBank = insh.ulBank; + pDlsIns->ulInstrument = insh.ulInstrument; + //Log("%3d regions, bank 0x%04X instrument %3d\n", insh.cRegions, pDlsIns->ulBank, pDlsIns->ulInstrument); + break; + } + + case IFFID_rgnh: + if(!pDlsIns->Regions.empty()) + { + RGNHChunk rgnh; + chunk.ReadStruct(rgnh); + DLSREGION ®ion = pDlsIns->Regions.back(); + region.uKeyMin = (uint8)rgnh.RangeKey.usLow; + region.uKeyMax = (uint8)rgnh.RangeKey.usHigh; + region.fuOptions = (uint8)(rgnh.usKeyGroup & DLSREGION_KEYGROUPMASK); + if(rgnh.fusOptions & F_RGN_OPTION_SELFNONEXCLUSIVE) + region.fuOptions |= DLSREGION_SELFNONEXCLUSIVE; + //Log(" Region %d: fusOptions=0x%02X usKeyGroup=0x%04X ", pDlsIns->nRegions, rgnh.fusOptions, rgnh.usKeyGroup); + //Log("KeyRange[%3d,%3d] ", rgnh.RangeKey.usLow, rgnh.RangeKey.usHigh); + } + break; + + case IFFID_wlnk: + if (!pDlsIns->Regions.empty()) + { + WLNKChunk wlnk; + chunk.ReadStruct(wlnk); + DLSREGION ®ion = pDlsIns->Regions.back(); + region.nWaveLink = (uint16)wlnk.ulTableIndex; + if((region.nWaveLink < Util::MaxValueOfType(region.nWaveLink)) && (region.nWaveLink >= m_nMaxWaveLink)) + m_nMaxWaveLink = region.nWaveLink + 1; + //Log(" WaveLink %d: fusOptions=0x%02X usPhaseGroup=0x%04X ", pDlsIns->nRegions, wlnk.fusOptions, wlnk.usPhaseGroup); + //Log("ulChannel=%d ulTableIndex=%4d\n", wlnk.ulChannel, wlnk.ulTableIndex); + } + break; + + case IFFID_wsmp: + if(!pDlsIns->Regions.empty()) + { + DLSREGION ®ion = pDlsIns->Regions.back(); + WSMPChunk wsmp; + chunk.ReadStruct(wsmp); + region.fuOptions |= DLSREGION_OVERRIDEWSMP; + region.uUnityNote = (uint8)wsmp.usUnityNote; + region.sFineTune = wsmp.sFineTune; + int32 lVolume = DLS32BitRelativeGainToLinear(wsmp.lAttenuation) / 256; + if (lVolume > 256) lVolume = 256; + if (lVolume < 4) lVolume = 4; + region.usVolume = (uint16)lVolume; + //Log(" WaveSample %d: usUnityNote=%2d sFineTune=%3d ", pDlsEnv->nRegions, p->usUnityNote, p->sFineTune); + //Log("fulOptions=0x%04X loops=%d\n", p->fulOptions, p->cSampleLoops); + if((wsmp.cSampleLoops) && (wsmp.cbSize + sizeof(WSMPSampleLoop) <= header.len)) + { + WSMPSampleLoop loop; + chunk.Seek(sizeof(IFFCHUNK) + wsmp.cbSize); + chunk.ReadStruct(loop); + //Log("looptype=%2d loopstart=%5d loopend=%5d\n", ploop->ulLoopType, ploop->ulLoopStart, ploop->ulLoopLength); + if(loop.ulLoopLength > 3) + { + region.fuOptions |= DLSREGION_SAMPLELOOP; + //if(loop.ulLoopType) region.fuOptions |= DLSREGION_PINGPONGLOOP; + region.ulLoopStart = loop.ulLoopStart; + region.ulLoopEnd = loop.ulLoopStart + loop.ulLoopLength; + } + } + } + break; + + case IFFID_art1: + case IFFID_art2: + { + ART1Chunk art1; + chunk.ReadStruct(art1); + if(!(pDlsIns->ulBank & F_INSTRUMENT_DRUMS)) + { + pDlsIns->nMelodicEnv = static_cast<uint32>(m_Envelopes.size() + 1); + } + if(art1.cbSize + art1.cConnectionBlocks * sizeof(ConnectionBlock) > header.len) + break; + DLSENVELOPE dlsEnv; + MemsetZero(dlsEnv); + dlsEnv.nDefPan = 128; + dlsEnv.nVolSustainLevel = 128; + //Log(" art1 (%3d bytes): cbSize=%d cConnectionBlocks=%d\n", p->len, p->cbSize, p->cConnectionBlocks); + chunk.Seek(sizeof(IFFCHUNK) + art1.cbSize); + for (uint32 iblk = 0; iblk < art1.cConnectionBlocks; iblk++) + { + ConnectionBlock blk; + chunk.ReadStruct(blk); + // [4-bit transform][12-bit dest][8-bit control][8-bit source] = 32-bit ID + uint32 dwArticulation = blk.usTransform; + dwArticulation = (dwArticulation << 12) | (blk.usDestination & 0x0FFF); + dwArticulation = (dwArticulation << 8) | (blk.usControl & 0x00FF); + dwArticulation = (dwArticulation << 8) | (blk.usSource & 0x00FF); + switch(dwArticulation) + { + case ART_DEFAULTPAN: + { + int32 pan = 128 + blk.lScale / (65536000/128); + dlsEnv.nDefPan = mpt::saturate_cast<uint8>(pan); + } + break; + + case ART_VOL_EG_ATTACKTIME: + // 32-bit time cents units. range = [0s, 20s] + dlsEnv.wVolAttack = 0; + if(blk.lScale > -0x40000000) + { + int32 l = blk.lScale - 78743200; // maximum velocity + if (l > 0) l = 0; + int32 attacktime = DLS32BitTimeCentsToMilliseconds(l); + if (attacktime < 0) attacktime = 0; + if (attacktime > 20000) attacktime = 20000; + if (attacktime >= 20) dlsEnv.wVolAttack = (uint16)(attacktime / 20); + //Log("%3d: Envelope Attack Time set to %d (%d time cents)\n", (uint32)(dlsEnv.ulInstrument & 0x7F)|((dlsEnv.ulBank >> 16) & 0x8000), attacktime, pblk->lScale); + } + break; + + case ART_VOL_EG_DECAYTIME: + // 32-bit time cents units. range = [0s, 20s] + dlsEnv.wVolDecay = 0; + if(blk.lScale > -0x40000000) + { + int32 decaytime = DLS32BitTimeCentsToMilliseconds(blk.lScale); + if (decaytime > 20000) decaytime = 20000; + if (decaytime >= 20) dlsEnv.wVolDecay = (uint16)(decaytime / 20); + //Log("%3d: Envelope Decay Time set to %d (%d time cents)\n", (uint32)(dlsEnv.ulInstrument & 0x7F)|((dlsEnv.ulBank >> 16) & 0x8000), decaytime, pblk->lScale); + } + break; + + case ART_VOL_EG_RELEASETIME: + // 32-bit time cents units. range = [0s, 20s] + dlsEnv.wVolRelease = 0; + if(blk.lScale > -0x40000000) + { + int32 releasetime = DLS32BitTimeCentsToMilliseconds(blk.lScale); + if (releasetime > 20000) releasetime = 20000; + if (releasetime >= 20) dlsEnv.wVolRelease = (uint16)(releasetime / 20); + //Log("%3d: Envelope Release Time set to %d (%d time cents)\n", (uint32)(dlsEnv.ulInstrument & 0x7F)|((dlsEnv.ulBank >> 16) & 0x8000), dlsEnv.wVolRelease, pblk->lScale); + } + break; + + case ART_VOL_EG_SUSTAINLEVEL: + // 0.1% units + if(blk.lScale >= 0) + { + dlsEnv.nVolSustainLevel = DLSSustainLevelToLinear(blk.lScale); + } + break; + + //default: + // Log(" Articulation = 0x%08X value=%d\n", dwArticulation, blk.lScale); + } + } + m_Envelopes.push_back(dlsEnv); + } + break; + + case IFFID_INAM: + chunk.ReadString<mpt::String::spacePadded>(pDlsIns->szName, header.len); + break; + #if 0 + default: + { + char sid[5]; + memcpy(sid, &header.id, 4); + sid[4] = 0; + Log(" \"%s\": %d bytes\n", (uint32)sid, header.len.get()); + } + #endif + } + } + return true; +} + +/////////////////////////////////////////////////////////////// +// Converts SF2 chunks to DLS + +bool CDLSBank::UpdateSF2PresetData(SF2LoaderInfo &sf2info, const IFFCHUNK &header, FileReader &chunk) +{ + if (!chunk.IsValid()) return false; + switch(header.id) + { + case IFFID_phdr: + if(m_Instruments.empty()) + { + uint32 numIns = static_cast<uint32>(chunk.GetLength() / sizeof(SFPresetHeader)); + if(numIns <= 1) + 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. + numIns--; + m_Instruments.resize(numIns); + + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBank", MPT_UFORMAT("phdr: {} instruments")(m_Instruments.size())); + #endif + SFPresetHeader psfh; + chunk.ReadStruct(psfh); + for(auto &dlsIns : m_Instruments) + { + mpt::String::WriteAutoBuf(dlsIns.szName) = mpt::String::ReadAutoBuf(psfh.achPresetName); + dlsIns.ulInstrument = psfh.wPreset & 0x7F; + dlsIns.ulBank = (psfh.wBank >= 128) ? F_INSTRUMENT_DRUMS : (psfh.wBank << 8); + dlsIns.wPresetBagNdx = psfh.wPresetBagNdx; + dlsIns.wPresetBagNum = 1; + chunk.ReadStruct(psfh); + if (psfh.wPresetBagNdx > dlsIns.wPresetBagNdx) dlsIns.wPresetBagNum = static_cast<uint16>(psfh.wPresetBagNdx - dlsIns.wPresetBagNdx); + } + } + break; + + case IFFID_pbag: + if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFPresetBag))) + { + sf2info.presetBags = chunk.GetChunk(chunk.BytesLeft()); + } + #ifdef DLSINSTR_LOG + else MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", U_("pbag: no instruments!")); + #endif + break; + + case IFFID_pgen: + if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFGenList))) + { + sf2info.presetGens = chunk.GetChunk(chunk.BytesLeft()); + } + #ifdef DLSINSTR_LOG + else MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", U_("pgen: no instruments!")); + #endif + break; + + case IFFID_inst: + if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInst))) + { + sf2info.insts = chunk.GetChunk(chunk.BytesLeft()); + } + break; + + case IFFID_ibag: + if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInstBag))) + { + sf2info.instBags = chunk.GetChunk(chunk.BytesLeft()); + } + break; + + case IFFID_igen: + if(!m_Instruments.empty() && chunk.CanRead(sizeof(SFInstGenList))) + { + sf2info.instGens = chunk.GetChunk(chunk.BytesLeft()); + } + break; + + case IFFID_shdr: + if (m_SamplesEx.empty()) + { + uint32 numSmp = static_cast<uint32>(chunk.GetLength() / sizeof(SFSample)); + if (numSmp < 1) break; + m_SamplesEx.resize(numSmp); + m_WaveForms.resize(numSmp); + #ifdef DLSINSTR_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("shdr: {} samples")(m_SamplesEx.size())); + #endif + + for (uint32 i = 0; i < numSmp; i++) + { + SFSample p; + chunk.ReadStruct(p); + DLSSAMPLEEX &dlsSmp = m_SamplesEx[i]; + mpt::String::WriteAutoBuf(dlsSmp.szName) = mpt::String::ReadAutoBuf(p.achSampleName); + dlsSmp.dwLen = 0; + dlsSmp.dwSampleRate = p.dwSampleRate; + dlsSmp.byOriginalPitch = p.byOriginalPitch; + dlsSmp.chPitchCorrection = static_cast<int8>(Util::muldivr(p.chPitchCorrection, 128, 100)); + // cognitone's sf2convert tool doesn't set the correct sample flags (0x01 / 0x02 instead of 0x10/ 0x20). + // For SF3, we ignore this and go by https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format instead + // As cognitone's tool is the only tool writing SF4 files, we always assume compressed samples with SF4 files if bits 0/1 are set. + uint16 sampleType = p.sfSampleType; + if(m_sf2version >= 0x4'0000 && m_sf2version <= 0x4'FFFF && (sampleType & 0x03)) + sampleType = (sampleType & 0xFFFC) | 0x10; + + dlsSmp.compressed = (sampleType & 0x10); + if(((sampleType & 0x7FCF) <= 4) && (p.dwEnd >= p.dwStart + 4)) + { + m_WaveForms[i] = p.dwStart; + dlsSmp.dwLen = (p.dwEnd - p.dwStart); + if(!dlsSmp.compressed) + { + m_WaveForms[i] *= 2; + dlsSmp.dwLen *= 2; + if((p.dwEndloop > p.dwStartloop + 7) && (p.dwStartloop >= p.dwStart)) + { + dlsSmp.dwStartloop = p.dwStartloop - p.dwStart; + dlsSmp.dwEndloop = p.dwEndloop - p.dwStart; + } + } else + { + if(p.dwEndloop > p.dwStartloop + 7) + { + dlsSmp.dwStartloop = p.dwStartloop; + dlsSmp.dwEndloop = p.dwEndloop; + } + } + } + } + } + break; + + #ifdef DLSINSTR_LOG + default: + { + char sdbg[5]; + memcpy(sdbg, &header.id, 4); + sdbg[4] = 0; + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("Unsupported SF2 chunk: {} ({} bytes)")(mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(sdbg)), header.len.get())); + } + #endif + } + return true; +} + + +static int16 SF2TimeToDLS(int16 amount) +{ + int32 time = CDLSBank::DLS32BitTimeCentsToMilliseconds(static_cast<int32>(amount) << 16); + return static_cast<int16>(Clamp(time, 20, 20000) / 20); +} + + +// Convert all instruments to the DLS format +bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info) +{ + if (m_Instruments.empty() || m_SamplesEx.empty()) + return false; + + const uint32 numInsts = static_cast<uint32>(sf2info.insts.GetLength() / sizeof(SFInst)); + const uint32 numInstBags = static_cast<uint32>(sf2info.instBags.GetLength() / sizeof(SFInstBag)); + + 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; + int32 instrAttenuation = 0; + int16 instrFinetune = 0; + // Default Envelope Values + dlsEnv.wVolAttack = 0; + dlsEnv.wVolDecay = 0; + dlsEnv.wVolRelease = 0; + dlsEnv.nVolSustainLevel = 128; + dlsEnv.nDefPan = 128; + // Load Preset Bags + sf2info.presetBags.Seek(dlsIns.wPresetBagNdx * sizeof(SFPresetBag)); + for(uint32 ipbagcnt = 0; ipbagcnt < dlsIns.wPresetBagNum; ipbagcnt++) + { + // Load generators for each preset bag + SFPresetBag bag[2]; + if(!sf2info.presetBags.ReadArray(bag)) + break; + sf2info.presetBags.SkipBack(sizeof(SFPresetBag)); + + sf2info.presetGens.Seek(bag[0].wGenNdx * sizeof(SFGenList)); + uint16 keyRange = 0xFFFF; + if(!sf2info.presetGens.ReadVector(generators, bag[1].wGenNdx - bag[0].wGenNdx)) + continue; + for(const auto &gen : generators) + { + const int16 value = static_cast<int16>(gen.genAmount); + switch(gen.sfGenOper) + { + case SF2_GEN_ATTACKVOLENV: + dlsEnv.wVolAttack = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_DECAYVOLENV: + dlsEnv.wVolDecay = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_SUSTAINVOLENV: + // 0.1% units + if(gen.genAmount >= 0) + { + dlsEnv.nVolSustainLevel = SF2SustainLevelToLinear(gen.genAmount); + } + break; + case SF2_GEN_RELEASEVOLENV: + dlsEnv.wVolRelease = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_INSTRUMENT: + if(const auto instr = std::make_pair(gen.genAmount.get(), keyRange); !mpt::contains(instruments, instr)) + instruments.push_back(instr); + keyRange = 0xFFFF; + break; + case SF2_GEN_KEYRANGE: + keyRange = gen.genAmount; + break; + case SF2_GEN_ATTENUATION: + instrAttenuation = -value; + break; + case SF2_GEN_COARSETUNE: + instrFinetune += value * 128; + break; + case SF2_GEN_FINETUNE: + instrFinetune += static_cast<int16>(Util::muldiv(static_cast<int8>(value), 128, 100)); + break; +#if 0 + default: + Log("Ins %3d: bag %3d gen %3d: ", nIns, ipbagndx, ipgenndx); + Log("genoper=%d amount=0x%04X ", gen.sfGenOper, gen.genAmount); + Log((pSmp->ulBank & F_INSTRUMENT_DRUMS) ? "(drum)\n" : "\n"); +#endif + } + } + } + // Envelope + if (!(dlsIns.ulBank & F_INSTRUMENT_DRUMS)) + { + m_Envelopes.push_back(dlsEnv); + dlsIns.nMelodicEnv = static_cast<uint32>(m_Envelopes.size()); + } + // Load Instrument Bags + dlsIns.Regions.clear(); + for(const auto & [nInstrNdx, keyRange] : instruments) + { + if(nInstrNdx >= numInsts) + continue; + sf2info.insts.Seek(nInstrNdx * sizeof(SFInst)); + SFInst insts[2]; + sf2info.insts.ReadArray(insts); + const uint32 numRegions = insts[1].wInstBagNdx - insts[0].wInstBagNdx; + dlsIns.Regions.reserve(dlsIns.Regions.size() + numRegions); + //Log("\nIns %3d, %2d regions:\n", nIns, pSmp->nRegions); + DLSREGION globalZone{}; + globalZone.uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note + globalZone.tuning = 100; + globalZone.sFineTune = instrFinetune; + globalZone.nWaveLink = Util::MaxValueOfType(globalZone.nWaveLink); + if(keyRange != 0xFFFF) + { + 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) + break; + // Create a new envelope for drums + DLSENVELOPE *pDlsEnv = &dlsEnv; + if(!(dlsIns.ulBank & F_INSTRUMENT_DRUMS) && dlsIns.nMelodicEnv > 0 && dlsIns.nMelodicEnv <= m_Envelopes.size()) + { + pDlsEnv = &m_Envelopes[dlsIns.nMelodicEnv - 1]; + } + + DLSREGION rgn = globalZone; + + // Region Default Values + int32 regionAttn = 0; + // Load Generators + sf2info.instBags.Seek(ibagcnt * sizeof(SFInstBag)); + SFInstBag bags[2]; + sf2info.instBags.ReadArray(bags); + sf2info.instGens.Seek(bags[0].wGenNdx * sizeof(SFInstGenList)); + uint16 lastOp = SF2_GEN_SAMPLEID; + int32 loopStart = 0, loopEnd = 0; + if(!sf2info.instGens.ReadVector(instrGenerators, bags[1].wGenNdx - bags[0].wGenNdx)) + break; + for(const auto &gen : instrGenerators) + { + uint16 value = gen.genAmount; + lastOp = gen.sfGenOper; + + switch(gen.sfGenOper) + { + case SF2_GEN_KEYRANGE: + { + uint8 keyMin = static_cast<uint8>(value & 0xFF); + uint8 keyMax = static_cast<uint8>(value >> 8); + 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; + case SF2_GEN_UNITYNOTE: + if (value < 128) rgn.uUnityNote = static_cast<uint8>(value); + break; + case SF2_GEN_ATTACKVOLENV: + pDlsEnv->wVolAttack = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_DECAYVOLENV: + pDlsEnv->wVolDecay = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_SUSTAINVOLENV: + // 0.1% units + if(gen.genAmount >= 0) + { + pDlsEnv->nVolSustainLevel = SF2SustainLevelToLinear(gen.genAmount); + } + break; + case SF2_GEN_RELEASEVOLENV: + pDlsEnv->wVolRelease = SF2TimeToDLS(gen.genAmount); + break; + case SF2_GEN_PAN: + { + int32 pan = static_cast<int16>(value); + pan = std::clamp(Util::muldivr(pan + 500, 256, 1000), 0, 256); + rgn.panning = static_cast<int16>(pan); + pDlsEnv->nDefPan = mpt::saturate_cast<uint8>(pan); + } + break; + case SF2_GEN_ATTENUATION: + regionAttn = -static_cast<int16>(value); + break; + case SF2_GEN_SAMPLEID: + if (value < m_SamplesEx.size()) + { + rgn.nWaveLink = value; + rgn.ulLoopStart = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwStartloop + loopStart); + rgn.ulLoopEnd = mpt::saturate_cast<uint32>(m_SamplesEx[value].dwEndloop + loopEnd); + } + break; + case SF2_GEN_SAMPLEMODES: + value &= 3; + rgn.fuOptions &= uint16(~(DLSREGION_SAMPLELOOP|DLSREGION_PINGPONGLOOP|DLSREGION_SUSTAINLOOP)); + if(value == 1) + rgn.fuOptions |= DLSREGION_SAMPLELOOP; + else if(value == 2) + rgn.fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_PINGPONGLOOP; + else if(value == 3) + rgn.fuOptions |= DLSREGION_SAMPLELOOP | DLSREGION_SUSTAINLOOP; + rgn.fuOptions |= DLSREGION_OVERRIDEWSMP; + break; + case SF2_GEN_KEYGROUP: + rgn.fuOptions |= (value & DLSREGION_KEYGROUPMASK); + break; + case SF2_GEN_COARSETUNE: + rgn.sFineTune += static_cast<int16>(value) * 128; + break; + case SF2_GEN_FINETUNE: + rgn.sFineTune += static_cast<int16>(Util::muldiv(static_cast<int8>(value), 128, 100)); + break; + case SF2_GEN_SCALE_TUNING: + rgn.tuning = mpt::saturate_cast<uint8>(value); + break; + case SF2_GEN_START_LOOP_FINE: + loopStart += static_cast<int16>(value); + break; + case SF2_GEN_END_LOOP_FINE: + loopEnd += static_cast<int16>(value); + break; + case SF2_GEN_START_LOOP_COARSE: + loopStart += static_cast<int16>(value) * 32768; + break; + case SF2_GEN_END_LOOP_COARSE: + loopEnd += static_cast<int16>(value) * 32768; + break; + //default: + // Log(" gen=%d value=%04X\n", pgen->sfGenOper, pgen->genAmount); + } + } + int32 linearVol = DLS32BitRelativeGainToLinear(((instrAttenuation + regionAttn) * 65536) / 10) / 256; + Limit(linearVol, 16, 256); + 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"); + } + } + } + return true; +} + + +/////////////////////////////////////////////////////////////// +// Open: opens a DLS bank + +bool CDLSBank::Open(const mpt::PathString &filename) +{ + if(filename.empty()) return false; + m_szFileName = filename; + InputFile f(filename, SettingCacheCompleteFileBeforeLoading()); + if(!f.IsValid()) return false; + return Open(GetFileReader(f)); +} + + +bool CDLSBank::Open(FileReader file) +{ + uint32 nInsDef; + + if(file.GetOptionalFileName()) + m_szFileName = file.GetOptionalFileName().value(); + + file.Rewind(); + size_t dwMemLength = file.GetLength(); + size_t dwMemPos = 0; + if(!file.CanRead(256)) + { + return false; + } + + RIFFChunkID riff; + file.ReadStruct(riff); + // Check DLS sections embedded in RMI midi files + if(riff.id_RIFF == IFFID_RIFF && riff.id_DLS == IFFID_RMID) + { + while(file.ReadStruct(riff)) + { + if(riff.id_RIFF == IFFID_RIFF && riff.id_DLS == IFFID_DLS) + { + file.SkipBack(sizeof(riff)); + break; + } + uint32 len = riff.riff_len; + if((len % 2u) != 0) + len++; + file.SkipBack(4); + file.Skip(len); + } + } + + // Check XDLS sections embedded in big endian IFF files (Miles Sound System) + if (riff.id_RIFF == IFFID_FORM) + { + do + { + if(riff.id_DLS == IFFID_XDLS) + { + file.ReadStruct(riff); + break; + } + uint32 len = mpt::bit_cast<uint32be>(riff.riff_len); + if((len % 2u) != 0) + len++; + file.SkipBack(4); + file.Skip(len); + } while(file.ReadStruct(riff)); + } + if (riff.id_RIFF != IFFID_RIFF + || (riff.id_DLS != IFFID_DLS && riff.id_DLS != IFFID_MLS && riff.id_DLS != IFFID_sfbk) + || !file.CanRead(riff.riff_len - 4)) + { + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", U_("Invalid DLS bank!")); + #endif + return false; + } + SF2LoaderInfo sf2info; + m_nType = (riff.id_DLS == IFFID_sfbk) ? SOUNDBANK_TYPE_SF2 : SOUNDBANK_TYPE_DLS; + m_dwWavePoolOffset = 0; + m_sf2version = 0; + m_Instruments.clear(); + m_WaveForms.clear(); + m_Envelopes.clear(); + nInsDef = 0; + if (dwMemLength > 8 + riff.riff_len + dwMemPos) dwMemLength = 8 + riff.riff_len + dwMemPos; + bool applyPaddingToSampleChunk = true; + while(file.CanRead(sizeof(IFFCHUNK))) + { + IFFCHUNK chunkHeader; + file.ReadStruct(chunkHeader); + dwMemPos = file.GetPosition(); + FileReader chunk = file.ReadChunk(chunkHeader.len); + + bool applyPadding = (chunkHeader.len % 2u) != 0; + + if(!chunk.LengthIsAtLeast(chunkHeader.len)) + break; + + switch(chunkHeader.id) + { + // DLS 1.0: Instruments Collection Header + case IFFID_colh: + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("colh ({} bytes)")(chunkHeader.len.get())); + #endif + if (m_Instruments.empty()) + { + m_Instruments.resize(chunk.ReadUint32LE()); + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT(" {} instruments")(m_Instruments.size())); + #endif + } + break; + + // DLS 1.0: Instruments Pointers Table + case IFFID_ptbl: + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("ptbl ({} bytes)")(chunkHeader.len.get())); + #endif + if (m_WaveForms.empty()) + { + PTBLChunk ptbl; + chunk.ReadStruct(ptbl); + chunk.Skip(ptbl.cbSize - 8); + uint32 cues = std::min(ptbl.cCues.get(), mpt::saturate_cast<uint32>(chunk.BytesLeft() / sizeof(uint32))); + m_WaveForms.reserve(cues); + for(uint32 i = 0; i < cues; i++) + { + m_WaveForms.push_back(chunk.ReadUint32LE()); + } + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT(" {} waveforms")(m_WaveForms.size())); + #endif + } + break; + + // DLS 1.0: LIST section + case IFFID_LIST: + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", U_("LIST")); + #endif + { + uint32 listid = chunk.ReadUint32LE(); + if (((listid == IFFID_wvpl) && (m_nType & SOUNDBANK_TYPE_DLS)) + || ((listid == IFFID_sdta) && (m_nType & SOUNDBANK_TYPE_SF2))) + { + m_dwWavePoolOffset = dwMemPos + 4; + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("Wave Pool offset: {}")(m_dwWavePoolOffset)); + #endif + if(!applyPaddingToSampleChunk) + applyPadding = false; + break; + } + + while (chunk.CanRead(12)) + { + IFFCHUNK listHeader; + chunk.ReadStruct(listHeader); + + if(!chunk.CanRead(listHeader.len)) + break; + + FileReader subData = chunk.GetChunkAt(chunk.GetPosition() - sizeof(IFFCHUNK), listHeader.len + 8); + FileReader listChunk = chunk.ReadChunk(listHeader.len); + if(listHeader.len % 2u) + chunk.Skip(1); + // DLS Instrument Headers + if (listHeader.id == IFFID_LIST && (m_nType & SOUNDBANK_TYPE_DLS)) + { + uint32 subID = listChunk.ReadUint32LE(); + if ((subID == IFFID_ins) && (nInsDef < m_Instruments.size())) + { + DLSINSTRUMENT &dlsIns = m_Instruments[nInsDef]; + //Log("Instrument %d:\n", nInsDef); + dlsIns.Regions.push_back({}); + UpdateInstrumentDefinition(&dlsIns, subData); + nInsDef++; + } + } else + // DLS/SF2 Bank Information + if (listid == IFFID_INFO && listHeader.len) + { + switch(listHeader.id) + { + case IFFID_ifil: + m_sf2version = listChunk.ReadUint16LE() << 16; + m_sf2version |= listChunk.ReadUint16LE(); + if(m_sf2version >= 0x3'0000 && m_sf2version <= 0x4'FFFF) + { + // "SF3" / "SF4" with compressed samples. The padding of the sample chunk is now optional (probably because it was simply forgotten to be added) + applyPaddingToSampleChunk = false; + } + listChunk.Skip(2); + break; + case IFFID_INAM: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szBankName, listChunk.BytesLeft()); + break; + case IFFID_IENG: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szEngineer, listChunk.BytesLeft()); + break; + case IFFID_ICOP: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szCopyRight, listChunk.BytesLeft()); + break; + case IFFID_ICMT: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szComments, listChunk.BytesLeft()); + break; + case IFFID_ISFT: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szSoftware, listChunk.BytesLeft()); + break; + case IFFID_ISBJ: + listChunk.ReadString<mpt::String::maybeNullTerminated>(m_BankInfo.szDescription, listChunk.BytesLeft()); + break; + } + } else + if ((listid == IFFID_pdta) && (m_nType & SOUNDBANK_TYPE_SF2)) + { + UpdateSF2PresetData(sf2info, listHeader, listChunk); + } + } + } + break; + + #ifdef DLSBANK_LOG + default: + { + char sdbg[5]; + memcpy(sdbg, &chunkHeader.id, 4); + sdbg[4] = 0; + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("Unsupported chunk: {} ({} bytes)")(mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(sdbg)), chunkHeader.len.get())); + } + break; + #endif + } + + if(applyPadding) + file.Skip(1); + } + // Build the ptbl is not present in file + if ((m_WaveForms.empty()) && (m_dwWavePoolOffset) && (m_nType & SOUNDBANK_TYPE_DLS) && (m_nMaxWaveLink > 0)) + { + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("ptbl not present: building table ({} wavelinks)...")(m_nMaxWaveLink)); + #endif + m_WaveForms.reserve(m_nMaxWaveLink); + file.Seek(m_dwWavePoolOffset); + while(m_WaveForms.size() < m_nMaxWaveLink && file.CanRead(sizeof(IFFCHUNK))) + { + IFFCHUNK chunk; + file.ReadStruct(chunk); + if (chunk.id == IFFID_LIST) + m_WaveForms.push_back(file.GetPosition() - m_dwWavePoolOffset - sizeof(IFFCHUNK)); + file.Skip(chunk.len); + } +#ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("Found {} waveforms")(m_WaveForms.size())); +#endif + } + // Convert the SF2 data to DLS + if ((m_nType & SOUNDBANK_TYPE_SF2) && !m_SamplesEx.empty() && !m_Instruments.empty()) + { + ConvertSF2ToDLS(sf2info); + } +#ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", U_("DLS bank closed")); +#endif + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Extracts the Waveforms from a DLS/SF2 bank + +uint32 CDLSBank::GetRegionFromKey(uint32 nIns, uint32 nKey) const +{ + if(nIns >= m_Instruments.size()) + return 0; + const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; + for(uint32 rgn = 0; rgn < static_cast<uint32>(dlsIns.Regions.size()); rgn++) + { + const auto ®ion = dlsIns.Regions[rgn]; + if(nKey < region.uKeyMin || nKey > region.uKeyMax) + continue; + if(region.nWaveLink == Util::MaxValueOfType(region.nWaveLink)) + continue; + return rgn; + } + return 0; +} + + +bool CDLSBank::ExtractWaveForm(uint32 nIns, uint32 nRgn, std::vector<uint8> &waveData, uint32 &length) const +{ + waveData.clear(); + length = 0; + + if (nIns >= m_Instruments.size() || !m_dwWavePoolOffset) + { + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("ExtractWaveForm({}) failed: m_Instruments.size()={} m_dwWavePoolOffset={} m_WaveForms.size()={}")(nIns, m_Instruments.size(), m_dwWavePoolOffset, m_WaveForms.size())); + #endif + return false; + } + const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; + if(nRgn >= dlsIns.Regions.size()) + { + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("invalid waveform region: nIns={} nRgn={} pSmp->nRegions={}")(nIns, nRgn, dlsIns.Regions.size())); + #endif + return false; + } + uint32 nWaveLink = dlsIns.Regions[nRgn].nWaveLink; + if(nWaveLink >= m_WaveForms.size()) + { + #ifdef DLSBANK_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("Invalid wavelink id: nWaveLink={} nWaveForms={}")(nWaveLink, m_WaveForms.size())); + #endif + return false; + } + + mpt::ifstream f(m_szFileName, std::ios::binary); + if(!f) + { + return false; + } + + mpt::IO::Offset sampleOffset = mpt::saturate_cast<mpt::IO::Offset>(m_WaveForms[nWaveLink] + m_dwWavePoolOffset); + if(mpt::IO::SeekAbsolute(f, sampleOffset)) + { + if (m_nType & SOUNDBANK_TYPE_SF2) + { + if (m_SamplesEx[nWaveLink].dwLen) + { + if (mpt::IO::SeekRelative(f, 8)) + { + length = m_SamplesEx[nWaveLink].dwLen; + try + { + waveData.assign(length + 8, 0); + mpt::IO::ReadRaw(f, waveData.data(), length); + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + } + } + } + } else + { + LISTChunk chunk; + if(mpt::IO::Read(f, chunk)) + { + if((chunk.id == IFFID_LIST) && (chunk.listid == IFFID_wave) && (chunk.len > 4)) + { + length = chunk.len + 8; + try + { + waveData.assign(chunk.len + sizeof(IFFCHUNK), 0); + memcpy(waveData.data(), &chunk, sizeof(chunk)); + mpt::IO::ReadRaw(f, &waveData[sizeof(chunk)], length - sizeof(chunk)); + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + } + } + } + } + } + return !waveData.empty(); +} + + +bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nIns, uint32 nRgn, int transpose) const +{ + std::vector<uint8> pWaveForm; + uint32 dwLen = 0; + bool ok, hasWaveform; + + if(nIns >= m_Instruments.size()) + return false; + const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; + if(nRgn >= dlsIns.Regions.size()) + return false; + if(!ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen)) + return false; + if(dwLen < 16) + return false; + ok = false; + + FileReader wsmpChunk; + if (m_nType & SOUNDBANK_TYPE_SF2) + { + sndFile.DestroySample(nSample); + uint32 nWaveLink = dlsIns.Regions[nRgn].nWaveLink; + ModSample &sample = sndFile.GetSample(nSample); + if (sndFile.m_nSamples < nSample) sndFile.m_nSamples = nSample; + if (nWaveLink < m_SamplesEx.size()) + { + const DLSSAMPLEEX &p = m_SamplesEx[nWaveLink]; + #ifdef DLSINSTR_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" SF2 WaveLink #{}: {}Hz")(nWaveLink, p.dwSampleRate)); + #endif + sample.Initialize(); + + FileReader chunk{mpt::as_span(pWaveForm.data(), dwLen)}; + if(!p.compressed || !sndFile.ReadSampleFromFile(nSample, chunk, false, false)) + { + sample.nLength = dwLen / 2; + SampleIO( + SampleIO::_16bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(sample, chunk); + } + sample.nLoopStart = dlsIns.Regions[nRgn].ulLoopStart; + sample.nLoopEnd = dlsIns.Regions[nRgn].ulLoopEnd; + sample.nC5Speed = p.dwSampleRate; + sample.RelativeTone = p.byOriginalPitch; + sample.nFineTune = p.chPitchCorrection; + if(p.szName[0]) + sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(p.szName); + else if(dlsIns.szName[0]) + sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(dlsIns.szName); + } + hasWaveform = sample.HasSampleData(); + } else + { + FileReader file(mpt::as_span(pWaveForm.data(), dwLen)); + hasWaveform = sndFile.ReadWAVSample(nSample, file, false, &wsmpChunk); + if(dlsIns.szName[0]) + sndFile.m_szNames[nSample] = mpt::String::ReadAutoBuf(dlsIns.szName); + } + if (hasWaveform) + { + ModSample &sample = sndFile.GetSample(nSample); + const DLSREGION &rgn = dlsIns.Regions[nRgn]; + 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_SUSTAINLOOP) sample.uFlags.set(CHN_SUSTAINLOOP); + if (rgn.fuOptions & DLSREGION_PINGPONGLOOP) sample.uFlags.set(CHN_PINGPONGLOOP); + if (sample.uFlags[CHN_LOOP | CHN_SUSTAINLOOP]) + { + if (rgn.ulLoopEnd > rgn.ulLoopStart) + { + if (sample.uFlags[CHN_SUSTAINLOOP]) + { + sample.nSustainStart = rgn.ulLoopStart; + sample.nSustainEnd = rgn.ulLoopEnd; + } else + { + sample.nLoopStart = rgn.ulLoopStart; + sample.nLoopEnd = rgn.ulLoopEnd; + } + } else + { + sample.uFlags.reset(CHN_LOOP|CHN_SUSTAINLOOP); + } + } + // WSMP chunk + { + uint32 usUnityNote = rgn.uUnityNote; + int sFineTune = rgn.sFineTune; + int lVolume = rgn.usVolume; + + WSMPChunk wsmp; + if(!(rgn.fuOptions & DLSREGION_OVERRIDEWSMP) && wsmpChunk.IsValid() && wsmpChunk.Skip(sizeof(IFFCHUNK)) && wsmpChunk.ReadStructPartial(wsmp)) + { + usUnityNote = wsmp.usUnityNote; + sFineTune = wsmp.sFineTune; + lVolume = DLS32BitRelativeGainToLinear(wsmp.lAttenuation) / 256; + if(wsmp.cSampleLoops) + { + WSMPSampleLoop loop; + wsmpChunk.Seek(sizeof(IFFCHUNK) + wsmp.cbSize); + wsmpChunk.ReadStruct(loop); + if(loop.ulLoopLength > 3) + { + sample.uFlags.set(CHN_LOOP); + //if (loop.ulLoopType) sample.uFlags |= CHN_PINGPONGLOOP; + sample.nLoopStart = loop.ulLoopStart; + sample.nLoopEnd = loop.ulLoopStart + loop.ulLoopLength; + } + } + } else if (m_nType & SOUNDBANK_TYPE_SF2) + { + usUnityNote = (usUnityNote < 0x80) ? usUnityNote : sample.RelativeTone; + sFineTune += sample.nFineTune; + } + #ifdef DLSINSTR_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("WSMP: usUnityNote={}.{}, {}Hz (transp={})")(usUnityNote, sFineTune, sample.nC5Speed, transpose)); + #endif + if (usUnityNote > 0x7F) usUnityNote = 60; + int steps = (60 + transpose - usUnityNote) * 128 + sFineTune; + sample.Transpose(steps * (1.0 / (12.0 * 128.0))); + sample.RelativeTone = 0; + + Limit(lVolume, 16, 256); + sample.nGlobalVol = (uint8)(lVolume / 4); // 0-64 + } + sample.nPan = GetPanning(nIns, nRgn); + + sample.Convert(MOD_TYPE_IT, sndFile.GetType()); + sample.PrecomputeLoops(sndFile, false); + ok = true; + } + return ok; +} + + +static uint16 ScaleEnvelope(uint32 time, float tempoScale) +{ + return std::max(mpt::saturate_round<uint16>(time * tempoScale), uint16(1)); +} + + +bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, uint32 nIns, uint32 nDrumRgn) const +{ + uint32 minRegion, maxRegion, nEnv; + + if (nIns >= m_Instruments.size()) + return false; + const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; + const bool isDrum = (dlsIns.ulBank & F_INSTRUMENT_DRUMS) && nDrumRgn != uint32_max; + if(isDrum) + { + if(nDrumRgn >= dlsIns.Regions.size()) + return false; + minRegion = nDrumRgn; + maxRegion = nDrumRgn + 1; + nEnv = dlsIns.Regions[nDrumRgn].uPercEnv; + } else + { + if(dlsIns.Regions.empty()) + return false; + minRegion = 0; + maxRegion = static_cast<uint32>(dlsIns.Regions.size()); + nEnv = dlsIns.nMelodicEnv; + } +#ifdef DLSINSTR_LOG + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("DLS Instrument #{}: {}")(nIns, mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(dlsIns.szName)))); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" Bank=0x{} Instrument=0x{}")(mpt::ufmt::HEX0<4>(dlsIns.ulBank), mpt::ufmt::HEX0<4>(dlsIns.ulInstrument))); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" {} regions, nMelodicEnv={}")(dlsIns.Regions.size(), dlsIns.nMelodicEnv)); + for (uint32 iDbg=0; iDbg<dlsIns.Regions.size(); iDbg++) + { + const DLSREGION *prgn = &dlsIns.Regions[iDbg]; + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" Region {}:")(iDbg)); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" WaveLink = {} (loop [{}, {}])")(prgn->nWaveLink, prgn->ulLoopStart, prgn->ulLoopEnd)); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" Key Range: [{}, {}]")(prgn->uKeyMin, prgn->uKeyMax)); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" fuOptions = 0x{}")(mpt::ufmt::HEX0<4>(prgn->fuOptions))); + MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT(" usVolume = {}, Unity Note = {}")(prgn->usVolume, prgn->uUnityNote)); + } +#endif + + ModInstrument *pIns = new (std::nothrow) ModInstrument(); + if(pIns == nullptr) + { + return false; + } + + if(sndFile.Instruments[nInstr]) + { + sndFile.DestroyInstrument(nInstr, deleteAssociatedSamples); + } + // Initializes Instrument + if(isDrum) + { + uint32 key = dlsIns.Regions[nDrumRgn].uKeyMin; + if((key >= 24) && (key <= 84)) + { + std::string s = szMidiPercussionNames[key-24]; + if(!mpt::String::ReadAutoBuf(dlsIns.szName).empty()) + { + s += MPT_AFORMAT(" ({})")(mpt::trim_right<std::string>(mpt::String::ReadAutoBuf(dlsIns.szName))); + } + pIns->name = s; + } else + { + pIns->name = mpt::String::ReadAutoBuf(dlsIns.szName); + } + } else + { + pIns->name = mpt::String::ReadAutoBuf(dlsIns.szName); + } + int transpose = 0; + if(isDrum) + { + for(uint32 iNoteMap = 0; iNoteMap < NOTE_MAX; iNoteMap++) + { + if(sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MID | MOD_TYPE_MPT)) + { + // Format has instrument note mapping + if(dlsIns.Regions[nDrumRgn].tuning == 0) + pIns->NoteMap[iNoteMap] = NOTE_MIDDLEC; + else if (iNoteMap < dlsIns.Regions[nDrumRgn].uKeyMin) + pIns->NoteMap[iNoteMap] = (uint8)(dlsIns.Regions[nDrumRgn].uKeyMin + NOTE_MIN); + else if(iNoteMap > dlsIns.Regions[nDrumRgn].uKeyMax) + pIns->NoteMap[iNoteMap] = (uint8)(dlsIns.Regions[nDrumRgn].uKeyMax + NOTE_MIN); + } else + { + if(iNoteMap == dlsIns.Regions[nDrumRgn].uKeyMin) + { + transpose = (dlsIns.Regions[nDrumRgn].uKeyMin + (dlsIns.Regions[nDrumRgn].uKeyMax - dlsIns.Regions[nDrumRgn].uKeyMin) / 2) - 60; + } + } + } + } + pIns->nFadeOut = 1024; + pIns->nMidiProgram = (uint8)(dlsIns.ulInstrument & 0x7F) + 1; + pIns->nMidiChannel = (uint8)(isDrum ? 10 : 0); + pIns->wMidiBank = (uint16)(((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F)); + pIns->nNNA = NewNoteAction::NoteOff; + pIns->nDCT = DuplicateCheckType::Note; + pIns->nDNA = DuplicateNoteAction::NoteFade; + sndFile.Instruments[nInstr] = pIns; + uint32 nLoadedSmp = 0; + SAMPLEINDEX nextSample = 0; + // Extract Samples + std::vector<SAMPLEINDEX> RgnToSmp(dlsIns.Regions.size()); + std::set<uint16> extractedSamples; + for(uint32 nRgn = minRegion; nRgn < maxRegion; nRgn++) + { + bool duplicateRegion = false; + SAMPLEINDEX nSmp = 0; + const DLSREGION &rgn = dlsIns.Regions[nRgn]; + if(rgn.IsDummy()) + continue; + // Elimitate Duplicate Regions + uint32 dupRegion; + for(dupRegion = minRegion; dupRegion < nRgn; dupRegion++) + { + const DLSREGION &rgn2 = dlsIns.Regions[dupRegion]; + if(RgnToSmp[dupRegion] == 0 || rgn2.IsDummy()) + continue; + // No need to extract the same sample data twice + const bool sameSample = (rgn2.nWaveLink == rgn.nWaveLink) && (rgn2.ulLoopEnd == rgn.ulLoopEnd) && (rgn2.ulLoopStart == rgn.ulLoopStart) && extractedSamples.count(rgn.nWaveLink); + // Candidate for stereo sample creation + const bool sameKeyRange = (rgn2.uKeyMin == rgn.uKeyMin) && (rgn2.uKeyMax == rgn.uKeyMax); + if(sameSample || sameKeyRange) + { + duplicateRegion = true; + if(!sameKeyRange) + nSmp = RgnToSmp[dupRegion]; + break; + } + } + // Create a new sample + if (!duplicateRegion) + { + uint32 nmaxsmp = (m_nType & MOD_TYPE_XM) ? 16 : (NOTE_MAX - NOTE_MIN + 1); + if (nLoadedSmp >= nmaxsmp) + { + nSmp = RgnToSmp[nRgn - 1]; + } else + { + nextSample = sndFile.GetNextFreeSample(nInstr, nextSample + 1); + if (nextSample == SAMPLEINDEX_INVALID) break; + if (nextSample > sndFile.GetNumSamples()) sndFile.m_nSamples = nextSample; + nSmp = nextSample; + nLoadedSmp++; + } + } + + RgnToSmp[nRgn] = nSmp; + // Map all notes to the right sample + if(nSmp) + { + for(uint8 key = 0; key < NOTE_MAX; key++) + { + if(isDrum || (key >= rgn.uKeyMin && key <= rgn.uKeyMax)) + { + pIns->Keyboard[key] = nSmp; + } + } + // Load the sample + if(!duplicateRegion || !sndFile.GetSample(nSmp).HasSampleData()) + { + ExtractSample(sndFile, nSmp, nIns, nRgn, transpose); + extractedSamples.insert(rgn.nWaveLink); + } + } else if(duplicateRegion && sndFile.GetSample(RgnToSmp[dupRegion]).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)) + { + ModSample &sample = sndFile.GetSample(RgnToSmp[dupRegion]); + ModSample sampleCopy = sample; + sampleCopy.pData.pSample = nullptr; + sampleCopy.uFlags.set(CHN_16BIT | CHN_STEREO); + if(!sampleCopy.AllocateSample()) + continue; + + const uint8 offsetOrig = (pan1 < pan2) ? 1 : 0; + const uint8 offsetNew = (pan1 < pan2) ? 0 : 1; + + std::vector<uint8> pWaveForm; + uint32 dwLen = 0; + if(!ExtractWaveForm(nIns, nRgn, pWaveForm, dwLen)) + continue; + extractedSamples.insert(rgn.nWaveLink); + + // First copy over original channel + auto pDest = sampleCopy.sample16() + offsetOrig; + if(sample.uFlags[CHN_16BIT]) + CopySample<SC::ConversionChain<SC::Convert<int16, int16>, SC::DecodeIdentity<int16>>>(pDest, sample.nLength, 2, sample.sample16(), sample.GetSampleSizeInBytes(), 1); + else + CopySample<SC::ConversionChain<SC::Convert<int16, int8>, SC::DecodeIdentity<int8>>>(pDest, sample.nLength, 2, sample.sample8(), sample.GetSampleSizeInBytes(), 1); + sample.FreeSample(); + + // Now read the other channel + if(m_SamplesEx[m_Instruments[nIns].Regions[nRgn].nWaveLink].compressed) + { + FileReader file{mpt::as_span(pWaveForm)}; + if(sndFile.ReadSampleFromFile(nSmp, file, false, false)) + { + pDest = sampleCopy.sample16() + offsetNew; + const SmpLength copyLength = std::min(sample.nLength, sampleCopy.nLength); + if(sample.uFlags[CHN_16BIT]) + CopySample<SC::ConversionChain<SC::Convert<int16, int16>, SC::DecodeIdentity<int16>>>(pDest, copyLength, 2, sample.sample16(), sample.GetSampleSizeInBytes(), sample.GetNumChannels()); + else + CopySample<SC::ConversionChain<SC::Convert<int16, int8>, SC::DecodeIdentity<int8>>>(pDest, copyLength, 2, sample.sample8(), sample.GetSampleSizeInBytes(), sample.GetNumChannels()); + } + } else + { + SmpLength len = std::min(dwLen / 2u, sampleCopy.nLength); + const int16 *src = reinterpret_cast<int16 *>(pWaveForm.data()); + int16 *dst = sampleCopy.sample16() + offsetNew; + CopySample<SC::ConversionChain<SC::Convert<int16, int16>, SC::DecodeIdentity<int16>>>(dst, len, 2, src, pWaveForm.size(), 1); + } + sample.FreeSample(); + sample = sampleCopy; + } + } + } + + float tempoScale = 1.0f; + if(sndFile.m_nTempoMode == TempoMode::Modern) + { + uint32 ticksPerBeat = sndFile.m_nDefaultRowsPerBeat * sndFile.m_nDefaultSpeed; + if(ticksPerBeat != 0) + tempoScale = ticksPerBeat / 24.0f; + } + + // Initializes Envelope + if ((nEnv) && (nEnv <= m_Envelopes.size())) + { + const DLSENVELOPE &part = m_Envelopes[nEnv - 1]; + // Volume Envelope + if ((part.wVolAttack) || (part.wVolDecay < 20*50) || (part.nVolSustainLevel) || (part.wVolRelease < 20*50)) + { + pIns->VolEnv.dwFlags.set(ENV_ENABLED); + // Delay section + // -> DLS level 2 + // Attack section + pIns->VolEnv.clear(); + if (part.wVolAttack) + { + pIns->VolEnv.push_back(0, (uint8)(ENVELOPE_MAX / (part.wVolAttack / 2 + 2) + 8)); // /----- + pIns->VolEnv.push_back(ScaleEnvelope(part.wVolAttack, tempoScale), ENVELOPE_MAX); // | + } else + { + pIns->VolEnv.push_back(0, ENVELOPE_MAX); + } + // Hold section + // -> DLS Level 2 + // Sustain Level + if (part.nVolSustainLevel > 0) + { + if (part.nVolSustainLevel < 128) + { + uint16 lStartTime = pIns->VolEnv.back().tick; + int32 lSusLevel = - DLS32BitRelativeLinearToGain(part.nVolSustainLevel << 9) / 65536; + int32 lDecayTime = 1; + if (lSusLevel > 0) + { + lDecayTime = (lSusLevel * (int32)part.wVolDecay) / 960; + for (uint32 i=0; i<7; i++) + { + int32 lFactor = 128 - (1 << i); + if (lFactor <= part.nVolSustainLevel) break; + int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 9) / 65536; + if (lev > 0) + { + int32 ltime = (lev * (int32)part.wVolDecay) / 960; + if ((ltime > 1) && (ltime < lDecayTime)) + { + uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale); + if(tick > pIns->VolEnv.back().tick) + { + pIns->VolEnv.push_back(tick, (uint8)(lFactor / 2)); + } + } + } + } + } + + uint16 decayEnd = lStartTime + ScaleEnvelope(lDecayTime, tempoScale); + if (decayEnd > pIns->VolEnv.back().tick) + { + pIns->VolEnv.push_back(decayEnd, (uint8)((part.nVolSustainLevel+1) / 2)); + } + } + pIns->VolEnv.dwFlags.set(ENV_SUSTAIN); + } else + { + pIns->VolEnv.dwFlags.set(ENV_SUSTAIN); + pIns->VolEnv.push_back(pIns->VolEnv.back().tick + 1u, pIns->VolEnv.back().value); + } + pIns->VolEnv.nSustainStart = pIns->VolEnv.nSustainEnd = (uint8)(pIns->VolEnv.size() - 1); + // Release section + if ((part.wVolRelease) && (pIns->VolEnv.back().value > 1)) + { + int32 lReleaseTime = part.wVolRelease; + uint16 lStartTime = pIns->VolEnv.back().tick; + int32 lStartFactor = pIns->VolEnv.back().value; + int32 lSusLevel = - DLS32BitRelativeLinearToGain(lStartFactor << 10) / 65536; + int32 lDecayEndTime = (lReleaseTime * lSusLevel) / 960; + lReleaseTime -= lDecayEndTime; + if(pIns->VolEnv.nSustainEnd > 0) + pIns->VolEnv.nReleaseNode = pIns->VolEnv.nSustainEnd; + for (uint32 i=0; i<5; i++) + { + int32 lFactor = 1 + ((lStartFactor * 3) >> (i+2)); + if ((lFactor <= 1) || (lFactor >= lStartFactor)) continue; + int32 lev = - DLS32BitRelativeLinearToGain(lFactor << 10) / 65536; + if (lev > 0) + { + int32 ltime = (((int32)part.wVolRelease * lev) / 960) - lDecayEndTime; + if ((ltime > 1) && (ltime < lReleaseTime)) + { + uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale); + if(tick > pIns->VolEnv.back().tick) + { + pIns->VolEnv.push_back(tick, (uint8)lFactor); + } + } + } + } + if (lReleaseTime < 1) lReleaseTime = 1; + auto releaseTicks = ScaleEnvelope(lReleaseTime, tempoScale); + pIns->VolEnv.push_back(lStartTime + releaseTicks, ENVELOPE_MIN); + if(releaseTicks > 0) + { + pIns->nFadeOut = 32768 / releaseTicks; + } + } else + { + pIns->VolEnv.push_back(pIns->VolEnv.back().tick + 1u, ENVELOPE_MIN); + } + } + } + if(isDrum) + { + // Create a default envelope for drums + pIns->VolEnv.dwFlags.reset(ENV_SUSTAIN); + if(!pIns->VolEnv.dwFlags[ENV_ENABLED]) + { + pIns->VolEnv.dwFlags.set(ENV_ENABLED); + pIns->VolEnv.resize(4); + pIns->VolEnv[0] = EnvelopeNode(0, ENVELOPE_MAX); + pIns->VolEnv[1] = EnvelopeNode(ScaleEnvelope(5, tempoScale), ENVELOPE_MAX); + pIns->VolEnv[2] = EnvelopeNode(pIns->VolEnv[1].tick * 2u, ENVELOPE_MID); + pIns->VolEnv[3] = EnvelopeNode(pIns->VolEnv[2].tick * 2u, ENVELOPE_MIN); // 1 second max. for drums + } + } + pIns->Sanitize(MOD_TYPE_MPT); + pIns->Convert(MOD_TYPE_MPT, sndFile.GetType()); + return true; +} + + +const char *CDLSBank::GetRegionName(uint32 nIns, uint32 nRgn) const +{ + if(nIns >= m_Instruments.size()) + return nullptr; + const DLSINSTRUMENT &dlsIns = m_Instruments[nIns]; + if(nRgn >= dlsIns.Regions.size()) + return nullptr; + + if (m_nType & SOUNDBANK_TYPE_SF2) + { + uint32 nWaveLink = dlsIns.Regions[nRgn].nWaveLink; + if (nWaveLink < m_SamplesEx.size()) + { + return m_SamplesEx[nWaveLink].szName; + } + } + return nullptr; +} + + +uint16 CDLSBank::GetPanning(uint32 ins, uint32 region) const +{ + const DLSINSTRUMENT &dlsIns = m_Instruments[ins]; + if(region >= std::size(dlsIns.Regions)) + return 128; + const DLSREGION &rgn = dlsIns.Regions[region]; + if(rgn.panning >= 0) + return static_cast<uint16>(rgn.panning); + + if(dlsIns.ulBank & F_INSTRUMENT_DRUMS) + { + if(rgn.uPercEnv > 0 && rgn.uPercEnv <= m_Envelopes.size()) + { + return m_Envelopes[rgn.uPercEnv - 1].nDefPan; + } + } else + { + if(dlsIns.nMelodicEnv > 0 && dlsIns.nMelodicEnv <= m_Envelopes.size()) + { + return m_Envelopes[dlsIns.nMelodicEnv - 1].nDefPan; + } + } + return 128; +} + + +#else // !MODPLUG_TRACKER + +MPT_MSVC_WORKAROUND_LNK4221(Dlsbank) + +#endif // MODPLUG_TRACKER + + +OPENMPT_NAMESPACE_END |