From c81f74100b4e1e7251d81ee58f3227200dd3dcc1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 14 Dec 2020 19:35:21 +0100 Subject: [PATCH 01/13] Add CC message processing for OSC --- src/sfizz/Synth.cpp | 34 +++++++++++++++++++++++++----- src/sfizz/SynthMessaging.cpp | 41 ++++++++++++++++++++++++++++++++++++ src/sfizz/SynthPrivate.h | 5 +++++ 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 93617268..e50df231 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -244,7 +244,7 @@ void Synth::Impl::clear() resources_.midiState.reset(); resources_.filePool.clear(); resources_.filePool.setRamLoading(config::loadInRam); - ccLabels_.clear(); + clearCCLabels(); keyLabels_.clear(); keyswitchLabels_.clear(); globalOpcodes_.clear(); @@ -261,9 +261,9 @@ void Synth::Impl::clear() setDefaultHdcc(11, 1.0f); // set default controller labels - insertPairUniquely(ccLabels_, 7, "Volume"); - insertPairUniquely(ccLabels_, 10, "Pan"); - insertPairUniquely(ccLabels_, 11, "Expression"); + setCCLabel(7, "Volume"); + setCCLabel(10, "Pan"); + setCCLabel(11, "Expression"); } void Synth::Impl::handleMasterOpcodes(const std::vector& members) @@ -365,7 +365,7 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) break; case hash("label_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) - insertPairUniquely(ccLabels_, member.parameters.back(), std::string(member.value)); + setCCLabel(member.parameters.back(), std::string(member.value)); break; case hash("label_key&"): if (member.parameters.back() <= Default::keyRange.getEnd()) { @@ -1794,6 +1794,30 @@ std::bitset Synth::Impl::collectAllUsedCCs() return used; } +const std::string* Synth::Impl::getCCLabel(int ccNumber) +{ + auto it = ccLabelsMap_.find(ccNumber); + return (it == ccLabelsMap_.end()) ? nullptr : &ccLabels_[it->second].second; +} + +void Synth::Impl::setCCLabel(int ccNumber, std::string name) +{ + auto it = ccLabelsMap_.find(ccNumber); + if (it != ccLabelsMap_.end()) + ccLabels_[it->second].second = std::move(name); + else { + size_t index = ccLabels_.size(); + ccLabels_.emplace_back(ccNumber, std::move(name)); + ccLabelsMap_[ccNumber] = index; + } +} + +void Synth::Impl::clearCCLabels() +{ + ccLabels_.clear(); + ccLabelsMap_.clear(); +} + Parser& Synth::getParser() noexcept { Impl& impl = *impl_; diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index cabb85ec..68dbf362 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -36,6 +36,47 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive(delay, "/hello", "", nullptr); } break; + //---------------------------------------------------------------------- + + MATCH("/cc/slots", "") { + uint8_t data[(config::numCCs + 7) / 8] = {}; + + const std::bitset& ccs = impl.currentUsedCCs_; + for (unsigned i = 0; i < config::numCCs; ++i) + data[i / 8] |= ccs.test(i) << (i % 8); + + sfizz_blob_t blob { data, sizeof(data) }; + client.receive<'b'>(delay, path, &blob); + } break; + + MATCH("/cc&/default", "") { + if (indices[0] >= config::numCCs) + break; + client.receive<'f'>(delay, path, impl.defaultCCValues_[indices[0]]); + } break; + + MATCH("/cc&/value", "") { + if (indices[0] >= config::numCCs) + break; + // Note: result value is not frame-exact + client.receive<'f'>(delay, path, impl.resources_.midiState.getCCValue(indices[0])); + } break; + + MATCH("/cc&/value", "f") { + if (indices[0] >= config::numCCs) + break; + impl.resources_.midiState.ccEvent(delay, indices[0], args[0].f); + } break; + + MATCH("/cc&/label", "") { + if (indices[0] >= config::numCCs) + break; + const std::string* label = impl.getCCLabel(indices[0]); + client.receive<'s'>(delay, path, label ? label->c_str() : ""); + } break; + + //---------------------------------------------------------------------- + MATCH("/region&/delay", "") { GET_REGION_OR_BREAK(indices[0]) client.receive<'f'>(delay, path, region.delay); diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 42063ed0..43c5b5a8 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -179,6 +179,10 @@ struct Synth::Impl final: public Parser::Listener { std::bitset collectAllUsedCCs(); + const std::string* getCCLabel(int ccNumber); + void setCCLabel(int ccNumber, std::string name); + void clearCCLabels(); + /** * @brief Perform a CC event * @@ -208,6 +212,7 @@ struct Synth::Impl final: public Parser::Listener { // Names for the CC and notes as set by label_cc and label_key std::vector ccLabels_; + std::map ccLabelsMap_; std::vector keyLabels_; std::vector keyswitchLabels_; From 751e4f96bb2d21c8a7ed07f26b258fa1380ecca7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 14 Dec 2020 21:14:35 +0100 Subject: [PATCH 02/13] Add the reception of CC messages for editor --- editor/src/editor/Editor.cpp | 125 ++++++++++++++++++++++++++++++++++- src/sfizz/Synth.cpp | 20 +++--- src/sfizz/Synth.h | 3 +- src/sfizz/SynthMessaging.cpp | 9 +-- src/sfizz/SynthPrivate.h | 13 ++-- src/sfizz/utility/BitArray.h | 81 +++++++++++++++++++++++ tests/SynthT.cpp | 73 ++++++++++---------- 7 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 src/sfizz/utility/BitArray.h diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 525689bf..72c99ed7 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -13,14 +13,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include "utility/vstgui_before.h" #include "vstgui/vstgui.h" @@ -106,6 +109,13 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { void uiReceiveValue(EditId id, const EditValue& v) override; void uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args) override; + // queued OSC API; sends OSC with intermediate delay between messages + // to prevent message bursts overloading the buffer + void sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args); + void tickOSCQueue(CVSTGUITimer* timer); + std::queue oscSendQueue_; + SharedPointer oscSendQueueTimer_; + void createFrameContents(); template @@ -142,6 +152,10 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { void updateTuningFrequencyLabel(float tuningFrequency); void updateStretchedTuningLabel(float stretchedTuning); + void updateCCUsed(unsigned cc, bool used); + void updateCCValue(unsigned cc, float value); + void updateCCLabel(unsigned cc, const char* label); + void setActivePanel(unsigned panelId); static void formatLabel(CTextLabel* label, const char* fmt, ...); @@ -164,6 +178,11 @@ Editor::Editor(EditorController& ctrl) ctrl.decorate(&impl); impl.createFrameContents(); + + uint32_t oscSendInterval = 1; // milliseconds + impl.oscSendQueueTimer_ = makeOwned( + [this](CVSTGUITimer* timer) { impl_->tickOSCQueue(timer); }, + oscSendInterval, false); } Editor::~Editor() @@ -182,6 +201,9 @@ void Editor::open(CFrame& frame) impl.frame_ = &frame; frame.addView(impl.mainView_.get()); + + // request the whole CC information + impl.sendQueuedOSC("/cc/slots", "", nullptr); } void Editor::close() @@ -336,9 +358,92 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) } } +/// +static constexpr unsigned kMessageMaxIndices = 8; + +static bool matchMessage(const char* pattern, const char* path, unsigned* indices) +{ + unsigned nthIndex = 0; + + while (const char *endp = strchr(pattern, '&')) { + if (nthIndex == kMessageMaxIndices) + return false; + + size_t length = endp - pattern; + if (strncmp(pattern, path, length)) + return false; + pattern += length; + path += length; + + length = 0; + while (absl::ascii_isdigit(path[length])) + ++length; + + if (!absl::SimpleAtoi(absl::string_view(path, length), &indices[nthIndex++])) + return false; + + pattern += 1; + path += length; + } + + return !strcmp(path, pattern); +} + +/// void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args) { - // TODO handle the message... + unsigned indices[kMessageMaxIndices]; + + if (!strcmp(path, "/cc/slots") && !strcmp(sig, "b")) { + const uint8_t* bitChunks = args[0].b->data; + uint32_t byteSize = args[0].b->size; + + for (unsigned cc = 0; cc < 8 * byteSize; ++cc) { + bool used = bitChunks[cc / 8] & (1u << (cc % 8)); + updateCCUsed(cc, used); + if (used) { + char pathBuf[256]; + sprintf(pathBuf, "/cc%u/value", cc); + sendQueuedOSC(pathBuf, "", nullptr); + sprintf(pathBuf, "/cc%u/label", cc); + sendQueuedOSC(pathBuf, "", nullptr); + } + } + } + else if (matchMessage("/cc&/value", path, indices) && !strcmp(sig, "f")) { + updateCCValue(indices[0], args[0].f); + } + else if (matchMessage("/cc&/label", path, indices) && !strcmp(sig, "s")) { + updateCCLabel(indices[0], args[0].s); + } + else { + //fprintf(stderr, "Receive unhandled OSC: %s\n", path); + } +} + +void Editor::Impl::sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args) +{ + uint32_t oscSize = sfizz_prepare_message(nullptr, 0, path, sig, args); + std::string oscData(oscSize, '\0'); + sfizz_prepare_message(&oscData[0], oscSize, path, sig, args); + oscSendQueue_.push(std::move(oscData)); + oscSendQueueTimer_->start(); +} + +void Editor::Impl::tickOSCQueue(CVSTGUITimer* timer) +{ + if (oscSendQueue_.empty()) { + timer->stop(); + return; + } + const std::string& msg = oscSendQueue_.front(); + const char* path; + const char* sig; + const sfizz_arg_t* args; + uint8_t buffer[1024]; + if (sfizz_extract_message(msg.data(), msg.size(), buffer, sizeof(buffer), &path, &sig, &args) > 0) + ctrl_->uiSendMessage(path, sig, args); + oscSendQueue_.pop(); } void Editor::Impl::createFrameContents() @@ -1062,6 +1167,24 @@ void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning) label->setText(text); } +void Editor::Impl::updateCCUsed(unsigned cc, bool used) +{ + // TODO + fprintf(stderr, "CC%u used: %d\n", cc, used); +} + +void Editor::Impl::updateCCValue(unsigned cc, float value) +{ + // TODO + fprintf(stderr, "CC%u value: %f\n", cc, value); +} + +void Editor::Impl::updateCCLabel(unsigned cc, const char* label) +{ + // TODO + fprintf(stderr, "CC%u label: %s\n", cc, label); +} + void Editor::Impl::setActivePanel(unsigned panelId) { panelId = std::max(0, std::min(kNumPanels - 1, static_cast(panelId))); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e50df231..6e736690 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -696,7 +696,7 @@ void Synth::Impl::finalizeSfzLoad() regions_.resize(currentRegionCount); // collect all CCs used in regions, with matrix not yet connected - std::bitset usedCCs; + BitArray usedCCs; for (const RegionPtr& regionPtr : regions_) { const Region& region = *regionPtr; collectUsedCCsFromRegion(usedCCs, region); @@ -1328,8 +1328,8 @@ std::string Synth::exportMidnam(absl::string_view model) const } } - for (unsigned i = 0, n = std::min(128, anonymousCCs.size()); i < n; ++i) { - if (anonymousCCs[i]) { + for (unsigned i = 0, n = std::min(128, anonymousCCs.bit_size()); i < n; ++i) { + if (anonymousCCs.test(i)) { pugi::xml_node cn = cns.append_child("Control"); cn.append_attribute("Type").set_value("7bit"); cn.append_attribute("Number").set_value(std::to_string(i).c_str()); @@ -1716,7 +1716,7 @@ void Synth::allSoundOff() noexcept effectBus->clear(); } -const std::bitset& Synth::getUsedCCs() const noexcept +const BitArray& Synth::getUsedCCs() const noexcept { Impl& impl = *impl_; return impl.currentUsedCCs_; @@ -1729,7 +1729,7 @@ void sfz::Synth::setBroadcastCallback(sfizz_receive_t* broadcast, void* data) impl.broadcastData = data; } -void Synth::Impl::collectUsedCCsFromRegion(std::bitset& usedCCs, const Region& region) +void Synth::Impl::collectUsedCCsFromRegion(BitArray& usedCCs, const Region& region) { collectUsedCCsFromCCMap(usedCCs, region.offsetCC); collectUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccAttack); @@ -1763,11 +1763,11 @@ void Synth::Impl::collectUsedCCsFromRegion(std::bitset& usedCCs, collectUsedCCsFromCCMap(usedCCs, region.crossfadeCCOutRange); } -void Synth::Impl::collectUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm) +void Synth::Impl::collectUsedCCsFromModulations(BitArray& usedCCs, const ModMatrix& mm) { class CCSourceCollector : public ModMatrix::KeyVisitor { public: - explicit CCSourceCollector(std::bitset& used) + explicit CCSourceCollector(BitArray& used) : used_(used) { } @@ -1778,16 +1778,16 @@ void Synth::Impl::collectUsedCCsFromModulations(std::bitset& use used_.set(key.parameters().cc); return true; } - std::bitset& used_; + BitArray& used_; }; CCSourceCollector vtor(usedCCs); mm.visitSources(vtor); } -std::bitset Synth::Impl::collectAllUsedCCs() +BitArray Synth::Impl::collectAllUsedCCs() { - std::bitset used; + BitArray used; for (const Impl::RegionPtr& region : regions_) collectUsedCCsFromRegion(used, *region); collectUsedCCsFromModulations(used, resources_.modMatrix); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 9afb3104..168900f9 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -17,6 +17,7 @@ #include #include #include +template class BitArray; namespace sfz { @@ -599,7 +600,7 @@ public: * * @return const std::bitset& */ - const std::bitset& getUsedCCs() const noexcept; + const BitArray& getUsedCCs() const noexcept; /** * @brief Dispatch the incoming message to the synth engine diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 68dbf362..9902b58b 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -39,13 +39,8 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co //---------------------------------------------------------------------- MATCH("/cc/slots", "") { - uint8_t data[(config::numCCs + 7) / 8] = {}; - - const std::bitset& ccs = impl.currentUsedCCs_; - for (unsigned i = 0; i < config::numCCs; ++i) - data[i / 8] |= ccs.test(i) << (i % 8); - - sfizz_blob_t blob { data, sizeof(data) }; + const BitArray& ccs = impl.currentUsedCCs_; + sfizz_blob_t blob { ccs.data(), static_cast(ccs.byte_size()) }; client.receive<'b'>(delay, path, &blob); } break; diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 43c5b5a8..893e436e 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -9,6 +9,7 @@ #include "modulations/sources/Controller.h" #include "modulations/sources/FlexEnvelope.h" #include "modulations/sources/LFO.h" +#include "utility/BitArray.h" namespace sfz { @@ -168,16 +169,16 @@ struct Synth::Impl final: public Parser::Listener { void finalizeSfzLoad(); template - static void collectUsedCCsFromCCMap(std::bitset& usedCCs, const CCMap map) noexcept + static void collectUsedCCsFromCCMap(BitArray& usedCCs, const CCMap map) noexcept { for (auto& mod : map) - usedCCs[mod.cc] = true; + usedCCs.set(mod.cc); } - static void collectUsedCCsFromRegion(std::bitset& usedCCs, const Region& region); - static void collectUsedCCsFromModulations(std::bitset& usedCCs, const ModMatrix& mm); + static void collectUsedCCsFromRegion(BitArray& usedCCs, const Region& region); + static void collectUsedCCsFromModulations(BitArray& usedCCs, const ModMatrix& mm); - std::bitset collectAllUsedCCs(); + BitArray collectAllUsedCCs(); const std::string* getCCLabel(int ccNumber); void setCCLabel(int ccNumber, std::string name); @@ -283,7 +284,7 @@ struct Synth::Impl final: public Parser::Listener { absl::optional modificationTime_ { }; std::array defaultCCValues_; - std::bitset currentUsedCCs_; + BitArray currentUsedCCs_; // Messaging sfizz_receive_t* broadcastReceiver = nullptr; diff --git a/src/sfizz/utility/BitArray.h b/src/sfizz/utility/BitArray.h new file mode 100644 index 00000000..020a7433 --- /dev/null +++ b/src/sfizz/utility/BitArray.h @@ -0,0 +1,81 @@ +// 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 + +#pragma once +#include +#include +#include + +/// +class ConstBitSpan { +public: + ConstBitSpan() noexcept = default; + ConstBitSpan(const uint8_t* data, size_t bits) noexcept : data_(data), bits_(bits) {} + const uint8_t* data() const noexcept { return data_; } + size_t bit_size() const noexcept { return bits_; } + size_t byte_size() const noexcept { return (bits_ + 7) / 8; } + bool test(size_t i) const noexcept { return data_[i / 8] & (1u << (i % 8)); } + bool all() const noexcept + { + size_t n = bits_; + for (size_t i = 0; i < n / 8; ++i) { + if (data_[i] != 0xff) + return false; + } + return n % 8 == 0 || data_[n / 8] == (1u << (n % 8)) - 1u; + } + bool any() const noexcept + { + size_t n = bits_; + for (size_t i = 0; i < n / 8; ++i) { + if (data_[i] != 0x00) + return true; + } + return n % 8 != 0 && (data_[n / 8] & ((1u << (n % 8)) - 1u)) != 0; + } + bool none() const noexcept { return !any(); } + +private: + const uint8_t* data_ = nullptr; + size_t bits_ = 0; +}; + +/// +class BitSpan : public ConstBitSpan { +public: + BitSpan() noexcept = default; + BitSpan(const uint8_t* data, size_t bits) noexcept : ConstBitSpan(data, bits) {} + uint8_t* data() const noexcept { return const_cast(ConstBitSpan::data()); } + void clear() { memset(data(), 0, byte_size()); } + void set(size_t i) noexcept { data()[i / 8] |= 1u << (i % 8); } + void set(size_t i, bool b) noexcept { if (b) set(i); else reset(i); } + void reset(size_t i) noexcept { data()[i / 8] &= ~(1u << (i % 8)); } + void flip(size_t i) noexcept { data()[i / 8] ^= 1u << (i % 8); } +}; + +/// +template +class BitArray { +public: + BitArray() noexcept = default; + uint8_t* data() noexcept { return data_; } + const uint8_t* data() const noexcept { return data_; } + static constexpr size_t bit_size() noexcept { return N; } + static constexpr size_t byte_size() noexcept { return (N + 7) / 8; } + BitSpan span() noexcept { return BitSpan(data_, N); }; + ConstBitSpan span() const noexcept { return ConstBitSpan(data_, N); }; + bool test(size_t i) const noexcept { return span().test(i); } + void set(size_t i) noexcept { span().set(i); } + void set(size_t i, bool b) noexcept { span().set(i, b); } + void reset(size_t i) noexcept { span().reset(i); } + void flip(size_t i) noexcept { span().flip(i); } + bool all() const noexcept { return span().all(); } + bool any() const noexcept { return span().any(); } + bool none() const noexcept { return span().none(); } + +private: + uint8_t data_[byte_size()] {}; +}; diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 5565d5ff..b9bf7ab8 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -8,6 +8,7 @@ #include "sfizz/SisterVoiceRing.h" #include "sfizz/SfzHelpers.h" #include "sfizz/utility/NumericId.h" +#include "sfizz/utility/BitArray.h" #include "TestHelpers.h" #include #include "catch2/catch.hpp" @@ -1087,18 +1088,18 @@ TEST_CASE("[Synth] Used CCs") start_locc44=200 hikey=-1 sample=*sine )"); auto usedCCs = synth.getUsedCCs(); - REQUIRE( usedCCs[1] ); - REQUIRE( usedCCs[2] ); - REQUIRE( !usedCCs[3] ); - REQUIRE( usedCCs[4] ); - REQUIRE( usedCCs[5] ); - REQUIRE( !usedCCs[6] ); - REQUIRE( usedCCs[42] ); - REQUIRE( usedCCs[44] ); - REQUIRE( usedCCs[56] ); - REQUIRE( usedCCs[67] ); - REQUIRE( usedCCs[98] ); - REQUIRE( !usedCCs[127] ); + REQUIRE( usedCCs.test(1) ); + REQUIRE( usedCCs.test(2) ); + REQUIRE( !usedCCs.test(3) ); + REQUIRE( usedCCs.test(4) ); + REQUIRE( usedCCs.test(5) ); + REQUIRE( !usedCCs.test(6) ); + REQUIRE( usedCCs.test(42) ); + REQUIRE( usedCCs.test(44) ); + REQUIRE( usedCCs.test(56) ); + REQUIRE( usedCCs.test(67) ); + REQUIRE( usedCCs.test(98) ); + REQUIRE( !usedCCs.test(127) ); } TEST_CASE("[Synth] Used CCs EGs") @@ -1135,31 +1136,31 @@ TEST_CASE("[Synth] Used CCs EGs") sample=*sine )"); auto usedCCs = synth.getUsedCCs(); - REQUIRE( usedCCs[1] ); - REQUIRE( usedCCs[2] ); - REQUIRE( usedCCs[3] ); - REQUIRE( usedCCs[4] ); - REQUIRE( usedCCs[5] ); - REQUIRE( usedCCs[6] ); - REQUIRE( usedCCs[7] ); + REQUIRE( usedCCs.test(1) ); + REQUIRE( usedCCs.test(2) ); + REQUIRE( usedCCs.test(3) ); + REQUIRE( usedCCs.test(4) ); + REQUIRE( usedCCs.test(5) ); + REQUIRE( usedCCs.test(6) ); + REQUIRE( usedCCs.test(7) ); // FIXME: enable when supported - // REQUIRE( !usedCCs[8] ); - // REQUIRE( usedCCs[11] ); - // REQUIRE( usedCCs[12] ); - // REQUIRE( usedCCs[13] ); - // REQUIRE( usedCCs[14] ); - // REQUIRE( usedCCs[15] ); - // REQUIRE( usedCCs[16] ); - // REQUIRE( usedCCs[17] ); - // REQUIRE( !usedCCs[18] ); - // REQUIRE( usedCCs[21] ); - // REQUIRE( usedCCs[22] ); - // REQUIRE( usedCCs[23] ); - // REQUIRE( usedCCs[24] ); - // REQUIRE( usedCCs[25] ); - // REQUIRE( usedCCs[26] ); - // REQUIRE( usedCCs[27] ); - // REQUIRE( !usedCCs[28] ); + // REQUIRE( !usedCCs.test(8) ); + // REQUIRE( usedCCs.test(11) ); + // REQUIRE( usedCCs.test(12) ); + // REQUIRE( usedCCs.test(13) ); + // REQUIRE( usedCCs.test(14) ); + // REQUIRE( usedCCs.test(15) ); + // REQUIRE( usedCCs.test(16) ); + // REQUIRE( usedCCs.test(17) ); + // REQUIRE( !usedCCs.test(18) ); + // REQUIRE( usedCCs.test(21) ); + // REQUIRE( usedCCs.test(22) ); + // REQUIRE( usedCCs.test(23) ); + // REQUIRE( usedCCs.test(24) ); + // REQUIRE( usedCCs.test(25) ); + // REQUIRE( usedCCs.test(26) ); + // REQUIRE( usedCCs.test(27) ); + // REQUIRE( !usedCCs.test(28) ); } TEST_CASE("[Synth] Activate also on the sustain CC") From 2ede8fd710c661529e73a35dfa866f6e99550777 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 02:05:50 +0100 Subject: [PATCH 03/13] User interface with CC knobs --- editor/layout/main.fl | 19 ++- editor/src/editor/Editor.cpp | 57 +++++++- editor/src/editor/GUIComponents.cpp | 206 ++++++++++++++++++++++++++++ editor/src/editor/GUIComponents.h | 58 ++++++++ editor/src/editor/layout/main.hpp | 5 +- 5 files changed, 327 insertions(+), 18 deletions(-) diff --git a/editor/layout/main.fl b/editor/layout/main.fl index 59ed5298..b96960f6 100644 --- a/editor/layout/main.fl +++ b/editor/layout/main.fl @@ -3,7 +3,7 @@ version 1.0305 header_name {.h} code_name {.cxx} widget_class mainView {open - xywh {576 416 800 475} type Double + xywh {571 362 800 475} type Double class LogicalGroup visible } { Fl_Box {} { @@ -197,23 +197,22 @@ widget_class mainView {open } } } - Fl_Group {subPanels_[kPanelControls]} { - xywh {5 110 790 285} hide + Fl_Group {subPanels_[kPanelControls]} {open + xywh {5 110 790 285} class LogicalGroup } { Fl_Group {} {open xywh {5 110 790 285} box ROUNDED_BOX class RoundedGroup } { - Fl_Box {} { - label {Controls not available} - xywh {5 110 790 285} labelsize 40 - class Label - } + Fl_Group controlsPanel_ {open selected + xywh {5 110 790 285} box THIN_DOWN_FRAME + class ControlsPanel + } {} } } Fl_Group {subPanels_[kPanelSettings]} {open - xywh {5 109 790 316} + xywh {5 109 790 316} hide class LogicalGroup } { Fl_Group {} { @@ -305,7 +304,7 @@ widget_class mainView {open } } Fl_Group userFilesGroup_ { - label Files open selected + label Files open xywh {620 270 139 100} box ROUNDED_BOX labelsize 12 align 17 class TitleGroup } { diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 72c99ed7..3390e290 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -106,6 +106,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { SPiano* piano_ = nullptr; + SControlsPanel* controlsPanel_ = nullptr; + void uiReceiveValue(EditId id, const EditValue& v) override; void uiReceiveMessage(const char* path, const char* sig, const sfizz_arg_t* args) override; @@ -156,6 +158,11 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { void updateCCValue(unsigned cc, float value); void updateCCLabel(unsigned cc, const char* label); + // edition of CC by UI + void performCCValueChange(unsigned cc, float value); + void performCCBeginEdit(unsigned cc); + void performCCEndEdit(unsigned cc); + void setActivePanel(unsigned panelId); static void formatLabel(CTextLabel* label, const char* fmt, ...); @@ -533,6 +540,7 @@ void Editor::Impl::createFrameContents() typedef STextButton NextFileButton; typedef SPiano Piano; typedef SActionMenu ChevronDropDown; + typedef SControlsPanel ControlsPanel; auto createLogicalGroup = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { CViewContainer* container = new CViewContainer(bounds); @@ -706,6 +714,10 @@ void Editor::Impl::createFrameContents() container->setBackground(background); return container; }; + auto createControlsPanel = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + auto* panel = new SControlsPanel(bounds); + return panel; + }; #include "layout/main.hpp" @@ -844,6 +856,18 @@ void Editor::Impl::createFrameContents() }; } + if (SControlsPanel* panel = controlsPanel_) { + panel->ValueChangeFunction = [this](uint32_t cc, float value) { + performCCValueChange(cc, value); + }; + panel->BeginEditFunction = [this](uint32_t cc) { + performCCBeginEdit(cc); + }; + panel->EndEditFunction = [this](uint32_t cc) { + performCCEndEdit(cc); + }; + } + /// CViewContainer* panel; activePanel_ = 0; @@ -1169,20 +1193,41 @@ void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning) void Editor::Impl::updateCCUsed(unsigned cc, bool used) { - // TODO - fprintf(stderr, "CC%u used: %d\n", cc, used); + if (SControlsPanel* panel = controlsPanel_) + panel->setControlUsed(cc, used); } void Editor::Impl::updateCCValue(unsigned cc, float value) { - // TODO - fprintf(stderr, "CC%u value: %f\n", cc, value); + if (SControlsPanel* panel = controlsPanel_) + panel->setControlValue(cc, value); } void Editor::Impl::updateCCLabel(unsigned cc, const char* label) { - // TODO - fprintf(stderr, "CC%u label: %s\n", cc, label); + if (SControlsPanel* panel = controlsPanel_) + panel->setControlLabelText(cc, label); +} + +void Editor::Impl::performCCValueChange(unsigned cc, float value) +{ + // TODO(jpc) CC as parameters and automation + + char pathBuf[256]; + sprintf(pathBuf, "/cc%u/value", cc); + sfizz_arg_t args[1]; + args[0].f = value; + sendQueuedOSC(pathBuf, "f", args); +} + +void Editor::Impl::performCCBeginEdit(unsigned cc) +{ + // TODO(jpc) CC as parameters and automation +} + +void Editor::Impl::performCCEndEdit(unsigned cc) +{ + // TODO(jpc) CC as parameters and automation } void Editor::Impl::setActivePanel(unsigned panelId) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 16ad7dd9..5f7f7e0f 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -516,3 +516,209 @@ void SStyledKnob::draw(CDrawContext* dc) dc->drawLine(p1, p2); } } + +/// +SControlsPanel::SControlsPanel(const CRect& size) + : CScrollView( + size, CRect(), + CScrollView::kVerticalScrollbar|CScrollView::kDontDrawFrame|CScrollView::kAutoHideScrollbars), + listener_(new ControlSlotListener(this)) +{ + setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); + + setScrollbarWidth(10.0); +} + +void SControlsPanel::setControlUsed(uint32_t index, bool used) +{ + bool changed = false; + + if (used) { + if (index + 1 > slots_.size()) + slots_.resize(index + 1); + ControlSlot* slot = slots_[index].get(); + changed = !slot; + if (changed) { + slot = new ControlSlot; + slots_[index].reset(slot); + + // create controls etc... + CCoord knobWidth = 48.0; + CCoord knobHeight = 72.0; + CCoord labelWidth = 96.0; + CCoord labelHeight = 24.0; + CCoord verticalPadding = -12.0; + + CCoord totalWidth = std::max(knobWidth, labelWidth); + CCoord knobX = (totalWidth - knobWidth) / 2.0; + CCoord labelX = (totalWidth - labelWidth) / 2.0; + + CRect knobBounds(knobX, 0.0, knobX + knobWidth, knobHeight); + CRect labelBounds(labelX, knobHeight + verticalPadding, labelX + labelWidth, knobHeight + verticalPadding + labelHeight); + CRect boxBounds = CRect(knobBounds).unite(labelBounds); + + SharedPointer knob = owned(new SStyledKnob(knobBounds, listener_.get(), index)); + SharedPointer label = owned(new CTextLabel(labelBounds)); + SharedPointer box = owned(new CViewContainer(boxBounds)); + + box->addView(knob); + knob->remember(); + box->addView(label); + label->remember(); + + box->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setStyle(CTextLabel::kRoundRectStyle); + label->setRoundRectRadius(5.0); + label->setBackColor(CColor(0x2e, 0x34, 0x36)); + label->setText(("CC " + std::to_string(index)).c_str()); + knob->setActiveTrackColor(CColor(0x00, 0xb6, 0x2a)); + knob->setInactiveTrackColor(CColor(0x30, 0x30, 0x30)); + knob->setLineIndicatorColor(CColor(0x00, 0x00, 0x00)); + + slot->knob = knob; + slot->label = label; + slot->box = box; + } + } + else { + if (index < slots_.size() && slots_[index]) { + changed = true; + slots_[index].reset(); + } + } + + if (changed) + updateLayout(); +} + +void SControlsPanel::setControlValue(uint32_t index, float value) +{ + if (index >= slots_.size()) + return; + + ControlSlot* slot = slots_[index].get(); + if (!slot) + return; + + slot->knob->setValue(value); +} + +void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) +{ + if (index >= slots_.size()) + return; + + ControlSlot* slot = slots_[index].get(); + if (!slot) + return; + + slot->label->setText(text); +} + +void SControlsPanel::recalculateSubViews() +{ + CScrollView::recalculateSubViews(); + + // maybe the operation just created the scroll bar + if (CScrollbar* vsb = getVerticalScrollbar()) { + // update scrollbar style + vsb->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + vsb->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); + vsb->setScrollerColor(CColor(0x00, 0x00, 0x00, 0x80)); + } +} + +void SControlsPanel::updateLayout() +{ + removeAll(); + + const CRect viewBounds = getViewSize(); + + bool isFirstSlot = true; + CCoord itemWidth {}; + CCoord itemHeight {}; + CCoord itemOffsetX {}; + + int numColumns {}; + const CCoord horizontalPadding = 24.0; + const CCoord verticalPadding = 5.0; + + int currentRow = 0; + int currentColumn = 0; + int containerBottom = 0; + + uint32_t numSlots = static_cast(slots_.size()); + for (uint32_t i = 0; i < numSlots; ++i) { + ControlSlot* slot = slots_[i].get(); + if (!slot) + continue; + + CViewContainer* box = slot->box; + + if (isFirstSlot) { + itemWidth = box->getWidth(); + itemHeight = box->getHeight(); + isFirstSlot = false; + numColumns = int((viewBounds.getWidth() - horizontalPadding) / + (itemWidth + horizontalPadding)); + numColumns = std::max(1, numColumns); + itemOffsetX = (viewBounds.getWidth() - horizontalPadding - + numColumns * (itemWidth + horizontalPadding)) / 2.0; + } + + CRect itemBounds = box->getViewSize(); + itemBounds.moveTo( + itemOffsetX + horizontalPadding + currentColumn * (horizontalPadding + itemWidth), + verticalPadding + currentRow * (verticalPadding + itemHeight)); + + box->setViewSize(itemBounds); + + containerBottom = itemBounds.bottom; + + addView(box); + box->remember(); + + if (++currentColumn == numColumns) { + currentColumn = 0; + ++currentRow; + } + } + + CRect containerSize = getContainerSize(); + containerSize.bottom = containerBottom; + setContainerSize(CRect(0.0, 0.0, viewBounds.getWidth(), containerBottom + verticalPadding)); +} + +void SControlsPanel::ControlSlotListener::valueChanged(CControl* pControl) +{ + if (panel_->ValueChangeFunction) + panel_->ValueChangeFunction(pControl->getTag(), pControl->getValue()); +} + +void SControlsPanel::ControlSlotListener::controlBeginEdit(CControl* pControl) +{ + if (panel_->BeginEditFunction) + panel_->BeginEditFunction(pControl->getTag()); +} + +void SControlsPanel::ControlSlotListener::controlEndEdit(CControl* pControl) +{ + if (panel_->EndEditFunction) + panel_->EndEditFunction(pControl->getTag()); +} + +/// +SPlaceHolder::SPlaceHolder(const CRect& size, const CColor& color) + : CView(size), color_(color) +{ +} + +void SPlaceHolder::draw(CDrawContext* dc) +{ + const CRect bounds = getViewSize(); + dc->setDrawMode(kAliasing); + dc->setFrameColor(color_); + dc->drawRect(bounds); + dc->drawLine(bounds.getTopLeft(), bounds.getBottomRight()); + dc->drawLine(bounds.getTopRight(), bounds.getBottomLeft()); +} diff --git a/editor/src/editor/GUIComponents.h b/editor/src/editor/GUIComponents.h index 8f497a15..a5899550 100644 --- a/editor/src/editor/GUIComponents.h +++ b/editor/src/editor/GUIComponents.h @@ -6,6 +6,9 @@ #pragma once #include +#include +#include +#include #include "utility/vstgui_before.h" #include "vstgui/lib/controls/cslider.h" @@ -13,8 +16,10 @@ #include "vstgui/lib/controls/ctextlabel.h" #include "vstgui/lib/controls/cbuttons.h" #include "vstgui/lib/controls/coptionmenu.h" +#include "vstgui/lib/controls/cscrollbar.h" #include "vstgui/lib/controls/icontrollistener.h" #include "vstgui/lib/cviewcontainer.h" +#include "vstgui/lib/cscrollview.h" #include "vstgui/lib/ccolor.h" #include "vstgui/lib/dragging.h" #include "utility/vstgui_after.h" @@ -210,3 +215,56 @@ private: CColor inactiveTrackColor_; CColor lineIndicatorColor_; }; + +/// +class SControlsPanel : public CScrollView { +public: + explicit SControlsPanel(const CRect& size); + + void setControlUsed(uint32_t index, bool used); + void setControlValue(uint32_t index, float value); + void setControlLabelText(uint32_t index, UTF8StringPtr text); + + std::function ValueChangeFunction; + std::function BeginEditFunction; + std::function EndEditFunction; + +protected: + void recalculateSubViews() override; + +private: + void updateLayout(); + +private: + struct ControlSlot { + SharedPointer knob; + SharedPointer label; + SharedPointer box; + }; + + class ControlSlotListener : public IControlListener { + public: + explicit ControlSlotListener(SControlsPanel* panel) : panel_(panel) {} + void valueChanged(CControl* pControl) override; + void controlBeginEdit(CControl* pControl) override; + void controlEndEdit(CControl* pControl) override; + + private: + SControlsPanel* panel_ = nullptr; + }; + + std::vector> slots_; + std::unique_ptr listener_; +}; + +/// +class SPlaceHolder : public CView { +public: + explicit SPlaceHolder(const CRect& size, const CColor& color = {0xff, 0x00, 0x00, 0xff}); + +protected: + void draw(CDrawContext* dc) override; + +private: + CColor color_; +}; diff --git a/editor/src/editor/layout/main.hpp b/editor/src/editor/layout/main.hpp index 6f4da9e3..6ea0a316 100644 --- a/editor/src/editor/layout/main.hpp +++ b/editor/src/editor/layout/main.hpp @@ -100,14 +100,15 @@ view__29->addView(view__39); LogicalGroup* const view__40 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); subPanels_[kPanelControls] = view__40; view__0->addView(view__40); -view__40->setVisible(false); RoundedGroup* const view__41 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); view__40->addView(view__41); -Label* const view__42 = createLabel(CRect(0, 0, 790, 285), -1, "Controls not available", kCenterText, 40); +ControlsPanel* const view__42 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); +controlsPanel_ = view__42; view__41->addView(view__42); LogicalGroup* const view__43 = createLogicalGroup(CRect(5, 109, 795, 425), -1, "", kCenterText, 14); subPanels_[kPanelSettings] = view__43; view__0->addView(view__43); +view__43->setVisible(false); TitleGroup* const view__44 = createTitleGroup(CRect(255, 26, 535, 126), -1, "Engine", kCenterText, 12); view__43->addView(view__44); ValueMenu* const view__45 = createValueMenu(CRect(25, 60, 85, 85), kTagSetNumVoices, "", kCenterText, 12); From 80eb73f4cb41169c5bf83b5f165667209ee0fa14 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 11:39:26 +0100 Subject: [PATCH 04/13] Also receive CC default value --- editor/src/editor/Editor.cpp | 12 ++++++++++++ editor/src/editor/GUIComponents.cpp | 12 ++++++++++++ editor/src/editor/GUIComponents.h | 1 + 3 files changed, 25 insertions(+) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 3390e290..21462e79 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -156,6 +156,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { void updateCCUsed(unsigned cc, bool used); void updateCCValue(unsigned cc, float value); + void updateCCDefaultValue(unsigned cc, float value); void updateCCLabel(unsigned cc, const char* label); // edition of CC by UI @@ -412,6 +413,8 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi char pathBuf[256]; sprintf(pathBuf, "/cc%u/value", cc); sendQueuedOSC(pathBuf, "", nullptr); + sprintf(pathBuf, "/cc%u/default", cc); + sendQueuedOSC(pathBuf, "", nullptr); sprintf(pathBuf, "/cc%u/label", cc); sendQueuedOSC(pathBuf, "", nullptr); } @@ -420,6 +423,9 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi else if (matchMessage("/cc&/value", path, indices) && !strcmp(sig, "f")) { updateCCValue(indices[0], args[0].f); } + else if (matchMessage("/cc&/default", path, indices) && !strcmp(sig, "f")) { + updateCCDefaultValue(indices[0], args[0].f); + } else if (matchMessage("/cc&/label", path, indices) && !strcmp(sig, "s")) { updateCCLabel(indices[0], args[0].s); } @@ -1203,6 +1209,12 @@ void Editor::Impl::updateCCValue(unsigned cc, float value) panel->setControlValue(cc, value); } +void Editor::Impl::updateCCDefaultValue(unsigned cc, float value) +{ + if (SControlsPanel* panel = controlsPanel_) + panel->setControlDefaultValue(cc, value); +} + void Editor::Impl::updateCCLabel(unsigned cc, const char* label) { if (SControlsPanel* panel = controlsPanel_) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 5f7f7e0f..24efe68b 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -603,6 +603,18 @@ void SControlsPanel::setControlValue(uint32_t index, float value) slot->knob->setValue(value); } +void SControlsPanel::setControlDefaultValue(uint32_t index, float value) +{ + if (index >= slots_.size()) + return; + + ControlSlot* slot = slots_[index].get(); + if (!slot) + return; + + slot->knob->setDefaultValue(value); +} + void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) { if (index >= slots_.size()) diff --git a/editor/src/editor/GUIComponents.h b/editor/src/editor/GUIComponents.h index a5899550..5ba9a6df 100644 --- a/editor/src/editor/GUIComponents.h +++ b/editor/src/editor/GUIComponents.h @@ -223,6 +223,7 @@ public: void setControlUsed(uint32_t index, bool used); void setControlValue(uint32_t index, float value); + void setControlDefaultValue(uint32_t index, float value); void setControlLabelText(uint32_t index, UTF8StringPtr text); std::function ValueChangeFunction; From 4c833b17491a1bb3ff990a2edd4f436b05ec2c5d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 11:48:43 +0100 Subject: [PATCH 05/13] Workaround for knob behavior --- editor/src/editor/GUIComponents.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 24efe68b..36217278 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -544,10 +544,10 @@ void SControlsPanel::setControlUsed(uint32_t index, bool used) // create controls etc... CCoord knobWidth = 48.0; - CCoord knobHeight = 72.0; + CCoord knobHeight = knobWidth; CCoord labelWidth = 96.0; CCoord labelHeight = 24.0; - CCoord verticalPadding = -12.0; + CCoord verticalPadding = 0.0; CCoord totalWidth = std::max(knobWidth, labelWidth); CCoord knobX = (totalWidth - knobWidth) / 2.0; @@ -653,7 +653,7 @@ void SControlsPanel::updateLayout() int numColumns {}; const CCoord horizontalPadding = 24.0; - const CCoord verticalPadding = 5.0; + const CCoord verticalPadding = 18.0; int currentRow = 0; int currentColumn = 0; From 27ec95566a28e10b3f213786a2665ce8de0152a3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 14:30:16 +0100 Subject: [PATCH 06/13] Notify editor of CC changed by MIDI --- editor/src/editor/Editor.cpp | 12 ++++++++++++ src/sfizz/Synth.cpp | 20 +++++++++++++++++++- src/sfizz/SynthPrivate.h | 8 ++++++++ src/sfizz/utility/BitArray.h | 1 + 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 21462e79..128dd9df 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -420,6 +420,18 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi } } } + else if (!strcmp(path, "/cc/changed") && !strcmp(sig, "b")) { + const uint8_t* bitChunks = args[0].b->data; + uint32_t byteSize = args[0].b->size; + for (unsigned cc = 0; cc < 8 * byteSize; ++cc) { + bool changed = bitChunks[cc / 8] & (1u << (cc % 8)); + if (changed) { + char pathBuf[256]; + sprintf(pathBuf, "/cc%u/value", cc); + sendQueuedOSC(pathBuf, "", nullptr); + } + } + } else if (matchMessage("/cc&/value", path, indices) && !strcmp(sig, "f")) { updateCCValue(indices[0], args[0].f); } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 6e736690..d9ecb85e 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -245,6 +245,8 @@ void Synth::Impl::clear() resources_.filePool.clear(); resources_.filePool.setRamLoading(config::loadInRam); clearCCLabels(); + currentUsedCCs_.clear(); + changedCCsThisCycle_.clear(); keyLabels_.clear(); keyswitchLabels_.clear(); globalOpcodes_.clear(); @@ -855,6 +857,12 @@ void Synth::renderBlock(AudioSpan buffer) noexcept buffer.fill(0.0f); } + const size_t numFrames = buffer.getNumFrames(); + if (numFrames < 1) { + CHECKFALSE; + return; + } + if (impl.resources_.synthConfig.freeWheeling) impl.resources_.filePool.waitForBackgroundLoading(); @@ -867,7 +875,6 @@ void Synth::renderBlock(AudioSpan buffer) noexcept impl.resources_.filePool.triggerGarbageCollection(); } - size_t numFrames = buffer.getNumFrames(); auto tempSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); auto tempMixSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); auto rampSpan = impl.resources_.bufferPool.getBuffer(numFrames); @@ -958,6 +965,15 @@ void Synth::renderBlock(AudioSpan buffer) noexcept // Advance the clock to the end of cycle bc.endCycle(); + // Send the set of changed CCs + Client broadcaster = impl.getBroadcaster(); + const BitArray& changedCCs = impl.changedCCsThisCycle_; + if (broadcaster.canReceive()) { + sfizz_blob_t blob { changedCCs.data(), static_cast(changedCCs.byte_size()) }; + broadcaster.receive<'b'>(numFrames - 1, "/cc/changed", &blob); + } + impl.changedCCsThisCycle_.clear(); + { // Clear events and advance midi time ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; impl.resources_.midiState.advanceTime(buffer.getNumFrames()); @@ -1137,6 +1153,8 @@ void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asM ScopedTiming logger { dispatchDuration_, ScopedTiming::Operation::addToDuration }; resources_.midiState.ccEvent(delay, ccNumber, normValue); + changedCCsThisCycle_.set(ccNumber); + if (asMidi) { if (ccNumber == config::resetCC) { resetAllControllers(delay); diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 893e436e..0e251024 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -285,10 +285,18 @@ struct Synth::Impl final: public Parser::Listener { std::array defaultCCValues_; BitArray currentUsedCCs_; + BitArray changedCCsThisCycle_; // Messaging sfizz_receive_t* broadcastReceiver = nullptr; void* broadcastData = nullptr; + + Client getBroadcaster() const + { + Client client(broadcastData); + client.setReceiveCallback(broadcastReceiver); + return client; + } }; } // namespace sfz diff --git a/src/sfizz/utility/BitArray.h b/src/sfizz/utility/BitArray.h index 020a7433..a55dccaf 100644 --- a/src/sfizz/utility/BitArray.h +++ b/src/sfizz/utility/BitArray.h @@ -67,6 +67,7 @@ public: static constexpr size_t byte_size() noexcept { return (N + 7) / 8; } BitSpan span() noexcept { return BitSpan(data_, N); }; ConstBitSpan span() const noexcept { return ConstBitSpan(data_, N); }; + void clear() { span().clear(); } bool test(size_t i) const noexcept { return span().test(i); } void set(size_t i) noexcept { span().set(i); } void set(size_t i, bool b) noexcept { span().set(i, b); } From 3a70c37d931cab7972a3839205dec6571e2c2db3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 14:37:07 +0100 Subject: [PATCH 07/13] Default text for CC labels --- editor/src/editor/GUIComponents.cpp | 12 ++++++++++-- editor/src/editor/GUIComponents.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 36217278..157b6b49 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -570,7 +570,7 @@ void SControlsPanel::setControlUsed(uint32_t index, bool used) label->setStyle(CTextLabel::kRoundRectStyle); label->setRoundRectRadius(5.0); label->setBackColor(CColor(0x2e, 0x34, 0x36)); - label->setText(("CC " + std::to_string(index)).c_str()); + label->setText(getDefaultLabelText(index)); knob->setActiveTrackColor(CColor(0x00, 0xb6, 0x2a)); knob->setInactiveTrackColor(CColor(0x30, 0x30, 0x30)); knob->setLineIndicatorColor(CColor(0x00, 0x00, 0x00)); @@ -591,6 +591,11 @@ void SControlsPanel::setControlUsed(uint32_t index, bool used) updateLayout(); } +std::string SControlsPanel::getDefaultLabelText(uint32_t index) +{ + return "CC " + std::to_string(index); +} + void SControlsPanel::setControlValue(uint32_t index, float value) { if (index >= slots_.size()) @@ -624,7 +629,10 @@ void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) if (!slot) return; - slot->label->setText(text); + if (text && text[0] != '\0') + slot->label->setText(text); + else + slot->label->setText(getDefaultLabelText(index).c_str()); } void SControlsPanel::recalculateSubViews() diff --git a/editor/src/editor/GUIComponents.h b/editor/src/editor/GUIComponents.h index 5ba9a6df..e2c987f6 100644 --- a/editor/src/editor/GUIComponents.h +++ b/editor/src/editor/GUIComponents.h @@ -235,6 +235,7 @@ protected: private: void updateLayout(); + static std::string getDefaultLabelText(uint32_t index); private: struct ControlSlot { From 20d5d93b7ec47d57d7ee3be3226c97f59e024efe Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 14:41:16 +0100 Subject: [PATCH 08/13] Request all CC slots on file change --- editor/src/editor/Editor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 128dd9df..01923b98 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -924,6 +924,9 @@ void Editor::Impl::changeSfzFile(const std::string& filePath) ctrl_->uiSendValue(EditId::SfzFile, filePath); currentSfzFile_ = filePath; updateSfzFileLabel(filePath); + + // request the whole CC information + sendQueuedOSC("/cc/slots", "", nullptr); } void Editor::Impl::changeToNextSfzFile(long offset) From fb012c4bf8e60da3f85cc53f6770a969496a8aaa Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 14:42:24 +0100 Subject: [PATCH 09/13] Redraw CC panel on layout --- editor/src/editor/GUIComponents.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 157b6b49..3f126dcb 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -707,6 +707,8 @@ void SControlsPanel::updateLayout() CRect containerSize = getContainerSize(); containerSize.bottom = containerBottom; setContainerSize(CRect(0.0, 0.0, viewBounds.getWidth(), containerBottom + verticalPadding)); + + invalid(); } void SControlsPanel::ControlSlotListener::valueChanged(CControl* pControl) From 36672546bc9892cb4b390ce1788e4c3d6252703f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 14:54:21 +0100 Subject: [PATCH 10/13] Update CC slots on SFZ file changed externally (LV2) --- editor/src/editor/Editor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 01923b98..ed85232f 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -232,6 +232,9 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) const std::string& value = v.to_string(); currentSfzFile_ = value; updateSfzFileLabel(value); + + // request the whole CC information + sendQueuedOSC("/cc/slots", "", nullptr); } break; case EditId::Volume: From 660dcb01d11153c687588d989ae7b3d138415528 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 15:22:08 +0100 Subject: [PATCH 11/13] Fix: do not activate the timer while UI is closed --- editor/src/editor/Editor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index ed85232f..69d0f0ff 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -451,6 +451,9 @@ void Editor::Impl::uiReceiveMessage(const char* path, const char* sig, const sfi void Editor::Impl::sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args) { + if (!frame_) + return; + uint32_t oscSize = sfizz_prepare_message(nullptr, 0, path, sig, args); std::string oscData(oscSize, '\0'); sfizz_prepare_message(&oscData[0], oscSize, path, sig, args); From 787f388a268050c24b30fe8dd01ff0927368eb43 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 15:25:33 +0100 Subject: [PATCH 12/13] Invalidate the controls after changes --- editor/src/editor/GUIComponents.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/editor/GUIComponents.cpp b/editor/src/editor/GUIComponents.cpp index 3f126dcb..5051e952 100644 --- a/editor/src/editor/GUIComponents.cpp +++ b/editor/src/editor/GUIComponents.cpp @@ -606,6 +606,7 @@ void SControlsPanel::setControlValue(uint32_t index, float value) return; slot->knob->setValue(value); + slot->knob->invalid(); } void SControlsPanel::setControlDefaultValue(uint32_t index, float value) @@ -633,6 +634,7 @@ void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) slot->label->setText(text); else slot->label->setText(getDefaultLabelText(index).c_str()); + slot->label->invalid(); } void SControlsPanel::recalculateSubViews() From e431591d418fc528b95f769e20f6dead38e521ad Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 3 Feb 2021 16:00:45 +0100 Subject: [PATCH 13/13] Clear OSC queue on editor closed --- editor/src/editor/Editor.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/editor/src/editor/Editor.cpp b/editor/src/editor/Editor.cpp index 69d0f0ff..a8a90650 100644 --- a/editor/src/editor/Editor.cpp +++ b/editor/src/editor/Editor.cpp @@ -114,6 +114,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { // queued OSC API; sends OSC with intermediate delay between messages // to prevent message bursts overloading the buffer void sendQueuedOSC(const char* path, const char* sig, const sfizz_arg_t* args); + void clearQueuedOSC(); void tickOSCQueue(CVSTGUITimer* timer); std::queue oscSendQueue_; SharedPointer oscSendQueueTimer_; @@ -218,6 +219,8 @@ void Editor::close() { Impl& impl = *impl_; + impl.clearQueuedOSC(); + if (impl.frame_) { impl.frame_->removeView(impl.mainView_.get(), false); impl.frame_ = nullptr; @@ -461,6 +464,12 @@ void Editor::Impl::sendQueuedOSC(const char* path, const char* sig, const sfizz_ oscSendQueueTimer_->start(); } +void Editor::Impl::clearQueuedOSC() +{ + while (!oscSendQueue_.empty()) + oscSendQueue_.pop(); +} + void Editor::Impl::tickOSCQueue(CVSTGUITimer* timer) { if (oscSendQueue_.empty()) {