AUDIO: SoundFont2 creation support

This commit is contained in:
Eric Fry 2020-07-27 22:41:11 +10:00 committed by Eugene Sandulenko
parent 09c12a820a
commit f2e4cc35a6
23 changed files with 3575 additions and 1 deletions

View File

@ -60,7 +60,17 @@ MODULE_OBJS := \
softsynth/eas.o \
softsynth/pcspk.o \
softsynth/sid.o \
softsynth/wave6581.o
softsynth/wave6581.o \
soundfont/rawfile.o \
soundfont/rifffile.o \
soundfont/sf2file.o \
soundfont/synthfile.o \
soundfont/vgmcoll.o \
soundfont/vgminstrset.o \
soundfont/vgmitem.o \
soundfont/vgmsamp.o \
soundfont/vab/psxspu.o \
soundfont/vab/vab.o
ifndef DISABLE_NUKED_OPL
MODULE_OBJS += \

View File

@ -0,0 +1,22 @@
The zlib/libpng License
VGMTrans Copyright (c) 2002-2020 Mike, VGMTrans Team
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

66
audio/soundfont/common.h Normal file
View File

@ -0,0 +1,66 @@
/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef AUDIO_SOUNDFONT_COMMON_H
#define AUDIO_SOUNDFONT_COMMON_H
#include "common/scummsys.h"
#include "common/array.h"
enum LoopMeasure {
LM_SAMPLES, LM_BYTES
};
struct Loop {
Loop()
: loopStatus(-1),
loopType(0),
loopStartMeasure(LM_BYTES),
loopLengthMeasure(LM_BYTES),
loopStart(0),
loopLength(0) {}
int loopStatus;
uint32 loopType;
uint8 loopStartMeasure;
uint8 loopLengthMeasure;
uint32 loopStart;
uint32 loopLength;
};
struct SizeOffsetPair {
uint32 size;
uint32 offset;
SizeOffsetPair() : size(0), offset(0) {}
SizeOffsetPair(uint32 offset_, uint32 size_) : size(size_), offset(offset_) {}
};
template<class T>
void DeleteVect(Common::Array<T *> &array) {
for (typename Common::Array<T *>::iterator iter = array.begin(); iter != array.end(); iter++) {
delete (*iter);
}
array.clear();
}
#endif // AUDIO_SOUNDFONT_COMMON_H

View File

@ -0,0 +1,44 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common/memstream.h"
#include "rawfile.h"
uint32 RawFile::GetBytes(size_t offset, uint32 nCount, void *pBuffer) const {
memcpy(pBuffer, data() + offset, nCount);
return nCount;
}
const char *MemFile::data() const {
return (const char *) _data;
}
uint8 MemFile::GetByte(size_t offset) const {
_seekableReadStream->seek(offset);
return _seekableReadStream->readByte();
}
uint16 MemFile::GetShort(size_t offset) const {
_seekableReadStream->seek(offset);
return _seekableReadStream->readUint16LE();
}
uint32 MemFile::GetWord(size_t offset) const {
_seekableReadStream->seek(offset);
return _seekableReadStream->readUint32LE();
}
size_t MemFile::size() const {
return _seekableReadStream->size();
}
MemFile::~MemFile() {
delete _seekableReadStream;
}
MemFile::MemFile(const byte *data, uint32 size) : _data(data) {
_seekableReadStream = new Common::MemoryReadStream(data, size, DisposeAfterUse::YES);
}

56
audio/soundfont/rawfile.h Normal file
View File

@ -0,0 +1,56 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_RAWFILE_H
#define AUDIO_SOUNDFONT_RAWFILE_H
#include <climits>
#include <cassert>
#include "common/stream.h"
#include "common/str.h"
class VGMFile;
class RawFile {
public:
virtual ~RawFile() {};
virtual size_t size() const = 0;
bool IsValidOffset(uint32 ofs) { return ofs < size(); }
const char *begin() const { return data(); }
const char *end() { return data() + size(); }
virtual const char *data() const = 0;
virtual uint8 GetByte(size_t offset) const = 0;
virtual uint16 GetShort(size_t offset) const = 0;
virtual uint32 GetWord(size_t offset) const = 0;
uint32 GetBytes(size_t offset, uint32 nCount, void *pBuffer) const;
private:
};
class MemFile : public RawFile {
private:
Common::SeekableReadStream *_seekableReadStream;
const byte *_data;
public:
MemFile(const byte *data, uint32 size);
~MemFile() override;
const char *data() const override;
uint8 GetByte(size_t offset) const override;
uint16 GetShort(size_t offset) const override;
uint32 GetWord(size_t offset) const override;
size_t size() const override;
};
#endif // AUDIO_SOUNDFONT_RAWFILE_H

View File

@ -0,0 +1,77 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "rifffile.h"
using namespace std;
uint32 Chunk::GetSize() {
return 8 + GetPaddedSize(size);
}
void Chunk::SetData(const void *src, uint32 datasize) {
size = datasize;
// set the size and copy from the data source
datasize = GetPaddedSize(size);
if (data != NULL) {
delete[] data;
data = NULL;
}
data = new uint8[datasize];
memcpy(data, src, size);
// Add pad byte
uint32 padsize = datasize - size;
if (padsize != 0) {
memset(data + size, 0, padsize);
}
}
void Chunk::Write(uint8 *buffer) {
uint32 padsize = GetPaddedSize(size) - size;
memcpy(buffer, id, 4);
*(uint32 * )(buffer + 4) =
size + padsize; // Microsoft says the chunkSize doesn't contain padding size, but many
// software cannot handle the alignment.
memcpy(buffer + 8, data, GetPaddedSize(size));
}
Chunk *ListTypeChunk::AddChildChunk(Chunk *ck) {
childChunks.push_back(ck);
return ck;
}
uint32 ListTypeChunk::GetSize() {
uint32 listChunkSize = 12; // id + size + "LIST"
for (Common::List<Chunk *>::iterator iter = this->childChunks.begin(); iter != childChunks.end(); iter++)
listChunkSize += (*iter)->GetSize();
return GetPaddedSize(listChunkSize);
}
void ListTypeChunk::Write(uint8 *buffer) {
memcpy(buffer, this->id, 4);
memcpy(buffer + 8, this->type, 4);
uint32 bufOffset = 12;
for (Common::List<Chunk *>::iterator iter = this->childChunks.begin(); iter != childChunks.end(); iter++) {
(*iter)->Write(buffer + bufOffset);
bufOffset += (*iter)->GetSize();
}
uint32 unpaddedSize = bufOffset;
uint32 padsize = GetPaddedSize(unpaddedSize) - unpaddedSize;
*(uint32 *) (buffer + 4) =
unpaddedSize + padsize - 8; // Microsoft says the chunkSize doesn't contain padding size, but many
// software cannot handle the alignment.
// Add pad byte
if (padsize != 0) {
memset(data + unpaddedSize, 0, padsize);
}
}
RiffFile::RiffFile(const Common::String &file_name, const Common::String &form) : RIFFChunk(form), name(file_name) {}

113
audio/soundfont/rifffile.h Normal file
View File

@ -0,0 +1,113 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_RIFFFILE_H
#define AUDIO_SOUNDFONT_RIFFFILE_H
#include <cstring>
#include <cassert>
#include "common/scummsys.h"
#include "common/list.h"
#include "common/str.h"
#include "common/array.h"
//////////////////////////////////////////////
// Chunk - Riff format chunk
//////////////////////////////////////////////
class Chunk {
public:
char id[4]; // A chunk ID identifies the type of data within the chunk.
uint32 size; // The size of the chunk data in bytes, excluding any pad byte.
uint8 *data; // The actual data not including a possible pad byte to word align
public:
Chunk(Common::String theId) : data(NULL), size(0) {
assert(theId.size() == 4);
memcpy(id, theId.c_str(), 4);
}
virtual ~Chunk() {
if (data != NULL) {
delete[] data;
data = NULL;
}
}
void SetData(const void *src, uint32 datasize);
virtual uint32 GetSize(); // Returns the size of the chunk in bytes, including any pad byte.
virtual void Write(uint8 *buffer);
protected:
static inline uint32 GetPaddedSize(uint32 originalSize) { return originalSize + (originalSize % 2); }
};
////////////////////////////////////////////////////////////////////////////
// ListTypeChunk - Riff chunk type where the first 4 data bytes are a sig
// and the rest of the data is a collection of child chunks
////////////////////////////////////////////////////////////////////////////
class ListTypeChunk : public Chunk {
public:
char type[4]; // 4 byte sig that begins the data field, "LIST" or "sfbk" for ex
Common::List<Chunk *> childChunks;
public:
ListTypeChunk(Common::String theId, Common::String theType) : Chunk(theId) {
assert(theType.size() == 4);
memcpy(type, theType.c_str(), 4);
}
virtual ~ListTypeChunk() {
childChunks.erase(childChunks.begin(), childChunks.end());
}
Chunk *AddChildChunk(Chunk *ck);
virtual uint32 GetSize(); // Returns the size of the chunk in bytes, including any pad byte.
virtual void Write(uint8 *buffer);
};
////////////////////////////////////////////////////////////////////////////
// RIFFChunk
////////////////////////////////////////////////////////////////////////////
class RIFFChunk : public ListTypeChunk {
public:
RIFFChunk(Common::String form) : ListTypeChunk("RIFF", form) {}
};
////////////////////////////////////////////////////////////////////////////
// LISTChunk
////////////////////////////////////////////////////////////////////////////
class LISTChunk : public ListTypeChunk {
public:
LISTChunk(Common::String theType) : ListTypeChunk("LIST", theType) {}
};
////////////////////////////////////////////////////////////////////////////
// RiffFile -
////////////////////////////////////////////////////////////////////////////
class RiffFile : public RIFFChunk {
public:
RiffFile(const Common::String &file_name, const Common::String &form);
static void WriteLIST(Common::Array<uint8> &buf, uint32 listName, uint32 listSize) {
//TODO
// PushTypeOnVectBE<uint32>(buf, 0x4C495354); // write "LIST"
// PushTypeOnVect<uint32>(buf, listSize);
// PushTypeOnVectBE<uint32>(buf, listName);
}
// Adds a null byte and ensures 16 bit alignment of a text string
static void AlignName(Common::String &name) {
name += (char) 0x00;
if (name.size() % 2) // if the size of the name string is odd
name += (char) 0x00; // add another null byte
}
protected:
Common::String name;
};
#endif // AUDIO_SOUNDFONT_RIFFFILE_H

445
audio/soundfont/sf2file.cpp Normal file
View File

@ -0,0 +1,445 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include <math.h>
#include "common/scummsys.h"
#include "common/str.h"
#include "sf2file.h"
#include "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");
sfVersionTag versionTag; // soundfont version 2.01
versionTag.wMajor = 2;
versionTag.wMinor = 1;
ifilCk->SetData(&versionTag, sizeof(versionTag));
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;
memcpy(phdrCk->data + (i * sizeof(sfPresetHeader)), &presetHdr, sizeof(sfPresetHeader));
}
// add terminal sfPresetBag
sfPresetHeader presetHdr;
memset(&presetHdr, 0, sizeof(sfPresetHeader));
presetHdr.wPresetBagNdx = (uint16) numInstrs;
memcpy(phdrCk->data + (numInstrs * sizeof(sfPresetHeader)), &presetHdr, 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;
memcpy(pbagCk->data + (i * sizeof(sfPresetBag)), &presetBag, sizeof(sfPresetBag));
}
// add terminal sfPresetBag
sfPresetBag presetBag;
memset(&presetBag, 0, sizeof(sfPresetBag));
presetBag.wGenNdx = (uint16) (numInstrs * ITEMS_IN_PGEN);
memcpy(pbagCk->data + (numInstrs * sizeof(sfPresetBag)), &presetBag, 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.shAmount = 250;
memcpy(pgenCk->data + dataPtr, &genList, sizeof(sfGenList));
dataPtr += sizeof(sfGenList);
genList.sfGenOper = instrument;
genList.genAmount.wAmount = (uint16) i;
memcpy(pgenCk->data + dataPtr, &genList, sizeof(sfGenList));
dataPtr += sizeof(sfGenList);
}
// add terminal sfGenList
sfGenList genList;
memset(&genList, 0, sizeof(sfGenList));
memcpy(pgenCk->data + dataPtr, &genList, sizeof(sfGenList));
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();
memcpy(instCk->data + (i * sizeof(sfInst)), &inst, sizeof(sfInst));
}
// add terminal sfInst
sfInst inst;
memset(&inst, 0, sizeof(sfInst));
inst.wInstBagNdx = (uint16) rgnCounter;
memcpy(instCk->data + (numInstrs * sizeof(sfInst)), &inst, 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;
memcpy(ibagCk->data + (rgnCounter++ * sizeof(sfInstBag)), &instBag, sizeof(sfInstBag));
}
}
// add terminal sfInstBag
sfInstBag instBag;
memset(&instBag, 0, sizeof(sfInstBag));
instBag.wInstGenNdx = instGenCounter;
instBag.wInstModNdx = 0;
memcpy(ibagCk->data + (rgnCounter * sizeof(sfInstBag)), &instBag, 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.ranges.byLo = (uint8) rgn->usKeyLow;
instGenList.genAmount.ranges.byHi = (uint8) rgn->usKeyHigh;
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
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.ranges.byLo = (uint8) rgn->usVelLow;
instGenList.genAmount.ranges.byHi = (uint8) rgn->usVelHigh;
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
}
// initialAttenuation
instGenList.sfGenOper = initialAttenuation;
instGenList.genAmount.shAmount = (int16_t) (rgn->sampinfo->attenuation * 10);
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// pan
instGenList.sfGenOper = pan;
instGenList.genAmount.shAmount =
(int16_t) ConvertPercentPanTo10thPercentUnits(rgn->art->pan);
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// sampleModes
instGenList.sfGenOper = sampleModes;
instGenList.genAmount.wAmount = rgn->sampinfo->cSampleLoops;
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// overridingRootKey
instGenList.sfGenOper = overridingRootKey;
instGenList.genAmount.wAmount = rgn->sampinfo->usUnityNote;
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// attackVolEnv
instGenList.sfGenOper = attackVolEnv;
instGenList.genAmount.shAmount =
(rgn->art->attack_time == 0)
? -32768
: round(SecondsToTimecents(rgn->art->attack_time));
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// decayVolEnv
instGenList.sfGenOper = decayVolEnv;
instGenList.genAmount.shAmount =
(rgn->art->decay_time == 0) ? -32768
: round(SecondsToTimecents(rgn->art->decay_time));
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// sustainVolEnv
instGenList.sfGenOper = sustainVolEnv;
if (rgn->art->sustain_lev > 100.0)
rgn->art->sustain_lev = 100.0;
instGenList.genAmount.shAmount = (int16_t) (rgn->art->sustain_lev * 10);
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// releaseVolEnv
instGenList.sfGenOper = releaseVolEnv;
instGenList.genAmount.shAmount =
(rgn->art->release_time == 0)
? -32768
: round(SecondsToTimecents(rgn->art->release_time));
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
dataPtr += sizeof(sfInstGenList);
// reverbEffectsSend
// instGenList.sfGenOper = reverbEffectsSend;
// instGenList.genAmount.shAmount = 800;
// memcpy(pgenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
// dataPtr += sizeof(sfInstGenList);
// sampleID - this is the terminal chunk
instGenList.sfGenOper = sampleID;
instGenList.genAmount.wAmount = (uint16) (rgn->tableIndex);
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
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));
memcpy(igenCk->data + dataPtr, &instGenList, sizeof(sfInstGenList));
// 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 = NULL;
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 != NULL) {
sampInfo = rgn->sampinfo;
break;
}
}
if (sampInfo != NULL)
break;
}
// If we didn't find a rgn association, then it should be in the SynthWave structure.
if (sampInfo == NULL)
sampInfo = wave->sampinfo;
assert(sampInfo != NULL);
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;
memcpy(shdrCk->data + (i * sizeof(sfSample)), &samp, 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;
}

325
audio/soundfont/sf2file.h Normal file
View File

@ -0,0 +1,325 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_SF2FILE_H
#define AUDIO_SOUNDFONT_SF2FILE_H
#include <common/endian.h>
#include "common/scummsys.h"
#include "common/array.h"
#include "common/str.h"
#include "rifffile.h"
typedef enum {
// Oscillator
startAddrsOffset, // sample start address -4 (0 to 0xffffff) 0
endAddrsOffset,
startloopAddrsOffset, // loop start address -4 (0 to 0xffffff)
endloopAddrsOffset, // loop end address -3 (0 to 0xffffff)
// Pitch
startAddrsCoarseOffset, // CHANGED FOR SF2
modLfoToPitch, // main fm: lfo1-> pitch 5
vibLfoToPitch, // aux fm: lfo2-> pitch
modEnvToPitch, // pitch env: env1(aux)-> pitch
// Filter
initialFilterFc, // initial filter cutoff
initialFilterQ, // filter Q
modLfoToFilterFc, // filter modulation: lfo1 -> filter cutoff 10
modEnvToFilterFc, // filter env: env1(aux)-> filter cutoff
// Amplifier
endAddrsCoarseOffset, // CHANGED FOR SF2
modLfoToVolume, // tremolo: lfo1-> volume
unused1,
// Effects
chorusEffectsSend, // chorus 15
reverbEffectsSend, // reverb
pan,
unused2,
unused3,
unused4, // 20
// Main lfo1
delayModLFO, // delay 0x8000-n*(725us)
freqModLFO, // frequency
// Aux lfo2
delayVibLFO, // delay 0x8000-n*(725us)
freqVibLFO, // frequency
// Env1(aux/value)
delayModEnv, // delay 0x8000 - n(725us) 25
attackModEnv, // attack
holdModEnv, // hold
decayModEnv, // decay
sustainModEnv, // sustain
releaseModEnv, // release 30
keynumToModEnvHold,
keynumToModEnvDecay,
// Env2(ampl/vol)
delayVolEnv, // delay 0x8000 - n(725us)
attackVolEnv, // attack
holdVolEnv, // hold 35
decayVolEnv, // decay
sustainVolEnv, // sustain
releaseVolEnv, // release
keynumToVolEnvHold,
keynumToVolEnvDecay, // 40
// Preset
instrument,
reserved1,
keyRange,
velRange,
startloopAddrCoarseOffset, // CHANGED FOR SF2 45
keynum,
velocity,
initialAttenuation, // CHANGED FOR SF2
reserved2,
endloopAddrsCoarseOffset, // CHANGED FOR SF2 50
coarseTune,
fineTune,
sampleID,
sampleModes, // CHANGED FOR SF2
reserved3, // 55
scaleTuning,
exclusiveClass,
overridingRootKey,
unused5,
endOper // 60
} SFGeneratorType;
typedef uint16 SFGenerator;
typedef enum {
/* Start of MIDI modulation operators */
cc1_Mod,
cc7_Vol,
cc10_Pan,
cc64_Sustain,
cc91_Reverb,
cc93_Chorus,
ccPitchBend,
ccIndirectModX,
ccIndirectModY,
endMod
} SFModulatorType;
typedef uint16 SFModulator;
typedef enum {
linear
} SFTransformType;
typedef uint16 SFTransform;
/*
#define monoSample 0x0001
#define rightSample 0x0002
#define leftSample 0x0004
#define linkedSample 0x0008
#define ROMSample 0x8000 //32768
#define ROMMonoSample 0x8001 //32769
#define ROMRightSample 0x8002 //32770
#define ROMLeftSample 0x8004 //32772
#define ROMLinkedSample 0x8008 //32776
*/
// enum scaleTuning
//{
// equalTemp,
// fiftyCents
//};
//
// enum SFSampleField //used by Sample Read Module
//{
// NAME_FIELD = 1,
// START_FIELD,
// END_FIELD,
// START_LOOP_FIELD,
// END_LOOP_FIELD,
// SMPL_RATE_FIELD,
// ORG_KEY_FIELD,
// CORRECTION_FIELD,
// SMPL_LINK_FIELD,
// SMPL_TYPE_FIELD
//};
//
// enum SFInfoChunkField //used by Bank Read Module
//{
// IFIL_FIELD = 1,
// IROM_FIELD,
// IVER_FIELD,
// ISNG_FIELD,
// INAM_FIELD,
// IPRD_FIELD,
// IENG_FIELD,
// ISFT_FIELD,
// ICRD_FIELD,
// ICMT_FIELD,
// ICOP_FIELD
//};
#pragma pack(push) /* push current alignment to stack */
#pragma pack(2) /* set alignment to 2 byte boundary */
struct sfVersionTag {
uint16 wMajor;
uint16 wMinor;
};
struct sfPresetHeader {
char achPresetName[20];
uint16 wPreset;
uint16 wBank;
uint16 wPresetBagNdx;
uint32 dwLibrary;
uint32 dwGenre;
uint32 dwMorphology;
};
struct sfPresetBag {
uint16 wGenNdx;
uint16 wModNdx;
};
struct sfModList {
SFModulator sfModSrcOper;
SFGenerator sfModDestOper;
int16_t modAmount;
SFModulator sfModAmtSrcOper;
SFTransform sfModTransOper;
};
typedef struct {
uint8 byLo;
uint8 byHi;
uint8 *write(uint8 *buffer, uint32 *offset) {
buffer[0] = byLo;
buffer[1] = byHi;
*offset += 2;
return buffer + 2;
}
} rangesType;
typedef union {
rangesType ranges;
int16_t shAmount;
uint16 wAmount;
//TODO fix union.
uint8 *write(uint8 *buffer, uint32 *offset) {
buffer = ranges.write(buffer, offset);
WRITE_LE_INT16(buffer, shAmount);
buffer += 2;
*offset += 2;
WRITE_LE_UINT16(buffer, wAmount);
buffer += 2;
*offset += 2;
return buffer;
}
} genAmountType;
struct sfGenList {
SFGenerator sfGenOper;
genAmountType genAmount;
uint8 *write(uint8 *buffer, uint32 *offset) {
WRITE_LE_UINT16(buffer, sfGenOper);
buffer += 2;
*offset += 2;
return genAmount.write(buffer, offset);
}
};
struct sfInstModList {
SFModulator sfModSrcOper;
SFGenerator sfModDestOper;
int16_t modAmount;
SFModulator sfModAmtSrcOper;
SFTransform sfModTransOper;
};
struct sfInstGenList {
SFGenerator sfGenOper;
genAmountType genAmount;
};
struct sfInst {
char achInstName[20];
uint16 wInstBagNdx;
};
struct sfInstBag {
uint16 wInstGenNdx;
uint16 wInstModNdx;
};
typedef enum {
monoSample = 1,
rightSample = 2,
leftSample = 4,
linkedSample = 8,
RomMonoSample = 0x8001,
RomRightSample = 0x8002,
RomLeftSample = 0x8004,
RomLinkedSample = 0x8008
} SFSampleLinkType;
typedef uint16 SFSampleLink;
struct sfSample {
char achSampleName[20];
uint32 dwStart;
uint32 dwEnd;
uint32 dwStartloop;
uint32 dwEndloop;
uint32 dwSampleRate;
uint8 byOriginalKey;
char chCorrection;
uint16 wSampleLink;
SFSampleLink sfSampleType;
};
#pragma pack(pop) /* restore original alignment from stack */
class SF2StringChunk : public Chunk {
public:
SF2StringChunk(Common::String ckSig, Common::String info) : Chunk(ckSig) {
SetData(info.c_str(), (uint32) info.size());
}
};
class SF2InfoListChunk : public LISTChunk {
public:
SF2InfoListChunk(Common::String name);
};
class SF2sdtaChunk : public LISTChunk {
public:
SF2sdtaChunk();
};
inline void WriteLIST(Common::Array<uint8> &buf, Common::String listName, uint32 listSize);
inline void AlignName(Common::String &name);
class SynthFile;
class SF2File : public RiffFile {
public:
SF2File(SynthFile *synthfile);
~SF2File(void);
const void *SaveToMem();
};
#endif // AUDIO_SOUNDFONT_SF2FILE_H

View File

@ -0,0 +1,199 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "synthfile.h"
#include "vgmsamp.h"
using namespace std;
// **********************************************************************************
// SynthFile - An intermediate class to lay out all of the the data necessary for Coll conversion
// to DLS or SF2 formats. Currently, the structure is identical to
//DLS.
// **********************************************************************************
SynthFile::SynthFile(Common::String synth_name) : name(synth_name) {}
SynthFile::~SynthFile() {
DeleteVect(vInstrs);
DeleteVect(vWaves);
}
SynthInstr *SynthFile::AddInstr(uint32 bank, uint32 instrNum) {
Common::String str = Common::String::format("Instr bnk%d num%d", bank, instrNum);
vInstrs.insert(vInstrs.end(), new SynthInstr(bank, instrNum, str));
return vInstrs.back();
}
SynthInstr *SynthFile::AddInstr(uint32 bank, uint32 instrNum, Common::String instrName) {
vInstrs.insert(vInstrs.end(), new SynthInstr(bank, instrNum, instrName));
return vInstrs.back();
}
void SynthFile::DeleteInstr(uint32 bank, uint32 instrNum) {}
SynthWave *SynthFile::AddWave(uint16 formatTag, uint16 channels, int samplesPerSec,
int aveBytesPerSec, uint16 blockAlign, uint16 bitsPerSample,
uint32 waveDataSize, unsigned char *waveData, Common::String WaveName) {
vWaves.insert(vWaves.end(),
new SynthWave(formatTag, channels, samplesPerSec, aveBytesPerSec, blockAlign,
bitsPerSample, waveDataSize, waveData, WaveName));
return vWaves.back();
}
// **********
// SynthInstr
// **********
SynthInstr::SynthInstr(uint32 bank, uint32 instrument)
: ulBank(bank), ulInstrument(instrument) {
name = Common::String::format("Instr bnk %d num %d", bank, instrument);
// RiffFile::AlignName(name);
}
SynthInstr::SynthInstr(uint32 bank, uint32 instrument, Common::String instrName)
: ulBank(bank), ulInstrument(instrument), name(instrName) {
// RiffFile::AlignName(name);
}
SynthInstr::SynthInstr(uint32 bank, uint32 instrument, Common::String instrName,
Common::Array<SynthRgn *> listRgns)
: ulBank(bank), ulInstrument(instrument), name(instrName) {
// RiffFile::AlignName(name);
vRgns = listRgns;
}
SynthInstr::~SynthInstr() {
DeleteVect(vRgns);
}
SynthRgn *SynthInstr::AddRgn(void) {
vRgns.insert(vRgns.end(), new SynthRgn());
return vRgns.back();
}
SynthRgn *SynthInstr::AddRgn(SynthRgn rgn) {
SynthRgn *newRgn = new SynthRgn();
*newRgn = rgn;
vRgns.insert(vRgns.end(), newRgn);
return vRgns.back();
}
// ********
// SynthRgn
// ********
SynthRgn::~SynthRgn(void) {
if (sampinfo)
delete sampinfo;
if (art)
delete art;
}
SynthArt *SynthRgn::AddArt(void) {
art = new SynthArt();
return art;
}
SynthSampInfo *SynthRgn::AddSampInfo(void) {
sampinfo = new SynthSampInfo();
return sampinfo;
}
void SynthRgn::SetRanges(uint16 keyLow, uint16 keyHigh, uint16 velLow, uint16 velHigh) {
usKeyLow = keyLow;
usKeyHigh = keyHigh;
usVelLow = velLow;
usVelHigh = velHigh;
}
void SynthRgn::SetWaveLinkInfo(uint16 options, uint16 phaseGroup, uint32 theChannel,
uint32 theTableIndex) {
fusOptions = options;
usPhaseGroup = phaseGroup;
channel = theChannel;
tableIndex = theTableIndex;
}
// ********
// SynthArt
// ********
SynthArt::~SynthArt() {
}
void SynthArt::AddADSR(double attack, Transform atk_transform, double decay, double sustain_level,
double sustain, double release, Transform rls_transform) {
this->attack_time = attack;
this->attack_transform = atk_transform;
this->decay_time = decay;
this->sustain_lev = sustain_level;
this->sustain_time = sustain;
this->release_time = release;
this->release_transform = rls_transform;
}
void SynthArt::AddPan(double thePan) {
this->pan = thePan;
}
// *************
// SynthSampInfo
// *************
void SynthSampInfo::SetLoopInfo(Loop &loop, VGMSamp *samp) {
const int origFormatBytesPerSamp = samp->bps / 8;
double compressionRatio = samp->GetCompressionRatio();
// If the sample loops, but the loop length is 0, then assume the length should
// extend to the end of the sample.
if (loop.loopStatus && loop.loopLength == 0)
loop.loopLength = samp->dataLength - loop.loopStart;
cSampleLoops = loop.loopStatus;
ulLoopType = loop.loopType;
ulLoopStart = (loop.loopStartMeasure == LM_BYTES)
? (uint32) ((loop.loopStart * compressionRatio) / origFormatBytesPerSamp)
: loop.loopStart;
ulLoopLength = (loop.loopLengthMeasure == LM_BYTES)
? (uint32) ((loop.loopLength * compressionRatio) / origFormatBytesPerSamp)
: loop.loopLength;
}
void SynthSampInfo::SetPitchInfo(uint16 unityNote, short fineTune, double atten) {
usUnityNote = unityNote;
sFineTune = fineTune;
attenuation = atten;
}
// *********
// SynthWave
// *********
void SynthWave::ConvertTo16bitSigned() {
if (wBitsPerSample == 8) {
this->wBitsPerSample = 16;
this->wBlockAlign = 16 / 8 * this->wChannels;
this->dwAveBytesPerSec *= 2;
int16_t *newData = new int16_t[this->dataSize];
for (unsigned int i = 0; i < this->dataSize; i++)
newData[i] = ((int16_t) this->data[i] - 128) << 8;
delete[] this->data;
this->data = (uint8 *) newData;
this->dataSize *= 2;
}
}
SynthWave::~SynthWave() {
delete sampinfo;
delete[] data;
}
SynthSampInfo *SynthWave::AddSampInfo(void) {
sampinfo = new SynthSampInfo();
return sampinfo;
}

213
audio/soundfont/synthfile.h Normal file
View File

@ -0,0 +1,213 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_SYNTHFILE_H
#define AUDIO_SOUNDFONT_SYNTHFILE_H
#include "common/scummsys.h"
#include "common/list.h"
#include "common/str.h"
#include "common/array.h"
#include "rifffile.h"
struct Loop;
class VGMSamp;
class SynthInstr;
class SynthRgn;
class SynthArt;
class SynthConnectionBlock;
class SynthSampInfo;
class SynthWave;
typedef enum {
no_transform, concave_transform
} Transform;
class SynthFile {
public:
SynthFile(const Common::String synth_name = "Instrument Set");
~SynthFile();
SynthInstr *AddInstr(uint32 bank, uint32 instrNum);
SynthInstr *AddInstr(uint32 bank, uint32 instrNum, Common::String Name);
void DeleteInstr(uint32 bank, uint32 instrNum);
SynthWave *AddWave(uint16 formatTag, uint16 channels, int samplesPerSec, int aveBytesPerSec,
uint16 blockAlign, uint16 bitsPerSample, uint32 waveDataSize,
uint8 *waveData, Common::String name = "Unnamed Wave");
void SetName(Common::String synth_name);
// int WriteDLSToBuffer(Common::Array<uint8> &buf);
// bool SaveDLSFile(const char* filepath);
public:
Common::Array<SynthInstr *> vInstrs;
Common::Array<SynthWave *> vWaves;
Common::String name;
};
class SynthInstr {
public:
SynthInstr(void);
SynthInstr(uint32 bank, uint32 instrument);
SynthInstr(uint32 bank, uint32 instrument, Common::String instrName);
SynthInstr(uint32 bank, uint32 instrument, Common::String instrName,
Common::Array<SynthRgn *> listRgns);
~SynthInstr(void);
void AddRgnList(Common::Array<SynthRgn> &RgnList);
SynthRgn *AddRgn(void);
SynthRgn *AddRgn(SynthRgn rgn);
public:
uint32 ulBank;
uint32 ulInstrument;
Common::Array<SynthRgn *> vRgns;
Common::String name;
};
class SynthRgn {
public:
SynthRgn()
: usKeyLow(0),
usKeyHigh(0),
usVelLow(0),
usVelHigh(0),
sampinfo(NULL),
art(NULL),
fusOptions(0),
usPhaseGroup(0),
channel(0),
tableIndex(0) {}
~SynthRgn();
SynthArt *AddArt(void);
SynthArt *AddArt(Common::Array<SynthConnectionBlock *> connBlocks);
SynthSampInfo *AddSampInfo(void);
SynthSampInfo *AddSampInfo(SynthSampInfo wsmp);
void SetRanges(uint16 keyLow = 0, uint16 keyHigh = 0x7F, uint16 velLow = 0,
uint16 velHigh = 0x7F);
void SetWaveLinkInfo(uint16 options, uint16 phaseGroup, uint32 theChannel,
uint32 theTableIndex);
public:
uint16 usKeyLow;
uint16 usKeyHigh;
uint16 usVelLow;
uint16 usVelHigh;
uint16 fusOptions;
uint16 usPhaseGroup;
uint32 channel;
uint32 tableIndex;
SynthSampInfo *sampinfo;
SynthArt *art;
};
class SynthArt {
public:
SynthArt() {}
~SynthArt();
void AddADSR(double attack, Transform atk_transform, double decay, double sustain_lev,
double sustain_time, double release_time, Transform rls_transform);
void AddPan(double pan);
double pan; // -100% = left channel 100% = right channel 0 = 50/50
double attack_time; // rate expressed as seconds from 0 to 100% level
double decay_time; // rate expressed as seconds from 100% to 0% level, even though the sustain
// level isn't necessarily 0%
double sustain_lev; // db of attenuation at sustain level
double sustain_time; // this is part of the PSX envelope (and can actually be positive), but is
// not in DLS or SF2. from 100 to 0, like release
double release_time; // rate expressed as seconds from 100% to 0% level, even though the
// sustain level may not be 100%
Transform attack_transform;
Transform release_transform;
private:
};
class SynthSampInfo {
public:
SynthSampInfo() {}
SynthSampInfo(uint16 unityNote, int16 fineTune, double atten, int8 sampleLoops,
uint32 loopType, uint32 loopStart, uint32 loopLength)
: usUnityNote(unityNote),
sFineTune(fineTune),
attenuation(atten),
cSampleLoops(sampleLoops),
ulLoopType(loopType),
ulLoopStart(loopStart),
ulLoopLength(loopLength) {}
~SynthSampInfo() {}
void SetLoopInfo(Loop &loop, VGMSamp *samp);
// void SetPitchInfo(uint16 unityNote, int16 fineTune, double attenuation);
void SetPitchInfo(uint16 unityNote, int16 fineTune, double attenuation);
public:
uint16 usUnityNote;
int16 sFineTune;
double attenuation; // in decibels.
int8 cSampleLoops;
uint32 ulLoopType;
uint32 ulLoopStart;
uint32 ulLoopLength;
};
class SynthWave {
public:
SynthWave(void) : sampinfo(NULL), data(NULL), name("Untitled Wave") {
RiffFile::AlignName(name);
}
SynthWave(uint16 formatTag, uint16 channels, int samplesPerSec, int aveBytesPerSec,
uint16 blockAlign, uint16 bitsPerSample, uint32 waveDataSize, uint8 *waveData,
Common::String waveName = "Untitled Wave")
: wFormatTag(formatTag),
wChannels(channels),
dwSamplesPerSec(samplesPerSec),
dwAveBytesPerSec(aveBytesPerSec),
wBlockAlign(blockAlign),
wBitsPerSample(bitsPerSample),
dataSize(waveDataSize),
data(waveData),
sampinfo(NULL),
name(waveName) {
RiffFile::AlignName(name);
}
~SynthWave(void);
SynthSampInfo *AddSampInfo(void);
void ConvertTo16bitSigned();
public:
SynthSampInfo *sampinfo;
uint16 wFormatTag;
uint16 wChannels;
uint32 dwSamplesPerSec;
uint32 dwAveBytesPerSec;
uint16 wBlockAlign;
uint16 wBitsPerSample;
uint32 dataSize;
uint8 *data;
Common::String name;
};
#endif // AUDIO_SOUNDFONT_SYNTHFILE_H

View File

@ -0,0 +1,302 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common/debug.h"
#include "psxspu.h"
// A lot of games use a simple linear amplitude decay/release for their envelope.
// In other words, the envelope level drops at a constant rate (say from
// 0xFFFF to 0 (cps2) ), and to get the attenuation we multiply by this
// percent value (env_level / 0xFFFF). This means the attenuation will be
// -20*log10( env_level / 0xFFFF ) decibels. Wonderful, but SF2 and DLS have
// the a linear decay in decibels - not amplitude - for their decay/release slopes.
// So if you were to graph it, the SF2/DLS attenuation over time graph would be
// a simple line.
// (Note these are obviously crude ASCII drawings and in no way accurate!)
// 100db
// | /
// | /
// | /
// | /
// | /
// 10db / - half volume
// |/
// |--------------------TIME
// But games using linear amplitude have a convex curve
// 100db
// | -
// | -
// | -
// | -
// | -
// 10db x - half volume
// |- -
// |-------------------TIME
// Now keep in mind that 10db of attenuation is half volume to the human ear.
// What this mean is that SF2/DLS are going to sound like they have much shorter
// decay/release rates if we simply plug in a time value from 0 atten to full atten
// from a linear amplitude game.
// My approach at the moment is to calculate the time it takes to get to half volume
// and then use that value accordingly with the SF2/DLS decay time. In other words
// Take the second graph, find where y = 10db, and the draw a line from the origin
// through it to get your DLS/SF2 decay/release line
// (the actual output value is time where y = 100db for sf2 or 96db for DLS, SynthFile class uses
// 100db).
// This next function converts seconds to full attenuation in a linear amplitude decay scale
// and approximates the time to full attenuation in a linear DB decay scale.
double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange) {
double expMinDecibel = -100.0;
double linearMinDecibel = log10(1.0 / linearVolumeRange) * 20.0;
double linearToExpScale = log(linearMinDecibel - expMinDecibel) / log(2.0);
return secondsToFullAtten * linearToExpScale;
}
/*
* PSX's PSU analysis was done by Neill Corlett.
* Thanks to Antires for his ADPCM decompression routine.
*/
PSXSampColl::PSXSampColl(VGMInstrSet *instrset, uint32 offset,
uint32 length, const Common::Array<SizeOffsetPair> &vagLocations)
: VGMSampColl(instrset->GetRawFile(), instrset, offset, length),
_vagLocations(vagLocations) {}
bool PSXSampColl::GetSampleInfo() {
if (_vagLocations.empty()) {
/*
* We scan through the sample section, and determine the offsets and size of each sample
* We do this by searching for series of 16 0x00 value bytes. These indicate the beginning
* of a sample, and they will never be found at any other point within the adpcm sample
* data.
*/
uint32 nEndOffset = _dwOffset + _unLength;
if (_unLength == 0) {
nEndOffset = GetEndOffset();
}
uint32 i = _dwOffset;
while (i + 32 <= nEndOffset) {
bool isSample = false;
if (GetWord(i) == 0 && GetWord(i + 4) == 0 && GetWord(i + 8) == 0 &&
GetWord(i + 12) == 0) {
// most of samples starts with 0s
isSample = true;
} else {
// some sample blocks may not start with 0.
// so here is a dirty hack for it.
// (Dragon Quest VII, for example)
int countOfContinue = 0;
uint8 continueByte = 0xff;
bool badBlock = false;
while (i + (countOfContinue * 16) + 16 <= nEndOffset) {
uint8 keyFlagByte = GetByte(i + (countOfContinue * 16) + 1);
if ((keyFlagByte & 0xF8) != 0) {
badBlock = true;
break;
}
if (continueByte == 0xff) {
if (keyFlagByte == 0 || keyFlagByte == 2) {
continueByte = keyFlagByte;
}
}
if (keyFlagByte != continueByte) {
if (keyFlagByte == 0 || keyFlagByte == 2) {
badBlock = true;
}
break;
}
countOfContinue++;
}
if (!badBlock && ((continueByte == 0 && countOfContinue >= 16) ||
(continueByte == 2 && countOfContinue >= 3))) {
isSample = true;
}
}
if (isSample) {
uint32 extraGunkLength = 0;
uint8 filterRangeByte = GetByte(i + 16);
uint8 keyFlagByte = GetByte(i + 16 + 1);
if ((keyFlagByte & 0xF8) != 0)
break;
// if (filterRangeByte == 0 && keyFlagByte == 0) // Breaking on FFXII 309 -
// Eruyt Village at 61D50 of the WD
if (GetWord(i + 16) == 0 && GetWord(i + 20) == 0 && GetWord(i + 24) == 0 &&
GetWord(i + 28) == 0)
break;
uint32 beginOffset = i;
i += 16;
// skip through until we reach the chunk with the end flag set
bool loopEnd = false;
while (i + 16 <= nEndOffset && !loopEnd) {
loopEnd = ((GetByte(i + 1) & 1) != 0);
i += 16;
}
// deal with exceptional cases where we see 00 07 77 77 77 77 77 etc.
while (i + 16 <= nEndOffset) {
loopEnd = ((GetByte(i + 1) & 1) != 0);
if (!loopEnd) {
break;
}
extraGunkLength += 16;
i += 16;
}
PSXSamp *samp = new PSXSamp(this, beginOffset, i - beginOffset, beginOffset,
i - beginOffset - extraGunkLength, 1, 16, 44100,
Common::String::format("Sample %d", samples.size()));
samples.push_back(samp);
} else {
break;
}
}
_unLength = i - _dwOffset;
} else {
uint32 sampleIndex = 0;
for (Common::Array<SizeOffsetPair>::iterator it = _vagLocations.begin();
it != _vagLocations.end(); ++it) {
uint32 offSampStart = _dwOffset + it->offset;
uint32 offDataEnd = offSampStart + it->size;
uint32 offSampEnd = offSampStart;
// detect loop end and ignore garbages like 00 07 77 77 77 77 77 etc.
bool lastBlock;
do {
if (offSampEnd + 16 > offDataEnd) {
offSampEnd = offDataEnd;
break;
}
lastBlock = ((GetByte(offSampEnd + 1) & 1) != 0);
offSampEnd += 16;
} while (!lastBlock);
PSXSamp *samp = new PSXSamp(this, _dwOffset + it->offset, it->size,
_dwOffset + it->offset, offSampEnd - offSampStart, 1, 16,
44100, Common::String::format("Sample %d", sampleIndex));
samples.push_back(samp);
sampleIndex++;
}
}
return true;
}
// *******
// PSXSamp
// *******
PSXSamp::PSXSamp(VGMSampColl *sampColl, uint32 offset, uint32 length, uint32 dataOffset,
uint32 dataLen, uint8 nChannels, uint16 theBPS, uint32 theRate,
Common::String name, bool bSetloopOnConversion)
: VGMSamp(sampColl, offset, length, dataOffset, dataLen, nChannels, theBPS, theRate, name),
_setLoopOnConversion(bSetloopOnConversion) {
bPSXLoopInfoPrioritizing = true;
}
double PSXSamp::GetCompressionRatio() {
return ((28.0 / 16.0) * 2); // aka 3.5;
}
void PSXSamp::ConvertToStdWave(uint8 *buf) {
int16 *uncompBuf = (int16 *) buf;
VAGBlk theBlock;
f32 prev1 = 0;
f32 prev2 = 0;
if (this->_setLoopOnConversion)
SetLoopStatus(0); // loopStatus is initiated to -1. We should default it now to not loop
bool addrOutOfVirtFile = false;
for (uint32 k = 0; k < dataLength; k += 0x10) // for every adpcm chunk
{
if (_dwOffset + k + 16 > _vgmfile->GetEndOffset()) {
debug("Unexpected EOF (%s)", _name.c_str());
break;
} else if (!addrOutOfVirtFile && k + 16 > _unLength) {
debug("Unexpected end of PSXSamp (%s)", _name.c_str());
addrOutOfVirtFile = true;
}
theBlock.range = GetByte(_dwOffset + k) & 0xF;
theBlock.filter = (GetByte(_dwOffset + k) & 0xF0) >> 4;
theBlock.flag.end = GetByte(_dwOffset + k + 1) & 1;
theBlock.flag.looping = (GetByte(_dwOffset + k + 1) & 2) > 0;
// this can be the loop point, but in wd, this info is stored in the instrset
theBlock.flag.loop = (GetByte(_dwOffset + k + 1) & 4) > 0;
if (this->_setLoopOnConversion) {
if (theBlock.flag.loop) {
this->SetLoopOffset(k);
this->SetLoopLength(dataLength - k);
}
if (theBlock.flag.end && theBlock.flag.looping) {
SetLoopStatus(1);
}
}
GetRawFile()->GetBytes(_dwOffset + k + 2, 14, theBlock.brr);
// each decompressed pcm block is 52 bytes EDIT: (wait, isn't it 56 bytes? or is it 28?)
DecompVAGBlk(uncompBuf + ((k * 28) / 16), &theBlock, &prev1, &prev2);
}
}
// This next function is taken from Antires's work
void PSXSamp::DecompVAGBlk(int16 *pSmp, VAGBlk *pVBlk, f32 *prev1, f32 *prev2) {
uint32 i, shift; // Shift amount for compressed samples
f32 t; // Temporary sample
f32 f1, f2;
f32 p1, p2;
static const f32 Coeff[5][2] = {{0.0, 0.0},
{60.0 / 64.0, 0.0},
{115.0 / 64.0, 52.0 / 64.0},
{98.0 / 64.0, 55.0 / 64.0},
{122.0 / 64.0, 60.0 / 64.0}};
// Expand samples ---------------------------
shift = pVBlk->range + 16;
for (i = 0; i < 14; i++) {
pSmp[i * 2] = ((int32) pVBlk->brr[i] << 28) >> shift;
pSmp[i * 2 + 1] = ((int32) (pVBlk->brr[i] & 0xF0) << 24) >> shift;
}
// Apply ADPCM decompression ----------------
i = pVBlk->filter;
if (i) {
f1 = Coeff[i][0];
f2 = Coeff[i][1];
p1 = *prev1;
p2 = *prev2;
for (i = 0; i < 28; i++) {
t = pSmp[i] + (p1 * f1) - (p2 * f2);
pSmp[i] = (int16) t;
p2 = p1;
p1 = t;
}
*prev1 = p1;
*prev2 = p2;
} else {
*prev2 = pSmp[26];
*prev1 = pSmp[27];
}
}

View File

@ -0,0 +1,406 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_PSXSPU_H
#define AUDIO_SOUNDFONT_PSXSPU_H
#include "audio/soundfont/common.h"
#include "common/str.h"
#include "audio/soundfont/vgminstrset.h"
#include "audio/soundfont/vgmsamp.h"
#include "audio/soundfont/vgmitem.h"
// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's
// work in reverse-engineering the Playstation 1/2 SPU unit.
//**************************************************************************************************
// Type Redefinitions
typedef void v0;
#ifdef __cplusplus
#if defined __BORLANDC__
typedef bool b8;
#else
typedef unsigned char b8;
#endif
#else
typedef char b8;
#endif
typedef float f32;
//***********************************************************************************************
static unsigned long RateTable[160];
static bool bRateTableInitialized = 0;
// VAG format -----------------------------------
// Sample Block
typedef struct _VAGBlk {
uint8 range;
uint8 filter;
struct {
b8 end: 1; // End block
b8 looping: 1; // VAG loops
b8 loop: 1; // Loop start point
} flag;
int8 brr[14]; // Compressed samples
} VAGBlk;
double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange);
// InitADSR is shamelessly ripped from P.E.Op.S
static void InitADSR() {
unsigned long r, rs, rd;
int i;
// build the rate table according to Neill's rules
memset(RateTable, 0, sizeof(unsigned long) * 160);
r = 3;
rs = 1;
rd = 0;
// we start at pos 32 with the real values... everything before is 0
for (i = 32; i < 160; i++) {
if (r < 0x3FFFFFFF) {
r += rs;
rd++;
if (rd == 5) {
rd = 1;
rs *= 2;
}
}
if (r > 0x3FFFFFFF)
r = 0x3FFFFFFF;
RateTable[i] = r;
}
}
inline int RoundToZero(int val) {
if (val < 0)
val = 0;
return val;
}
template<class T>
void PSXConvADSR(T *realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2) {
uint8 Am = (ADSR1 & 0x8000) >> 15; // if 1, then Exponential, else linear
uint8 Ar = (ADSR1 & 0x7F00) >> 8;
uint8 Dr = (ADSR1 & 0x00F0) >> 4;
uint8 Sl = ADSR1 & 0x000F;
uint8 Rm = (ADSR2 & 0x0020) >> 5;
uint8 Rr = ADSR2 & 0x001F;
// The following are unimplemented in conversion (because DLS and SF2 do not support Sustain
// Rate)
uint8 Sm = (ADSR2 & 0x8000) >> 15;
uint8 Sd = (ADSR2 & 0x4000) >> 14;
uint8 Sr = (ADSR2 >> 6) & 0x7F;
PSXConvADSR(realADSR, Am, Ar, Dr, Sl, Sm, Sd, Sr, Rm, Rr, bPS2);
}
template<class T>
void PSXConvADSR(T *realADSR, uint8 Am, uint8 Ar, uint8 Dr, uint8 Sl, uint8 Sm,
uint8 Sd, uint8 Sr, uint8 Rm, uint8 Rr, bool bPS2) {
// Make sure all the ADSR values are within the valid ranges
if (((Am & ~0x01) != 0) || ((Ar & ~0x7F) != 0) || ((Dr & ~0x0F) != 0) || ((Sl & ~0x0F) != 0) ||
((Rm & ~0x01) != 0) || ((Rr & ~0x1F) != 0) || ((Sm & ~0x01) != 0) || ((Sd & ~0x01) != 0) ||
((Sr & ~0x7F) != 0)) {
error("ADSR parameter(s) out of range");
}
// PS1 games use 44k, PS2 uses 48k
double sampleRate = bPS2 ? 48000 : 44100;
long envelope_level;
double samples = 0.0;
unsigned long rate;
unsigned long remainder;
double timeInSecs;
int l;
if (!bRateTableInitialized) {
InitADSR();
bRateTableInitialized = true;
}
// to get the dls 32 bit time cents, take log base 2 of number of seconds * 1200 * 65536
// (dls1v11a.pdf p25).
// if (RateTable[(Ar^0x7F)-0x10 + 32] == 0)
// realADSR->attack_time = 0;
// else
// {
if ((Ar ^ 0x7F) < 0x10)
Ar = 0;
// if linear Ar Mode
if (Am == 0) {
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32];
samples = ceil(0x7FFFFFFF / (double) rate);
} else if (Am == 1) {
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32];
samples = 0x60000000 / rate;
remainder = 0x60000000 % rate;
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x18) + 32];
samples += ceil(fmax(0, 0x1FFFFFFF - (long) remainder) / (double) rate);
}
timeInSecs = samples / sampleRate;
realADSR->_attack_time = timeInSecs;
// }
// Decay Time
envelope_level = 0x7FFFFFFF;
bool bSustainLevFound = false;
uint32 realSustainLevel;
// DLS decay rate value is to -96db (silence) not the sustain level
for (l = 0; envelope_level > 0; l++) {
if (4 * (Dr ^ 0x1F) < 0x18)
Dr = 0;
switch ((envelope_level >> 28) & 0x7) {
case 0:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 0) + 32];
break;
case 1:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 4) + 32];
break;
case 2:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 6) + 32];
break;
case 3:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 8) + 32];
break;
case 4:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 9) + 32];
break;
case 5:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 10) + 32];
break;
case 6:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 11) + 32];
break;
case 7:
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 12) + 32];
break;
}
if (!bSustainLevFound && ((envelope_level >> 27) & 0xF) <= Sl) {
realSustainLevel = envelope_level;
bSustainLevFound = true;
}
}
samples = l;
timeInSecs = samples / sampleRate;
realADSR->_decay_time = timeInSecs;
// Sustain Rate
envelope_level = 0x7FFFFFFF;
// increasing... we won't even bother
if (Sd == 0) {
realADSR->_sustain_time = -1;
} else {
if (Sr == 0x7F)
realADSR->_sustain_time = -1; // this is actually infinite
else {
// linear
if (Sm == 0) {
rate = RateTable[RoundToZero((Sr ^ 0x7F) - 0x0F) + 32];
samples = ceil(0x7FFFFFFF / (double) rate);
} else {
l = 0;
// DLS decay rate value is to -96db (silence) not the sustain level
while (envelope_level > 0) {
long envelope_level_diff;
long envelope_level_target;
switch ((envelope_level >> 28) & 0x7) {
case 0:
envelope_level_target = 0x00000000;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 0) + 32];
break;
case 1:
envelope_level_target = 0x0fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 4) + 32];
break;
case 2:
envelope_level_target = 0x1fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 6) + 32];
break;
case 3:
envelope_level_target = 0x2fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 8) + 32];
break;
case 4:
envelope_level_target = 0x3fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 9) + 32];
break;
case 5:
envelope_level_target = 0x4fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 10) + 32];
break;
case 6:
envelope_level_target = 0x5fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 11) + 32];
break;
case 7:
envelope_level_target = 0x6fffffff;
envelope_level_diff =
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 12) + 32];
break;
}
long steps =
(envelope_level - envelope_level_target + (envelope_level_diff - 1)) /
envelope_level_diff;
envelope_level -= (envelope_level_diff * steps);
l += steps;
}
samples = l;
}
timeInSecs = samples / sampleRate;
realADSR->_sustain_time =
/*Sm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800);
}
}
// Sustain Level
// realADSR->sustain_level =
// (double)envelope_level/(double)0x7FFFFFFF;//(long)ceil((double)envelope_level *
// 0.030517578139210854); //in DLS, sustain level is measured as a percentage
if (Sl == 0)
realSustainLevel = 0x07FFFFFF;
realADSR->_sustain_level = realSustainLevel / (double) 0x7FFFFFFF;
// If decay is going unused, and there's a sustain rate with sustain level close to max...
// we'll put the sustain_rate in place of the decay rate.
if ((realADSR->_decay_time < 2 || (Dr == 0x0F && Sl >= 0x0C)) && Sr < 0x7E && Sd == 1) {
realADSR->_sustain_level = 0;
realADSR->_decay_time = realADSR->_sustain_time;
// realADSR->decay_time = 0.5;
}
// Release Time
// sustain_envelope_level = envelope_level;
// We do this because we measure release time from max volume to 0, not from sustain level to 0
envelope_level = 0x7FFFFFFF;
// if linear Rr Mode
if (Rm == 0) {
rate = RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x0C) + 32];
if (rate != 0)
samples = ceil((double) envelope_level / (double) rate);
else
samples = 0;
} else if (Rm == 1) {
if ((Rr ^ 0x1F) * 4 < 0x18)
Rr = 0;
for (l = 0; envelope_level > 0; l++) {
switch ((envelope_level >> 28) & 0x7) {
case 0:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 0) + 32];
break;
case 1:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 4) + 32];
break;
case 2:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 6) + 32];
break;
case 3:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 8) + 32];
break;
case 4:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 9) + 32];
break;
case 5:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 10) + 32];
break;
case 6:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 11) + 32];
break;
case 7:
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 12) + 32];
break;
}
}
samples = l;
}
timeInSecs = samples / sampleRate;
// theRate = timeInSecs / sustain_envelope_level;
// timeInSecs = 0x7FFFFFFF * theRate; //the release time value is more like a rate. It is the
// time from max value to 0, not from sustain level. if (Rm == 0) // if it's linear timeInSecs *=
//LINEAR_RELEASE_COMPENSATION;
realADSR->_release_time =
/*Rm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800);
// We need to compensate the decay and release times to represent them as the time from full vol
// to -100db where the drop in db is a fixed amount per time unit (SoundFont2 spec for vol
// envelopes, pg44.)
// We assume the psx envelope is using a linear scale wherein envelope_level / 2 == half
// loudness. For a linear release mode (Rm == 0), the time to reach half volume is simply half
// the time to reach 0.
// Half perceived loudness is -10db. Therefore, time_to_half_vol * 10 == full_time * 5 == the
// correct SF2 time
// realADSR->decay_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->decay_time, 0x800);
// realADSR->sustain_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->sustain_time, 0x800);
// realADSR->release_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->release_time, 0x800);
// Calculations are done, so now add the articulation data
// artic->AddADSR(attack_time, Am, decay_time, sustain_lev, release_time, 0);
}
class PSXSampColl : public VGMSampColl {
public:
PSXSampColl(VGMInstrSet *instrset, uint32 offset, uint32 length,
const Common::Array<SizeOffsetPair> &vagLocations);
virtual bool
GetSampleInfo(); // retrieve sample info, including pointers to data, # channels, rate, etc.
protected:
Common::Array<SizeOffsetPair> _vagLocations;
};
class PSXSamp : public VGMSamp {
public:
PSXSamp(VGMSampColl *sampColl, uint32 offset, uint32 length, uint32 dataOffset,
uint32 dataLen, uint8 nChannels, uint16 theBPS, uint32 theRate,
Common::String name, bool bSetLoopOnConversion = true);
~PSXSamp() override {}
// ratio of space conserved. should generally be > 1
// used to calculate both uncompressed sample size and loopOff after conversion
double GetCompressionRatio() override;
void ConvertToStdWave(uint8 *buf) override;
private:
void DecompVAGBlk(int16 *pSmp, VAGBlk *pVBlk, f32 *prev1, f32 *prev2);
public:
bool _setLoopOnConversion;
};
#endif // AUDIO_SOUNDFONT_PSXSPU_H

243
audio/soundfont/vab/vab.cpp Normal file
View File

@ -0,0 +1,243 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common/debug.h"
#include "common/scummsys.h"
#include "vab.h"
#include "psxspu.h"
using namespace std;
Vab::Vab(RawFile *file, uint32 offset) : VGMInstrSet(file, offset) {}
Vab::~Vab() {}
bool Vab::GetHeaderInfo() {
uint32 nEndOffset = GetEndOffset();
uint32 nMaxLength = nEndOffset - _dwOffset;
if (nMaxLength < 0x20) {
return false;
}
_name = "VAB";
VGMHeader *vabHdr = AddHeader(_dwOffset, 0x20, "VAB Header");
vabHdr->AddSimpleItem(_dwOffset + 0x00, 4, "ID");
vabHdr->AddSimpleItem(_dwOffset + 0x04, 4, "Version");
vabHdr->AddSimpleItem(_dwOffset + 0x08, 4, "VAB ID");
vabHdr->AddSimpleItem(_dwOffset + 0x0c, 4, "Total Size");
vabHdr->AddSimpleItem(_dwOffset + 0x10, 2, "Reserved");
vabHdr->AddSimpleItem(_dwOffset + 0x12, 2, "Number of Programs");
vabHdr->AddSimpleItem(_dwOffset + 0x14, 2, "Number of Tones");
vabHdr->AddSimpleItem(_dwOffset + 0x16, 2, "Number of VAGs");
vabHdr->AddSimpleItem(_dwOffset + 0x18, 1, "Master Volume");
vabHdr->AddSimpleItem(_dwOffset + 0x19, 1, "Master Pan");
vabHdr->AddSimpleItem(_dwOffset + 0x1a, 1, "Bank Attributes 1");
vabHdr->AddSimpleItem(_dwOffset + 0x1b, 1, "Bank Attributes 2");
vabHdr->AddSimpleItem(_dwOffset + 0x1c, 4, "Reserved");
return true;
}
bool Vab::GetInstrPointers() {
uint32 nEndOffset = GetEndOffset();
uint32 offProgs = _dwOffset + 0x20;
uint32 offToneAttrs = offProgs + (16 * 128);
uint16 numPrograms = GetShort(_dwOffset + 0x12);
uint16 numVAGs = GetShort(_dwOffset + 0x16);
uint32 offVAGOffsets = offToneAttrs + (32 * 16 * numPrograms);
VGMHeader *progsHdr = AddHeader(offProgs, 16 * 128, "Program Table");
VGMHeader *toneAttrsHdr = AddHeader(offToneAttrs, 32 * 16, "Tone Attributes Table");
if (numPrograms > 128) {
debug("Too many programs %x, offset %x", numPrograms, _dwOffset);
return false;
}
if (numVAGs > 255) {
debug("Too many VAGs %x, offset %x", numVAGs, _dwOffset);
return false;
}
// Load each instruments.
//
// Rule 1. Valid instrument pointers are not always sequentially located from 0 to (numProgs -
// 1). Number of tones can be 0. That's an empty instrument. We need to ignore it. See Clock
// Tower PSF for example.
//
// Rule 2. Do not load programs more than number of programs. Even if a program table value is
// provided. Otherwise an out-of-order access can be caused in Tone Attributes Table. See the
// swimming event BGM of Aitakute... ~your smiles in my heart~ for example. (github issue #115)
uint32 numProgramsLoaded = 0;
for (uint32 progIndex = 0; progIndex < 128 && numProgramsLoaded < numPrograms; progIndex++) {
uint32 offCurrProg = offProgs + (progIndex * 16);
uint32 offCurrToneAttrs = offToneAttrs + (uint32) (_aInstrs.size() * 32 * 16);
if (offCurrToneAttrs + (32 * 16) > nEndOffset) {
break;
}
uint8 numTonesPerInstr = GetByte(offCurrProg);
if (numTonesPerInstr > 32) {
debug("Program %x contains too many tones (%d)", progIndex, numTonesPerInstr);
} else if (numTonesPerInstr != 0) {
VabInstr *newInstr = new VabInstr(this, offCurrToneAttrs, 0x20 * 16, 0, progIndex);
_aInstrs.push_back(newInstr);
newInstr->_tones = GetByte(offCurrProg + 0);
VGMHeader *progHdr = progsHdr->AddHeader(offCurrProg, 0x10, "Program");
progHdr->AddSimpleItem(offCurrProg + 0x00, 1, "Number of Tones");
progHdr->AddSimpleItem(offCurrProg + 0x01, 1, "Volume");
progHdr->AddSimpleItem(offCurrProg + 0x02, 1, "Priority");
progHdr->AddSimpleItem(offCurrProg + 0x03, 1, "Mode");
progHdr->AddSimpleItem(offCurrProg + 0x04, 1, "Pan");
progHdr->AddSimpleItem(offCurrProg + 0x05, 1, "Reserved");
progHdr->AddSimpleItem(offCurrProg + 0x06, 2, "Attribute");
progHdr->AddSimpleItem(offCurrProg + 0x08, 4, "Reserved");
progHdr->AddSimpleItem(offCurrProg + 0x0c, 4, "Reserved");
newInstr->_masterVol = GetByte(offCurrProg + 0x01);
toneAttrsHdr->_unLength = offCurrToneAttrs + (32 * 16) - offToneAttrs;
numProgramsLoaded++;
}
}
if ((offVAGOffsets + 2 * 256) <= nEndOffset) {
char name[256];
Common::Array<SizeOffsetPair> vagLocations;
uint32 totalVAGSize = 0;
VGMHeader *vagOffsetHdr = AddHeader(offVAGOffsets, 2 * 256, "VAG Pointer Table");
uint32 vagStartOffset = GetShort(offVAGOffsets) * 8;
vagOffsetHdr->AddSimpleItem(offVAGOffsets, 2, "VAG Size /8 #0");
totalVAGSize = vagStartOffset;
for (uint32 i = 0; i < numVAGs; i++) {
uint32 vagOffset;
uint32 vagSize;
if (i == 0) {
vagOffset = vagStartOffset;
vagSize = GetShort(offVAGOffsets + (i + 1) * 2) * 8;
} else {
vagOffset = vagStartOffset + vagLocations[i - 1].offset + vagLocations[i - 1].size;
vagSize = GetShort(offVAGOffsets + (i + 1) * 2) * 8;
}
sprintf(name, "VAG Size /8 #%u", i + 1);
vagOffsetHdr->AddSimpleItem(offVAGOffsets + (i + 1) * 2, 2, name);
if (vagOffset + vagSize <= nEndOffset) {
vagLocations.push_back(SizeOffsetPair(vagOffset, vagSize));
totalVAGSize += vagSize;
} else {
debug("VAG #%d at %x with size %x) is invalid", i + 1, vagOffset, vagSize);
}
}
_unLength = (offVAGOffsets + 2 * 256) - _dwOffset;
// single VAB file?
uint32 offVAGs = offVAGOffsets + 2 * 256;
if (_dwOffset == 0 && vagLocations.size() != 0) {
// load samples as well
PSXSampColl *newSampColl =
new PSXSampColl(this, offVAGs, totalVAGSize, vagLocations);
if (newSampColl->LoadVGMFile()) {
this->_sampColl = newSampColl;
} else {
delete newSampColl;
}
}
}
return true;
}
// ********
// VabInstr
// ********
VabInstr::VabInstr(VGMInstrSet *instrSet, uint32 offset, uint32 length, uint32 theBank,
uint32 theInstrNum, const Common::String &name)
: VGMInstr(instrSet, offset, length, theBank, theInstrNum, name), _masterVol(127) {}
VabInstr::~VabInstr(void) {}
bool VabInstr::LoadInstr() {
int8_t numRgns = _tones;
for (int i = 0; i < numRgns; i++) {
VabRgn *rgn = new VabRgn(this, _dwOffset + i * 0x20);
if (!rgn->LoadRgn()) {
delete rgn;
return false;
}
_aRgns.push_back(rgn);
}
return true;
}
// ******
// VabRgn
// ******
VabRgn::VabRgn(VabInstr *instr, uint32 offset) : VGMRgn(instr, offset) {}
bool VabRgn::LoadRgn() {
VabInstr *instr = (VabInstr *) _parInstr;
_unLength = 0x20;
AddGeneralItem(_dwOffset, 1, "Priority");
AddGeneralItem(_dwOffset + 1, 1, "Mode (use reverb?)");
AddVolume((GetByte(_dwOffset + 2) * instr->_masterVol) / (127.0 * 127.0), _dwOffset + 2, 1);
AddPan(GetByte(_dwOffset + 3), _dwOffset + 3);
AddUnityKey(GetByte(_dwOffset + 4), _dwOffset + 4);
AddGeneralItem(_dwOffset + 5, 1, "Pitch Tune");
AddKeyLow(GetByte(_dwOffset + 6), _dwOffset + 6);
AddKeyHigh(GetByte(_dwOffset + 7), _dwOffset + 7);
AddGeneralItem(_dwOffset + 8, 1, "Vibrato Width");
AddGeneralItem(_dwOffset + 9, 1, "Vibrato Time");
AddGeneralItem(_dwOffset + 10, 1, "Portamento Width");
AddGeneralItem(_dwOffset + 11, 1, "Portamento Holding Time");
AddGeneralItem(_dwOffset + 12, 1, "Pitch Bend Min");
AddGeneralItem(_dwOffset + 13, 1, "Pitch Bend Max");
AddGeneralItem(_dwOffset + 14, 1, "Reserved");
AddGeneralItem(_dwOffset + 15, 1, "Reserved");
AddGeneralItem(_dwOffset + 16, 2, "ADSR1");
AddGeneralItem(_dwOffset + 18, 2, "ADSR2");
AddGeneralItem(_dwOffset + 20, 2, "Parent Program");
AddSampNum(GetShort(_dwOffset + 22) - 1, _dwOffset + 22, 2);
AddGeneralItem(_dwOffset + 24, 2, "Reserved");
AddGeneralItem(_dwOffset + 26, 2, "Reserved");
AddGeneralItem(_dwOffset + 28, 2, "Reserved");
AddGeneralItem(_dwOffset + 30, 2, "Reserved");
_ADSR1 = GetShort(_dwOffset + 16);
_ADSR2 = GetShort(_dwOffset + 18);
if ((int) _sampNum < 0)
_sampNum = 0;
if (_keyLow > _keyHigh) {
debug("Low key higher than high key %d > %d (at %x)", _keyLow, _keyHigh, _dwOffset);
return false;
}
// gocha: AFAIK, the valid range of pitch is 0-127. It must not be negative.
// If it exceeds 127, driver clips the value and it will become 127. (In Hokuto no Ken, at
// least) I am not sure if the interpretation of this value depends on a driver or VAB version.
// The following code takes the byte as signed, since it could be a typical extended
// implementation.
int8_t ft = (int8_t) GetByte(_dwOffset + 5);
double cents = ft * 100.0 / 128.0;
SetFineTune((int16_t) cents);
PSXConvADSR<VabRgn>(this, _ADSR1, _ADSR2, false);
return true;
}

55
audio/soundfont/vab/vab.h Normal file
View File

@ -0,0 +1,55 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_VAB_H
#define AUDIO_SOUNDFONT_VAB_H
#include "audio/soundfont/common.h"
#include "common/str.h"
#include "audio/soundfont/vgminstrset.h"
#include "audio/soundfont/vgmsamp.h"
class Vab : public VGMInstrSet {
public:
Vab(RawFile *file, uint32 offset);
virtual ~Vab(void);
virtual bool GetHeaderInfo();
virtual bool GetInstrPointers();
};
// ********
// VabInstr
// ********
class VabInstr : public VGMInstr {
public:
VabInstr(VGMInstrSet *instrSet, uint32 offset, uint32 length, uint32 theBank,
uint32 theInstrNum, const Common::String &name = "Instrument");
virtual ~VabInstr(void);
virtual bool LoadInstr();
public:
uint8 _tones;
uint8 _masterVol;
};
// ******
// VabRgn
// ******
class VabRgn : public VGMRgn {
public:
VabRgn(VabInstr *instr, uint32 offset);
virtual bool LoadRgn();
public:
uint16 _ADSR1; // raw ps2 ADSR1 value (articulation data)
uint16 _ADSR2; // raw ps2 ADSR2 value (articulation data)
};
#endif // AUDIO_SOUNDFONT_VAB_H

243
audio/soundfont/vgmcoll.cpp Normal file
View File

@ -0,0 +1,243 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common/debug.h"
#include "vgmcoll.h"
#include "vgminstrset.h"
#include "vgmsamp.h"
using namespace std;
double ConvertLogScaleValToAtten(double percent) {
if (percent == 0)
return 100.0; // assume 0 is -100.0db attenuation
double atten = 20 * log10(percent) * 2;
return MIN(-atten, 100.0);
}
// Convert a percent of volume value to it's attenuation in decibels.
// ex: ConvertPercentVolToAttenDB_SF2(0.5) returns -(-6.02db) = half perceived loudness
double ConvertPercentAmplitudeToAttenDB_SF2(double percent) {
if (percent == 0)
return 100.0; // assume 0 is -100.0db attenuation
double atten = 20 * log10(percent);
return MIN(-atten, 100.0);
}
void VGMColl::UnpackSampColl(SynthFile &synthfile, VGMSampColl *sampColl,
Common::Array<VGMSamp *> &finalSamps) {
assert(sampColl != nullptr);
size_t nSamples = sampColl->samples.size();
for (size_t i = 0; i < nSamples; i++) {
VGMSamp *samp = sampColl->samples[i];
uint32 bufSize;
if (samp->ulUncompressedSize)
bufSize = samp->ulUncompressedSize;
else
bufSize = (uint32) ceil((double) samp->dataLength * samp->GetCompressionRatio());
uint8 *uncompSampBuf =
new uint8[bufSize]; // create a new memory space for the uncompressed wave
samp->ConvertToStdWave(uncompSampBuf); // and uncompress into that space
uint16 blockAlign = samp->bps / 8 * samp->channels;
SynthWave *wave =
synthfile.AddWave(1, samp->channels, samp->rate, samp->rate * blockAlign, blockAlign,
samp->bps, bufSize, uncompSampBuf, (samp->_name));
finalSamps.push_back(samp);
// If we don't have any loop information, then don't create a sampInfo structure for the
// Wave
if (samp->loop.loopStatus == -1) {
debug("No loop information for %s - some parameters might be incorrect",
samp->sampName.c_str());
return;
}
SynthSampInfo *sampInfo = wave->AddSampInfo();
if (samp->bPSXLoopInfoPrioritizing) {
if (samp->loop.loopStart != 0 || samp->loop.loopLength != 0)
sampInfo->SetLoopInfo(samp->loop, samp);
} else
sampInfo->SetLoopInfo(samp->loop, samp);
double attenuation = (samp->volume != -1) ? ConvertLogScaleValToAtten(samp->volume) : 0;
uint8 unityKey = (samp->unityKey != -1) ? samp->unityKey : 0x3C;
short fineTune = samp->fineTune;
sampInfo->SetPitchInfo(unityKey, fineTune, attenuation);
}
}
SF2File *VGMColl::CreateSF2File(VGMInstrSet *theInstrSet) {
SynthFile *synthfile = CreateSynthFile(theInstrSet);
if (!synthfile) {
debug("SF2 conversion aborted");
return nullptr;
}
SF2File *sf2file = new SF2File(synthfile);
delete synthfile;
return sf2file;
}
SynthFile *VGMColl::CreateSynthFile(VGMInstrSet *theInstrSet) {
Common::Array<VGMInstrSet *> instrsets;
instrsets.push_back(theInstrSet);
if (instrsets.empty()) {
debug("No instruments found.");
return nullptr;
}
/* FIXME: shared_ptr eventually */
SynthFile *synthfile = new SynthFile("SynthFile");
Common::Array<VGMSamp *> finalSamps;
Common::Array<VGMSampColl *> finalSampColls;
for (uint32 i = 0; i < instrsets.size(); i++) {
VGMSampColl *instrset_sampcoll = instrsets[i]->_sampColl;
if (instrset_sampcoll) {
finalSampColls.push_back(instrset_sampcoll);
UnpackSampColl(*synthfile, instrset_sampcoll, finalSamps);
}
}
if (finalSamps.empty()) {
debug("No sample collection present");
delete synthfile;
return nullptr;
}
for (size_t inst = 0; inst < instrsets.size(); inst++) {
VGMInstrSet *set = instrsets[inst];
size_t nInstrs = set->_aInstrs.size();
for (size_t i = 0; i < nInstrs; i++) {
VGMInstr *vgminstr = set->_aInstrs[i];
size_t nRgns = vgminstr->_aRgns.size();
if (nRgns == 0) // do not write an instrument if it has no regions
continue;
SynthInstr *newInstr = synthfile->AddInstr(vgminstr->_bank, vgminstr->_instrNum);
for (uint32 j = 0; j < nRgns; j++) {
VGMRgn *rgn = vgminstr->_aRgns[j];
// if (rgn->sampNum+1 > sampColl->samples.size())
////does thereferenced sample exist? continue;
// Determine the SampColl associated with this rgn. If there's an explicit pointer
// to it, use that.
VGMSampColl *sampColl = rgn->_sampCollPtr;
if (!sampColl) {
// If rgn is of an InstrSet with an embedded SampColl, use that SampColl.
if (((VGMInstrSet *) rgn->_vgmfile)->_sampColl)
sampColl = ((VGMInstrSet *) rgn->_vgmfile)->_sampColl;
// If that does not exist, assume the first SampColl
else
sampColl = finalSampColls[0];
}
// Determine the sample number within the rgn's associated SampColl
size_t realSampNum = rgn->_sampNum;
// Determine the sampCollNum (index into our finalSampColls vector)
size_t sampCollNum = finalSampColls.size();
for (size_t k = 0; k < finalSampColls.size(); k++) {
if (finalSampColls[k] == sampColl)
sampCollNum = k;
}
if (sampCollNum == finalSampColls.size()) {
debug("SampColl does not exist");
return nullptr;
}
// now we add the number of samples from the preceding SampColls to the value to
// get the real sampNum in the final DLS file.
for (uint32 k = 0; k < sampCollNum; k++)
realSampNum += finalSampColls[k]->samples.size();
SynthRgn *newRgn = newInstr->AddRgn();
newRgn->SetRanges(rgn->_keyLow, rgn->_keyHigh, rgn->_velLow, rgn->_velHigh);
newRgn->SetWaveLinkInfo(0, 0, 1, (uint32) realSampNum);
if (realSampNum >= finalSamps.size()) {
debug("Sample %lu does not exist", realSampNum);
realSampNum = finalSamps.size() - 1;
}
VGMSamp *samp = finalSamps[realSampNum]; // sampColl->samples[rgn->sampNum];
SynthSampInfo *sampInfo = newRgn->AddSampInfo();
// This is a really loopy way of determining the loop information, pardon the pun.
// However, it works. There might be a way to simplify this, but I don't want to
// test out whether another method breaks anything just yet Use the sample's
// loopStatus to determine if a loop occurs. If it does, see if the sample provides
// loop info (gathered during ADPCM > PCM conversion. If the sample doesn't provide
// loop offset info, then use the region's loop info.
if (samp->bPSXLoopInfoPrioritizing) {
if (samp->loop.loopStatus != -1) {
if (samp->loop.loopStart != 0 || samp->loop.loopLength != 0)
sampInfo->SetLoopInfo(samp->loop, samp);
else {
rgn->_loop.loopStatus = samp->loop.loopStatus;
sampInfo->SetLoopInfo(rgn->_loop, samp);
}
} else {
delete synthfile;
error("argh"); //TODO
}
}
// The normal method: First, we check if the rgn has loop info defined.
// If it doesn't, then use the sample's loop info.
else if (rgn->_loop.loopStatus == -1) {
if (samp->loop.loopStatus != -1)
sampInfo->SetLoopInfo(samp->loop, samp);
else {
delete synthfile;
error("argh2"); //TODO
}
} else
sampInfo->SetLoopInfo(rgn->_loop, samp);
int8_t realUnityKey = -1;
if (rgn->_unityKey == -1)
realUnityKey = samp->unityKey;
else
realUnityKey = rgn->_unityKey;
if (realUnityKey == -1)
realUnityKey = 0x3C;
short realFineTune;
if (rgn->_fineTune == 0)
realFineTune = samp->fineTune;
else
realFineTune = rgn->_fineTune;
double attenuation;
if (rgn->_volume != -1)
attenuation = ConvertLogScaleValToAtten(rgn->_volume);
else if (samp->volume != -1)
attenuation = ConvertLogScaleValToAtten(samp->volume);
else
attenuation = 0;
double sustainLevAttenDb;
if (rgn->_sustain_level == -1)
sustainLevAttenDb = 0.0;
else
sustainLevAttenDb = ConvertPercentAmplitudeToAttenDB_SF2(rgn->_sustain_level);
SynthArt *newArt = newRgn->AddArt();
newArt->AddPan(rgn->_pan);
newArt->AddADSR(rgn->_attack_time, (Transform) rgn->_attack_transform, rgn->_decay_time,
sustainLevAttenDb, rgn->_sustain_time, rgn->_release_time,
(Transform) rgn->_release_transform);
sampInfo->SetPitchInfo(realUnityKey, realFineTune, attenuation);
}
}
}
return synthfile;
}

28
audio/soundfont/vgmcoll.h Normal file
View File

@ -0,0 +1,28 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_VGMCOLL_H
#define AUDIO_SOUNDFONT_VGMCOLL_H
#include "common.h"
#include "common/array.h"
class VGMInstrSet;
class VGMSampColl;
class VGMSamp;
class SF2File;
class SynthFile;
class VGMColl {
public:
SF2File *CreateSF2File(VGMInstrSet *theInstrSet);
private:
SynthFile *CreateSynthFile(VGMInstrSet *theInstrSet);
void UnpackSampColl(SynthFile &synthfile, VGMSampColl *sampColl,
Common::Array<VGMSamp *> &finalSamps);
};
#endif // AUDIO_SOUNDFONT_VGMCOLL_H

View File

@ -0,0 +1,85 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common.h"
#include "vgminstrset.h"
#include "vgmsamp.h"
using namespace std;
// ***********
// VGMInstrSet
// ***********
VGMInstrSet::VGMInstrSet(RawFile *file, uint32 offset, uint32 length, Common::String theName,
VGMSampColl *theSampColl)
: VGMFile(file, offset, length, theName),
_sampColl(theSampColl) {
AddContainer<VGMInstr>(_aInstrs);
}
VGMInstrSet::~VGMInstrSet() {
DeleteVect<VGMInstr>(_aInstrs);
delete _sampColl;
}
bool VGMInstrSet::Load() {
if (!GetHeaderInfo())
return false;
if (!GetInstrPointers())
return false;
if (!LoadInstrs())
return false;
if (_aInstrs.size() == 0)
return false;
if (_sampColl != NULL) {
if (!_sampColl->Load()) {
error("Failed to load VGMSampColl");
}
}
return true;
}
bool VGMInstrSet::GetHeaderInfo() {
return true;
}
bool VGMInstrSet::GetInstrPointers() {
return true;
}
bool VGMInstrSet::LoadInstrs() {
size_t nInstrs = _aInstrs.size();
for (size_t i = 0; i < nInstrs; i++) {
if (!_aInstrs[i]->LoadInstr())
return false;
}
return true;
}
// ********
// VGMInstr
// ********
VGMInstr::VGMInstr(VGMInstrSet *instrSet, uint32 offset, uint32 length, uint32 theBank,
uint32 theInstrNum, const Common::String &name)
: VGMContainerItem(instrSet, offset, length, name),
_parInstrSet(instrSet),
_bank(theBank),
_instrNum(theInstrNum) {
AddContainer<VGMRgn>(_aRgns);
}
VGMInstr::~VGMInstr() {
DeleteVect<VGMRgn>(_aRgns);
}
bool VGMInstr::LoadInstr() {
return true;
}

View File

@ -0,0 +1,62 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_VGMINSTRSET_H
#define AUDIO_SOUNDFONT_VGMINSTRSET_H
#include "common/scummsys.h"
#include "common/str.h"
#include "common/array.h"
#include "vgmitem.h"
#include "sf2file.h"
class VGMSampColl;
class VGMInstr;
class VGMRgn;
class VGMSamp;
class VGMRgnItem;
// ***********
// VGMInstrSet
// ***********
class VGMInstrSet : public VGMFile {
public:
VGMInstrSet(RawFile *file, uint32 offset, uint32 length = 0,
Common::String name = "VGMInstrSet", VGMSampColl *theSampColl = NULL);
virtual ~VGMInstrSet(void);
virtual bool Load();
virtual bool GetHeaderInfo();
virtual bool GetInstrPointers();
virtual bool LoadInstrs();
public:
Common::Array<VGMInstr *> _aInstrs;
VGMSampColl *_sampColl;
};
// ********
// VGMInstr
// ********
class VGMInstr : public VGMContainerItem {
public:
VGMInstr(VGMInstrSet *parInstrSet, uint32 offset, uint32 length, uint32 bank,
uint32 instrNum, const Common::String &name = "Instrument");
virtual ~VGMInstr(void);
virtual bool LoadInstr();
public:
uint32 _bank;
uint32 _instrNum;
VGMInstrSet *_parInstrSet;
Common::Array<VGMRgn *> _aRgns;
};
#endif // AUDIO_SOUNDFONT_VGMINSTRSET_H

203
audio/soundfont/vgmitem.cpp Normal file
View File

@ -0,0 +1,203 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "common.h"
#include "vgmitem.h"
#include "vgminstrset.h"
using namespace std;
VGMItem::VGMItem() {}
VGMItem::VGMItem(VGMFile *thevgmfile, uint32 theOffset, uint32 theLength, const Common::String theName)
: _vgmfile(thevgmfile),
_name(theName),
_dwOffset(theOffset),
_unLength(theLength) {}
VGMItem::~VGMItem() {}
RawFile *VGMItem::GetRawFile() {
return _vgmfile->_rawfile;
}
uint32 VGMItem::GetBytes(uint32 nIndex, uint32 nCount, void *pBuffer) {
return _vgmfile->GetBytes(nIndex, nCount, pBuffer);
}
uint8 VGMItem::GetByte(uint32 offset) {
return _vgmfile->GetByte(offset);
}
uint16 VGMItem::GetShort(uint32 offset) {
return _vgmfile->GetShort(offset);
}
// ****************
// VGMContainerItem
// ****************
VGMContainerItem::VGMContainerItem() : VGMItem() {
AddContainer(_headers);
AddContainer(_localitems);
}
VGMContainerItem::VGMContainerItem(VGMFile *thevgmfile, uint32 theOffset, uint32 theLength,
const Common::String theName)
: VGMItem(thevgmfile, theOffset, theLength, theName) {
AddContainer(_headers);
AddContainer(_localitems);
}
VGMContainerItem::~VGMContainerItem() {
DeleteVect(_headers);
DeleteVect(_localitems);
}
VGMHeader *VGMContainerItem::AddHeader(uint32 offset, uint32 length, const Common::String &name) {
VGMHeader *header = new VGMHeader(this, offset, length, name);
_headers.push_back(header);
return header;
}
void VGMContainerItem::AddSimpleItem(uint32 offset, uint32 length, const Common::String &name) {
_localitems.push_back(new VGMItem(this->_vgmfile, offset, length, name));
}
// *********
// VGMFile
// *********
VGMFile::VGMFile(RawFile *theRawFile, uint32 offset,
uint32 length, Common::String theName)
: VGMContainerItem(this, offset, length, theName),
_rawfile(theRawFile) {}
VGMFile::~VGMFile(void) {}
bool VGMFile::LoadVGMFile() {
bool val = Load();
if (!val)
return false;
return val;
}
// These functions are common to all VGMItems, but no reason to refer to vgmfile
// or call GetRawFile() if the item itself is a VGMFile
RawFile *VGMFile::GetRawFile() {
return _rawfile;
}
uint32 VGMFile::GetBytes(uint32 nIndex, uint32 nCount, void *pBuffer) {
// if unLength != 0, verify that we're within the bounds of the file, and truncate num read
// bytes to end of file
if (_unLength != 0) {
uint32 endOff = _dwOffset + _unLength;
assert(nIndex >= _dwOffset && nIndex < endOff);
if (nIndex + nCount > endOff)
nCount = endOff - nIndex;
}
return _rawfile->GetBytes(nIndex, nCount, pBuffer);
}
// *********
// VGMHeader
// *********
VGMHeader::VGMHeader(VGMItem *parItem, uint32 offset, uint32 length, const Common::String &name)
: VGMContainerItem(parItem->_vgmfile, offset, length, name) {}
VGMHeader::~VGMHeader() {}
// ******
// VGMRgn
// ******
VGMRgn::VGMRgn(VGMInstr *instr, uint32 offset, uint32 length, Common::String name)
: VGMContainerItem(instr->_parInstrSet, offset, length, name),
_keyLow(0),
_keyHigh(127),
_velLow(0),
_velHigh(127),
_unityKey(-1),
_fineTune(0),
_sampNum(0),
_sampCollPtr(nullptr),
_volume(-1),
_pan(0.5),
_attack_time(0),
_decay_time(0),
_release_time(0),
_sustain_level(-1),
_sustain_time(0),
_attack_transform(no_transform),
_release_transform(no_transform),
_parInstr(instr) {
AddContainer<VGMRgnItem>(_items);
}
VGMRgn::~VGMRgn() {
DeleteVect<VGMRgnItem>(_items);
}
void VGMRgn::SetPan(uint8 p) {
if (p == 127) {
_pan = 1.0;
} else if (p == 0) {
_pan = 0;
} else if (p == 64) {
_pan = 0.5;
} else {
_pan = _pan / 127.0;
}
}
void VGMRgn::AddGeneralItem(uint32 offset, uint32 length, const Common::String &name) {
_items.push_back(new VGMRgnItem(this, VGMRgnItem::RIT_GENERIC, offset, length, name));
}
// assumes pan is given as 0-127 value, converts it to our double -1.0 to 1.0 format
void VGMRgn::AddPan(uint8 p, uint32 offset, uint32 length) {
SetPan(p);
_items.push_back(new VGMRgnItem(this, VGMRgnItem::RIT_PAN, offset, length, "Pan"));
}
void VGMRgn::AddVolume(double vol, uint32 offset, uint32 length) {
_volume = vol;
_items.push_back(new VGMRgnItem(this, VGMRgnItem::RIT_VOL, offset, length, "Volume"));
}
void VGMRgn::AddUnityKey(int8_t uk, uint32 offset, uint32 length) {
this->_unityKey = uk;
_items.push_back(new VGMRgnItem(this, VGMRgnItem::RIT_UNITYKEY, offset, length, "Unity Key"));
}
void VGMRgn::AddKeyLow(uint8 kl, uint32 offset, uint32 length) {
_keyLow = kl;
_items.push_back(
new VGMRgnItem(this, VGMRgnItem::RIT_KEYLOW, offset, length, "Note Range: Low Key"));
}
void VGMRgn::AddKeyHigh(uint8 kh, uint32 offset, uint32 length) {
_keyHigh = kh;
_items.push_back(
new VGMRgnItem(this, VGMRgnItem::RIT_KEYHIGH, offset, length, "Note Range: High Key"));
}
void VGMRgn::AddSampNum(int sn, uint32 offset, uint32 length) {
_sampNum = sn;
_items.push_back(new VGMRgnItem(this, VGMRgnItem::RIT_SAMPNUM, offset, length, "Sample Number"));
}
// **********
// VGMRgnItem
// **********
VGMRgnItem::VGMRgnItem(VGMRgn *rgn, RgnItemType theType, uint32 offset, uint32 length,
const Common::String &name)
: VGMItem(rgn->_vgmfile, offset, length, name), _type(theType) {}

193
audio/soundfont/vgmitem.h Normal file
View File

@ -0,0 +1,193 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_VGMITEM_H
#define AUDIO_SOUNDFONT_VGMITEM_H
#include "common/scummsys.h"
#include "common/str.h"
#include "common/array.h"
#include "rawfile.h"
#include "synthfile.h"
class RawFile;
//template <class T>
class VGMFile;
class VGMItem;
class VGMHeader;
class VGMItem {
public:
VGMItem();
VGMItem(VGMFile *thevgmfile, uint32 theOffset, uint32 theLength = 0,
const Common::String theName = "");
virtual ~VGMItem(void);
public:
RawFile *GetRawFile();
protected:
// TODO make inline
uint32 GetBytes(uint32 nIndex, uint32 nCount, void *pBuffer);
uint8 GetByte(uint32 offset);
uint16 GetShort(uint32 offset);
public:
VGMFile *_vgmfile;
Common::String _name;
uint32 _dwOffset; // offset in the pDoc data buffer
uint32 _unLength; // num of bytes the event engulfs
};
class VGMContainerItem : public VGMItem {
public:
VGMContainerItem();
VGMContainerItem(VGMFile *thevgmfile, uint32 theOffset, uint32 theLength = 0,
const Common::String theName = "");
virtual ~VGMContainerItem(void);
VGMHeader *AddHeader(uint32 offset, uint32 length, const Common::String &name = "Header");
void AddSimpleItem(uint32 offset, uint32 length, const Common::String &theName);
template<class T>
void AddContainer(Common::Array<T *> &container) {
_containers.push_back(reinterpret_cast<Common::Array<VGMItem *> *>(&container));
}
public:
Common::Array<VGMHeader *> _headers;
Common::Array<Common::Array<VGMItem *> *> _containers;
Common::Array<VGMItem *> _localitems;
};
class VGMColl;
class VGMFile : public VGMContainerItem {
public:
public:
VGMFile(RawFile *theRawFile, uint32 offset, uint32 length = 0,
Common::String theName = "VGM File");
virtual ~VGMFile();
bool LoadVGMFile();
virtual bool Load() = 0;
RawFile *GetRawFile();
size_t size() const { return _unLength; }
Common::String name() const { return _name; }
uint32 GetBytes(uint32 nIndex, uint32 nCount, void *pBuffer);
inline uint8 GetByte(uint32 offset) const { return _rawfile->GetByte(offset); }
inline uint16 GetShort(uint32 offset) const { return _rawfile->GetShort(offset); }
inline uint32 GetWord(uint32 offset) const { return _rawfile->GetWord(offset); }
/*
* For whatever reason, you can create null-length VGMItems.
* The only safe way for now is to
* assume maximum length
*/
size_t GetEndOffset() { return _rawfile->size(); }
const char *data() const { return _rawfile->data() + _dwOffset; }
RawFile *_rawfile;
};
// *********
// VGMHeader
// *********
class VGMHeader : public VGMContainerItem {
public:
VGMHeader(VGMItem *parItem, uint32 offset = 0, uint32 length = 0,
const Common::String &name = "Header");
virtual ~VGMHeader();
};
class VGMInstr;
class VGMRgnItem;
class VGMSampColl;
// ******
// VGMRgn
// ******
class VGMRgn : public VGMContainerItem {
public:
VGMRgn(VGMInstr *instr, uint32 offset, uint32 length = 0, Common::String name = "Region");
~VGMRgn();
virtual bool LoadRgn() { return true; }
void AddGeneralItem(uint32 offset, uint32 length, const Common::String &name);
void SetFineTune(int16_t relativePitchCents) { _fineTune = relativePitchCents; }
void SetPan(uint8 pan);
void AddPan(uint8 pan, uint32 offset, uint32 length = 1);
void AddVolume(double volume, uint32 offset, uint32 length = 1);
void AddUnityKey(int8_t unityKey, uint32 offset, uint32 length = 1);
void AddKeyLow(uint8 keyLow, uint32 offset, uint32 length = 1);
void AddKeyHigh(uint8 keyHigh, uint32 offset, uint32 length = 1);
void AddSampNum(int sampNum, uint32 offset, uint32 length = 1);
VGMInstr *_parInstr;
uint8 _keyLow;
uint8 _keyHigh;
uint8 _velLow;
uint8 _velHigh;
int8_t _unityKey;
short _fineTune;
Loop _loop;
int _sampNum;
VGMSampColl *_sampCollPtr;
double _volume; /* Percentage of full volume */
double _pan; /* Left 0 <- 0.5 Center -> 1 Right */
double _attack_time; /* In seconds */
double _decay_time; /* In seconds */
double _release_time; /* In seconds */
double _sustain_level; /* Percentage */
double _sustain_time; /* In seconds (no positive rate!) */
uint16 _attack_transform;
uint16 _release_transform;
Common::Array<VGMRgnItem *> _items;
};
// **********
// VGMRgnItem
// **********
class VGMRgnItem : public VGMItem {
public:
enum RgnItemType {
RIT_GENERIC,
RIT_UNKNOWN,
RIT_UNITYKEY,
RIT_FINETUNE,
RIT_KEYLOW,
RIT_KEYHIGH,
RIT_VELLOW,
RIT_VELHIGH,
RIT_PAN,
RIT_VOL,
RIT_SAMPNUM
};
VGMRgnItem(VGMRgn *rgn, RgnItemType theType, uint32 offset, uint32 length,
const Common::String &name);
public:
RgnItemType _type;
};
#endif // AUDIO_SOUNDFONT_VGMITEM_H

102
audio/soundfont/vgmsamp.cpp Normal file
View File

@ -0,0 +1,102 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#include "vgmsamp.h"
// *******
// VGMSamp
// *******
VGMSamp::VGMSamp(VGMSampColl *sampColl, uint32 offset, uint32 length, uint32 dataOffset,
uint32 dataLen, uint8 nChannels, uint16 theBPS, uint32 theRate,
Common::String theName)
: parSampColl(sampColl),
sampName(theName),
VGMItem(sampColl->_vgmfile, offset, length),
dataOff(dataOffset),
dataLength(dataLen),
bps(theBPS),
rate(theRate),
ulUncompressedSize(0),
channels(nChannels),
pan(0),
unityKey(-1),
fineTune(0),
volume(-1),
waveType(WT_UNDEFINED),
bPSXLoopInfoPrioritizing(false) {
_name = sampName; // I would do this in the initialization list, but VGMItem()
// constructor is called before sampName is initialized,
// so data() ends up returning a bad pointer
}
VGMSamp::~VGMSamp() {}
double VGMSamp::GetCompressionRatio() {
return 1.0;
}
// ***********
// VGMSampColl
// ***********
VGMSampColl::VGMSampColl(RawFile *rawfile, uint32 offset, uint32 length,
Common::String theName)
: VGMFile(rawfile, offset, length, theName),
parInstrSet(NULL),
bLoaded(false),
sampDataOffset(0) {
AddContainer<VGMSamp>(samples);
}
VGMSampColl::VGMSampColl(RawFile *rawfile, VGMInstrSet *instrset,
uint32 offset, uint32 length, Common::String theName)
: VGMFile(rawfile, offset, length, theName),
parInstrSet(instrset),
bLoaded(false),
sampDataOffset(0) {
AddContainer<VGMSamp>(samples);
}
VGMSampColl::~VGMSampColl(void) {
DeleteVect<VGMSamp>(samples);
}
bool VGMSampColl::Load() {
if (bLoaded)
return true;
if (!GetHeaderInfo())
return false;
if (!GetSampleInfo())
return false;
if (samples.size() == 0)
return false;
if (_unLength == 0) {
for (Common::Array<VGMSamp *>::iterator itr = samples.begin(); itr != samples.end(); ++itr) {
VGMSamp *samp = (*itr);
// Some formats can have negative sample offset
// For example, Konami's SNES format and Hudson's SNES format
// TODO: Fix negative sample offset without breaking instrument
// assert(dwOffset <= samp->dwOffset);
// if (dwOffset > samp->dwOffset)
//{
// unLength += samp->dwOffset - dwOffset;
// dwOffset = samp->dwOffset;
//}
if (_dwOffset + _unLength < samp->_dwOffset + samp->_unLength) {
_unLength = (samp->_dwOffset + samp->_unLength) - _dwOffset;
}
}
}
bLoaded = true;
return true;
}

82
audio/soundfont/vgmsamp.h Normal file
View File

@ -0,0 +1,82 @@
/*
* VGMTrans (c) 2002-2019
* Licensed under the zlib license,
* refer to the included LICENSE.txt file
*/
#ifndef AUDIO_SOUNDFONT_VGMSAMP_H
#define AUDIO_SOUNDFONT_VGMSAMP_H
#include "common.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "vgmitem.h"
class VGMSampColl;
class VGMInstrSet;
enum WAVE_TYPE {
WT_UNDEFINED, WT_PCM8, WT_PCM16
};
class VGMSamp : public VGMItem {
public:
VGMSamp(VGMSampColl *sampColl, uint32 offset = 0, uint32 length = 0,
uint32 dataOffset = 0, uint32 dataLength = 0, uint8 channels = 1,
uint16 bps = 16, uint32 rate = 0, Common::String name = "Sample");
virtual ~VGMSamp();
virtual double GetCompressionRatio(); // ratio of space conserved. should generally be > 1
// used to calculate both uncompressed sample size and loopOff after conversion
virtual void ConvertToStdWave(uint8 *buf) {};
inline void SetLoopStatus(int loopStat) { loop.loopStatus = loopStat; }
inline void SetLoopOffset(uint32 loopStart) { loop.loopStart = loopStart; }
inline void SetLoopLength(uint32 theLoopLength) { loop.loopLength = theLoopLength; }
public:
WAVE_TYPE waveType;
uint32 dataOff; // offset of original sample data
uint32 dataLength;
uint16 bps; // bits per sample
uint32 rate; // sample rate in herz (samples per second)
uint8 channels; // mono or stereo?
uint32 ulUncompressedSize;
bool bPSXLoopInfoPrioritizing;
Loop loop;
int8_t unityKey;
short fineTune;
double volume; // as percent of full volume. This will be converted to attenuation for SynthFile
long pan;
VGMSampColl *parSampColl;
Common::String sampName;
};
class VGMSampColl : public VGMFile {
public:
VGMSampColl(RawFile *rawfile, uint32 offset, uint32 length = 0,
Common::String theName = "VGMSampColl");
VGMSampColl(RawFile *rawfile, VGMInstrSet *instrset, uint32 offset,
uint32 length = 0, Common::String theName = "VGMSampColl");
virtual ~VGMSampColl(void);
virtual bool Load();
virtual bool GetHeaderInfo() { return true; } // retrieve any header data
virtual bool GetSampleInfo() { return true; } // retrieve sample info, including pointers to data, # channels, rate, etc.
protected:
public:
bool bLoaded;
uint32 sampDataOffset; // offset of the beginning of the sample data. Used for
// rgn->sampOffset matching
VGMInstrSet *parInstrSet;
Common::Array<VGMSamp *> samples;
};
#endif // AUDIO_SOUNDFONT_VGMSAMP_H