Cog/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.cpp
Christopher Snowhill 9ed4e4e8d9 Updated libOpenMPT to version 0.7.13
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-01-10 14:39:38 -08:00

2353 lines
69 KiB
C++

/*
* 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 "mpt/io_file/inputfile.hpp"
#include "mpt/io_file_read/inputfile_filecursor.hpp"
#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),
ART_PITCH_EG_DELAYTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_DELAYTIME),
ART_PITCH_EG_HOLDTIME = DLSArt(CONN_SRC_NONE, CONN_SRC_NONE, CONN_DST_EG2_HOLDTIME),
ART_PITCH_EG_DEPTH = DLSArt(CONN_SRC_EG2, CONN_SRC_NONE, CONN_DST_PITCH),
// 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_MODENVTOPITCH = 7,
SF2_GEN_MODENVTOFILTERFC = 11,
SF2_GEN_PAN = 17,
SF2_GEN_DELAYMODENV = 25,
SF2_GEN_ATTACKMODENV = 26,
SF2_GEN_HOLDMODENV = 27,
SF2_GEN_DECAYMODENV = 28,
SF2_GEN_SUSTAINMODENV = 29,
SF2_GEN_RELEASEMODENV = 30,
SF2_GEN_DELAYVOLENV = 33,
SF2_GEN_ATTACKVOLENV = 34,
SF2_GEN_HOLDVOLENV = 35,
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;
bool ApplyToEnvelope(DLSENVELOPE &env) const;
};
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)
using SFInstGenList = SFGenList;
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 int16 SF2TimeToDLS(int16 amount)
{
int32 time = CDLSBank::DLS32BitTimeCentsToMilliseconds(static_cast<int32>(amount) << 16);
return static_cast<int16>(Clamp(time, 20, 20000) / 20);
}
static uint8 SF2SustainLevelToLinear(int32 sustain)
{
// 0.1% units
int32 l = 128 * (1000 - Clamp(sustain, 0, 1000)) / 1000;
return static_cast<uint8>(l);
}
bool SFGenList::ApplyToEnvelope(DLSENVELOPE &env) const
{
switch(sfGenOper)
{
case SF2_GEN_DELAYVOLENV:
env.volumeEnv.delay = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_ATTACKVOLENV:
env.volumeEnv.attack = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_HOLDVOLENV:
env.volumeEnv.hold = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_DECAYVOLENV:
env.volumeEnv.decay = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_SUSTAINVOLENV:
// 0.1% units
if(genAmount >= 0)
{
env.volumeEnv.sustainLevel = SF2SustainLevelToLinear(genAmount);
}
break;
case SF2_GEN_RELEASEVOLENV:
env.volumeEnv.release = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_DELAYMODENV:
env.pitchEnv.delay = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_ATTACKMODENV:
env.pitchEnv.attack = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_HOLDMODENV:
env.pitchEnv.hold = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_DECAYMODENV:
env.pitchEnv.decay = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_SUSTAINMODENV:
env.pitchEnv.sustainLevel = SF2SustainLevelToLinear(genAmount);
break;
case SF2_GEN_RELEASEMODENV:
env.pitchEnv.release = SF2TimeToDLS(genAmount);
break;
case SF2_GEN_MODENVTOPITCH:
env.pitchEnvDepth = static_cast<int16>(genAmount);
break;
default: return false;
}
return true;
}
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;
}
uint16 CDLSBank::DLSEnvelopeTimeCentsToMilliseconds(int32 value)
{
if(value <= -0x40000000)
return 0;
int32 decaytime = DLS32BitTimeCentsToMilliseconds(value);
if(decaytime > 20000)
decaytime = 20000;
if(decaytime >= 20)
return static_cast<uint16>(decaytime / 20);
return 0;
}
// 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)
{
if(filename.empty())
return false;
mpt::IO::InputFile f(filename, false);
if(!f.IsValid())
return false;
return IsDLSBank(GetFileReader(f));
}
bool CDLSBank::IsDLSBank(FileReader file)
{
file.Rewind();
RIFFChunkID riff;
if(!file.ReadStruct(riff))
return false;
// 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)
{
if(!file.ReadStruct(riff))
return false;
break; // found it
}
if((len % 2u) != 0)
len++;
if(!file.Skip(len - 4))
return false;
} while(file.ReadStruct(riff));
} else if(riff.id_RIFF == IFFID_RIFF && riff.id_DLS == IFFID_RMID)
{
for (;;)
{
if(!file.ReadStruct(riff))
return false;
if(riff.id_DLS == IFFID_DLS || riff.id_DLS == IFFID_sfbk)
break; // found it
int len = riff.riff_len;
if((len % 2u) != 0)
len++;
if((len <= 4) || !file.Skip(len - 4))
return false;
}
}
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
{
uint32 minBank = ((bank << 1) & 0x7F00) | (bank & 0x7F);
uint32 maxBank = minBank;
if(bank >= 0x4000)
{
minBank = 0x0000;
maxBank = 0x7F7F;
}
if(isDrum)
{
minBank |= F_INSTRUMENT_DRUMS;
maxBank |= F_INSTRUMENT_DRUMS;
}
const bool singleInstr = (minBank == maxBank) && (program < 0x80);
const auto CompareInstrFunc = [singleInstr](const DLSINSTRUMENT &l, const DLSINSTRUMENT &r)
{
if(singleInstr)
return l < r;
else
return l.ulBank < r.ulBank;
};
DLSINSTRUMENT findInstr{};
findInstr.ulInstrument = program;
findInstr.ulBank = minBank;
const auto minInstr = std::lower_bound(m_Instruments.begin(), m_Instruments.end(), findInstr, CompareInstrFunc);
findInstr.ulBank = maxBank;
const auto maxInstr = std::upper_bound(m_Instruments.begin(), m_Instruments.end(), findInstr, CompareInstrFunc);
const auto instrRange = mpt::as_span(m_Instruments.data() + std::distance(m_Instruments.begin(), minInstr), std::distance(minInstr, maxInstr));
for(const DLSINSTRUMENT &dlsIns : instrRange)
{
if((program < 0x80) && program != (dlsIns.ulInstrument & 0x7F))
continue;
if(isDrum)
{
const bool anyKey = !key || key >= 0x80;
for(const auto &region : dlsIns.Regions)
{
if(region.IsDummy())
continue;
if(anyKey || (key >= region.uKeyMin && key <= region.uKeyMax))
{
if(pInsNo)
*pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns));
return &dlsIns;
} else if(region.uKeyMin > key)
{
// Regions are sorted, if we arrived here we won't find anything in the remaining regions
break;
}
}
} else
{
if(pInsNo)
*pInsNo = static_cast<uint32>(std::distance(m_Instruments.data(), &dlsIns));
return &dlsIns;
}
}
return nullptr;
}
bool CDLSBank::FindAndExtract(CSoundFile &sndFile, const INSTRUMENTINDEX ins, const bool isDrum, FileReader *file) 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, file))
{
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 &region = 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 &region = 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 &region = 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);
} else
{
if(!pDlsIns->Regions.empty())
pDlsIns->Regions.back().uPercEnv = static_cast<uint32>(m_Envelopes.size() + 1);
}
if(art1.cbSize + art1.cConnectionBlocks * sizeof(ConnectionBlock) > header.len)
break;
DLSENVELOPE dlsEnv;
//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.defaultPan = mpt::saturate_cast<uint8>(pan);
}
break;
case ART_VOL_EG_DELAYTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.volumeEnv.delay = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_VOL_EG_ATTACKTIME:
// 32-bit time cents units. range = [0s, 20s]
if(blk.lScale > -0x40000000)
{
int32 l = std::min(0, blk.lScale - 78743200); // maximum velocity
dlsEnv.volumeEnv.attack = DLSEnvelopeTimeCentsToMilliseconds(l);
}
break;
case ART_VOL_EG_HOLDTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.volumeEnv.hold = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_VOL_EG_DECAYTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.volumeEnv.decay = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_VOL_EG_RELEASETIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.volumeEnv.release = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_VOL_EG_SUSTAINLEVEL:
// 0.1% units
if(blk.lScale >= 0)
{
dlsEnv.volumeEnv.sustainLevel = DLSSustainLevelToLinear(blk.lScale);
}
break;
case ART_PITCH_EG_DELAYTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.pitchEnv.delay = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_PITCH_EG_ATTACKTIME:
// 32-bit time cents units. range = [0s, 20s]
if(blk.lScale > -0x40000000)
{
int32 l = std::min(0, blk.lScale - 78743200); // maximum velocity
dlsEnv.pitchEnv.attack = DLSEnvelopeTimeCentsToMilliseconds(l);
}
break;
case ART_PITCH_EG_HOLDTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.pitchEnv.hold = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_PITCH_EG_DECAYTIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.pitchEnv.decay = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_PITCH_EG_RELEASETIME:
// 32-bit time cents units. range = [0s, 20s]
dlsEnv.pitchEnv.release = DLSEnvelopeTimeCentsToMilliseconds(blk.lScale);
break;
case ART_PITCH_EG_SUSTAINLEVEL:
// 0.1% units
dlsEnv.pitchEnv.sustainLevel = DLSSustainLevelToLinear(blk.lScale);
break;
case ART_PITCH_EG_DEPTH:
dlsEnv.pitchEnvDepth = mpt::saturate_cast<int16>(blk.lScale / 65536);
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;
default:
#ifdef DLSINSTR_LOG
{
char sid[5]{};
memcpy(sid, &header.id, 4);
MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("Unsupported DLS chunk: {} ({} bytes)")(mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(sid)), header.len.get()));
}
#endif
break;
}
}
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;
default:
#ifdef DLSINSTR_LOG
{
char sdbg[5]{};
memcpy(sdbg, &header.id, 4);
MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("Unsupported SF2 chunk: {} ({} bytes)")(mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadAutoBuf(sdbg)), header.len.get()));
}
#endif
break;
}
return true;
}
// 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;
// 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_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;
default:
if(!gen.ApplyToEnvelope(dlsEnv))
{
#ifdef DLSINSTR_LOG
MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("Preset {} bag {} gen {}: genoper={} amount={}{}")
(static_cast<int>(&dlsIns - m_Instruments.data()), ipbagcnt, static_cast<int>(&gen - generators.data()), gen.sfGenOper, gen.genAmount, (dlsIns.ulBank & F_INSTRUMENT_DRUMS) ? U_(" (drum)") : U_("")));
#endif
}
break;
}
}
}
// 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;
if(dlsIns.ulBank & F_INSTRUMENT_DRUMS)
{
m_Envelopes.push_back(dlsEnv);
rgn.uPercEnv = static_cast<uint32>(m_Envelopes.size());
pDlsEnv = &m_Envelopes[rgn.uPercEnv - 1];
if(globalZone.uPercEnv)
*pDlsEnv = m_Envelopes[globalZone.uPercEnv - 1];
}
// 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_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->defaultPan = 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:
if(!gen.ApplyToEnvelope(*pDlsEnv))
{
#ifdef DLSINSTR_LOG
MPT_LOG_GLOBAL(LogDebug, "DLSINSTR", MPT_UFORMAT("Instr {} region {} gen {}: genoper={} amount={}{}")
(nInstrNdx, nRgn, static_cast<int>(&gen - instrGenerators.data()), gen.sfGenOper, gen.genAmount, (dlsIns.ulBank & F_INSTRUMENT_DRUMS) ? U_(" (drum)") : U_("")));
#endif
}
break;
}
}
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);
}
}
}
return true;
}
///////////////////////////////////////////////////////////////
// Open: opens a DLS bank
bool CDLSBank::Open(const mpt::PathString &filename)
{
if(filename.empty()) return false;
m_szFileName = filename;
mpt::IO::InputFile f(filename, false);
if(!f.IsValid()) return false;
return Open(GetFileReader(f));
}
bool CDLSBank::Open(FileReader file)
{
uint32 nInsDef;
if(file.GetOptionalFileName())
{
#if defined(MPT_LIBCXX_QUIRK_NO_OPTIONAL_VALUE)
m_szFileName = *(file.GetOptionalFileName());
#else
m_szFileName = file.GetOptionalFileName().value();
#endif
}
file.Rewind();
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 || riff.id_DLS == IFFID_sfbk))
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;
bool applyPaddingToSampleChunk = true;
while(file.CanRead(sizeof(IFFCHUNK)))
{
IFFCHUNK chunkHeader;
file.ReadStruct(chunkHeader);
const auto chunkStartPos = 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 = chunkStartPos + 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);
}
// FindInstrument requires the instrument to be sorted for picking the best instrument from the MIDI library when there are multiple banks.
// And of course this is also helpful for creating the treeview UI
std::sort(m_Instruments.begin(), m_Instruments.end());
// Sort regions (for drums)
for(auto &instr : m_Instruments)
{
std::sort(instr.Regions.begin(), instr.Regions.end(), [](const DLSREGION &l, const DLSREGION &r)
{ return std::tie(l.uKeyMin, l.uKeyMax) < std::tie(r.uKeyMin, r.uKeyMax); });
}
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 &region = dlsIns.Regions[rgn];
// Regions are sorted, if we arrived here we won't find anything in the remaining regions
if(region.uKeyMin > nKey)
break;
if(nKey > region.uKeyMax)
continue;
if(region.nWaveLink == Util::MaxValueOfType(region.nWaveLink))
continue;
return rgn;
}
return 0;
}
std::vector<uint8> CDLSBank::ExtractWaveForm(uint32 nIns, uint32 nRgn, FileReader *file) const
{
std::vector<uint8> waveData;
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 waveData;
}
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 waveData;
}
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 waveData;
}
std::unique_ptr<mpt::IO::InputFile> inputFile;
FileReader f;
if(file)
{
f = *file;
} else
{
inputFile = std::make_unique<mpt::IO::InputFile>(m_szFileName, false);
if(!inputFile->IsValid())
return waveData;
f = GetFileReader(*inputFile);
}
auto sampleOffset = mpt::saturate_cast<FileReader::pos_type>(m_WaveForms[nWaveLink] + m_dwWavePoolOffset);
if(f.Seek(sampleOffset))
{
if (m_nType & SOUNDBANK_TYPE_SF2)
{
if (m_SamplesEx[nWaveLink].dwLen)
{
if (f.Skip(8))
{
try
{
f.ReadVector(waveData, m_SamplesEx[nWaveLink].dwLen);
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
}
}
}
} else
{
LISTChunk chunk;
if(f.ReadStruct(chunk))
{
if((chunk.id == IFFID_LIST) && (chunk.listid == IFFID_wave) && (chunk.len > 4))
{
try
{
f.SkipBack(sizeof(chunk));
f.ReadVector(waveData, chunk.len + sizeof(IFFCHUNK));
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
}
}
}
}
}
return waveData;
}
bool CDLSBank::ExtractSample(CSoundFile &sndFile, SAMPLEINDEX nSample, uint32 nIns, uint32 nRgn, int transpose, FileReader *file) const
{
bool ok, hasWaveform;
if(nIns >= m_Instruments.size())
return false;
const DLSINSTRUMENT &dlsIns = m_Instruments[nIns];
if(nRgn >= dlsIns.Regions.size())
return false;
const std::vector<uint8> waveData = ExtractWaveForm(nIns, nRgn, file);
if(waveData.size() < 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(waveData)};
if(!p.compressed || !sndFile.ReadSampleFromFile(nSample, chunk, false, false))
{
sample.nLength = mpt::saturate_cast<SmpLength>(waveData.size() / 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 wavChunk(mpt::as_span(waveData));
hasWaveform = sndFile.ReadWAVSample(nSample, wavChunk, 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));
}
uint32 DLSENVELOPE::Envelope::ConvertToMPT(InstrumentEnvelope &mptEnv, const EnvelopeType envType, const float tempoScale, const int16 valueScale) const
{
if(!attack && decay >= 20 * 50 && (!sustainLevel && envType != ENV_PITCH) && release >= 20 * 50)
return uint32_max;
const EnvelopeNode::value_t neutralValue = (envType == ENV_VOLUME) ? 0 : ENVELOPE_MID;
mptEnv.dwFlags.set(ENV_ENABLED);
mptEnv.clear();
// Delay section
uint16 attackStart = 0;
if(delay)
{
mptEnv.push_back(0, neutralValue);
attackStart = ScaleEnvelope(delay, tempoScale);
}
// Attack section
const auto attackValue = mpt::saturate_cast<EnvelopeNode::value_t>(neutralValue + Util::muldivr(ENVELOPE_MAX, valueScale, 3200));
if(attack)
{
mptEnv.push_back(attackStart, static_cast<EnvelopeNode::value_t>(attackValue / (attack / 2 + 2) + 8)); // /-----
mptEnv.push_back(attackStart + ScaleEnvelope(attack, tempoScale), attackValue); // |
} else
{
mptEnv.push_back(attackStart, attackValue);
}
// Hold section
if(EnvelopeNode::tick_t holdTick = attackStart + ScaleEnvelope(attack + hold, tempoScale); hold > 0 && holdTick > mptEnv.back().tick)
mptEnv.push_back(holdTick, attackValue);
// Sustain Level
if(sustainLevel > 0 || envType == ENV_PITCH)
{
if(sustainLevel < 128)
{
uint16 lStartTime = mptEnv.back().tick;
int32 lSusLevel = -CDLSBank::DLS32BitRelativeLinearToGain(sustainLevel << 9) / 65536;
int32 lDecayTime = 1;
if(lSusLevel > 0)
{
lDecayTime = (lSusLevel * (int32)decay) / 960;
for(uint32 i = 0; i < 7; i++)
{
int32 lFactor = 128 - (1 << i);
if(lFactor <= sustainLevel) break;
int32 lev = -CDLSBank::DLS32BitRelativeLinearToGain(lFactor << 9) / 65536;
if(lev > 0)
{
int32 ltime = (lev * (int32)decay) / 960;
if((ltime > 1) && (ltime < lDecayTime))
{
uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale);
if(tick > mptEnv.back().tick)
{
mptEnv.push_back(tick, mpt::saturate_cast<EnvelopeNode::value_t>(neutralValue + Util::muldivr(lFactor, valueScale, 6400)));
}
}
}
}
}
uint16 decayEnd = lStartTime + ScaleEnvelope(lDecayTime, tempoScale);
if(decayEnd > mptEnv.back().tick)
{
mptEnv.push_back(decayEnd, mpt::saturate_cast<EnvelopeNode::value_t>(neutralValue + Util::muldivr(sustainLevel + 1, valueScale, 6400)));
}
}
mptEnv.dwFlags.set(ENV_SUSTAIN);
} else
{
mptEnv.dwFlags.set(ENV_SUSTAIN);
mptEnv.push_back(mptEnv.back().tick + 1u, mptEnv.back().value);
}
mptEnv.nSustainStart = mptEnv.nSustainEnd = (uint8)(mptEnv.size() - 1);
if(mptEnv.nSustainEnd > 0 && envType == ENV_VOLUME)
mptEnv.nReleaseNode = mptEnv.nSustainEnd;
// Release section
const bool lastIsNeutral = mptEnv.back().value > 1 && mptEnv.back().value == neutralValue;
if(release && mptEnv.back().value > 1 && !lastIsNeutral)
{
int32 lReleaseTime = release;
uint16 lStartTime = mptEnv.back().tick;
int32 lStartFactor = mptEnv.back().value;
int32 lSusLevel = -CDLSBank::DLS32BitRelativeLinearToGain(lStartFactor << 10) / 65536;
int32 lDecayEndTime = (lReleaseTime * lSusLevel) / 960;
lReleaseTime -= lDecayEndTime;
int32 prevFactor = lStartFactor;
for(uint32 i = 0; i < 7; i++)
{
int32 lFactor = 1 + ((lStartFactor * 3) >> (i + 2));
if((lFactor < 1) ||(lFactor == 1 && prevFactor == 1) || (lFactor >= lStartFactor))
continue;
prevFactor = lFactor;
int32 lev = -CDLSBank::DLS32BitRelativeLinearToGain(lFactor << 10) / 65536;
if(lev > 0)
{
int32 ltime = (((int32)release * lev) / 960) - lDecayEndTime;
if((ltime > 1) && (ltime < lReleaseTime))
{
uint16 tick = lStartTime + ScaleEnvelope(ltime, tempoScale);
if(tick > mptEnv.back().tick)
{
mptEnv.push_back(tick, mpt::saturate_cast<EnvelopeNode::value_t>(neutralValue + Util::muldivr(lFactor, valueScale, 3200)));
if(mptEnv.back().value == neutralValue)
break;
}
}
}
}
if(lReleaseTime < 1) lReleaseTime = 1;
auto releaseTicks = ScaleEnvelope(lReleaseTime, tempoScale);
mptEnv.push_back(lStartTime + releaseTicks, neutralValue);
if(releaseTicks > 0)
{
return 32768 / releaseTicks;
}
} else if(!lastIsNeutral)
{
mptEnv.push_back(mptEnv.back().tick + 1u, neutralValue);
}
return uint32_max;
}
bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, uint32 nIns, uint32 nDrumRgn, FileReader *file) 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 = static_cast<uint8>(1 + (dlsIns.ulInstrument & 0x7F));
pIns->nMidiChannel = static_cast<uint8>(isDrum ? 10 : 0);
pIns->wMidiBank = static_cast<uint16>(1 + (((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;
// Eliminate 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, file);
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;
const std::vector<uint8> waveData = ExtractWaveForm(nIns, nRgn, file);
if(waveData.empty())
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 smpChunk{mpt::as_span(waveData)};
if(sndFile.ReadSampleFromFile(nSmp, smpChunk, 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(mpt::saturate_cast<SmpLength>(waveData.size() / 2u), sampleCopy.nLength);
const std::byte *src = mpt::byte_cast<const std::byte *>(waveData.data());
int16 *dst = sampleCopy.sample16() + offsetNew;
CopySample<SC::ConversionChain<SC::Convert<int16, int16>, SC::DecodeInt16<0, littleEndian16>>>(dst, len, 2, src, waveData.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];
if(const auto fadeout = part.volumeEnv.ConvertToMPT(pIns->VolEnv, ENV_VOLUME, tempoScale, 3200); fadeout != uint32_max)
pIns->nFadeOut = fadeout;
if(std::abs(part.pitchEnvDepth) >= 50)
part.pitchEnv.ConvertToMPT(pIns->PitchEnv, ENV_PITCH, tempoScale, part.pitchEnvDepth);
}
if(isDrum)
{
// Create a default envelope for drums
if(!pIns->VolEnv.dwFlags[ENV_ENABLED])
{
pIns->VolEnv.dwFlags.set(ENV_ENABLED);
pIns->VolEnv.dwFlags.reset(ENV_SUSTAIN);
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].defaultPan;
}
} else
{
if(dlsIns.nMelodicEnv > 0 && dlsIns.nMelodicEnv <= m_Envelopes.size())
{
return m_Envelopes[dlsIns.nMelodicEnv - 1].defaultPan;
}
}
return 128;
}
#else // !MODPLUG_TRACKER
MPT_MSVC_WORKAROUND_LNK4221(Dlsbank)
#endif // MODPLUG_TRACKER
OPENMPT_NAMESPACE_END