/* * 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(l); } return 128; } static uint8 SF2SustainLevelToLinear(int32 sustain) { // 0.1% units int32 l = 128 * (1000 - Clamp(sustain, 0, 1000)) / 1000; return static_cast(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(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> 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(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(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(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(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(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(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(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(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(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(amount) << 16); return static_cast(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(sf2info.insts.GetLength() / sizeof(SFInst)); const uint32 numInstBags = static_cast(sf2info.instBags.GetLength() / sizeof(SFInstBag)); std::vector> instruments; // instrument, key range std::vector generators; std::vector 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(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(Util::muldiv(static_cast(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(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(keyRange & 0xFF); globalZone.uKeyMax = static_cast(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(value & 0xFF); uint8 keyMax = static_cast(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(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(value); pan = std::clamp(Util::muldivr(pan + 500, 256, 1000), 0, 256); rgn.panning = static_cast(pan); pDlsEnv->nDefPan = mpt::saturate_cast(pan); } break; case SF2_GEN_ATTENUATION: regionAttn = -static_cast(value); break; case SF2_GEN_SAMPLEID: if (value < m_SamplesEx.size()) { rgn.nWaveLink = value; rgn.ulLoopStart = mpt::saturate_cast(m_SamplesEx[value].dwStartloop + loopStart); rgn.ulLoopEnd = mpt::saturate_cast(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(value) * 128; break; case SF2_GEN_FINETUNE: rgn.sFineTune += static_cast(Util::muldiv(static_cast(value), 128, 100)); break; case SF2_GEN_SCALE_TUNING: rgn.tuning = mpt::saturate_cast(value); break; case SF2_GEN_START_LOOP_FINE: loopStart += static_cast(value); break; case SF2_GEN_END_LOOP_FINE: loopEnd += static_cast(value); break; case SF2_GEN_START_LOOP_COARSE: loopStart += static_cast(value) * 32768; break; case SF2_GEN_END_LOOP_COARSE: loopEnd += static_cast(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(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(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(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(m_BankInfo.szBankName, listChunk.BytesLeft()); break; case IFFID_IENG: listChunk.ReadString(m_BankInfo.szEngineer, listChunk.BytesLeft()); break; case IFFID_ICOP: listChunk.ReadString(m_BankInfo.szCopyRight, listChunk.BytesLeft()); break; case IFFID_ICMT: listChunk.ReadString(m_BankInfo.szComments, listChunk.BytesLeft()); break; case IFFID_ISFT: listChunk.ReadString(m_BankInfo.szSoftware, listChunk.BytesLeft()); break; case IFFID_ISBJ: listChunk.ReadString(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(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 &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(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 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(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(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; iDbgnWaveLink, 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(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 RgnToSmp(dlsIns.Regions.size()); std::set 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 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::DecodeIdentity>>(pDest, sample.nLength, 2, sample.sample16(), sample.GetSampleSizeInBytes(), 1); else CopySample, SC::DecodeIdentity>>(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::DecodeIdentity>>(pDest, copyLength, 2, sample.sample16(), sample.GetSampleSizeInBytes(), sample.GetNumChannels()); else CopySample, SC::DecodeIdentity>>(pDest, copyLength, 2, sample.sample8(), sample.GetSampleSizeInBytes(), sample.GetNumChannels()); } } else { SmpLength len = std::min(dwLen / 2u, sampleCopy.nLength); const int16 *src = reinterpret_cast(pWaveForm.data()); int16 *dst = sampleCopy.sample16() + offsetNew; CopySample, SC::DecodeIdentity>>(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(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