gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit c0bf7f744168c1879cc697fff6f630c013780b1e
parent 7b155c8b212ffbd6ae0f805dbac11dda209aa03b
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Mon, 11 Nov 2024 19:19:55 +0100

add VST2/VST3 preset file parser

Diffstat:
Msource/baseLib/binarystream.h | 31+++++++++++++++++++++++++++++++
Msource/synthLib/CMakeLists.txt | 1+
Asource/synthLib/vstpreset.cpp | 270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/vstpreset.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 349 insertions(+), 0 deletions(-)

diff --git a/source/baseLib/binarystream.h b/source/baseLib/binarystream.h @@ -1,5 +1,6 @@ #pragma once +#include <array> #include <cassert> #include <cstdint> #include <functional> @@ -217,6 +218,13 @@ namespace baseLib Base::write(reinterpret_cast<const uint8_t*>(&_value), sizeof(_value)); } + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const T* _data, const size_t _size) + { + if(!_size) + return; + Base::write(reinterpret_cast<const uint8_t*>(_data), sizeof(T) * _size); + } + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const std::vector<T>& _vector) { const auto size = static_cast<SizeType>(_vector.size()); @@ -259,6 +267,19 @@ namespace baseLib return v; } + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> T& read(T& _dst) + { + Base::read(reinterpret_cast<uint8_t*>(&_dst), sizeof(_dst)); + checkFail(); + return _dst; + } + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void read(T* _out, const size_t _size) + { + if(_size) + Base::read(reinterpret_cast<uint8_t*>(_out), sizeof(T) * _size); + } + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void read(std::vector<T>& _vector) { const auto size = read<SizeType>(); @@ -292,6 +313,16 @@ namespace baseLib return strcmp(res, _str) == 0; } + void read4CC(std::array<char, 5>& _fourCC) + { + _fourCC.fill(0); + + _fourCC[0] = read<char>(); + _fourCC[1] = read<char>(); + _fourCC[2] = read<char>(); + _fourCC[3] = read<char>(); + } + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> void read4CC(char (&_str)[N]) { diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES resamplerInOut.cpp resamplerInOut.h romLoader.cpp romLoader.h sysexToMidi.cpp sysexToMidi.h + vstpreset.cpp vstpreset.h wavReader.cpp wavReader.h wavTypes.h wavWriter.cpp wavWriter.h diff --git a/source/synthLib/vstpreset.cpp b/source/synthLib/vstpreset.cpp @@ -0,0 +1,270 @@ +#include "vstpreset.h" + +#include "baseLib/binarystream.h" + +#include "dsp56kEmu/logging.h" + +namespace synthLib +{ + namespace + { + bool check4CC(const VstPreset::FourCC& _4CC, const char* _expected) + { + return 0 == strcmp(_4CC.data(), _expected); + } + bool check4CC(baseLib::BinaryStream& _stream, const char* _expected) + { + VstPreset::FourCC fourCC; + _stream.read4CC(fourCC); + return check4CC(fourCC, _expected); + } + template<VstPreset::Endian SourceEndian> + uint32_t readUint32(baseLib::BinaryStream& _s) + { + const auto i = _s.read<uint32_t>(); + + if constexpr (VstPreset::hostEndian() == SourceEndian) + return i; + return + ((i & 0xFF000000) >> 24) | + ((i & 0x00FF0000) >> 8) | + ((i & 0x0000FF00) << 8) | + ((i & 0x000000FF) << 24); + } + template<VstPreset::Endian SourceEndian> + uint64_t readUint64(baseLib::BinaryStream& _s) + { + const auto i = _s.read<uint64_t>(); + + if constexpr (VstPreset::hostEndian() == SourceEndian) + return i; + return + ((i & 0xFF00000000000000) >> 56) | + ((i & 0x00FF000000000000) >> 40) | + ((i & 0x0000FF0000000000) >> 24) | + ((i & 0x000000FF00000000) >> 8) | + ((i & 0x00000000FF000000) << 8) | + ((i & 0x0000000000FF0000) << 24) | + ((i & 0x000000000000FF00) << 40) | + ((i & 0x00000000000000FF) << 56); + } + uint32_t readUint32Vst2(baseLib::BinaryStream& _s) + { + return readUint32<VstPreset::Endian::Big>(_s); + } + uint32_t readUint32Vst3(baseLib::BinaryStream& _s) + { + return readUint32<VstPreset::Endian::Little>(_s); + } + uint64_t readUint64Vst3(baseLib::BinaryStream& _s) + { + return readUint64<VstPreset::Endian::Little>(_s); + } + } + + std::optional<VstPreset::ChunkList> VstPreset::read(const std::vector<uint8_t>& _data) + { + try + { + baseLib::BinaryStream binaryStream(_data); + + FourCC fourCC; + binaryStream.read4CC(fourCC); + + if(check4CC(fourCC, "CcnK")) + { + binaryStream.setReadPos(0); + return readFxbFxp(binaryStream); + } + if(check4CC(fourCC, "VST3")) + { + binaryStream.setReadPos(0); + return readVst3(binaryStream); + } + } + catch (const std::exception& e) + { + LOG("Error reading VstPreset: " << e.what()); + } + return {}; + } + + std::optional<VstPreset::ChunkList> VstPreset::readFxbFxp(baseLib::BinaryStream& _binaryStream) + { + if(!check4CC(_binaryStream, "CcnK")) + return {}; + /*const auto len = */readUint32Vst2(_binaryStream); + FourCC dataType; + _binaryStream.read4CC(dataType); + + ChunkList chunkList; + + // bank + const auto isRegularBank = check4CC(dataType, "FxBk"); + const auto isOpaqueBank = check4CC(dataType, "FBCh"); + + if(isRegularBank || isOpaqueBank) + { + const auto version = readUint32Vst2(_binaryStream); + FourCC pluginId; + _binaryStream.read4CC(pluginId); + /*const auto pluginVersion = */readUint32Vst2(_binaryStream); + const auto numPrograms = readUint32Vst2(_binaryStream); + + if(version >= 2) + { + /*const auto currentProgram = */(void)readUint32Vst2(_binaryStream); + char future[124]; + _binaryStream.read(future, sizeof(future)); + } + else + { + char future[128]; + _binaryStream.read(future, sizeof(future)); + } + + for(uint32_t i=0; i<numPrograms; ++i) + { + if(isOpaqueBank) + { + const auto chunkSize = readUint32Vst2(_binaryStream); + if(!chunkSize) + continue; + + Chunk c; + c.data.resize(chunkSize); + _binaryStream.read(c.data.data(), chunkSize); + chunkList.push_back(c); + } + else + { + auto programChunks = readFxbFxp(_binaryStream); + if(!programChunks) + return {}; + if(!programChunks->empty()) + chunkList.insert(chunkList.end(), programChunks->begin(), programChunks->end()); + } + } + return chunkList; + } + + // program + const auto isRegularProgram = check4CC(dataType, "FxCk"); + const auto isOpaqueProgram = check4CC(dataType, "FPCh"); + + if(isRegularProgram || isOpaqueProgram) + { + /*const auto version = */readUint32Vst2(_binaryStream); + FourCC pluginId; + _binaryStream.read4CC(pluginId); + /*const auto pluginVersion = */readUint32Vst2(_binaryStream); + + const auto numParams = readUint32Vst2(_binaryStream); + + char programName[29]{}; + _binaryStream.read(programName, sizeof(programName)-1); + + if(isOpaqueProgram) + { + const auto chunkSize = readUint32Vst2(_binaryStream); + if(!chunkSize) + return {}; + + Chunk c; + c.data.resize(chunkSize); + _binaryStream.read(c.data.data(), chunkSize); + chunkList.push_back(c); + } + else + { + for (size_t i = 0; i < numParams; i++) + _binaryStream.read<float>(); + } + return chunkList; + } + return {}; + } + + std::optional<VstPreset::ChunkList> VstPreset::readVst3(baseLib::BinaryStream& _binaryStream) + { + if(!check4CC(_binaryStream, "VST3")) + return {}; + + const auto version = readUint32Vst3(_binaryStream); + + if(version != 1) + return {}; + + std::array<char, 32> classId; + _binaryStream.read(classId); + + const auto chunkListOffset = readUint32Vst3(_binaryStream); + +// auto posChunkDataBegin = _binaryStream.getReadPos(); + + _binaryStream.setReadPos(chunkListOffset); + + if(!check4CC(_binaryStream, "List")) + return {}; + + const auto chunkListEntryCount = readUint32Vst3(_binaryStream); + + ChunkList chunkList; + + for(uint32_t i=0; i<chunkListEntryCount; ++i) + { + Chunk c; + _binaryStream.read4CC(c.id); + + const auto chunkOffset = readUint64Vst3(_binaryStream); + const auto chunkSize = readUint64Vst3(_binaryStream); + + if(!chunkSize) + continue; + + c.data.resize(chunkSize); + + const auto readPos = _binaryStream.getReadPos(); + _binaryStream.setReadPos(static_cast<uint32_t>(chunkOffset)); + +// posChunkDataBegin += chunkSize; + + _binaryStream.read(c.data.data(), chunkSize); + _binaryStream.setReadPos(readPos); + + // VST3 can embed a VST2 fxb/fxp, unpack it + baseLib::BinaryStream bs(c.data); + FourCC fourCC; + bs.read4CC(fourCC); + + // according to vst2persistence.cpp of the VST3 SDK, this one is optional + if(check4CC(fourCC, "VstW")) + { + const auto len = readUint32Vst2(bs); + const auto vst2version = readUint32Vst2(bs); + /*const auto bypassed = */readUint32Vst2(bs); + + assert(len == 8 && vst2version == 1); + + const auto vst2Chunks = readFxbFxp(bs); + + if(vst2Chunks) + chunkList.insert(chunkList.end(), vst2Chunks->begin(), vst2Chunks->end()); + } + else if(check4CC(fourCC, "CcnK")) + { + bs.setReadPos(0); + + const auto vst2Chunks = readFxbFxp(bs); + + if(vst2Chunks) + chunkList.insert(chunkList.end(), vst2Chunks->begin(), vst2Chunks->end()); + } + else + { + chunkList.push_back(c); + } + } + return chunkList; + } +} diff --git a/source/synthLib/vstpreset.h b/source/synthLib/vstpreset.h @@ -0,0 +1,47 @@ +#pragma once + +#include <cstdint> +#include <vector> +#include <array> +#include <optional> + +#include "baseLib/binarystream.h" + +namespace synthLib +{ + class VstPreset + { + public: + using ChunkData = std::vector<uint8_t>; + using FourCC = std::array<char, 5>; // + null terminator + + struct Chunk + { + FourCC id = {0,0,0,0,0}; + ChunkData data; + }; + + using ChunkList = std::vector<Chunk>; + + enum class Endian : uint8_t + { + Big, + Little + }; + + static std::optional<ChunkList> read(const std::vector<uint8_t>& _data); + + static std::optional<ChunkList> readFxbFxp(baseLib::BinaryStream& _binaryStream); + static std::optional<ChunkList> readVst3(baseLib::BinaryStream& _binaryStream); + + static constexpr Endian hostEndian() + { + constexpr uint32_t test32 = 0x01020304; + constexpr uint8_t test8 = static_cast<const uint8_t&>(test32); + + static_assert(test8 == 0x01 || test8 == 0x04, "unable to determine endianess"); + + return test8 == 0x01 ? Endian::Big : Endian::Little; + } + }; +}