winamp/Src/external_dependencies/openmpt-trunk/mptrack/VstPresets.cpp
2024-09-24 14:54:57 +02:00

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