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:
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;
+ }
+ };
+}