Add the reception of CC messages for editor

This commit is contained in:
Jean Pierre Cimalando 2020-12-14 21:14:35 +01:00
parent c81f74100b
commit 751e4f96bb
7 changed files with 263 additions and 61 deletions

View file

@ -13,14 +13,17 @@
#include <absl/strings/string_view.h>
#include <absl/strings/match.h>
#include <absl/strings/ascii.h>
#include <absl/strings/numbers.h>
#include <ghc/fs_std.hpp>
#include <array>
#include <queue>
#include <algorithm>
#include <functional>
#include <type_traits>
#include <system_error>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#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<std::string> oscSendQueue_;
SharedPointer<CVSTGUITimer> oscSendQueueTimer_;
void createFrameContents();
template <class Control>
@ -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<CVSTGUITimer>(
[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<int>(panelId)));

View file

@ -696,7 +696,7 @@ void Synth::Impl::finalizeSfzLoad()
regions_.resize(currentRegionCount);
// collect all CCs used in regions, with matrix not yet connected
std::bitset<config::numCCs> usedCCs;
BitArray<config::numCCs> 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<unsigned>(128, anonymousCCs.size()); i < n; ++i) {
if (anonymousCCs[i]) {
for (unsigned i = 0, n = std::min<unsigned>(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<config::numCCs>& Synth::getUsedCCs() const noexcept
const BitArray<config::numCCs>& 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<config::numCCs>& usedCCs, const Region& region)
void Synth::Impl::collectUsedCCsFromRegion(BitArray<config::numCCs>& usedCCs, const Region& region)
{
collectUsedCCsFromCCMap(usedCCs, region.offsetCC);
collectUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccAttack);
@ -1763,11 +1763,11 @@ void Synth::Impl::collectUsedCCsFromRegion(std::bitset<config::numCCs>& usedCCs,
collectUsedCCsFromCCMap(usedCCs, region.crossfadeCCOutRange);
}
void Synth::Impl::collectUsedCCsFromModulations(std::bitset<config::numCCs>& usedCCs, const ModMatrix& mm)
void Synth::Impl::collectUsedCCsFromModulations(BitArray<config::numCCs>& usedCCs, const ModMatrix& mm)
{
class CCSourceCollector : public ModMatrix::KeyVisitor {
public:
explicit CCSourceCollector(std::bitset<config::numCCs>& used)
explicit CCSourceCollector(BitArray<config::numCCs>& used)
: used_(used)
{
}
@ -1778,16 +1778,16 @@ void Synth::Impl::collectUsedCCsFromModulations(std::bitset<config::numCCs>& use
used_.set(key.parameters().cc);
return true;
}
std::bitset<config::numCCs>& used_;
BitArray<config::numCCs>& used_;
};
CCSourceCollector vtor(usedCCs);
mm.visitSources(vtor);
}
std::bitset<config::numCCs> Synth::Impl::collectAllUsedCCs()
BitArray<config::numCCs> Synth::Impl::collectAllUsedCCs()
{
std::bitset<config::numCCs> used;
BitArray<config::numCCs> used;
for (const Impl::RegionPtr& region : regions_)
collectUsedCCsFromRegion(used, *region);
collectUsedCCsFromModulations(used, resources_.modMatrix);

View file

@ -17,6 +17,7 @@
#include <bitset>
#include <string>
#include <vector>
template <size_t> class BitArray;
namespace sfz {
@ -599,7 +600,7 @@ public:
*
* @return const std::bitset<config::numCCs>&
*/
const std::bitset<config::numCCs>& getUsedCCs() const noexcept;
const BitArray<config::numCCs>& getUsedCCs() const noexcept;
/**
* @brief Dispatch the incoming message to the synth engine

View file

@ -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<config::numCCs>& 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<config::numCCs>& ccs = impl.currentUsedCCs_;
sfizz_blob_t blob { ccs.data(), static_cast<uint32_t>(ccs.byte_size()) };
client.receive<'b'>(delay, path, &blob);
} break;

View file

@ -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<class T>
static void collectUsedCCsFromCCMap(std::bitset<config::numCCs>& usedCCs, const CCMap<T> map) noexcept
static void collectUsedCCsFromCCMap(BitArray<config::numCCs>& usedCCs, const CCMap<T> map) noexcept
{
for (auto& mod : map)
usedCCs[mod.cc] = true;
usedCCs.set(mod.cc);
}
static void collectUsedCCsFromRegion(std::bitset<config::numCCs>& usedCCs, const Region& region);
static void collectUsedCCsFromModulations(std::bitset<config::numCCs>& usedCCs, const ModMatrix& mm);
static void collectUsedCCsFromRegion(BitArray<config::numCCs>& usedCCs, const Region& region);
static void collectUsedCCsFromModulations(BitArray<config::numCCs>& usedCCs, const ModMatrix& mm);
std::bitset<config::numCCs> collectAllUsedCCs();
BitArray<config::numCCs> 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<fs::file_time_type> modificationTime_ { };
std::array<float, config::numCCs> defaultCCValues_;
std::bitset<config::numCCs> currentUsedCCs_;
BitArray<config::numCCs> currentUsedCCs_;
// Messaging
sfizz_receive_t* broadcastReceiver = nullptr;

View file

@ -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 <cstdint>
#include <cstddef>
#include <cstring>
///
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<uint8_t*>(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 <size_t N>
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()] {};
};

View file

@ -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 <algorithm>
#include "catch2/catch.hpp"
@ -1087,18 +1088,18 @@ TEST_CASE("[Synth] Used CCs")
<region> 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")