diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 914181ae..5134b533 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -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")) { diff --git a/plugins/editor/src/editor/Editor.h b/plugins/editor/src/editor/Editor.h index 4345426c..dd3aebae 100644 --- a/plugins/editor/src/editor/Editor.h +++ b/plugins/editor/src/editor/Editor.h @@ -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_; diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index de2f1c32..27ed99d5 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -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 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(data); + const uint8* end = pos + size; + + while (end - pos >= sizeof(uint32) + sizeof(float)) { + Vst::ParamID pid = *reinterpret_cast(pos); + pos += sizeof(uint32); + float value = *reinterpret_cast(pos); + pos += sizeof(float); + setParam(pid, value); + } + } return result; } diff --git a/plugins/vst/SfizzVstController.h b/plugins/vst/SfizzVstController.h index 7ea01f60..b8d11cd8 100644 --- a/plugins/vst/SfizzVstController.h +++ b/plugins/vst/SfizzVstController.h @@ -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_; Steinberg::IPtr sfzDescriptionUpdate_; Steinberg::IPtr scalaUpdate_; - Steinberg::IPtr processorStateUpdate_; Steinberg::IPtr playStateUpdate_; Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {}; }; diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index eff7462f..8053e91a 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -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(changedUnknown)) { const std::string path = update->getPath(); uiReceiveValue(EditId::SfzFile, path); + return; } if (SfzDescriptionUpdate* update = FCast(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(changedUnknown)) { const std::string path = update->getPath(); uiReceiveValue(EditId::ScalaFile, path); - } - - if (ProcessorStateUpdate* update = FCast(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(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; } } diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 1ba11923..445b582c 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -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 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(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 lock(_processMutex); _state.sfzFile.assign(static_cast(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 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 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> newControllers(sfz::config::numCCs); + const std::vector> 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 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 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(&pid), sizeof(uint32)); + automateBlob.append(reinterpret_cast(&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 lock(_processMutex); - loadSfzFileOrDefault(_state.sfzFile); + loadSfzFileOrDefault(_state.sfzFile, false); } if (_synth->shouldReloadScala()) { fprintf(stderr, "[Sfizz] scala file has changed, reloading\n"); diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index 55dcd039..ad764ff7 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -52,6 +52,7 @@ private: // synth state. acquire processMutex before accessing std::unique_ptr _synth; Steinberg::IPtr _loadedSfzMessage; + Steinberg::IPtr _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 _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change diff --git a/plugins/vst/SfizzVstState.cpp b/plugins/vst/SfizzVstState.cpp index 2c3a6d09..a4e5ab68 100644 --- a/plugins/vst/SfizzVstState.cpp +++ b/plugins/vst/SfizzVstState.cpp @@ -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 ccValue = controllers[cc]) { + if (!s.writeInt16u(uint16(cc)) || !s.writeFloat(*ccValue)) + return kResultFalse; + } + } + } + return kResultTrue; } diff --git a/plugins/vst/SfizzVstState.h b/plugins/vst/SfizzVstState.h index dbae0a3e..d1c08795 100644 --- a/plugins/vst/SfizzVstState.h +++ b/plugins/vst/SfizzVstState.h @@ -6,7 +6,9 @@ #pragma once #include "base/source/fstreamer.h" +#include #include +#include using namespace Steinberg; @@ -23,8 +25,9 @@ public: int32 scalaRootKey = 60; float tuningFrequency = 440.0; float stretchedTuning = 0.0; + std::vector> controllers; - static constexpr uint64 currentStateVersion = 1; + static constexpr uint64 currentStateVersion = 2; tresult load(IBStream* state); tresult store(IBStream* state) const; diff --git a/plugins/vst/SfizzVstUpdates.h b/plugins/vst/SfizzVstUpdates.h index 3d0ce944..add4107e 100644 --- a/plugins/vst/SfizzVstUpdates.h +++ b/plugins/vst/SfizzVstUpdates.h @@ -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 lock(mutex_); - state_ = std::move(newState); - } - - SfizzVstState getState() const - { - std::lock_guard lock(mutex_); - return state_; - } - - template - void access(F&& fn) - { - std::lock_guard lock(mutex_); - fn(state_); - } - - OBJ_METHODS(ProcessorStateUpdate, FObject) - -private: - SfizzVstState state_; - mutable std::mutex mutex_; -}; - /** * @brief Update which indicates the playing SFZ status. */