From f2e4cc35a6d51887357abb5c59b403ba0c61af0f Mon Sep 17 00:00:00 2001 From: Eric Fry Date: Mon, 27 Jul 2020 22:41:11 +1000 Subject: [PATCH] AUDIO: SoundFont2 creation support --- audio/module.mk | 12 +- audio/soundfont/VGMTrans_LICENSE.txt | 22 ++ audio/soundfont/common.h | 66 ++++ audio/soundfont/rawfile.cpp | 44 +++ audio/soundfont/rawfile.h | 56 ++++ audio/soundfont/rifffile.cpp | 77 +++++ audio/soundfont/rifffile.h | 113 +++++++ audio/soundfont/sf2file.cpp | 445 +++++++++++++++++++++++++++ audio/soundfont/sf2file.h | 325 +++++++++++++++++++ audio/soundfont/synthfile.cpp | 199 ++++++++++++ audio/soundfont/synthfile.h | 213 +++++++++++++ audio/soundfont/vab/psxspu.cpp | 302 ++++++++++++++++++ audio/soundfont/vab/psxspu.h | 406 ++++++++++++++++++++++++ audio/soundfont/vab/vab.cpp | 243 +++++++++++++++ audio/soundfont/vab/vab.h | 55 ++++ audio/soundfont/vgmcoll.cpp | 243 +++++++++++++++ audio/soundfont/vgmcoll.h | 28 ++ audio/soundfont/vgminstrset.cpp | 85 +++++ audio/soundfont/vgminstrset.h | 62 ++++ audio/soundfont/vgmitem.cpp | 203 ++++++++++++ audio/soundfont/vgmitem.h | 193 ++++++++++++ audio/soundfont/vgmsamp.cpp | 102 ++++++ audio/soundfont/vgmsamp.h | 82 +++++ 23 files changed, 3575 insertions(+), 1 deletion(-) create mode 100644 audio/soundfont/VGMTrans_LICENSE.txt create mode 100644 audio/soundfont/common.h create mode 100644 audio/soundfont/rawfile.cpp create mode 100644 audio/soundfont/rawfile.h create mode 100644 audio/soundfont/rifffile.cpp create mode 100644 audio/soundfont/rifffile.h create mode 100644 audio/soundfont/sf2file.cpp create mode 100644 audio/soundfont/sf2file.h create mode 100644 audio/soundfont/synthfile.cpp create mode 100644 audio/soundfont/synthfile.h create mode 100644 audio/soundfont/vab/psxspu.cpp create mode 100644 audio/soundfont/vab/psxspu.h create mode 100644 audio/soundfont/vab/vab.cpp create mode 100644 audio/soundfont/vab/vab.h create mode 100644 audio/soundfont/vgmcoll.cpp create mode 100644 audio/soundfont/vgmcoll.h create mode 100644 audio/soundfont/vgminstrset.cpp create mode 100644 audio/soundfont/vgminstrset.h create mode 100644 audio/soundfont/vgmitem.cpp create mode 100644 audio/soundfont/vgmitem.h create mode 100644 audio/soundfont/vgmsamp.cpp create mode 100644 audio/soundfont/vgmsamp.h diff --git a/audio/module.mk b/audio/module.mk index 11a5aac7e28..a30ebf5e1a5 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -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 += \ diff --git a/audio/soundfont/VGMTrans_LICENSE.txt b/audio/soundfont/VGMTrans_LICENSE.txt new file mode 100644 index 00000000000..58a555d1624 --- /dev/null +++ b/audio/soundfont/VGMTrans_LICENSE.txt @@ -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. diff --git a/audio/soundfont/common.h b/audio/soundfont/common.h new file mode 100644 index 00000000000..1f8cbfcae4a --- /dev/null +++ b/audio/soundfont/common.h @@ -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 +void DeleteVect(Common::Array &array) { + for (typename Common::Array::iterator iter = array.begin(); iter != array.end(); iter++) { + delete (*iter); + } + array.clear(); +} + +#endif // AUDIO_SOUNDFONT_COMMON_H diff --git a/audio/soundfont/rawfile.cpp b/audio/soundfont/rawfile.cpp new file mode 100644 index 00000000000..92b76cfbea5 --- /dev/null +++ b/audio/soundfont/rawfile.cpp @@ -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); +} diff --git a/audio/soundfont/rawfile.h b/audio/soundfont/rawfile.h new file mode 100644 index 00000000000..0f47a08e930 --- /dev/null +++ b/audio/soundfont/rawfile.h @@ -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 +#include +#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 diff --git a/audio/soundfont/rifffile.cpp b/audio/soundfont/rifffile.cpp new file mode 100644 index 00000000000..bbc965cd4cb --- /dev/null +++ b/audio/soundfont/rifffile.cpp @@ -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::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::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) {} diff --git a/audio/soundfont/rifffile.h b/audio/soundfont/rifffile.h new file mode 100644 index 00000000000..5687a43d2e1 --- /dev/null +++ b/audio/soundfont/rifffile.h @@ -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 +#include +#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 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 &buf, uint32 listName, uint32 listSize) { + //TODO +// PushTypeOnVectBE(buf, 0x4C495354); // write "LIST" +// PushTypeOnVect(buf, listSize); +// PushTypeOnVectBE(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 diff --git a/audio/soundfont/sf2file.cpp b/audio/soundfont/sf2file.cpp new file mode 100644 index 00000000000..bd481f61a13 --- /dev/null +++ b/audio/soundfont/sf2file.cpp @@ -0,0 +1,445 @@ +/* + * VGMTrans (c) 2002-2019 + * Licensed under the zlib license, + * refer to the included LICENSE.txt file + */ + +#include +#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; +} diff --git a/audio/soundfont/sf2file.h b/audio/soundfont/sf2file.h new file mode 100644 index 00000000000..40a539a118d --- /dev/null +++ b/audio/soundfont/sf2file.h @@ -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 +#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 &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 diff --git a/audio/soundfont/synthfile.cpp b/audio/soundfont/synthfile.cpp new file mode 100644 index 00000000000..64821784926 --- /dev/null +++ b/audio/soundfont/synthfile.cpp @@ -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 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; +} diff --git a/audio/soundfont/synthfile.h b/audio/soundfont/synthfile.h new file mode 100644 index 00000000000..abd81a28ec1 --- /dev/null +++ b/audio/soundfont/synthfile.h @@ -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 &buf); + // bool SaveDLSFile(const char* filepath); + +public: + Common::Array vInstrs; + Common::Array 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 listRgns); + ~SynthInstr(void); + + void AddRgnList(Common::Array &RgnList); + SynthRgn *AddRgn(void); + SynthRgn *AddRgn(SynthRgn rgn); + +public: + uint32 ulBank; + uint32 ulInstrument; + + Common::Array 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 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 diff --git a/audio/soundfont/vab/psxspu.cpp b/audio/soundfont/vab/psxspu.cpp new file mode 100644 index 00000000000..4e16ede203e --- /dev/null +++ b/audio/soundfont/vab/psxspu.cpp @@ -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 &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::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]; + } +} diff --git a/audio/soundfont/vab/psxspu.h b/audio/soundfont/vab/psxspu.h new file mode 100644 index 00000000000..fa4d3fd8ef2 --- /dev/null +++ b/audio/soundfont/vab/psxspu.h @@ -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 +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 +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 &vagLocations); + + virtual bool + GetSampleInfo(); // retrieve sample info, including pointers to data, # channels, rate, etc. + +protected: + Common::Array _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 diff --git a/audio/soundfont/vab/vab.cpp b/audio/soundfont/vab/vab.cpp new file mode 100644 index 00000000000..c82ca8523f0 --- /dev/null +++ b/audio/soundfont/vab/vab.cpp @@ -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 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(this, _ADSR1, _ADSR2, false); + return true; +} diff --git a/audio/soundfont/vab/vab.h b/audio/soundfont/vab/vab.h new file mode 100644 index 00000000000..98d13147798 --- /dev/null +++ b/audio/soundfont/vab/vab.h @@ -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 diff --git a/audio/soundfont/vgmcoll.cpp b/audio/soundfont/vgmcoll.cpp new file mode 100644 index 00000000000..49409c6fae9 --- /dev/null +++ b/audio/soundfont/vgmcoll.cpp @@ -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 &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 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 finalSamps; + Common::Array 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; +} diff --git a/audio/soundfont/vgmcoll.h b/audio/soundfont/vgmcoll.h new file mode 100644 index 00000000000..9133f554ba8 --- /dev/null +++ b/audio/soundfont/vgmcoll.h @@ -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 &finalSamps); +}; + +#endif // AUDIO_SOUNDFONT_VGMCOLL_H diff --git a/audio/soundfont/vgminstrset.cpp b/audio/soundfont/vgminstrset.cpp new file mode 100644 index 00000000000..1520c5a0cc0 --- /dev/null +++ b/audio/soundfont/vgminstrset.cpp @@ -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(_aInstrs); +} + +VGMInstrSet::~VGMInstrSet() { + DeleteVect(_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(_aRgns); +} + +VGMInstr::~VGMInstr() { + DeleteVect(_aRgns); +} + +bool VGMInstr::LoadInstr() { + return true; +} diff --git a/audio/soundfont/vgminstrset.h b/audio/soundfont/vgminstrset.h new file mode 100644 index 00000000000..39026c8bf9a --- /dev/null +++ b/audio/soundfont/vgminstrset.h @@ -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 _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 _aRgns; +}; + +#endif // AUDIO_SOUNDFONT_VGMINSTRSET_H diff --git a/audio/soundfont/vgmitem.cpp b/audio/soundfont/vgmitem.cpp new file mode 100644 index 00000000000..2a4f4a4881e --- /dev/null +++ b/audio/soundfont/vgmitem.cpp @@ -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(_items); +} + +VGMRgn::~VGMRgn() { + DeleteVect(_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) {} diff --git a/audio/soundfont/vgmitem.h b/audio/soundfont/vgmitem.h new file mode 100644 index 00000000000..a557bb1f41c --- /dev/null +++ b/audio/soundfont/vgmitem.h @@ -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 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 + void AddContainer(Common::Array &container) { + _containers.push_back(reinterpret_cast *>(&container)); + } + +public: + Common::Array _headers; + Common::Array *> _containers; + Common::Array _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 _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 diff --git a/audio/soundfont/vgmsamp.cpp b/audio/soundfont/vgmsamp.cpp new file mode 100644 index 00000000000..6d431d05ec2 --- /dev/null +++ b/audio/soundfont/vgmsamp.cpp @@ -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(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(samples); +} + +VGMSampColl::~VGMSampColl(void) { + DeleteVect(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::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; +} diff --git a/audio/soundfont/vgmsamp.h b/audio/soundfont/vgmsamp.h new file mode 100644 index 00000000000..0c818fdc020 --- /dev/null +++ b/audio/soundfont/vgmsamp.h @@ -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 samples; +}; + +#endif // AUDIO_SOUNDFONT_VGMSAMP_H