Merge pull request #786 from jpcima/vst-cc-auto

CC automations for VST, saving and loading
This commit is contained in:
JP Cimalando 2021-04-06 20:33:56 +02:00 committed by GitHub
commit 0a6457fd83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 149 additions and 155 deletions

View file

@ -306,12 +306,6 @@ void Editor::close()
}
}
void Editor::sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args)
{
Impl& impl = *impl_;
impl.sendQueuedOSC(path, sig, args);
}
void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
{
switch (id) {
@ -488,12 +482,6 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
else if (editIdIsCCUsed(id)) {
bool used = v.to_float() != 0;
updateCCUsed(ccUsedForEditId(id), used);
// TODO(jpc) remove value requests, when implementing CC automation
if (used) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", ccUsedForEditId(id));
sendQueuedOSC(pathBuf, "", nullptr);
}
}
else if (editIdIsCCDefault(id)) {
updateCCDefaultValue(ccDefaultForEditId(id), v.to_float());
@ -509,22 +497,7 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi
{
unsigned indices[8];
if (Messages::matchOSC("/cc/changed~", path, indices) && !strcmp(sig, "b")) {
size_t numBits = 8 * args[0].b->size;
ConstBitSpan bits { args[0].b->data, numBits };
for (unsigned cc = 0; cc < numBits; ++cc) {
bool changed = bits.test(cc);
if (changed) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", cc);
sendQueuedOSC(pathBuf, "", nullptr);
}
}
}
else if (Messages::matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f")) {
updateCCValue(indices[0], args[0].f);
}
else if (Messages::matchOSC("/sw/last/current", path, indices) && !strcmp(sig, "i")) {
if (Messages::matchOSC("/sw/last/current", path, indices) && !strcmp(sig, "i")) {
updateSWLastCurrent(args[0].i);
}
else if (Messages::matchOSC("/sw/last/current", path, indices) && !strcmp(sig, "N")) {

View file

@ -25,9 +25,6 @@ public:
void open(CFrame& frame);
void close();
// TODO(jpc) remove me after doing parameter automations
void sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args);
private:
struct Impl;
std::unique_ptr<Impl> impl_;

View file

@ -25,7 +25,6 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
sfzUpdate_ = Steinberg::owned(new SfzUpdate);
sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate);
scalaUpdate_ = Steinberg::owned(new ScalaUpdate);
processorStateUpdate_ = Steinberg::owned(new ProcessorStateUpdate);
playStateUpdate_ = Steinberg::owned(new PlayStateUpdate);
// Parameters
@ -73,8 +72,9 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
shortTitle.printf("CC%u", i);
parameters.addParameter(
title, nullptr, 0, 0, Vst::ParameterInfo::kNoFlags,
pid++, Vst::kRootUnitId, shortTitle);
SfizzRange::getForParameter(kPidStretchedTuning).createParameter(
title, pid++, nullptr, 0, Vst::ParameterInfo::kCanAutomate,
Vst::kRootUnitId, shortTitle));
}
// Initial MIDI mapping
@ -158,42 +158,6 @@ tresult SfizzVstControllerNoUi::setParam(Vst::ParamID tag, float value)
return setParamNormalized(tag, range.normalize(value));
}
tresult PLUGIN_API SfizzVstControllerNoUi::setParamNormalized(Vst::ParamID tag, Vst::ParamValue normValue)
{
tresult r = EditController::setParamNormalized(tag, normValue);
if (r != kResultTrue)
return r;
processorStateUpdate_->access([tag, normValue](SfizzVstState& state) {
const SfizzRange range = SfizzRange::getForParameter(tag);
switch (tag) {
case kPidVolume:
state.volume = range.denormalize(normValue);
break;
case kPidNumVoices:
state.numVoices = (int32)range.denormalize(normValue);
break;
case kPidOversampling:
state.oversamplingLog2 = (int32)range.denormalize(normValue);
break;
case kPidPreloadSize:
state.preloadSize = (int32)range.denormalize(normValue);
break;
case kPidScalaRootKey:
state.scalaRootKey = (int32)range.denormalize(normValue);
break;
case kPidTuningFrequency:
state.tuningFrequency = (float)range.denormalize(normValue);
break;
case kPidStretchedTuning:
state.stretchedTuning = range.denormalize(normValue);
break;
}
});
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
{
SfizzVstState s;
@ -202,8 +166,6 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
if (r != kResultTrue)
return r;
processorStateUpdate_->setState(s);
setParam(kPidVolume, s.volume);
setParam(kPidNumVoices, s.numVoices);
setParam(kPidOversampling, s.oversamplingLog2);
@ -212,6 +174,12 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
setParam(kPidTuningFrequency, s.tuningFrequency);
setParam(kPidStretchedTuning, s.stretchedTuning);
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);
@ -254,9 +222,6 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
if (result != kResultTrue)
return result;
processorStateUpdate_->access([sfzFile](SfizzVstState& state) {
state.sfzFile = std::string(sfzFile);
});
sfzUpdate_->setPath(std::string(sfzFile));
sfzUpdate_->deferUpdate();
sfzDescriptionUpdate_->setDescription(std::string(sfzDescriptionBlob));
@ -269,9 +234,6 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
if (result != kResultTrue)
return result;
processorStateUpdate_->access([scalaFile](SfizzVstState& state) {
state.scalaFile = std::string(scalaFile);
});
scalaUpdate_->setPath(std::string(scalaFile));
scalaUpdate_->deferUpdate();
}
@ -313,6 +275,25 @@ tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
noteUpdate_->changed();
noteUpdate_->clear();
}
else if (!strcmp(id, "Automate")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Data", data, size);
if (result != kResultTrue)
return result;
const uint8* pos = reinterpret_cast<const uint8*>(data);
const uint8* end = pos + size;
while (end - pos >= sizeof(uint32) + sizeof(float)) {
Vst::ParamID pid = *reinterpret_cast<const uint32*>(pos);
pos += sizeof(uint32);
float value = *reinterpret_cast<const float*>(pos);
pos += sizeof(float);
setParam(pid, value);
}
}
return result;
}

View file

@ -33,7 +33,6 @@ public:
tresult PLUGIN_API getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized) override;
tresult setParam(Vst::ParamID tag, float value);
tresult PLUGIN_API setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) override;
tresult PLUGIN_API setComponentState(IBStream* stream) override;
tresult PLUGIN_API notify(Vst::IMessage* message) override;
@ -50,7 +49,6 @@ protected:
Steinberg::IPtr<SfzUpdate> sfzUpdate_;
Steinberg::IPtr<SfzDescriptionUpdate> sfzDescriptionUpdate_;
Steinberg::IPtr<ScalaUpdate> scalaUpdate_;
Steinberg::IPtr<ProcessorStateUpdate> processorStateUpdate_;
Steinberg::IPtr<PlayStateUpdate> playStateUpdate_;
Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {};
};

View file

@ -188,11 +188,13 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
if (NoteEventsVec* queue = noteEventQueue_.get())
std::copy(events, events + count, std::back_inserter(*queue));
}
return;
}
if (SfzUpdate* update = FCast<SfzUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::SfzFile, path);
return;
}
if (SfzDescriptionUpdate* update = FCast<SfzDescriptionUpdate>(changedUnknown)) {
@ -227,24 +229,13 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
uiReceiveValue(editIdForCCLabel(int(cc)), desc.ccLabel[cc]);
}
}
return;
}
if (ScalaUpdate* update = FCast<ScalaUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::ScalaFile, path);
}
if (ProcessorStateUpdate* update = FCast<ProcessorStateUpdate>(changedUnknown)) {
const SfizzVstState state = update->getState();
uiReceiveValue(EditId::SfzFile, state.sfzFile);
uiReceiveValue(EditId::Volume, state.volume);
uiReceiveValue(EditId::Polyphony, state.numVoices);
uiReceiveValue(EditId::Oversampling, float(1u << state.oversamplingLog2));
uiReceiveValue(EditId::PreloadSize, state.preloadSize);
uiReceiveValue(EditId::ScalaFile, state.scalaFile);
uiReceiveValue(EditId::ScalaRootKey, state.scalaRootKey);
uiReceiveValue(EditId::TuningFrequency, state.tuningFrequency);
uiReceiveValue(EditId::StretchTuning, state.stretchedTuning);
return;
}
if (PlayStateUpdate* update = FCast<PlayStateUpdate>(changedUnknown)) {
@ -279,6 +270,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
case kPidStretchedTuning:
uiReceiveValue(EditId::StretchTuning, range.denormalize(value));
break;
default:
if (id >= kPidCC0 && id <= kPidCCLast) {
int cc = int(id - kPidCC0);
uiReceiveValue(editIdForCC(cc), range.denormalize(value));
}
break;
}
return;
}
@ -329,18 +326,7 @@ void SfizzVstEditor::processNoteEventQueue()
///
void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
{
if (editIdIsCC(id)) {
int cc = ccForEditId(id);
// TODO(jpc) CC as parameters and automation
if (Editor* editor = editor_.get()) {
char pathBuf[256];
sprintf(pathBuf, "/cc%u/value", cc);
sfizz_arg_t args[1];
args[0].f = v.to_float();
editor->sendQueuedOSC(pathBuf, "f", args);
}
}
else if (id == EditId::SfzFile)
if (id == EditId::SfzFile)
loadSfzFile(v.to_string());
else if (id == EditId::ScalaFile)
loadScalaFile(v.to_string());
@ -384,6 +370,8 @@ void SfizzVstEditor::uiSendValue(EditId id, const EditValue& v)
break;
default:
if (editIdIsCC(id))
normalizeAndSet(kPidCC0 + ccForEditId(id), v.to_float());
break;
}
}
@ -482,6 +470,9 @@ Vst::ParamID SfizzVstEditor::parameterOfEditId(EditId id)
case EditId::ScalaRootKey: return kPidScalaRootKey;
case EditId::TuningFrequency: return kPidTuningFrequency;
case EditId::StretchTuning: return kPidStretchedTuning;
default: return Vst::kNoParamId;
default:
if (editIdIsCC(id))
return kPidCC0 + ccForEditId(id);
return Vst::kNoParamId;
}
}

View file

@ -84,6 +84,9 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
_state = SfizzVstState();
// allocate needed space to track CC values
_state.controllers.resize(sfz::config::numCCs);
fprintf(stderr, "[sfizz] new synth\n");
_synth.reset(new sfz::Sfizz);
@ -97,7 +100,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
_synth->setBroadcastCallback(onMessage, this);
_currentStretchedTuning = 0.0;
loadSfzFileOrDefault({});
loadSfzFileOrDefault({}, false);
_synth->tempo(0, 0.5);
_timeSigNumerator = 4;
@ -130,6 +133,8 @@ tresult PLUGIN_API SfizzVstProcessor::connect(IConnectionPoint* other)
// when controller connects, send these messages that we couldn't earlier
if (_loadedSfzMessage)
sendMessage(_loadedSfzMessage);
if (_automateMessage)
sendMessage(_automateMessage);
return kResultTrue;
}
@ -168,6 +173,9 @@ tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* stream)
std::lock_guard<SpinMutex> lock(_processMutex);
_state = s;
// allocate needed space to track CC values
_state.controllers.resize(sfz::config::numCCs);
syncStateToSynth();
return r;
@ -186,7 +194,7 @@ void SfizzVstProcessor::syncStateToSynth()
if (!synth)
return;
loadSfzFileOrDefault(_state.sfzFile);
loadSfzFileOrDefault(_state.sfzFile, true);
synth->setVolume(_state.volume);
synth->setNumVoices(_state.numVoices);
synth->setOversamplingFactor(1 << _state.oversamplingLog2);
@ -284,7 +292,6 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
// Request OSC updates
sfz::Client& client = *_client;
synth.sendMessage(client, 0, "/cc/changed~", "", nullptr);
synth.sendMessage(client, 0, "/sw/last/current", "", nullptr);
//
@ -409,8 +416,10 @@ void SfizzVstProcessor::processControllerChanges(Vst::IParameterChanges& pc)
if (id >= kPidCC0 && id <= kPidCCLast) {
auto ccNumber = static_cast<int>(id - kPidCC0);
for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue)
if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) {
synth.hdcc(sampleOffset, ccNumber, value);
_state.controllers[ccNumber] = value;
}
}
}
break;
@ -561,7 +570,7 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
std::unique_lock<SpinMutex> lock(_processMutex);
_state.sfzFile.assign(static_cast<const char *>(data), size);
loadSfzFileOrDefault(_state.sfzFile);
loadSfzFileOrDefault(_state.sfzFile, false);
lock.unlock();
}
else if (!std::strcmp(id, "LoadScala")) {
@ -614,7 +623,7 @@ void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char*
}
}
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath)
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState)
{
sfz::Sfizz& synth = *_synth;
@ -634,18 +643,56 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath)
synth.loadSfzString("default.sfz", defaultSfzText);
}
const std::string desc = getDescriptionBlob(synth.handle());
const std::string descBlob = getDescriptionBlob(synth.handle());
Steinberg::OPtr<Vst::IMessage> message { allocateMessage() };
message->setMessageID("LoadedSfz");
Vst::IAttributeList* attrs = message->getAttributes();
attrs->setBinary("File", filePath.data(), filePath.size());
attrs->setBinary("Description", desc.data(), desc.size());
Steinberg::OPtr<Vst::IMessage> loadMessage { allocateMessage() };
loadMessage->setMessageID("LoadedSfz");
Vst::IAttributeList* loadAttrs = loadMessage->getAttributes();
loadAttrs->setBinary("File", filePath.data(), filePath.size());
loadAttrs->setBinary("Description", descBlob.data(), descBlob.size());
{
std::vector<absl::optional<float>> newControllers(sfz::config::numCCs);
const std::vector<absl::optional<float>> oldControllers = std::move(_state.controllers);
// collect initial CC from instrument
const InstrumentDescription desc = parseDescriptionBlob(descBlob);
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
if (desc.ccUsed.test(cc))
newControllers[cc] = desc.ccDefault[cc];
}
// set CC from existing state
if (initParametersFromState) {
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
if (absl::optional<float> value = oldControllers[cc]) {
newControllers[cc] = *value;
synth.hdcc(0, int(cc), *value);
}
}
}
_state.controllers = std::move(newControllers);
}
// create a message which requests the controller to automate initial parameters
Steinberg::OPtr<Vst::IMessage> automateMessage { allocateMessage() };
automateMessage->setMessageID("Automate");
Vst::IAttributeList* automateAttrs = automateMessage->getAttributes();
std::string automateBlob;
automateBlob.reserve(sfz::config::numCCs * (sizeof(uint32) + sizeof(float)));
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
uint32 pid = kPidCC0 + cc;
float value = _state.controllers[cc].value_or(0.0f);
automateBlob.append(reinterpret_cast<const char*>(&pid), sizeof(uint32));
automateBlob.append(reinterpret_cast<const char*>(&value), sizeof(float));
}
automateAttrs->setBinary("Data", automateBlob.data(), uint32(automateBlob.size()));
// sending can fail if controller is not connected yet, so keep it around
_loadedSfzMessage = message;
_loadedSfzMessage = loadMessage;
_automateMessage = automateMessage;
sendMessage(message);
// send message
sendMessage(loadMessage);
sendMessage(automateMessage);
}
void SfizzVstProcessor::doBackgroundWork()
@ -726,7 +773,7 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
if (_synth->shouldReloadFile()) {
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);
loadSfzFileOrDefault(_state.sfzFile);
loadSfzFileOrDefault(_state.sfzFile, false);
}
if (_synth->shouldReloadScala()) {
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");

View file

@ -52,6 +52,7 @@ private:
// synth state. acquire processMutex before accessing
std::unique_ptr<sfz::Sfizz> _synth;
Steinberg::IPtr<Vst::IMessage> _loadedSfzMessage;
Steinberg::IPtr<Vst::IMessage> _automateMessage;
bool _isActive = false;
SfizzVstState _state;
float _currentStretchedTuning = 0;
@ -62,7 +63,7 @@ private:
void receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
// misc
void loadSfzFileOrDefault(const std::string& filePath);
void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState);
// note event tracking
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change

View file

@ -58,7 +58,26 @@ tresult SfizzVstState::load(IBStream* state)
stretchedTuning = defaults.stretchedTuning;
}
if (version > 1)
controllers.clear();
if (version >= 2) {
uint32 count;
if (!s.readInt32u(count))
return kResultFalse;
controllers.resize(0x10000);
uint32 size = 0;
for (uint32 i = 0; i < count; ++i) {
uint16 cc;
float value;
if (!s.readInt16u(cc) || !s.readFloat(value))
return kResultFalse;
controllers[cc] = value;
size = std::max(size, uint32(cc) + 1);
}
controllers.resize(size);
controllers.shrink_to_fit();
}
if (version > 2)
return kResultFalse;
return kResultTrue;
@ -98,6 +117,21 @@ tresult SfizzVstState::store(IBStream* state) const
if (!s.writeFloat(stretchedTuning))
return kResultFalse;
{
uint32 ccCount = 0;
uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000)));
for (uint32_t cc = 0; cc < ccLimit; ++cc)
ccCount += controllers[cc] != absl::nullopt;
if (!s.writeInt32u(ccCount))
return kResultFalse;
for (uint32_t cc = 0; cc < ccLimit; ++cc) {
if (absl::optional<float> ccValue = controllers[cc]) {
if (!s.writeInt16u(uint16(cc)) || !s.writeFloat(*ccValue))
return kResultFalse;
}
}
}
return kResultTrue;
}

View file

@ -6,7 +6,9 @@
#pragma once
#include "base/source/fstreamer.h"
#include <absl/types/optional.h>
#include <string>
#include <vector>
using namespace Steinberg;
@ -23,8 +25,9 @@ public:
int32 scalaRootKey = 60;
float tuningFrequency = 440.0;
float stretchedTuning = 0.0;
std::vector<absl::optional<float>> controllers;
static constexpr uint64 currentStateVersion = 1;
static constexpr uint64 currentStateVersion = 2;
tresult load(IBStream* state);
tresult store(IBStream* state) const;

View file

@ -138,37 +138,6 @@ private:
mutable std::mutex mutex_;
};
/**
* @brief Update which indicates the processor status.
*/
class ProcessorStateUpdate : public Steinberg::FObject {
public:
void setState(SfizzVstState newState)
{
std::lock_guard<std::mutex> lock(mutex_);
state_ = std::move(newState);
}
SfizzVstState getState() const
{
std::lock_guard<std::mutex> lock(mutex_);
return state_;
}
template <class F>
void access(F&& fn)
{
std::lock_guard<std::mutex> lock(mutex_);
fn(state_);
}
OBJ_METHODS(ProcessorStateUpdate, FObject)
private:
SfizzVstState state_;
mutable std::mutex mutex_;
};
/**
* @brief Update which indicates the playing SFZ status.
*/