commit
dda909013d
11 changed files with 748 additions and 72 deletions
|
|
@ -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
|
||||
} {
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
82
src/sfizz/utility/BitArray.h
Normal file
82
src/sfizz/utility/BitArray.h
Normal 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()] {};
|
||||
};
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue