308 lines
8.1 KiB
C++
308 lines
8.1 KiB
C++
/*
|
|
* VstPresets.cpp
|
|
* --------------
|
|
* Purpose: Plugin preset / bank handling
|
|
* Notes : (currently none)
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
#ifndef NO_PLUGINS
|
|
#include "../soundlib/Sndfile.h"
|
|
#include "../soundlib/plugins/PlugInterface.h"
|
|
#ifdef MPT_WITH_VST
|
|
#include "Vstplug.h"
|
|
#endif // MPT_WITH_VST
|
|
#include "VstPresets.h"
|
|
#include "../common/FileReader.h"
|
|
#include <ostream>
|
|
#include "mpt/io/base.hpp"
|
|
#include "mpt/io/io.hpp"
|
|
#include "mpt/io/io_stdstream.hpp"
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
// This part of the header is identical for both presets and banks.
|
|
struct ChunkHeader
|
|
{
|
|
char chunkMagic[4]; // 'CcnK'
|
|
int32be byteSize; // Size of this chunk, excluding magic + byteSize
|
|
|
|
char fxMagic[4]; // 'FxBk' (regular) or 'FBCh' (opaque chunk)
|
|
int32be version; // Format version (1 or 2)
|
|
int32be fxID; // Plugin unique ID
|
|
int32be fxVersion; // Plugin version
|
|
};
|
|
|
|
MPT_BINARY_STRUCT(ChunkHeader, 24)
|
|
|
|
|
|
VSTPresets::ErrorCode VSTPresets::LoadFile(FileReader &file, IMixPlugin &plugin)
|
|
{
|
|
const bool firstChunk = file.GetPosition() == 0;
|
|
ChunkHeader header;
|
|
if(!file.ReadStruct(header) || memcmp(header.chunkMagic, "CcnK", 4))
|
|
{
|
|
return invalidFile;
|
|
}
|
|
if(header.fxID != plugin.GetUID())
|
|
{
|
|
return wrongPlugin;
|
|
}
|
|
#ifdef MPT_WITH_VST
|
|
CVstPlugin *vstPlug = dynamic_cast<CVstPlugin *>(&plugin);
|
|
#endif // MPT_WITH_VST
|
|
|
|
if(!memcmp(header.fxMagic, "FxCk", 4) || !memcmp(header.fxMagic, "FPCh", 4))
|
|
{
|
|
// Program
|
|
PlugParamIndex numParams = file.ReadUint32BE();
|
|
|
|
#ifdef MPT_WITH_VST
|
|
if(vstPlug != nullptr)
|
|
{
|
|
Vst::VstPatchChunkInfo info;
|
|
info.version = 1;
|
|
info.pluginUniqueID = header.fxID;
|
|
info.pluginVersion = header.fxVersion;
|
|
info.numElements = numParams;
|
|
MemsetZero(info.reserved);
|
|
vstPlug->Dispatch(Vst::effBeginLoadProgram, 0, 0, &info, 0.0f);
|
|
}
|
|
#endif // MPT_WITH_VST
|
|
plugin.BeginSetProgram();
|
|
|
|
std::string prgName;
|
|
file.ReadString<mpt::String::maybeNullTerminated>(prgName, 28);
|
|
plugin.SetCurrentProgramName(mpt::ToCString(mpt::Charset::Locale, prgName));
|
|
|
|
if(!memcmp(header.fxMagic, "FxCk", 4))
|
|
{
|
|
if(plugin.GetNumParameters() != numParams)
|
|
{
|
|
return wrongParameters;
|
|
}
|
|
for(PlugParamIndex p = 0; p < numParams; p++)
|
|
{
|
|
const auto value = file.ReadFloatBE();
|
|
plugin.SetParameter(p, std::isfinite(value) ? value : 0.0f);
|
|
}
|
|
} else
|
|
{
|
|
uint32 chunkSize = file.ReadUint32BE();
|
|
// Some nasty plugins (e.g. SmartElectronix Ambience) write to our memory block.
|
|
// Directly writing to a memory-mapped file block results in a crash...
|
|
std::byte *chunkData = new (std::nothrow) std::byte[chunkSize];
|
|
if(chunkData)
|
|
{
|
|
file.ReadRaw(mpt::span(chunkData, chunkSize));
|
|
plugin.SetChunk(mpt::as_span(chunkData, chunkSize), false);
|
|
delete[] chunkData;
|
|
} else
|
|
{
|
|
return outOfMemory;
|
|
}
|
|
}
|
|
plugin.EndSetProgram();
|
|
} else if((!memcmp(header.fxMagic, "FxBk", 4) || !memcmp(header.fxMagic, "FBCh", 4)) && firstChunk)
|
|
{
|
|
// Bank - only read if it's the first chunk in the file, not if it's a sub chunk.
|
|
uint32 numProgs = file.ReadUint32BE();
|
|
uint32 currentProgram = file.ReadUint32BE();
|
|
file.Skip(124);
|
|
|
|
#ifdef MPT_WITH_VST
|
|
if(vstPlug != nullptr)
|
|
{
|
|
Vst::VstPatchChunkInfo info;
|
|
info.version = 1;
|
|
info.pluginUniqueID = header.fxID;
|
|
info.pluginVersion = header.fxVersion;
|
|
info.numElements = numProgs;
|
|
MemsetZero(info.reserved);
|
|
vstPlug->Dispatch(Vst::effBeginLoadBank, 0, 0, &info, 0.0f);
|
|
}
|
|
#endif // MPT_WITH_VST
|
|
|
|
if(!memcmp(header.fxMagic, "FxBk", 4))
|
|
{
|
|
int32 oldCurrentProgram = plugin.GetCurrentProgram();
|
|
for(uint32 p = 0; p < numProgs; p++)
|
|
{
|
|
plugin.BeginSetProgram(p);
|
|
ErrorCode retVal = LoadFile(file, plugin);
|
|
if(retVal != noError)
|
|
{
|
|
return retVal;
|
|
}
|
|
plugin.EndSetProgram();
|
|
}
|
|
plugin.SetCurrentProgram(oldCurrentProgram);
|
|
} else
|
|
{
|
|
uint32 chunkSize = file.ReadUint32BE();
|
|
// Some nasty plugins (e.g. SmartElectronix Ambience) write to our memory block.
|
|
// Directly writing to a memory-mapped file block results in a crash...
|
|
std::byte *chunkData = new (std::nothrow) std::byte[chunkSize];
|
|
if(chunkData)
|
|
{
|
|
file.ReadRaw(mpt::span(chunkData, chunkSize));
|
|
plugin.SetChunk(mpt::as_span(chunkData, chunkSize), true);
|
|
delete[] chunkData;
|
|
} else
|
|
{
|
|
return outOfMemory;
|
|
}
|
|
}
|
|
if(header.version >= 2)
|
|
{
|
|
plugin.SetCurrentProgram(currentProgram);
|
|
}
|
|
}
|
|
|
|
return noError;
|
|
}
|
|
|
|
|
|
bool VSTPresets::SaveFile(std::ostream &f, IMixPlugin &plugin, bool bank)
|
|
{
|
|
if(!bank)
|
|
{
|
|
SaveProgram(f, plugin);
|
|
} else
|
|
{
|
|
bool writeChunk = plugin.ProgramsAreChunks();
|
|
ChunkHeader header;
|
|
memcpy(header.chunkMagic, "CcnK", 4);
|
|
header.byteSize = 0; // will be corrected later
|
|
header.version = 2;
|
|
header.fxID = plugin.GetUID();
|
|
header.fxVersion = plugin.GetVersion();
|
|
|
|
// Write unfinished header... We need to update the size once we're done writing.
|
|
mpt::IO::Write(f, header);
|
|
|
|
uint32 numProgs = std::max(plugin.GetNumPrograms(), int32(1)), curProg = plugin.GetCurrentProgram();
|
|
mpt::IO::WriteIntBE(f, numProgs);
|
|
mpt::IO::WriteIntBE(f, curProg);
|
|
uint8 reserved[124];
|
|
MemsetZero(reserved);
|
|
mpt::IO::WriteRaw(f, reserved, sizeof(reserved));
|
|
|
|
if(writeChunk)
|
|
{
|
|
auto chunk = plugin.GetChunk(true);
|
|
uint32 chunkSize = mpt::saturate_cast<uint32>(chunk.size());
|
|
if(chunkSize)
|
|
{
|
|
mpt::IO::WriteIntBE(f, chunkSize);
|
|
mpt::IO::WriteRaw(f, chunk.data(), chunkSize);
|
|
} else
|
|
{
|
|
// The plugin returned no chunk! Gracefully go back and save parameters instead...
|
|
writeChunk = false;
|
|
}
|
|
}
|
|
if(!writeChunk)
|
|
{
|
|
for(uint32 p = 0; p < numProgs; p++)
|
|
{
|
|
plugin.SetCurrentProgram(p);
|
|
SaveProgram(f, plugin);
|
|
}
|
|
plugin.SetCurrentProgram(curProg);
|
|
}
|
|
|
|
// Now we know the correct chunk size.
|
|
std::streamoff end = f.tellp();
|
|
header.byteSize = static_cast<int32>(end - 8);
|
|
memcpy(header.fxMagic, writeChunk ? "FBCh" : "FxBk", 4);
|
|
mpt::IO::SeekBegin(f);
|
|
mpt::IO::Write(f, header);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void VSTPresets::SaveProgram(std::ostream &f, IMixPlugin &plugin)
|
|
{
|
|
bool writeChunk = plugin.ProgramsAreChunks();
|
|
ChunkHeader header;
|
|
memcpy(header.chunkMagic, "CcnK", 4);
|
|
header.byteSize = 0; // will be corrected later
|
|
header.version = 1;
|
|
header.fxID = plugin.GetUID();
|
|
header.fxVersion = plugin.GetVersion();
|
|
|
|
// Write unfinished header... We need to update the size once we're done writing.
|
|
mpt::IO::Offset start = mpt::IO::TellWrite(f);
|
|
mpt::IO::Write(f, header);
|
|
|
|
const uint32 numParams = plugin.GetNumParameters();
|
|
mpt::IO::WriteIntBE(f, numParams);
|
|
|
|
char name[28];
|
|
mpt::String::WriteBuf(mpt::String::maybeNullTerminated, name) = mpt::ToCharset(mpt::Charset::Locale, plugin.GetCurrentProgramName());
|
|
mpt::IO::WriteRaw(f, name, 28);
|
|
|
|
if(writeChunk)
|
|
{
|
|
auto chunk = plugin.GetChunk(false);
|
|
uint32 chunkSize = mpt::saturate_cast<uint32>(chunk.size());
|
|
if(chunkSize)
|
|
{
|
|
mpt::IO::WriteIntBE(f, chunkSize);
|
|
mpt::IO::WriteRaw(f, chunk.data(), chunkSize);
|
|
} else
|
|
{
|
|
// The plugin returned no chunk! Gracefully go back and save parameters instead...
|
|
writeChunk = false;
|
|
}
|
|
}
|
|
if(!writeChunk)
|
|
{
|
|
plugin.BeginGetProgram();
|
|
for(uint32 p = 0; p < numParams; p++)
|
|
{
|
|
mpt::IO::Write(f, IEEE754binary32BE(plugin.GetParameter(p)));
|
|
}
|
|
plugin.EndGetProgram();
|
|
}
|
|
|
|
// Now we know the correct chunk size.
|
|
mpt::IO::Offset end = mpt::IO::TellWrite(f);
|
|
header.byteSize = static_cast<int32>(end - start - 8);
|
|
memcpy(header.fxMagic, writeChunk ? "FPCh" : "FxCk", 4);
|
|
mpt::IO::SeekAbsolute(f, start);
|
|
mpt::IO::Write(f, header);
|
|
mpt::IO::SeekAbsolute(f, end);
|
|
}
|
|
|
|
|
|
// Translate error code to string. Returns nullptr if there was no error.
|
|
const char *VSTPresets::GetErrorMessage(ErrorCode code)
|
|
{
|
|
switch(code)
|
|
{
|
|
case VSTPresets::invalidFile:
|
|
return "This does not appear to be a valid preset file.";
|
|
case VSTPresets::wrongPlugin:
|
|
return "This file appears to be for a different plugin.";
|
|
case VSTPresets::wrongParameters:
|
|
return "The number of parameters in this file is incompatible with the current plugin.";
|
|
case VSTPresets::outOfMemory:
|
|
return "Not enough memory to load preset data.";
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // NO_PLUGINS
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|