mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-23 12:44:02 +00:00
AUDIO: SoundFont2 creation support
This commit is contained in:
parent
09c12a820a
commit
f2e4cc35a6
@ -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 += \
|
||||
|
22
audio/soundfont/VGMTrans_LICENSE.txt
Normal file
22
audio/soundfont/VGMTrans_LICENSE.txt
Normal 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
66
audio/soundfont/common.h
Normal 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
|
44
audio/soundfont/rawfile.cpp
Normal file
44
audio/soundfont/rawfile.cpp
Normal 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
56
audio/soundfont/rawfile.h
Normal 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
|
77
audio/soundfont/rifffile.cpp
Normal file
77
audio/soundfont/rifffile.cpp
Normal 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
113
audio/soundfont/rifffile.h
Normal 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
445
audio/soundfont/sf2file.cpp
Normal 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
325
audio/soundfont/sf2file.h
Normal 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
|
199
audio/soundfont/synthfile.cpp
Normal file
199
audio/soundfont/synthfile.cpp
Normal 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
213
audio/soundfont/synthfile.h
Normal 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
|
302
audio/soundfont/vab/psxspu.cpp
Normal file
302
audio/soundfont/vab/psxspu.cpp
Normal 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];
|
||||
}
|
||||
}
|
406
audio/soundfont/vab/psxspu.h
Normal file
406
audio/soundfont/vab/psxspu.h
Normal 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
243
audio/soundfont/vab/vab.cpp
Normal 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
55
audio/soundfont/vab/vab.h
Normal 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
243
audio/soundfont/vgmcoll.cpp
Normal 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
28
audio/soundfont/vgmcoll.h
Normal 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
|
85
audio/soundfont/vgminstrset.cpp
Normal file
85
audio/soundfont/vgminstrset.cpp
Normal 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;
|
||||
}
|
62
audio/soundfont/vgminstrset.h
Normal file
62
audio/soundfont/vgminstrset.h
Normal 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
203
audio/soundfont/vgmitem.cpp
Normal 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
193
audio/soundfont/vgmitem.h
Normal 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
102
audio/soundfont/vgmsamp.cpp
Normal 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
82
audio/soundfont/vgmsamp.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user