sfizz/plugins/vst/SfizzVstController.cpp
Paul Fd bdc9522662 Add multiple output capabilities to the library and plugins
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
2021-11-09 19:40:06 +01:00

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;