Merge pull request #624 from jpcima/cc-osc

CC panel for user interface
This commit is contained in:
JP Cimalando 2021-02-03 16:03:24 +01:00 committed by GitHub
commit dda909013d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 748 additions and 72 deletions

View file

@ -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
} {

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"
@ -103,9 +106,19 @@ 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;
// 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<std::string> oscSendQueue_;
SharedPointer<CVSTGUITimer> oscSendQueueTimer_;
void createFrameContents();
template <class Control>
@ -142,6 +155,16 @@ 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 updateCCDefaultValue(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, ...);
@ -164,6 +187,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,12 +210,17 @@ 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()
{
Impl& impl = *impl_;
impl.clearQueuedOSC();
if (impl.frame_) {
impl.frame_->removeView(impl.mainView_.get(), false);
impl.frame_ = nullptr;
@ -202,6 +235,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:
@ -336,9 +372,118 @@ 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/default", cc);
sendQueuedOSC(pathBuf, "", nullptr);
sprintf(pathBuf, "/cc%u/label", cc);
sendQueuedOSC(pathBuf, "", nullptr);
}
}
}
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);
}
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);
}
else {
//fprintf(stderr, "Receive unhandled OSC: %s\n", path);
}
}
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);
oscSendQueue_.push(std::move(oscData));
oscSendQueueTimer_->start();
}
void Editor::Impl::clearQueuedOSC()
{
while (!oscSendQueue_.empty())
oscSendQueue_.pop();
}
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()
@ -428,6 +573,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);
@ -601,6 +747,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"
@ -739,6 +889,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;
@ -777,6 +939,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)
@ -1062,6 +1227,51 @@ void Editor::Impl::updateStretchedTuningLabel(float stretchedTuning)
label->setText(text);
}
void Editor::Impl::updateCCUsed(unsigned cc, bool used)
{
if (SControlsPanel* panel = controlsPanel_)
panel->setControlUsed(cc, used);
}
void Editor::Impl::updateCCValue(unsigned cc, float value)
{
if (SControlsPanel* panel = controlsPanel_)
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_)
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)
{
panelId = std::max(0, std::min(kNumPanels - 1, static_cast<int>(panelId)));

View file

@ -516,3 +516,233 @@ 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 = knobWidth;
CCoord labelWidth = 96.0;
CCoord labelHeight = 24.0;
CCoord verticalPadding = 0.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<SStyledKnob> knob = owned(new SStyledKnob(knobBounds, listener_.get(), index));
SharedPointer<CTextLabel> label = owned(new CTextLabel(labelBounds));
SharedPointer<CViewContainer> 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(getDefaultLabelText(index));
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();
}
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())
return;
ControlSlot* slot = slots_[index].get();
if (!slot)
return;
slot->knob->setValue(value);
slot->knob->invalid();
}
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())
return;
ControlSlot* slot = slots_[index].get();
if (!slot)
return;
if (text && text[0] != '\0')
slot->label->setText(text);
else
slot->label->setText(getDefaultLabelText(index).c_str());
slot->label->invalid();
}
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 = 18.0;
int currentRow = 0;
int currentColumn = 0;
int containerBottom = 0;
uint32_t numSlots = static_cast<uint32_t>(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));
invalid();
}
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());
}

View file

@ -6,6 +6,9 @@
#pragma once
#include <bitset>
#include <vector>
#include <memory>
#include <functional>
#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,58 @@ 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 setControlDefaultValue(uint32_t index, float value);
void setControlLabelText(uint32_t index, UTF8StringPtr text);
std::function<void(uint32_t, float)> ValueChangeFunction;
std::function<void(uint32_t)> BeginEditFunction;
std::function<void(uint32_t)> EndEditFunction;
protected:
void recalculateSubViews() override;
private:
void updateLayout();
static std::string getDefaultLabelText(uint32_t index);
private:
struct ControlSlot {
SharedPointer<CControl> knob;
SharedPointer<CTextLabel> label;
SharedPointer<CViewContainer> 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<std::unique_ptr<ControlSlot>> slots_;
std::unique_ptr<ControlSlotListener> 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_;
};

View file

@ -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);

View file

@ -244,7 +244,9 @@ void Synth::Impl::clear()
resources_.midiState.reset();
resources_.filePool.clear();
resources_.filePool.setRamLoading(config::loadInRam);
ccLabels_.clear();
clearCCLabels();
currentUsedCCs_.clear();
changedCCsThisCycle_.clear();
keyLabels_.clear();
keyswitchLabels_.clear();
globalOpcodes_.clear();
@ -261,9 +263,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<Opcode>& members)
@ -365,7 +367,7 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& 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()) {
@ -696,7 +698,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);
@ -855,6 +857,12 @@ void Synth::renderBlock(AudioSpan<float> 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<float> 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<float> buffer) noexcept
// Advance the clock to the end of cycle
bc.endCycle();
// Send the set of changed CCs
Client broadcaster = impl.getBroadcaster();
const BitArray<config::numCCs>& changedCCs = impl.changedCCsThisCycle_;
if (broadcaster.canReceive()) {
sfizz_blob_t blob { changedCCs.data(), static_cast<uint32_t>(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);
@ -1328,8 +1346,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 +1734,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 +1747,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 +1781,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,22 +1796,46 @@ 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);
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_;

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

@ -36,6 +36,42 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
client.receive(delay, "/hello", "", nullptr);
} break;
//----------------------------------------------------------------------
MATCH("/cc/slots", "") {
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;
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);

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,20 @@ 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);
void clearCCLabels();
/**
* @brief Perform a CC event
@ -208,6 +213,7 @@ struct Synth::Impl final: public Parser::Listener {
// Names for the CC and notes as set by label_cc and label_key
std::vector<CCNamePair> ccLabels_;
std::map<int, size_t> ccLabelsMap_;
std::vector<NoteNamePair> keyLabels_;
std::vector<NoteNamePair> keyswitchLabels_;
@ -278,11 +284,19 @@ 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_;
BitArray<config::numCCs> changedCCsThisCycle_;
// Messaging
sfizz_receive_t* broadcastReceiver = nullptr;
void* broadcastData = nullptr;
Client getBroadcaster() const
{
Client client(broadcastData);
client.setReceiveCallback(broadcastReceiver);
return client;
}
};
} // namespace sfz

View file

@ -0,0 +1,82 @@
// 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); };
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); }
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")