Initial commit
This commit is contained in:
466
audio/soundfont/sf2file.cpp
Normal file
466
audio/soundfont/sf2file.cpp
Normal file
@@ -0,0 +1,466 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* VGMTrans (c) 2002-2019
|
||||
* Licensed under the zlib license,
|
||||
* refer to the included VGMTrans_LICENSE.txt file
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/str.h"
|
||||
#include "audio/soundfont/sf2file.h"
|
||||
#include "audio/soundfont/synthfile.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Convert a pan value where 0 = left 0.5 = center and 1 = right to
|
||||
// 0.1% units where -50% = left 0 = center 50% = right (shared by DLS and SF2)
|
||||
long ConvertPercentPanTo10thPercentUnits(double percentPan) {
|
||||
return round(percentPan * 1000) - 500;
|
||||
}
|
||||
|
||||
double SecondsToTimecents(double secs) {
|
||||
return log(secs) / log((double) 2) * 1200;
|
||||
}
|
||||
|
||||
SF2InfoListChunk::SF2InfoListChunk(Common::String name) : LISTChunk("INFO") {
|
||||
// Add the child info chunks
|
||||
Chunk *ifilCk = new Chunk("ifil");
|
||||
ifilCk->_size = sizeof(sfVersionTag);
|
||||
ifilCk->_data = new uint8[ifilCk->_size];
|
||||
sfVersionTag versionTag; // soundfont version 2.01
|
||||
versionTag.wMajor = 2;
|
||||
versionTag.wMinor = 1;
|
||||
versionTag.write(ifilCk->_data);
|
||||
AddChildChunk(ifilCk);
|
||||
AddChildChunk(new SF2StringChunk("isng", "EMU8000"));
|
||||
AddChildChunk(new SF2StringChunk("INAM", name));
|
||||
AddChildChunk(new SF2StringChunk("ISFT", Common::String("ScummVM")));
|
||||
}
|
||||
|
||||
// *******
|
||||
// SF2File
|
||||
// *******
|
||||
|
||||
SF2File::SF2File(SynthFile *synthfile) : RiffFile(synthfile->_name, "sfbk") {
|
||||
//***********
|
||||
// INFO chunk
|
||||
//***********
|
||||
AddChildChunk(new SF2InfoListChunk(_name));
|
||||
|
||||
// sdta chunk and its child smpl chunk containing all samples
|
||||
LISTChunk *sdtaCk = new LISTChunk("sdta");
|
||||
Chunk *smplCk = new Chunk("smpl");
|
||||
|
||||
// Concatanate all of the samples together and add the result to the smpl chunk data
|
||||
size_t numWaves = synthfile->_vWaves.size();
|
||||
smplCk->_size = 0;
|
||||
for (size_t i = 0; i < numWaves; i++) {
|
||||
SynthWave *wave = synthfile->_vWaves[i];
|
||||
wave->ConvertTo16bitSigned();
|
||||
smplCk->_size +=
|
||||
wave->_dataSize + (46 * 2); // plus the 46 padding samples required by sf2 spec
|
||||
}
|
||||
smplCk->_data = new uint8[smplCk->_size];
|
||||
uint32 bufPtr = 0;
|
||||
for (size_t i = 0; i < numWaves; i++) {
|
||||
SynthWave *wave = synthfile->_vWaves[i];
|
||||
|
||||
memcpy(smplCk->_data + bufPtr, wave->_data, wave->_dataSize);
|
||||
memset(smplCk->_data + bufPtr + wave->_dataSize, 0, 46 * 2);
|
||||
bufPtr += wave->_dataSize + (46 * 2); // plus the 46 padding samples required by sf2 spec
|
||||
}
|
||||
|
||||
sdtaCk->AddChildChunk(smplCk);
|
||||
this->AddChildChunk(sdtaCk);
|
||||
|
||||
//***********
|
||||
// pdta chunk
|
||||
//***********
|
||||
|
||||
LISTChunk *pdtaCk = new LISTChunk("pdta");
|
||||
|
||||
//***********
|
||||
// phdr chunk
|
||||
//***********
|
||||
Chunk *phdrCk = new Chunk("phdr");
|
||||
size_t numInstrs = synthfile->_vInstrs.size();
|
||||
phdrCk->_size = (uint32) ((numInstrs + 1) * sizeof(sfPresetHeader));
|
||||
phdrCk->_data = new uint8[phdrCk->_size];
|
||||
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
SynthInstr *instr = synthfile->_vInstrs[i];
|
||||
|
||||
sfPresetHeader presetHdr;
|
||||
memset(&presetHdr, 0, sizeof(sfPresetHeader));
|
||||
memcpy(presetHdr.achPresetName, instr->_name.c_str(),
|
||||
MIN((unsigned long) instr->_name.size(), (unsigned long) 20));
|
||||
presetHdr.wPreset = (uint16) instr->_ulInstrument;
|
||||
|
||||
// Despite being a 16-bit value, SF2 only supports banks up to 127. Since
|
||||
// it's pretty common to have either MSB or LSB be 0, we'll use whatever
|
||||
// one is not zero, with preference for MSB.
|
||||
uint16 bank16 = (uint16) instr->_ulBank;
|
||||
|
||||
if ((bank16 & 0xFF00) == 0) {
|
||||
presetHdr.wBank = bank16 & 0x7F;
|
||||
} else {
|
||||
presetHdr.wBank = (bank16 >> 8) & 0x7F;
|
||||
}
|
||||
presetHdr.wPresetBagNdx = (uint16) i;
|
||||
presetHdr.dwLibrary = 0;
|
||||
presetHdr.dwGenre = 0;
|
||||
presetHdr.dwMorphology = 0;
|
||||
|
||||
presetHdr.write(phdrCk->_data + (i * sizeof(sfPresetHeader)));
|
||||
}
|
||||
// add terminal sfPresetBag
|
||||
sfPresetHeader presetHdr;
|
||||
memset(&presetHdr, 0, sizeof(sfPresetHeader));
|
||||
presetHdr.wPresetBagNdx = (uint16) numInstrs;
|
||||
presetHdr.write(phdrCk->_data + (numInstrs * sizeof(sfPresetHeader)));
|
||||
pdtaCk->AddChildChunk(phdrCk);
|
||||
|
||||
//***********
|
||||
// pbag chunk
|
||||
//***********
|
||||
Chunk *pbagCk = new Chunk("pbag");
|
||||
const size_t ITEMS_IN_PGEN = 2;
|
||||
pbagCk->_size = (uint32) ((numInstrs + 1) * sizeof(sfPresetBag));
|
||||
pbagCk->_data = new uint8[pbagCk->_size];
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
sfPresetBag presetBag;
|
||||
memset(&presetBag, 0, sizeof(sfPresetBag));
|
||||
presetBag.wGenNdx = (uint16) (i * ITEMS_IN_PGEN);
|
||||
presetBag.wModNdx = 0;
|
||||
|
||||
presetBag.write(pbagCk->_data + (i * sizeof(sfPresetBag)));
|
||||
}
|
||||
// add terminal sfPresetBag
|
||||
sfPresetBag presetBag;
|
||||
memset(&presetBag, 0, sizeof(sfPresetBag));
|
||||
presetBag.wGenNdx = (uint16) (numInstrs * ITEMS_IN_PGEN);
|
||||
presetBag.write(pbagCk->_data + (numInstrs * sizeof(sfPresetBag)));
|
||||
pdtaCk->AddChildChunk(pbagCk);
|
||||
|
||||
//***********
|
||||
// pmod chunk
|
||||
//***********
|
||||
Chunk *pmodCk = new Chunk("pmod");
|
||||
// create the terminal field
|
||||
sfModList modList;
|
||||
memset(&modList, 0, sizeof(sfModList));
|
||||
pmodCk->SetData(&modList, sizeof(sfModList));
|
||||
// modList.sfModSrcOper = cc1_Mod;
|
||||
// modList.sfModDestOper = startAddrsOffset;
|
||||
// modList.modAmount = 0;
|
||||
// modList.sfModAmtSrcOper = cc1_Mod;
|
||||
// modList.sfModTransOper = linear;
|
||||
pdtaCk->AddChildChunk(pmodCk);
|
||||
|
||||
//***********
|
||||
// pgen chunk
|
||||
//***********
|
||||
Chunk *pgenCk = new Chunk("pgen");
|
||||
// pgenCk->size = (synthfile->vInstrs.size()+1) * sizeof(sfGenList);
|
||||
pgenCk->_size = (uint32) ((synthfile->_vInstrs.size() * sizeof(sfGenList) * ITEMS_IN_PGEN) +
|
||||
sizeof(sfGenList));
|
||||
pgenCk->_data = new uint8[pgenCk->_size];
|
||||
uint32 dataPtr = 0;
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
sfGenList genList;
|
||||
memset(&genList, 0, sizeof(sfGenList));
|
||||
|
||||
// reverbEffectsSend
|
||||
genList.sfGenOper = reverbEffectsSend;
|
||||
genList.genAmount.setShAmount(250);
|
||||
genList.write(pgenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfGenList);
|
||||
|
||||
genList.sfGenOper = instrument;
|
||||
genList.genAmount.setwAmount((uint16) i);
|
||||
genList.write(pgenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfGenList);
|
||||
}
|
||||
// add terminal sfGenList
|
||||
sfGenList genList;
|
||||
memset(&genList, 0, sizeof(sfGenList));
|
||||
genList.write(pgenCk->_data + dataPtr);
|
||||
|
||||
pdtaCk->AddChildChunk(pgenCk);
|
||||
|
||||
//***********
|
||||
// inst chunk
|
||||
//***********
|
||||
Chunk *instCk = new Chunk("inst");
|
||||
instCk->_size = (uint32) ((synthfile->_vInstrs.size() + 1) * sizeof(sfInst));
|
||||
instCk->_data = new uint8[instCk->_size];
|
||||
size_t rgnCounter = 0;
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
SynthInstr *instr = synthfile->_vInstrs[i];
|
||||
|
||||
sfInst inst;
|
||||
memset(&inst, 0, sizeof(sfInst));
|
||||
memcpy(inst.achInstName, instr->_name.c_str(),
|
||||
MIN((unsigned long) instr->_name.size(), (unsigned long) 20));
|
||||
inst.wInstBagNdx = (uint16) rgnCounter;
|
||||
rgnCounter += instr->_vRgns.size();
|
||||
|
||||
inst.write(instCk->_data + (i * sizeof(sfInst)));
|
||||
}
|
||||
// add terminal sfInst
|
||||
sfInst inst;
|
||||
memset(&inst, 0, sizeof(sfInst));
|
||||
inst.wInstBagNdx = (uint16) rgnCounter;
|
||||
inst.write(instCk->_data + (numInstrs * sizeof(sfInst)));
|
||||
pdtaCk->AddChildChunk(instCk);
|
||||
|
||||
//***********
|
||||
// ibag chunk - stores all zones (regions) for instruments
|
||||
//***********
|
||||
Chunk *ibagCk = new Chunk("ibag");
|
||||
|
||||
size_t totalNumRgns = 0;
|
||||
for (size_t i = 0; i < numInstrs; i++)
|
||||
totalNumRgns += synthfile->_vInstrs[i]->_vRgns.size();
|
||||
|
||||
ibagCk->_size = (uint32) ((totalNumRgns + 1) * sizeof(sfInstBag));
|
||||
ibagCk->_data = new uint8[ibagCk->_size];
|
||||
|
||||
rgnCounter = 0;
|
||||
int instGenCounter = 0;
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
SynthInstr *instr = synthfile->_vInstrs[i];
|
||||
|
||||
size_t numRgns = instr->_vRgns.size();
|
||||
for (size_t j = 0; j < numRgns; j++) {
|
||||
sfInstBag instBag;
|
||||
memset(&instBag, 0, sizeof(sfInstBag));
|
||||
instBag.wInstGenNdx = instGenCounter;
|
||||
instGenCounter += 11;
|
||||
instBag.wInstModNdx = 0;
|
||||
|
||||
instBag.write(ibagCk->_data + (rgnCounter++ * sizeof(sfInstBag)));
|
||||
}
|
||||
}
|
||||
// add terminal sfInstBag
|
||||
sfInstBag instBag;
|
||||
memset(&instBag, 0, sizeof(sfInstBag));
|
||||
instBag.wInstGenNdx = instGenCounter;
|
||||
instBag.wInstModNdx = 0;
|
||||
instBag.write(ibagCk->_data + (rgnCounter * sizeof(sfInstBag)));
|
||||
pdtaCk->AddChildChunk(ibagCk);
|
||||
|
||||
//***********
|
||||
// imod chunk
|
||||
//***********
|
||||
Chunk *imodCk = new Chunk("imod");
|
||||
// create the terminal field
|
||||
memset(&modList, 0, sizeof(sfModList));
|
||||
imodCk->SetData(&modList, sizeof(sfModList));
|
||||
pdtaCk->AddChildChunk(imodCk);
|
||||
|
||||
//***********
|
||||
// igen chunk
|
||||
//***********
|
||||
Chunk *igenCk = new Chunk("igen");
|
||||
igenCk->_size = (uint32) ((totalNumRgns * sizeof(sfInstGenList) * 11) + sizeof(sfInstGenList));
|
||||
igenCk->_data = new uint8[igenCk->_size];
|
||||
dataPtr = 0;
|
||||
for (size_t i = 0; i < numInstrs; i++) {
|
||||
SynthInstr *instr = synthfile->_vInstrs[i];
|
||||
|
||||
size_t numRgns = instr->_vRgns.size();
|
||||
for (size_t j = 0; j < numRgns; j++) {
|
||||
SynthRgn *rgn = instr->_vRgns[j];
|
||||
|
||||
sfInstGenList instGenList;
|
||||
// Key range - (if exists) this must be the first chunk
|
||||
instGenList.sfGenOper = keyRange;
|
||||
instGenList.genAmount.setRangeLo((uint8) rgn->_usKeyLow);
|
||||
instGenList.genAmount.setRangeHi((uint8) rgn->_usKeyHigh);
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
if (rgn->_usVelHigh) // 0 means 'not set', fixes TriAce instruments
|
||||
{
|
||||
// Velocity range (if exists) this must be the next chunk
|
||||
instGenList.sfGenOper = velRange;
|
||||
instGenList.genAmount.setRangeLo((uint8) rgn->_usVelLow);
|
||||
instGenList.genAmount.setRangeHi((uint8) rgn->_usVelHigh);
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
}
|
||||
|
||||
// initialAttenuation
|
||||
instGenList.sfGenOper = initialAttenuation;
|
||||
instGenList.genAmount.setShAmount((int16) (rgn->_sampinfo->_attenuation * 10));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// pan
|
||||
instGenList.sfGenOper = pan;
|
||||
instGenList.genAmount.setShAmount(
|
||||
(int16) ConvertPercentPanTo10thPercentUnits(rgn->_art->_pan));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// sampleModes
|
||||
instGenList.sfGenOper = sampleModes;
|
||||
instGenList.genAmount.setwAmount(rgn->_sampinfo->_cSampleLoops);
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// overridingRootKey
|
||||
instGenList.sfGenOper = overridingRootKey;
|
||||
instGenList.genAmount.setwAmount(rgn->_sampinfo->_usUnityNote);
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// attackVolEnv
|
||||
instGenList.sfGenOper = attackVolEnv;
|
||||
instGenList.genAmount.setShAmount(
|
||||
(rgn->_art->_attack_time == 0)
|
||||
? -32768
|
||||
: round(SecondsToTimecents(rgn->_art->_attack_time)));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// decayVolEnv
|
||||
instGenList.sfGenOper = decayVolEnv;
|
||||
instGenList.genAmount.setShAmount(
|
||||
(rgn->_art->_decay_time == 0) ? -32768
|
||||
: round(SecondsToTimecents(rgn->_art->_decay_time)));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// sustainVolEnv
|
||||
instGenList.sfGenOper = sustainVolEnv;
|
||||
if (rgn->_art->_sustain_lev > 100.0)
|
||||
rgn->_art->_sustain_lev = 100.0;
|
||||
instGenList.genAmount.setShAmount((int16) (rgn->_art->_sustain_lev * 10));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// releaseVolEnv
|
||||
instGenList.sfGenOper = releaseVolEnv;
|
||||
instGenList.genAmount.setShAmount(
|
||||
(rgn->_art->_release_time == 0)
|
||||
? -32768
|
||||
: round(SecondsToTimecents(rgn->_art->_release_time)));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// reverbEffectsSend
|
||||
// instGenList.sfGenOper = reverbEffectsSend;
|
||||
// instGenList.genAmount.setShAmount(800);
|
||||
// memcpy(pgenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
|
||||
// dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// sampleID - this is the terminal chunk
|
||||
instGenList.sfGenOper = sampleID;
|
||||
instGenList.genAmount.setwAmount((uint16) (rgn->_tableIndex));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
dataPtr += sizeof(sfInstGenList);
|
||||
|
||||
// int numConnBlocks = rgn->art->vConnBlocks.size();
|
||||
// for (int k = 0; k < numConnBlocks; k++)
|
||||
//{
|
||||
// SynthConnectionBlock* connBlock = rgn->art->vConnBlocks[k];
|
||||
// connBlock->
|
||||
//}
|
||||
}
|
||||
}
|
||||
// add terminal sfInstBag
|
||||
sfInstGenList instGenList;
|
||||
memset(&instGenList, 0, sizeof(sfInstGenList));
|
||||
instGenList.write(igenCk->_data + dataPtr);
|
||||
// memset(ibagCk->data + (totalNumRgns*sizeof(sfInstBag)), 0, sizeof(sfInstBag));
|
||||
// igenCk->SetData(&genList, sizeof(sfGenList));
|
||||
pdtaCk->AddChildChunk(igenCk);
|
||||
|
||||
//***********
|
||||
// shdr chunk
|
||||
//***********
|
||||
Chunk *shdrCk = new Chunk("shdr");
|
||||
|
||||
size_t numSamps = synthfile->_vWaves.size();
|
||||
shdrCk->_size = (uint32) ((numSamps + 1) * sizeof(sfSample));
|
||||
shdrCk->_data = new uint8[shdrCk->_size];
|
||||
|
||||
uint32 sampOffset = 0;
|
||||
for (size_t i = 0; i < numSamps; i++) {
|
||||
SynthWave *wave = synthfile->_vWaves[i];
|
||||
|
||||
sfSample samp;
|
||||
memset(&samp, 0, sizeof(sfSample));
|
||||
memcpy(samp.achSampleName, wave->_name.c_str(),
|
||||
MIN((unsigned long) wave->_name.size(), (unsigned long) 20));
|
||||
samp.dwStart = sampOffset;
|
||||
samp.dwEnd = samp.dwStart + (wave->_dataSize / sizeof(uint16));
|
||||
sampOffset = samp.dwEnd + 46; // plus the 46 padding samples required by sf2 spec
|
||||
|
||||
// Search through all regions for an associated sampInfo structure with this sample
|
||||
SynthSampInfo *sampInfo = nullptr;
|
||||
for (size_t j = 0; j < numInstrs; j++) {
|
||||
SynthInstr *instr = synthfile->_vInstrs[j];
|
||||
|
||||
size_t numRgns = instr->_vRgns.size();
|
||||
for (size_t k = 0; k < numRgns; k++) {
|
||||
SynthRgn *rgn = instr->_vRgns[k];
|
||||
if (rgn->_tableIndex == i && rgn->_sampinfo != nullptr) {
|
||||
sampInfo = rgn->_sampinfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sampInfo != nullptr)
|
||||
break;
|
||||
}
|
||||
// If we didn't find a rgn association, then it should be in the SynthWave structure.
|
||||
if (sampInfo == nullptr)
|
||||
sampInfo = wave->_sampinfo;
|
||||
assert(sampInfo != nullptr);
|
||||
|
||||
samp.dwStartloop = samp.dwStart + sampInfo->_ulLoopStart;
|
||||
samp.dwEndloop = samp.dwStartloop + sampInfo->_ulLoopLength;
|
||||
samp.dwSampleRate = wave->_dwSamplesPerSec;
|
||||
samp.byOriginalKey = (uint8) (sampInfo->_usUnityNote);
|
||||
samp.chCorrection = (char) (sampInfo->_sFineTune);
|
||||
samp.wSampleLink = 0;
|
||||
samp.sfSampleType = monoSample;
|
||||
|
||||
samp.write(shdrCk->_data + (i * sizeof(sfSample)));
|
||||
}
|
||||
|
||||
// add terminal sfSample
|
||||
memset(shdrCk->_data + (numSamps * sizeof(sfSample)), 0, sizeof(sfSample));
|
||||
pdtaCk->AddChildChunk(shdrCk);
|
||||
|
||||
this->AddChildChunk(pdtaCk);
|
||||
}
|
||||
|
||||
SF2File::~SF2File() {}
|
||||
|
||||
const void *SF2File::SaveToMem() {
|
||||
uint8 *buf = new uint8[this->GetSize()];
|
||||
this->Write(buf);
|
||||
return buf;
|
||||
}
|
||||
Reference in New Issue
Block a user