sfizz/plugins/vst/SfizzVstController.cpp

465 lines
16 KiB
C++
Raw Normal View History

2020-03-05 11:16:47 +01:00
// 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"
2021-02-04 02:48:49 +01:00
#include "SfizzVstParameters.h"
2021-04-08 04:13:57 +02:00
#include "SfizzVstIDs.h"
2021-05-01 21:50:47 +02:00
#include "plugin/InstrumentDescription.h"
2020-03-05 11:16:47 +01:00
#include "base/source/fstreamer.h"
2021-02-04 02:48:49 +01:00
#include "base/source/updatehandler.h"
2021-05-01 21:50:47 +02:00
#include <absl/strings/match.h>
#include <ghc/fs_std.hpp>
#include <atomic>
#include <cassert>
2020-03-05 11:16:47 +01:00
2021-05-01 21:50:47 +02:00
enum { kProgramListID = 0 };
2020-03-05 11:16:47 +01:00
tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
{
2021-05-01 20:25:59 +02:00
tresult result = EditControllerEx1::initialize(context);
2020-03-05 11:16:47 +01:00
if (result != kResultTrue)
return result;
2021-02-04 02:48:49 +01:00
// initialize the update handler
Steinberg::UpdateHandler::instance();
// initialize the thread checker
threadChecker_ = Vst::ThreadChecker::create();
2021-02-04 02:48:49 +01:00
// create update objects
2021-04-28 17:19:42 +02:00
queuedUpdates_ = Steinberg::owned(new QueuedUpdates);
2021-04-02 04:05:14 +02:00
sfzUpdate_ = Steinberg::owned(new SfzUpdate);
sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate);
2021-04-02 04:05:14 +02:00
scalaUpdate_ = Steinberg::owned(new ScalaUpdate);
2021-02-04 02:48:49 +01:00
playStateUpdate_ = Steinberg::owned(new PlayStateUpdate);
2021-05-01 21:50:47 +02:00
// Unit
addUnit(new Vst::Unit(Steinberg::String("Root"), Vst::kRootUnitId, Vst::kNoParentUnitId, kProgramListID));
2021-02-04 02:48:49 +01:00
// Parameters
2020-03-05 11:16:47 +01:00
Vst::ParamID pid = 0;
2020-03-05 18:21:46 +01:00
// Ordinary parameters
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidVolume).createParameter(
2020-03-05 18:21:46 +01:00
Steinberg::String("Volume"), pid++, Steinberg::String("dB"),
0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId));
2020-03-06 11:46:49 +01:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidNumVoices).createParameter(
2020-03-06 11:46:49 +01:00
Steinberg::String("Polyphony"), pid++, nullptr,
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2020-03-06 11:55:52 +01:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidOversampling).createParameter(
2020-03-06 11:55:52 +01:00
Steinberg::String("Oversampling"), pid++, nullptr,
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2020-03-06 13:15:32 +01:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidPreloadSize).createParameter(
2020-03-06 13:15:32 +01:00
Steinberg::String("Preload size"), pid++, nullptr,
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2020-06-20 05:49:00 +02:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidScalaRootKey).createParameter(
2020-06-20 05:49:00 +02:00
Steinberg::String("Scala root key"), pid++, nullptr,
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2020-06-20 05:49:00 +02:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidTuningFrequency).createParameter(
2020-06-20 05:49:00 +02:00
Steinberg::String("Tuning frequency"), pid++, Steinberg::String("Hz"),
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2020-06-20 05:49:00 +02:00
parameters.addParameter(
2021-02-04 02:48:49 +01:00
SfizzRange::getForParameter(kPidStretchedTuning).createParameter(
2020-06-20 05:49:00 +02:00
Steinberg::String("Stretched tuning"), pid++, nullptr,
2021-04-11 01:18:08 +02:00
0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId));
2021-04-10 17:48:13 +02:00
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));
2020-03-05 18:21:46 +01:00
// MIDI special controllers
2021-04-11 01:18:08 +02:00
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));
2020-03-05 11:16:47 +01:00
// MIDI controllers
for (unsigned i = 0; i < sfz::config::numCCs; ++i) {
2020-03-05 11:16:47 +01:00
Steinberg::String title;
Steinberg::String shortTitle;
title.printf("Controller %u", i);
shortTitle.printf("CC%u", i);
parameters.addParameter(
2021-04-10 17:48:13 +02:00
SfizzRange::getForParameter(kPidCC0 + i).createParameter(
title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate,
Vst::kRootUnitId, shortTitle));
2020-03-05 11:16:47 +01:00
}
2021-04-14 06:36:17 +02:00
// Volume levels
parameters.addParameter(
SfizzRange::getForParameter(kPidLeftLevel).createParameter(
Steinberg::String("Left level"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
parameters.addParameter(
SfizzRange::getForParameter(kPidRightLevel).createParameter(
Steinberg::String("Right level"), 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:
2021-02-08 20:14:07 +01:00
id = kPidAftertouch;
break;
case Vst::kPitchBend:
2021-02-08 20:14:07 +01:00
id = kPidPitchBend;
break;
default:
if (i < 128)
2021-02-08 20:14:07 +01:00
id = kPidCC0 + i;
break;
}
midiMapping_[i] = id;
}
2021-05-01 21:50:47 +02:00
// 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();
2020-03-05 11:16:47 +01:00
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::terminate()
{
2021-05-01 20:25:59 +02:00
return EditControllerEx1::terminate();
2020-03-05 11:16:47 +01:00
}
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;
}
2020-03-05 11:16:47 +01:00
id = midiMapping_[midiControllerNumber];
if (id == Vst::kNoParamId)
return kResultFalse;
2020-03-05 11:16:47 +01:00
return kResultTrue;
2020-03-05 11:16:47 +01:00
}
2021-05-02 11:57:10 +02:00
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;
}
2020-03-06 11:55:52 +01:00
tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string)
{
switch (tag) {
case kPidOversampling:
{
2021-02-04 02:48:49 +01:00
const SfizzRange range = SfizzRange::getForParameter(tag);
const int factorLog2 = static_cast<int>(range.denormalize(valueNormalized));
2020-03-06 11:55:52 +01:00
Steinberg::String buf;
2020-03-06 13:15:32 +01:00
buf.printf("%dX", 1 << factorLog2);
2020-03-06 11:55:52 +01:00
buf.copyTo(string);
return kResultTrue;
}
}
2021-05-01 20:25:59 +02:00
return EditControllerEx1::getParamStringByValue(tag, valueNormalized, string);
2020-03-06 11:55:52 +01:00
}
tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized)
{
switch (tag) {
case kPidOversampling:
{
int32 factor;
2021-02-04 02:48:49 +01:00
if (!Steinberg::String::scanInt32(string, factor, false))
2020-03-06 11:55:52 +01:00
factor = 1;
2020-03-06 13:15:32 +01:00
2021-02-04 02:48:49 +01:00
const SfizzRange range = SfizzRange::getForParameter(tag);
valueNormalized = range.normalize(integerLog2(factor));
2020-03-06 11:55:52 +01:00
return kResultTrue;
}
}
2021-05-01 20:25:59 +02:00
return EditControllerEx1::getParamValueByString(tag, string, valueNormalized);
2020-03-06 11:55:52 +01:00
}
2021-02-04 02:48:49 +01:00
tresult SfizzVstControllerNoUi::setParam(Vst::ParamID tag, float value)
2020-03-05 11:16:47 +01:00
{
2021-02-04 02:48:49 +01:00
const SfizzRange range = SfizzRange::getForParameter(tag);
return setParamNormalized(tag, range.normalize(value));
2020-03-05 11:16:47 +01:00
}
2021-02-04 02:48:49 +01:00
tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
2020-03-05 11:16:47 +01:00
{
SfizzVstState s;
2020-12-07 09:41:36 +01:00
tresult r = s.load(stream);
2020-03-05 11:16:47 +01:00
if (r != kResultTrue)
return r;
2021-02-04 02:48:49 +01:00
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);
2021-04-10 17:48:13 +02:00
setParam(kPidSampleQuality, s.sampleQuality);
setParam(kPidOscillatorQuality, s.oscillatorQuality);
2020-03-05 18:21:46 +01:00
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();
2021-04-02 04:05:14 +02:00
scalaUpdate_->setPath(s.scalaFile);
scalaUpdate_->deferUpdate();
2020-03-05 11:16:47 +01:00
return kResultTrue;
}
2021-02-04 02:48:49 +01:00
tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
2020-06-20 05:49:00 +02:00
{
// Note: is expected to be called from the controller thread only
2020-12-07 09:41:36 +01:00
2021-05-01 20:25:59 +02:00
tresult result = EditControllerEx1::notify(message);
2020-06-20 05:49:00 +02:00
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");
}
2020-06-20 05:49:00 +02:00
const char* id = message->getMessageID();
2021-04-02 04:05:14 +02:00
///
if (!strcmp(id, SfzUpdate::getFClassID())) {
if (!sfzUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
2021-05-01 21:50:47 +02:00
// 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);
//
2021-04-02 04:05:14 +02:00
sfzUpdate_->deferUpdate();
}
else if (!strcmp(id, SfzDescriptionUpdate::getFClassID())) {
if (!sfzDescriptionUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
2021-05-01 21:50:47 +02:00
// 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;
2021-05-02 11:57:10 +02:00
if (desc.keyUsed.test(pitch) && !desc.keyLabel[pitch].empty())
2021-05-01 21:50:47 +02:00
pitchName = Steinberg::String(desc.keyLabel[pitch].c_str());
2021-05-02 11:57:10 +02:00
else if (desc.keyswitchUsed.test(pitch) && !desc.keyswitchLabel[pitch].empty())
2021-05-01 21:50:47 +02:00
pitchName = Steinberg::String(desc.keyswitchLabel[pitch].c_str());
list->setPitchName(0, pitch, pitchName);
}
FUnknownPtr<Vst::IUnitHandler> unitHandler(getComponentHandler());
if (unitHandler)
unitHandler->notifyProgramListChange(kProgramListID, 0);
2021-05-02 11:57:10 +02:00
// 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;
}
2021-05-02 15:15:57 +02:00
if (Vst::IComponentHandler* componentHandler = getComponentHandler())
2021-05-02 11:57:10 +02:00
// NOTE(jpc) I think that's the right one, but it needs confirmation
componentHandler->restartComponent(Vst::kNoteExpressionChanged);
2021-05-02 15:15:57 +02:00
// 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);
2021-05-01 21:50:47 +02:00
//
sfzDescriptionUpdate_->deferUpdate();
2020-06-20 05:49:00 +02:00
}
else if (!strcmp(id, ScalaUpdate::getFClassID())) {
if (!scalaUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
2021-04-02 04:05:14 +02:00
scalaUpdate_->deferUpdate();
2020-06-20 05:49:00 +02:00
}
else if (!strcmp(id, PlayStateUpdate::getFClassID())) {
if (!playStateUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
2021-02-04 02:48:49 +01:00
playStateUpdate_->deferUpdate();
2020-08-12 19:23:50 +02:00
}
else if (!strcmp(id, OSCUpdate::getFClassID())) {
IPtr<OSCUpdate> update = OSCUpdate::createFromMessage(*message);
if (!update) {
assert(false);
return kResultFalse;
}
2021-04-28 17:19:42 +02:00
queuedUpdates_->enqueue(update);
queuedUpdates_->deferUpdate();
2020-10-19 02:58:01 +02:00
}
else if (!strcmp(id, NoteUpdate::getFClassID())) {
IPtr<NoteUpdate> update = NoteUpdate::createFromMessage(*message);
if (!update) {
assert(false);
return kResultFalse;
}
2021-04-28 17:19:42 +02:00
queuedUpdates_->enqueue(update);
queuedUpdates_->deferUpdate();
2021-02-23 09:27:20 +01:00
}
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);
}
2020-06-20 05:49:00 +02:00
return result;
}
2021-02-04 02:48:49 +01:00
// --- 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;
2021-04-28 17:19:42 +02:00
std::vector<FObject*> updates;
updates.push_back(queuedUpdates_);
updates.push_back(sfzUpdate_);
updates.push_back(sfzDescriptionUpdate_);
updates.push_back(scalaUpdate_);
updates.push_back(playStateUpdate_);
2021-02-04 02:48:49 +01:00
for (uint32 i = 0, n = parameters.getParameterCount(); i < n; ++i)
2021-04-28 17:19:42 +02:00
updates.push_back(parameters.getParameterByIndex(i));
2021-02-04 02:48:49 +01:00
IPtr<SfizzVstEditor> editor = Steinberg::owned(
2021-04-28 17:19:42 +02:00
new SfizzVstEditor(this, absl::MakeSpan(updates)));
2021-02-04 02:48:49 +01:00
editor->remember();
return editor;
}
2020-03-05 11:16:47 +01:00
FUnknown* SfizzVstController::createInstance(void*)
{
return static_cast<Vst::IEditController*>(new SfizzVstController);
}
2021-04-08 04:43:53 +02:00
template <>
FUnknown* createInstance<SfizzVstController>(void* context)
{
return SfizzVstController::createInstance(context);
}
2021-04-08 04:13:57 +02:00
FUID SfizzVstController::cid = SfizzVstController_cid;