Add a fast path to effect busses Get the number of outputs in the Synth Duplicate effect buses and process outputs separately Update the API wrappers to handle multichannel processing Accomodate multiple outputs in the VST plugin Publish a 8 stereo version of the LV2 plugin Publish a 8 stereo version of the VST3 plugin WIP Remove the duplication of effects Cleanups Debugs Tweaks Windows free error Port numbers change between plugin versions Fix warning Proper meter backgrounds
481 lines
17 KiB
C++
481 lines
17 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
|
// license. You should have receive a LICENSE.md file along with the code.
|
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
|
|
|
#include "SfizzVstController.h"
|
|
#include "SfizzVstEditor.h"
|
|
#include "SfizzVstParameters.h"
|
|
#include "SfizzVstIDs.h"
|
|
#include "plugin/InstrumentDescription.h"
|
|
#include "base/source/fstreamer.h"
|
|
#include "base/source/updatehandler.h"
|
|
#include <absl/strings/match.h>
|
|
#include <ghc/fs_std.hpp>
|
|
#include <atomic>
|
|
#include <cassert>
|
|
|
|
enum { kProgramListID = 0 };
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
|
|
{
|
|
tresult result = EditControllerEx1::initialize(context);
|
|
if (result != kResultTrue)
|
|
return result;
|
|
|
|
// initialize the update handler
|
|
Steinberg::UpdateHandler::instance();
|
|
|
|
// initialize the thread checker
|
|
threadChecker_ = Vst::ThreadChecker::create();
|
|
|
|
// create update objects
|
|
queuedUpdates_ = Steinberg::owned(new QueuedUpdates);
|
|
sfzUpdate_ = Steinberg::owned(new SfzUpdate);
|
|
sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate);
|
|
scalaUpdate_ = Steinberg::owned(new ScalaUpdate);
|
|
playStateUpdate_ = Steinberg::owned(new PlayStateUpdate);
|
|
|
|
// Unit
|
|
addUnit(new Vst::Unit(Steinberg::String("Root"), Vst::kRootUnitId, Vst::kNoParentUnitId, kProgramListID));
|
|
|
|
// Parameters
|
|
Vst::ParamID pid = 0;
|
|
|
|
// Ordinary parameters
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidVolume).createParameter(
|
|
Steinberg::String("Volume"), pid++, Steinberg::String("dB"),
|
|
0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidNumVoices).createParameter(
|
|
Steinberg::String("Polyphony"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidOversampling).createParameter(
|
|
Steinberg::String("Oversampling"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidPreloadSize).createParameter(
|
|
Steinberg::String("Preload size"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidScalaRootKey).createParameter(
|
|
Steinberg::String("Scala root key"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidTuningFrequency).createParameter(
|
|
Steinberg::String("Tuning frequency"), pid++, Steinberg::String("Hz"),
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidStretchedTuning).createParameter(
|
|
Steinberg::String("Stretched tuning"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidSampleQuality).createParameter(
|
|
Steinberg::String("Sample quality"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidOscillatorQuality).createParameter(
|
|
Steinberg::String("Oscillator quality"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
|
|
|
|
// MIDI special controllers
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidAftertouch).createParameter(
|
|
Steinberg::String("Aftertouch"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId));
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidPitchBend).createParameter(
|
|
Steinberg::String("Pitch bend"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId));
|
|
|
|
// MIDI controllers
|
|
for (unsigned i = 0; i < sfz::config::numCCs; ++i) {
|
|
Steinberg::String title;
|
|
Steinberg::String shortTitle;
|
|
title.printf("Controller %u", i);
|
|
shortTitle.printf("CC%u", i);
|
|
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidCC0 + i).createParameter(
|
|
title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate,
|
|
Vst::kRootUnitId, shortTitle));
|
|
}
|
|
|
|
// Volume levels
|
|
for (unsigned i = 0; i < 16; ++i) {
|
|
Steinberg::String title;
|
|
title.printf("Level %u", i);
|
|
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidLevel0 + i).createParameter(
|
|
title, pid++, nullptr, 0,
|
|
Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
|
|
}
|
|
|
|
// Editor status
|
|
parameters.addParameter(
|
|
SfizzRange::getForParameter(kPidEditorOpen).createParameter(
|
|
Steinberg::String("Editor open"), pid++, nullptr,
|
|
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
|
|
|
|
// Initial MIDI mapping
|
|
for (int32 i = 0; i < Vst::kCountCtrlNumber; ++i) {
|
|
Vst::ParamID id = Vst::kNoParamId;
|
|
switch (i) {
|
|
case Vst::kAfterTouch:
|
|
id = kPidAftertouch;
|
|
break;
|
|
case Vst::kPitchBend:
|
|
id = kPidPitchBend;
|
|
break;
|
|
default:
|
|
if (i < 128)
|
|
id = kPidCC0 + i;
|
|
break;
|
|
}
|
|
midiMapping_[i] = id;
|
|
}
|
|
|
|
// Program list
|
|
IPtr<Vst::ProgramListWithPitchNames> list = Steinberg::owned(
|
|
new Vst::ProgramListWithPitchNames(Steinberg::String("Programs"), kProgramListID, Vst::kRootUnitId));
|
|
list->addProgram(Steinberg::String("Default"));
|
|
addProgramList(list);
|
|
list->addRef();
|
|
|
|
// Use linear knobs
|
|
setKnobMode(kLinearMode);
|
|
|
|
return kResultTrue;
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::terminate()
|
|
{
|
|
return EditControllerEx1::terminate();
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id)
|
|
{
|
|
if (midiControllerNumber < 0 || midiControllerNumber >= Vst::kCountCtrlNumber) {
|
|
id = Vst::kNoParamId;
|
|
return kResultFalse;
|
|
}
|
|
|
|
id = midiMapping_[midiControllerNumber];
|
|
if (id == Vst::kNoParamId)
|
|
return kResultFalse;
|
|
|
|
return kResultTrue;
|
|
}
|
|
|
|
int32 PLUGIN_API SfizzVstControllerNoUi::getKeyswitchCount(int32 busIndex, int16 channel)
|
|
{
|
|
(void)channel;
|
|
|
|
if (busIndex != 0)
|
|
return 0;
|
|
|
|
return keyswitches_.size();
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::getKeyswitchInfo(int32 busIndex, int16 channel, int32 keySwitchIndex, Vst::KeyswitchInfo& info)
|
|
{
|
|
(void)channel;
|
|
|
|
if (busIndex != 0)
|
|
return kResultFalse;
|
|
|
|
if (keySwitchIndex < 0 || keySwitchIndex >= keyswitches_.size())
|
|
return kResultFalse;
|
|
|
|
info = keyswitches_[keySwitchIndex];
|
|
return kResultTrue;
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::beginEditFromHost(Vst::ParamID paramID)
|
|
{
|
|
// Note(jpc) implementing this interface is a workaround to make
|
|
// non-automatable parameters editable in Ardour (as of 6.6)
|
|
(void)paramID;
|
|
return kResultTrue;
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::endEditFromHost(Vst::ParamID paramID)
|
|
{
|
|
(void)paramID;
|
|
return kResultTrue;
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string)
|
|
{
|
|
switch (tag) {
|
|
case kPidOversampling:
|
|
{
|
|
const SfizzRange range = SfizzRange::getForParameter(tag);
|
|
const int factorLog2 = static_cast<int>(range.denormalize(valueNormalized));
|
|
Steinberg::String buf;
|
|
buf.printf("%dX", 1 << factorLog2);
|
|
buf.copyTo(string);
|
|
return kResultTrue;
|
|
}
|
|
}
|
|
|
|
return EditControllerEx1::getParamStringByValue(tag, valueNormalized, string);
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized)
|
|
{
|
|
switch (tag) {
|
|
case kPidOversampling:
|
|
{
|
|
int32 factor;
|
|
if (!Steinberg::String::scanInt32(string, factor, false))
|
|
factor = 1;
|
|
|
|
const SfizzRange range = SfizzRange::getForParameter(tag);
|
|
valueNormalized = range.normalize(integerLog2(factor));
|
|
return kResultTrue;
|
|
}
|
|
}
|
|
|
|
return EditControllerEx1::getParamValueByString(tag, string, valueNormalized);
|
|
}
|
|
|
|
tresult SfizzVstControllerNoUi::setParam(Vst::ParamID tag, float value)
|
|
{
|
|
const SfizzRange range = SfizzRange::getForParameter(tag);
|
|
return setParamNormalized(tag, range.normalize(value));
|
|
}
|
|
|
|
tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
|
|
{
|
|
SfizzVstState s;
|
|
|
|
tresult r = s.load(stream);
|
|
if (r != kResultTrue)
|
|
return r;
|
|
|
|
setParam(kPidVolume, s.volume);
|
|
setParam(kPidNumVoices, s.numVoices);
|
|
setParam(kPidOversampling, s.oversamplingLog2);
|
|
setParam(kPidPreloadSize, s.preloadSize);
|
|
setParam(kPidScalaRootKey, s.scalaRootKey);
|
|
setParam(kPidTuningFrequency, s.tuningFrequency);
|
|
setParam(kPidStretchedTuning, s.stretchedTuning);
|
|
setParam(kPidSampleQuality, s.sampleQuality);
|
|
setParam(kPidOscillatorQuality, s.oscillatorQuality);
|
|
|
|
uint32 ccLimit = uint32(std::min(s.controllers.size(), size_t(sfz::config::numCCs)));
|
|
for (uint32 cc = 0; cc < ccLimit; ++cc) {
|
|
if (absl::optional<float> value = s.controllers[cc])
|
|
setParam(kPidCC0 + cc, *value);
|
|
}
|
|
|
|
sfzUpdate_->setPath(s.sfzFile);
|
|
sfzUpdate_->deferUpdate();
|
|
scalaUpdate_->setPath(s.scalaFile);
|
|
scalaUpdate_->deferUpdate();
|
|
|
|
return kResultTrue;
|
|
}
|
|
|
|
tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
|
|
{
|
|
// Note: is expected to be called from the controller thread only
|
|
|
|
tresult result = EditControllerEx1::notify(message);
|
|
if (result != kResultFalse)
|
|
return result;
|
|
|
|
if (!threadChecker_->test()) {
|
|
static std::atomic_bool warn_once_flag { false };
|
|
if (!warn_once_flag.exchange(true))
|
|
fprintf(stderr, "[sfizz] controller notification arrives from the wrong thread\n");
|
|
}
|
|
|
|
const char* id = message->getMessageID();
|
|
|
|
///
|
|
if (!strcmp(id, SfzUpdate::getFClassID())) {
|
|
if (!sfzUpdate_->convertFromMessage(*message)) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
|
|
// update the program name and notify
|
|
std::string name = fs::u8path(sfzUpdate_->getPath()).filename().u8string();
|
|
if (absl::EndsWithIgnoreCase(name, ".sfz"))
|
|
name.resize(name.size() - 4);
|
|
setProgramName(kProgramListID, 0, Steinberg::String(name.c_str()));
|
|
|
|
FUnknownPtr<Vst::IUnitHandler> unitHandler(getComponentHandler());
|
|
if (unitHandler)
|
|
unitHandler->notifyProgramListChange(kProgramListID, 0);
|
|
|
|
//
|
|
sfzUpdate_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, SfzDescriptionUpdate::getFClassID())) {
|
|
if (!sfzDescriptionUpdate_->convertFromMessage(*message)) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
|
|
// parse the description blob
|
|
const InstrumentDescription desc = parseDescriptionBlob(
|
|
sfzDescriptionUpdate_->getDescription());
|
|
|
|
// update pitch names and notify
|
|
Vst::ProgramListWithPitchNames* list =
|
|
static_cast<Vst::ProgramListWithPitchNames*>(getProgramList(kProgramListID));
|
|
for (int16 pitch = 0; pitch < 128; ++pitch) {
|
|
Steinberg::String pitchName;
|
|
if (desc.keyUsed.test(pitch) && !desc.keyLabel[pitch].empty())
|
|
pitchName = Steinberg::String(desc.keyLabel[pitch].c_str());
|
|
else if (desc.keyswitchUsed.test(pitch) && !desc.keyswitchLabel[pitch].empty())
|
|
pitchName = Steinberg::String(desc.keyswitchLabel[pitch].c_str());
|
|
|
|
list->setPitchName(0, pitch, pitchName);
|
|
}
|
|
|
|
FUnknownPtr<Vst::IUnitHandler> unitHandler(getComponentHandler());
|
|
if (unitHandler)
|
|
unitHandler->notifyProgramListChange(kProgramListID, 0);
|
|
|
|
// update the key switches and notify
|
|
size_t idKeyswitch = 0;
|
|
for (int16 pitch = 0; pitch < 128; ++pitch)
|
|
idKeyswitch += desc.keyswitchUsed.test(pitch);
|
|
keyswitches_.resize(idKeyswitch);
|
|
|
|
idKeyswitch = 0;
|
|
for (int16 pitch = 0; pitch < 128; ++pitch) {
|
|
if (!desc.keyswitchUsed.test(pitch))
|
|
continue;
|
|
Vst::KeyswitchInfo info {};
|
|
info.typeId = Vst::kNoteOnKeyswitchTypeID;
|
|
Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.title);
|
|
Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.shortTitle);
|
|
info.keyswitchMin = pitch; // TODO reexamine this when supporting keyswitch groups
|
|
info.keyswitchMax = pitch; // TODO reexamine this when supporting keyswitch groups
|
|
info.keyRemapped = pitch;
|
|
info.unitId = Vst::kRootUnitId;
|
|
info.flags = 0;
|
|
keyswitches_[idKeyswitch++] = info;
|
|
}
|
|
|
|
if (Vst::IComponentHandler* componentHandler = getComponentHandler())
|
|
componentHandler->restartComponent(Vst::kKeyswitchChanged);
|
|
|
|
// update the parameter titles and notify
|
|
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
|
Vst::ParamID pid = kPidCC0 + cc;
|
|
Vst::Parameter* param = getParameterObject(pid);
|
|
Vst::ParameterInfo& info = param->getInfo();
|
|
Steinberg::String title;
|
|
Steinberg::String shortTitle;
|
|
if (!desc.ccLabel[cc].empty()) {
|
|
title = desc.ccLabel[cc].c_str();
|
|
shortTitle = title;
|
|
}
|
|
else {
|
|
title.printf("Controller %u", cc);
|
|
shortTitle.printf("CC%u", cc);
|
|
}
|
|
title.copyTo(info.title);
|
|
shortTitle.copyTo(info.shortTitle);
|
|
}
|
|
|
|
if (Vst::IComponentHandler* componentHandler = getComponentHandler())
|
|
componentHandler->restartComponent(Vst::kParamTitlesChanged);
|
|
|
|
//
|
|
sfzDescriptionUpdate_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, ScalaUpdate::getFClassID())) {
|
|
if (!scalaUpdate_->convertFromMessage(*message)) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
scalaUpdate_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, PlayStateUpdate::getFClassID())) {
|
|
if (!playStateUpdate_->convertFromMessage(*message)) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
playStateUpdate_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, OSCUpdate::getFClassID())) {
|
|
IPtr<OSCUpdate> update = OSCUpdate::createFromMessage(*message);
|
|
if (!update) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
queuedUpdates_->enqueue(update);
|
|
queuedUpdates_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, NoteUpdate::getFClassID())) {
|
|
IPtr<NoteUpdate> update = NoteUpdate::createFromMessage(*message);
|
|
if (!update) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
queuedUpdates_->enqueue(update);
|
|
queuedUpdates_->deferUpdate();
|
|
}
|
|
else if (!strcmp(id, AutomationUpdate::getFClassID())) {
|
|
IPtr<AutomationUpdate> update = AutomationUpdate::createFromMessage(*message);
|
|
if (!update) {
|
|
assert(false);
|
|
return kResultFalse;
|
|
}
|
|
for (AutomationUpdate::Item item : update->getItems())
|
|
setParam(item.first, item.second);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// --- Controller with UI --- //
|
|
|
|
IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name)
|
|
{
|
|
ConstString name(_name);
|
|
|
|
fprintf(stderr, "[sfizz] about to create view: %s\n", _name);
|
|
|
|
if (name != Vst::ViewType::kEditor)
|
|
return nullptr;
|
|
|
|
std::vector<FObject*> updates;
|
|
updates.push_back(queuedUpdates_);
|
|
updates.push_back(sfzUpdate_);
|
|
updates.push_back(sfzDescriptionUpdate_);
|
|
updates.push_back(scalaUpdate_);
|
|
updates.push_back(playStateUpdate_);
|
|
for (uint32 i = 0, n = parameters.getParameterCount(); i < n; ++i)
|
|
updates.push_back(parameters.getParameterByIndex(i));
|
|
|
|
IPtr<SfizzVstEditor> editor = Steinberg::owned(
|
|
new SfizzVstEditor(this, absl::MakeSpan(updates)));
|
|
|
|
editor->remember();
|
|
return editor;
|
|
}
|
|
|
|
FUnknown* SfizzVstController::createInstance(void*)
|
|
{
|
|
return static_cast<Vst::IEditController*>(new SfizzVstController);
|
|
}
|
|
|
|
template <>
|
|
FUnknown* createInstance<SfizzVstController>(void* context)
|
|
{
|
|
return SfizzVstController::createInstance(context);
|
|
}
|
|
|
|
FUID SfizzVstController::cid = SfizzVstController_cid;
|