Merge branch 'develop' into keydelta
This commit is contained in:
commit
d31d127ab9
51 changed files with 1848 additions and 336 deletions
|
|
@ -88,6 +88,8 @@ std::string getDescriptionBlob(sfizz_synth_t* handle)
|
||||||
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default");
|
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/default");
|
||||||
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
|
bufferedStrCat(cdata.pathbuf, "/cc", cc, "/value");
|
||||||
|
cdata.synth->sendMessage(*cdata.client, 0, cdata.pathbuf->c_str(), "", nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +165,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob)
|
||||||
desc.ccLabel[indices[0]] = args[0].s;
|
desc.ccLabel[indices[0]] = args[0].s;
|
||||||
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f"))
|
else if (Messages::matchOSC("/cc&/default", path, indices) && !strcmp(sig, "f"))
|
||||||
desc.ccDefault[indices[0]] = args[0].f;
|
desc.ccDefault[indices[0]] = args[0].f;
|
||||||
|
else if (Messages::matchOSC("/cc&/value", path, indices) && !strcmp(sig, "f"))
|
||||||
|
desc.ccValue[indices[0]] = args[0].f;
|
||||||
|
|
||||||
src += byteCount;
|
src += byteCount;
|
||||||
srcSize -= byteCount;
|
srcSize -= byteCount;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ struct InstrumentDescription {
|
||||||
std::array<std::string, 128> keyswitchLabel {};
|
std::array<std::string, 128> keyswitchLabel {};
|
||||||
std::array<std::string, sfz::config::numCCs> ccLabel {};
|
std::array<std::string, sfz::config::numCCs> ccLabel {};
|
||||||
std::array<float, sfz::config::numCCs> ccDefault {};
|
std::array<float, sfz::config::numCCs> ccDefault {};
|
||||||
|
std::array<float, sfz::config::numCCs> ccValue {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,48 @@
|
||||||
#include <simde/x86/sse.h>
|
#include <simde/x86/sse.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <malloc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
class RMSFollower {
|
class RMSFollower {
|
||||||
public:
|
public:
|
||||||
RMSFollower()
|
RMSFollower()
|
||||||
{
|
{
|
||||||
|
setNumOutputs(numOutputs_);
|
||||||
updatePole();
|
updatePole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~RMSFollower()
|
||||||
|
{
|
||||||
|
freeAlignedMemory();
|
||||||
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
mem_ = simde_mm_setzero_ps();
|
for (int c = 0; c < numOutputs_; c += 4) {
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + c);
|
||||||
|
*mem = simde_mm_setzero_ps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNumOutputs(int numOutputs)
|
||||||
|
{
|
||||||
|
freeAlignedMemory();
|
||||||
|
numOutputs_ = numOutputs;
|
||||||
|
memSize_ = numOutputs % 4 == 0 ? numOutputs * sizeof(float) : (numOutputs / 4 + 1) * 4 * sizeof(float);
|
||||||
|
#ifdef _WIN32
|
||||||
|
mem_ = (float *)_aligned_malloc(memSize_, 4 * sizeof(float));
|
||||||
|
#else
|
||||||
|
mem_ = (float *)aligned_alloc(4 * sizeof(float), memSize_);
|
||||||
|
#endif
|
||||||
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(float sampleRate)
|
void init(float sampleRate)
|
||||||
{
|
{
|
||||||
sampleRate_ = sampleRate;
|
sampleRate_ = sampleRate;
|
||||||
updatePole();
|
updatePole();
|
||||||
clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setT60(float t60)
|
void setT60(float t60)
|
||||||
|
|
@ -34,40 +58,129 @@ public:
|
||||||
updatePole();
|
updatePole();
|
||||||
}
|
}
|
||||||
|
|
||||||
void process(const float* leftBlock, const float* rightBlock, size_t numFrames)
|
void process(const float** blocks, size_t numFrames, size_t numChannels)
|
||||||
{
|
{
|
||||||
simde__m128 mem = mem_;
|
assert(numChannels <= static_cast<size_t>(numOutputs_));
|
||||||
const simde__m128 pole = simde_mm_load1_ps(&pole_);
|
const auto numSafeChannels = (numChannels / 4) * 4;
|
||||||
for (size_t i = 0; i < numFrames; ++i) {
|
for (size_t c = 0; c < numSafeChannels; c += 4) {
|
||||||
float left = leftBlock[i];
|
simde__m128* mem = (simde__m128*) (mem_ + c);
|
||||||
float right = rightBlock[i];
|
process4(mem, &blocks[c], numFrames);
|
||||||
simde__m128 input = simde_mm_setr_ps(left, right, 0.0f, 0.0f);
|
|
||||||
input = simde_mm_mul_ps(input, input);
|
|
||||||
simde__m128 output = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(mem, input)));
|
|
||||||
mem = output;
|
|
||||||
}
|
}
|
||||||
mem_ = mem;
|
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + numSafeChannels);
|
||||||
|
const auto remainingChannels = numChannels - numSafeChannels;
|
||||||
|
if (remainingChannels >= 4)
|
||||||
|
process4(mem, &blocks[numSafeChannels], numFrames);
|
||||||
|
else if (remainingChannels == 3)
|
||||||
|
process3(mem, &blocks[numSafeChannels], numFrames);
|
||||||
|
else if (remainingChannels == 2)
|
||||||
|
process2(mem, &blocks[numSafeChannels], numFrames);
|
||||||
|
else if (remainingChannels == 1)
|
||||||
|
process1(mem, &blocks[numSafeChannels], numFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
simde__m128 getMS() const
|
void getMS(float* ms, size_t numChannels) const
|
||||||
{
|
{
|
||||||
return mem_;
|
assert(numChannels <= static_cast<size_t>(numOutputs_));
|
||||||
|
const auto numSafeChannels = (numChannels / 4) * 4;
|
||||||
|
for (size_t c = 0; c < numSafeChannels; c += 4) {
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + c);
|
||||||
|
simde_mm_store_ps(ms + c, *mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + numSafeChannels);
|
||||||
|
float* temp = (float*)&temp;
|
||||||
|
simde_mm_store_ps(temp, *mem);
|
||||||
|
for (size_t c = numSafeChannels, t = 0; c < numChannels; c++, t++)
|
||||||
|
ms[c] = temp[t];
|
||||||
}
|
}
|
||||||
|
|
||||||
simde__m128 getRMS() const
|
void getRMS(float* rms, size_t numChannels) const
|
||||||
{
|
{
|
||||||
return simde_mm_sqrt_ps(mem_);
|
assert(numChannels <= static_cast<size_t>(numOutputs_));
|
||||||
|
const auto numSafeChannels = (numChannels / 4) * 4;
|
||||||
|
for (size_t c = 0; c < numSafeChannels; c += 4) {
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + c);
|
||||||
|
simde_mm_store_ps(rms + c, simde_mm_sqrt_ps(*mem));
|
||||||
|
}
|
||||||
|
|
||||||
|
simde__m128* mem = (simde__m128*) (mem_ + numSafeChannels);
|
||||||
|
float* temp = (float*)&temp_;
|
||||||
|
simde_mm_store_ps(temp, simde_mm_sqrt_ps(*mem));
|
||||||
|
for (size_t c = numSafeChannels, t = 0; c < numChannels; c++, t++)
|
||||||
|
rms[c] = temp[t];
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void freeAlignedMemory()
|
||||||
|
{
|
||||||
|
if (mem_)
|
||||||
|
#ifdef _WIN32
|
||||||
|
_aligned_free(mem_);
|
||||||
|
#else
|
||||||
|
free(mem_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void updatePole()
|
void updatePole()
|
||||||
{
|
{
|
||||||
pole_ = std::exp(float(-2.0 * M_PI) / (t60_ * sampleRate_));
|
pole_ = std::exp(float(-2.0 * M_PI) / (t60_ * sampleRate_));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void process1(simde__m128* mem, const float** blocks, size_t numFrames)
|
||||||
|
{
|
||||||
|
simde__m128 input;
|
||||||
|
const simde__m128 pole = simde_mm_load1_ps(&pole_);
|
||||||
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
input = simde_mm_setr_ps(
|
||||||
|
blocks[0][i], 0.0f, 0.0f, 0.0f);
|
||||||
|
input = simde_mm_mul_ps(input, input);
|
||||||
|
*mem = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(*mem, input)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process2(simde__m128* mem, const float** blocks, size_t numFrames)
|
||||||
|
{
|
||||||
|
simde__m128 input;
|
||||||
|
const simde__m128 pole = simde_mm_load1_ps(&pole_);
|
||||||
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
input = simde_mm_setr_ps(
|
||||||
|
blocks[0][i], blocks[1][i], 0.0f, 0.0f);
|
||||||
|
input = simde_mm_mul_ps(input, input);
|
||||||
|
*mem = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(*mem, input)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process3(simde__m128* mem, const float** blocks, size_t numFrames)
|
||||||
|
{
|
||||||
|
simde__m128 input;
|
||||||
|
const simde__m128 pole = simde_mm_load1_ps(&pole_);
|
||||||
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
input = simde_mm_setr_ps(
|
||||||
|
blocks[0][i], blocks[1][i], blocks[2][i], 0.0f);
|
||||||
|
input = simde_mm_mul_ps(input, input);
|
||||||
|
*mem = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(*mem, input)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process4(simde__m128* mem, const float** blocks, size_t numFrames)
|
||||||
|
{
|
||||||
|
simde__m128 input;
|
||||||
|
const simde__m128 pole = simde_mm_load1_ps(&pole_);
|
||||||
|
for (size_t i = 0; i < numFrames; ++i) {
|
||||||
|
input = simde_mm_setr_ps(
|
||||||
|
blocks[0][i], blocks[1][i], blocks[2][i], blocks[3][i]);
|
||||||
|
input = simde_mm_mul_ps(input, input);
|
||||||
|
*mem = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(*mem, input)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
simde__m128 mem_ {};
|
float* mem_ { nullptr };
|
||||||
|
simde__m128 temp_;
|
||||||
float pole_ {};
|
float pole_ {};
|
||||||
float t60_ = 300e-3;
|
float t60_ = 300e-3;
|
||||||
float sampleRate_ = 44100;
|
float sampleRate_ = 44100;
|
||||||
|
int numOutputs_ = 2;
|
||||||
|
int memSize_ = 4;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# data file for the Fltk User Interface Designer (fluid)
|
# data file for the Fltk User Interface Designer (fluid)
|
||||||
version 1.0306
|
version 1.0304
|
||||||
header_name {.h}
|
header_name {.h}
|
||||||
code_name {.cxx}
|
code_name {.cxx}
|
||||||
widget_class mainView {open
|
widget_class mainView {open
|
||||||
|
|
@ -12,10 +12,10 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
Fl_Group {} {
|
Fl_Group {} {
|
||||||
comment {palette=invertedPalette} open
|
comment {palette=invertedPalette} open
|
||||||
xywh {0 0 800 110}
|
xywh {0 0 814 110}
|
||||||
class LogicalGroup
|
class LogicalGroup
|
||||||
} {
|
} {
|
||||||
Fl_Group {} {
|
Fl_Group {} {open
|
||||||
xywh {5 4 175 101} box ROUNDED_BOX align 0
|
xywh {5 4 175 101} box ROUNDED_BOX align 0
|
||||||
class RoundedGroup
|
class RoundedGroup
|
||||||
} {
|
} {
|
||||||
|
|
@ -45,18 +45,18 @@ widget_class mainView {open
|
||||||
class HomeButton
|
class HomeButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Group {} {
|
Fl_Group {} {open
|
||||||
xywh {185 5 418 100} box ROUNDED_BOX
|
xywh {185 5 390 100} box ROUNDED_BOX
|
||||||
class RoundedGroup
|
class RoundedGroup
|
||||||
} {
|
} {
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Separator 1}
|
label {Separator 1}
|
||||||
xywh {195 41 395 5} box BORDER_BOX labeltype NO_LABEL
|
xywh {195 41 370 5} box BORDER_BOX labeltype NO_LABEL
|
||||||
class HLine
|
class HLine
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Separator 2}
|
label {Separator 2}
|
||||||
xywh {195 73 395 5} box BORDER_BOX labeltype NO_LABEL
|
xywh {195 73 370 5} box BORDER_BOX labeltype NO_LABEL
|
||||||
class HLine
|
class HLine
|
||||||
}
|
}
|
||||||
Fl_Box sfzFileLabel_ {
|
Fl_Box sfzFileLabel_ {
|
||||||
|
|
@ -66,7 +66,7 @@ widget_class mainView {open
|
||||||
class ClickableLabel
|
class ClickableLabel
|
||||||
}
|
}
|
||||||
Fl_Box keyswitchLabel_ {
|
Fl_Box keyswitchLabel_ {
|
||||||
xywh {265 45 325 35} labelsize 20 align 20
|
xywh {265 45 310 35} labelsize 20 align 20
|
||||||
class Label
|
class Label
|
||||||
}
|
}
|
||||||
Fl_Box keyswitchBadge_ {
|
Fl_Box keyswitchBadge_ {
|
||||||
|
|
@ -85,17 +85,17 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
Fl_Button {} {
|
Fl_Button {} {
|
||||||
comment {tag=kTagPreviousSfzFile}
|
comment {tag=kTagPreviousSfzFile}
|
||||||
xywh {515 18 25 25} labelsize 24
|
xywh {488 18 25 25} labelsize 24
|
||||||
class PreviousFileButton
|
class PreviousFileButton
|
||||||
}
|
}
|
||||||
Fl_Button {} {
|
Fl_Button {} {
|
||||||
comment {tag=kTagNextSfzFile}
|
comment {tag=kTagNextSfzFile}
|
||||||
xywh {540 18 25 25} labelsize 24
|
xywh {513 18 25 25} labelsize 24
|
||||||
class NextFileButton
|
class NextFileButton
|
||||||
}
|
}
|
||||||
Fl_Button fileOperationsMenu_ {
|
Fl_Button fileOperationsMenu_ {
|
||||||
comment {tag=kTagFileOperations}
|
comment {tag=kTagFileOperations}
|
||||||
xywh {565 18 25 25} labelsize 24
|
xywh {538 18 25 25} labelsize 24
|
||||||
class ChevronDropDown
|
class ChevronDropDown
|
||||||
}
|
}
|
||||||
Fl_Box infoVoicesLabel_ {
|
Fl_Box infoVoicesLabel_ {
|
||||||
|
|
@ -104,7 +104,7 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Max:}
|
label {Max:}
|
||||||
xywh {332 78 40 25} labelsize 12 align 24
|
xywh {322 78 40 25} labelsize 12 align 24
|
||||||
class Label
|
class Label
|
||||||
}
|
}
|
||||||
Fl_Box numVoicesLabel_ {
|
Fl_Box numVoicesLabel_ {
|
||||||
|
|
@ -113,11 +113,11 @@ widget_class mainView {open
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label {Memory:}
|
label {Memory:}
|
||||||
xywh {459 78 60 25} labelsize 12 align 24
|
xywh {456 78 60 25} labelsize 12 align 24
|
||||||
class Label
|
class Label
|
||||||
}
|
}
|
||||||
Fl_Box memoryLabel_ {
|
Fl_Box memoryLabel_ {
|
||||||
xywh {525 78 60 25} labelsize 12 align 16
|
xywh {522 78 50 25} labelsize 12 align 16
|
||||||
class Label
|
class Label
|
||||||
}
|
}
|
||||||
Fl_Button numVoicesSlider_ {
|
Fl_Button numVoicesSlider_ {
|
||||||
|
|
@ -126,42 +126,42 @@ widget_class mainView {open
|
||||||
class ChevronValueDropDown
|
class ChevronValueDropDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Group {} {
|
Fl_Group {} {open
|
||||||
xywh {608 5 187 100} box ROUNDED_BOX
|
xywh {580 5 215 100} box ROUNDED_BOX
|
||||||
class RoundedGroup
|
class RoundedGroup
|
||||||
} {
|
} {
|
||||||
Fl_Dial {} {
|
Fl_Dial {} {
|
||||||
xywh {615 20 48 48} value 0.5 hide
|
xywh {587 20 48 48} value 0.5 hide
|
||||||
class Knob48
|
class Knob48
|
||||||
}
|
}
|
||||||
Fl_Box {} {
|
Fl_Box {} {
|
||||||
label Center
|
label Center
|
||||||
xywh {610 70 60 5} labelsize 12 hide
|
xywh {582 70 60 5} labelsize 12 hide
|
||||||
class ValueLabel
|
class ValueLabel
|
||||||
}
|
}
|
||||||
Fl_Box volumeCCKnob_ {
|
Fl_Box volumeCCKnob_ {
|
||||||
label Volume
|
label Volume
|
||||||
comment {tag=kTagSetCCVolume}
|
comment {tag=kTagSetCCVolume}
|
||||||
xywh {614 10 70 90} box BORDER_BOX labelsize 12 align 17
|
xywh {586 10 70 90} box BORDER_BOX labelsize 12 align 17
|
||||||
class KnobCCBox
|
class KnobCCBox
|
||||||
}
|
}
|
||||||
Fl_Box panCCKnob_ {
|
Fl_Box panCCKnob_ {
|
||||||
label Pan
|
label Pan
|
||||||
comment {tag=kTagSetCCPan}
|
comment {tag=kTagSetCCPan}
|
||||||
xywh {691 10 70 90} box BORDER_BOX labelsize 12 align 17
|
xywh {663 10 70 90} box BORDER_BOX labelsize 12 align 17
|
||||||
class KnobCCBox
|
class KnobCCBox
|
||||||
}
|
}
|
||||||
Fl_Box leftMeter_ {
|
Fl_Box {meters_[0]} {
|
||||||
xywh {767 10 9 90} box BORDER_BOX
|
xywh {739 10 23 90} box BORDER_BOX
|
||||||
class VMeter
|
class VMeter
|
||||||
}
|
}
|
||||||
Fl_Box rightMeter_ {
|
Fl_Box {meters_[1]} {selected
|
||||||
xywh {779 10 9 90} box BORDER_BOX
|
xywh {763 10 23 90} box BORDER_BOX
|
||||||
class VMeter
|
class VMeter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Fl_Group {subPanels_[kPanelInfo]} {open
|
Fl_Group {subPanels_[kPanelInfo]} {
|
||||||
xywh {5 110 790 285} hide
|
xywh {5 110 790 285} hide
|
||||||
class LogicalGroup
|
class LogicalGroup
|
||||||
} {
|
} {
|
||||||
|
|
@ -386,7 +386,7 @@ widget_class mainView {open
|
||||||
xywh {5 400 790 70} labelsize 12
|
xywh {5 400 790 70} labelsize 12
|
||||||
class Piano
|
class Piano
|
||||||
}
|
}
|
||||||
Fl_Group {subPanels_[kPanelGeneral]} {open selected
|
Fl_Group {subPanels_[kPanelGeneral]} {
|
||||||
xywh {5 110 790 285} hide
|
xywh {5 110 790 285} hide
|
||||||
class LogicalGroup
|
class LogicalGroup
|
||||||
} {}
|
} {}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ enum class EditId : int {
|
||||||
//
|
//
|
||||||
#define KEY_RANGE(Name) Name##0, Name##Last = Name##0 + 128 - 1
|
#define KEY_RANGE(Name) Name##0, Name##Last = Name##0 + 128 - 1
|
||||||
#define CC_RANGE(Name) Name##0, Name##Last = Name##0 + sfz::config::numCCs - 1
|
#define CC_RANGE(Name) Name##0, Name##Last = Name##0 + sfz::config::numCCs - 1
|
||||||
|
#define METER_RANGE(Name) Name##0, Name##Last = Name##0 + 16 - 1
|
||||||
//
|
//
|
||||||
KEY_RANGE(Key),
|
KEY_RANGE(Key),
|
||||||
CC_RANGE(Controller),
|
CC_RANGE(Controller),
|
||||||
|
|
@ -38,8 +39,7 @@ enum class EditId : int {
|
||||||
CC_RANGE(ControllerDefault),
|
CC_RANGE(ControllerDefault),
|
||||||
CC_RANGE(ControllerLabel),
|
CC_RANGE(ControllerLabel),
|
||||||
//
|
//
|
||||||
LeftLevel,
|
METER_RANGE(Level),
|
||||||
RightLevel,
|
|
||||||
//
|
//
|
||||||
UINumCurves,
|
UINumCurves,
|
||||||
UINumMasters,
|
UINumMasters,
|
||||||
|
|
@ -53,9 +53,11 @@ enum class EditId : int {
|
||||||
//
|
//
|
||||||
PluginFormat,
|
PluginFormat,
|
||||||
PluginHost,
|
PluginHost,
|
||||||
|
PluginOutputs,
|
||||||
//
|
//
|
||||||
#undef KEY_RANGE
|
#undef KEY_RANGE
|
||||||
#undef CC_RANGE
|
#undef CC_RANGE
|
||||||
|
#undef METER_RANGE
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EditRange {
|
struct EditRange {
|
||||||
|
|
@ -89,6 +91,7 @@ struct EditRange {
|
||||||
|
|
||||||
// defines editIdForCC, ccForEditId, editIdIsCC, etc..
|
// defines editIdForCC, ccForEditId, editIdIsCC, etc..
|
||||||
DEFINE_EDIT_ID_RANGE_HELPERS(cc, CC, Controller)
|
DEFINE_EDIT_ID_RANGE_HELPERS(cc, CC, Controller)
|
||||||
|
DEFINE_EDIT_ID_RANGE_HELPERS(level, Level, Level)
|
||||||
DEFINE_EDIT_ID_RANGE_HELPERS(key, Key, Key)
|
DEFINE_EDIT_ID_RANGE_HELPERS(key, Key, Key)
|
||||||
DEFINE_EDIT_ID_RANGE_HELPERS(keyUsed, KeyUsed, KeyUsed)
|
DEFINE_EDIT_ID_RANGE_HELPERS(keyUsed, KeyUsed, KeyUsed)
|
||||||
DEFINE_EDIT_ID_RANGE_HELPERS(keyLabel, KeyLabel, KeyLabel)
|
DEFINE_EDIT_ID_RANGE_HELPERS(keyLabel, KeyLabel, KeyLabel)
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ struct Editor::Impl : EditorController::Receiver,
|
||||||
std::string currentThemeName_;
|
std::string currentThemeName_;
|
||||||
std::string userFilesDir_;
|
std::string userFilesDir_;
|
||||||
std::string fallbackFilesDir_;
|
std::string fallbackFilesDir_;
|
||||||
|
bool multi_ { false };
|
||||||
|
|
||||||
int currentKeyswitch_ = -1;
|
int currentKeyswitch_ = -1;
|
||||||
std::unordered_map<unsigned, std::string> keyswitchNames_;
|
std::unordered_map<unsigned, std::string> keyswitchNames_;
|
||||||
|
|
@ -154,9 +155,7 @@ struct Editor::Impl : EditorController::Receiver,
|
||||||
SKnobCCBox* volumeCCKnob_ = nullptr;
|
SKnobCCBox* volumeCCKnob_ = nullptr;
|
||||||
SKnobCCBox* panCCKnob_ = nullptr;
|
SKnobCCBox* panCCKnob_ = nullptr;
|
||||||
|
|
||||||
SLevelMeter* leftMeter_ = nullptr;
|
SLevelMeter* meters_[16] {};
|
||||||
SLevelMeter* rightMeter_ = nullptr;
|
|
||||||
|
|
||||||
SAboutDialog* aboutDialog_ = nullptr;
|
SAboutDialog* aboutDialog_ = nullptr;
|
||||||
|
|
||||||
SharedPointer<CBitmap> backgroundBitmap_;
|
SharedPointer<CBitmap> backgroundBitmap_;
|
||||||
|
|
@ -183,6 +182,7 @@ struct Editor::Impl : EditorController::Receiver,
|
||||||
SharedPointer<CVSTGUITimer> oscSendQueueTimer_;
|
SharedPointer<CVSTGUITimer> oscSendQueueTimer_;
|
||||||
|
|
||||||
void createFrameContents();
|
void createFrameContents();
|
||||||
|
SLevelMeter* createVMeter(const CRect& bounds, int, const char*, CHoriTxtAlign, int);
|
||||||
|
|
||||||
template <class Control>
|
template <class Control>
|
||||||
void adjustMinMaxToEditRange(Control* c, EditId id)
|
void adjustMinMaxToEditRange(Control* c, EditId id)
|
||||||
|
|
@ -333,6 +333,16 @@ void Editor::close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SLevelMeter* Editor::Impl::createVMeter(const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
|
||||||
|
SLevelMeter* meter = new SLevelMeter(bounds);
|
||||||
|
Palette* palette = &theme_->invertedPalette;
|
||||||
|
meter->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00));
|
||||||
|
meter->setNormalFillColor(CColor(0x00, 0xaa, 0x11));
|
||||||
|
meter->setDangerFillColor(CColor(0xaa, 0x00, 0x00));
|
||||||
|
meter->setBackColor(palette->knobInactiveTrack);
|
||||||
|
return meter;
|
||||||
|
};
|
||||||
|
|
||||||
void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
|
void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
|
||||||
{
|
{
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
|
@ -508,20 +518,51 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
|
||||||
updateBackgroundImage(value.c_str());
|
updateBackgroundImage(value.c_str());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EditId::LeftLevel:
|
case EditId::PluginOutputs:
|
||||||
{
|
{
|
||||||
const float value = v.to_float();
|
const int value = static_cast<int>(v.to_float());
|
||||||
if (SLevelMeter* meter = leftMeter_)
|
if (!meters_[0])
|
||||||
meter->setValue(value);
|
return;
|
||||||
|
|
||||||
|
const auto firstMeterRect = meters_[0]->getViewSize();
|
||||||
|
const auto minLeft = firstMeterRect.left;
|
||||||
|
auto maxRight = firstMeterRect.right;
|
||||||
|
auto numMeters = 1;
|
||||||
|
for (; numMeters < 16; ++numMeters) {
|
||||||
|
if (!meters_[numMeters])
|
||||||
|
break;
|
||||||
|
|
||||||
|
maxRight = meters_[numMeters]->getViewSize().right;
|
||||||
|
}
|
||||||
|
const auto meterWidth = std::max(1.0, (maxRight - minLeft - value + 1) / value);
|
||||||
|
|
||||||
|
auto meterParent = meters_[0]->getParentView()->asViewContainer();
|
||||||
|
const auto roundRectRadius = [value] {
|
||||||
|
if (value < 4)
|
||||||
|
return 5.0;
|
||||||
|
else if (value < 8)
|
||||||
|
return 3.0;
|
||||||
|
else if (value < 16)
|
||||||
|
return 1.0;
|
||||||
|
else
|
||||||
|
return 0.0;
|
||||||
|
}();
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
if (meters_[i]) {
|
||||||
|
meterParent->removeView(meters_[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < value) {
|
||||||
|
double left { minLeft + i * (meterWidth + 1) };
|
||||||
|
CRect rect { left, firstMeterRect.top, left + meterWidth, firstMeterRect.bottom };
|
||||||
|
meters_[i] = createVMeter(rect, -1, "", kCenterText, 14);
|
||||||
|
meters_[i]->setRoundRectRadius(roundRectRadius);
|
||||||
|
meterParent->addView(meters_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case EditId::RightLevel:
|
|
||||||
{
|
|
||||||
const float value = v.to_float();
|
|
||||||
if (SLevelMeter* meter = rightMeter_)
|
|
||||||
meter->setValue(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if (editIdIsKey(id)) {
|
if (editIdIsKey(id)) {
|
||||||
const int key = keyForEditId(id);
|
const int key = keyForEditId(id);
|
||||||
|
|
@ -554,6 +595,11 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
|
||||||
else if (editIdIsCCLabel(id)) {
|
else if (editIdIsCCLabel(id)) {
|
||||||
updateCCLabel(ccLabelForEditId(id), v.to_string().c_str());
|
updateCCLabel(ccLabelForEditId(id), v.to_string().c_str());
|
||||||
}
|
}
|
||||||
|
else if (editIdIsLevel(id)) {
|
||||||
|
const float value = v.to_float();
|
||||||
|
if (SLevelMeter* meter = meters_[levelForEditId(id)])
|
||||||
|
meter->setValue(value);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -721,16 +767,6 @@ void Editor::Impl::createFrameContents()
|
||||||
lbl->setFont(font);
|
lbl->setFont(font);
|
||||||
return lbl;
|
return lbl;
|
||||||
};
|
};
|
||||||
auto createVMeter = [this, &palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
|
|
||||||
SLevelMeter* meter = new SLevelMeter(bounds);
|
|
||||||
meter->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00));
|
|
||||||
meter->setNormalFillColor(CColor(0x00, 0xaa, 0x11));
|
|
||||||
meter->setDangerFillColor(CColor(0xaa, 0x00, 0x00));
|
|
||||||
OnThemeChanged.push_back([meter, palette]() {
|
|
||||||
meter->setBackColor(palette->knobInactiveTrack);
|
|
||||||
});
|
|
||||||
return meter;
|
|
||||||
};
|
|
||||||
#if 0
|
#if 0
|
||||||
auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) {
|
auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) {
|
||||||
CTextButton* button = new CTextButton(bounds, this, tag, label);
|
CTextButton* button = new CTextButton(bounds, this, tag, label);
|
||||||
|
|
@ -952,6 +988,15 @@ void Editor::Impl::createFrameContents()
|
||||||
mainView->setBackgroundColor(theme->frameBackground);
|
mainView->setBackgroundColor(theme->frameBackground);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
OnThemeChanged.push_back([this, theme]() {
|
||||||
|
for (unsigned i = 0, n = sizeof(meters_)/sizeof(meters_[0]); i < n; ++i ) {
|
||||||
|
Palette& palette = theme->invertedPalette;
|
||||||
|
const auto meter = meters_[i];
|
||||||
|
if (meter)
|
||||||
|
meter->setBackColor(palette.knobInactiveTrack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
#if LINUX
|
#if LINUX
|
||||||
if (!isZenityAvailable()) {
|
if (!isZenityAvailable()) {
|
||||||
CRect bounds = mainView->getViewSize();
|
CRect bounds = mainView->getViewSize();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ auto* const view__1 = createBackground(CRect(5, 110, 795, 395), -1, "", kCenterT
|
||||||
imageContainer_ = view__1;
|
imageContainer_ = view__1;
|
||||||
view__0->addView(view__1);
|
view__0->addView(view__1);
|
||||||
enterPalette(invertedPalette);
|
enterPalette(invertedPalette);
|
||||||
auto* const view__2 = createLogicalGroup(CRect(0, 0, 800, 110), -1, "", kCenterText, 14);
|
auto* const view__2 = createLogicalGroup(CRect(0, 0, 814, 110), -1, "", kCenterText, 14);
|
||||||
view__0->addView(view__2);
|
view__0->addView(view__2);
|
||||||
auto* const view__3 = createRoundedGroup(CRect(5, 4, 180, 105), -1, "", kCenterText, 14);
|
auto* const view__3 = createRoundedGroup(CRect(5, 4, 180, 105), -1, "", kCenterText, 14);
|
||||||
view__2->addView(view__3);
|
view__2->addView(view__3);
|
||||||
|
|
@ -23,16 +23,16 @@ view__3->addView(view__7);
|
||||||
auto* const view__8 = createHomeButton(CRect(11, 69, 43, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30);
|
auto* const view__8 = createHomeButton(CRect(11, 69, 43, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30);
|
||||||
panelButtons_[kPanelGeneral] = view__8;
|
panelButtons_[kPanelGeneral] = view__8;
|
||||||
view__3->addView(view__8);
|
view__3->addView(view__8);
|
||||||
auto* const view__9 = createRoundedGroup(CRect(185, 5, 603, 105), -1, "", kCenterText, 14);
|
auto* const view__9 = createRoundedGroup(CRect(185, 5, 575, 105), -1, "", kCenterText, 14);
|
||||||
view__2->addView(view__9);
|
view__2->addView(view__9);
|
||||||
auto* const view__10 = createHLine(CRect(10, 36, 405, 41), -1, "", kCenterText, 14);
|
auto* const view__10 = createHLine(CRect(10, 36, 380, 41), -1, "", kCenterText, 14);
|
||||||
view__9->addView(view__10);
|
view__9->addView(view__10);
|
||||||
auto* const view__11 = createHLine(CRect(10, 68, 405, 73), -1, "", kCenterText, 14);
|
auto* const view__11 = createHLine(CRect(10, 68, 380, 73), -1, "", kCenterText, 14);
|
||||||
view__9->addView(view__11);
|
view__9->addView(view__11);
|
||||||
auto* const view__12 = createClickableLabel(CRect(10, 8, 330, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20);
|
auto* const view__12 = createClickableLabel(CRect(10, 8, 330, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20);
|
||||||
sfzFileLabel_ = view__12;
|
sfzFileLabel_ = view__12;
|
||||||
view__9->addView(view__12);
|
view__9->addView(view__12);
|
||||||
auto* const view__13 = createLabel(CRect(80, 40, 405, 75), -1, "", kLeftText, 20);
|
auto* const view__13 = createLabel(CRect(80, 40, 390, 75), -1, "", kLeftText, 20);
|
||||||
keyswitchLabel_ = view__13;
|
keyswitchLabel_ = view__13;
|
||||||
view__9->addView(view__13);
|
view__9->addView(view__13);
|
||||||
auto* const view__14 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20);
|
auto* const view__14 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20);
|
||||||
|
|
@ -44,30 +44,30 @@ view__9->addView(view__15);
|
||||||
view__15->setVisible(false);
|
view__15->setVisible(false);
|
||||||
auto* const view__16 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12);
|
auto* const view__16 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12);
|
||||||
view__9->addView(view__16);
|
view__9->addView(view__16);
|
||||||
auto* const view__17 = createPreviousFileButton(CRect(330, 13, 355, 38), kTagPreviousSfzFile, "", kCenterText, 24);
|
auto* const view__17 = createPreviousFileButton(CRect(303, 13, 328, 38), kTagPreviousSfzFile, "", kCenterText, 24);
|
||||||
view__9->addView(view__17);
|
view__9->addView(view__17);
|
||||||
auto* const view__18 = createNextFileButton(CRect(355, 13, 380, 38), kTagNextSfzFile, "", kCenterText, 24);
|
auto* const view__18 = createNextFileButton(CRect(328, 13, 353, 38), kTagNextSfzFile, "", kCenterText, 24);
|
||||||
view__9->addView(view__18);
|
view__9->addView(view__18);
|
||||||
auto* const view__19 = createChevronDropDown(CRect(380, 13, 405, 38), kTagFileOperations, "", kCenterText, 24);
|
auto* const view__19 = createChevronDropDown(CRect(353, 13, 378, 38), kTagFileOperations, "", kCenterText, 24);
|
||||||
fileOperationsMenu_ = view__19;
|
fileOperationsMenu_ = view__19;
|
||||||
view__9->addView(view__19);
|
view__9->addView(view__19);
|
||||||
auto* const view__20 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12);
|
auto* const view__20 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12);
|
||||||
infoVoicesLabel_ = view__20;
|
infoVoicesLabel_ = view__20;
|
||||||
view__9->addView(view__20);
|
view__9->addView(view__20);
|
||||||
auto* const view__21 = createLabel(CRect(147, 73, 187, 98), -1, "Max:", kRightText, 12);
|
auto* const view__21 = createLabel(CRect(137, 73, 177, 98), -1, "Max:", kRightText, 12);
|
||||||
view__9->addView(view__21);
|
view__9->addView(view__21);
|
||||||
auto* const view__22 = createLabel(CRect(193, 73, 228, 98), -1, "", kCenterText, 12);
|
auto* const view__22 = createLabel(CRect(193, 73, 228, 98), -1, "", kCenterText, 12);
|
||||||
numVoicesLabel_ = view__22;
|
numVoicesLabel_ = view__22;
|
||||||
view__9->addView(view__22);
|
view__9->addView(view__22);
|
||||||
auto* const view__23 = createLabel(CRect(274, 73, 334, 98), -1, "Memory:", kRightText, 12);
|
auto* const view__23 = createLabel(CRect(271, 73, 331, 98), -1, "Memory:", kRightText, 12);
|
||||||
view__9->addView(view__23);
|
view__9->addView(view__23);
|
||||||
auto* const view__24 = createLabel(CRect(340, 73, 400, 98), -1, "", kCenterText, 12);
|
auto* const view__24 = createLabel(CRect(337, 73, 387, 98), -1, "", kCenterText, 12);
|
||||||
memoryLabel_ = view__24;
|
memoryLabel_ = view__24;
|
||||||
view__9->addView(view__24);
|
view__9->addView(view__24);
|
||||||
auto* const view__25 = createChevronValueDropDown(CRect(235, 77, 255, 97), kTagSetNumVoices, "", kCenterText, 16);
|
auto* const view__25 = createChevronValueDropDown(CRect(235, 77, 255, 97), kTagSetNumVoices, "", kCenterText, 16);
|
||||||
numVoicesSlider_ = view__25;
|
numVoicesSlider_ = view__25;
|
||||||
view__9->addView(view__25);
|
view__9->addView(view__25);
|
||||||
auto* const view__26 = createRoundedGroup(CRect(608, 5, 795, 105), -1, "", kCenterText, 14);
|
auto* const view__26 = createRoundedGroup(CRect(580, 5, 795, 105), -1, "", kCenterText, 14);
|
||||||
view__2->addView(view__26);
|
view__2->addView(view__26);
|
||||||
auto* const view__27 = createKnob48(CRect(7, 15, 55, 63), -1, "", kCenterText, 14);
|
auto* const view__27 = createKnob48(CRect(7, 15, 55, 63), -1, "", kCenterText, 14);
|
||||||
view__26->addView(view__27);
|
view__26->addView(view__27);
|
||||||
|
|
@ -81,11 +81,11 @@ view__26->addView(view__29);
|
||||||
auto* const view__30 = createKnobCCBox(CRect(83, 5, 153, 95), kTagSetCCPan, "Pan", kCenterText, 12);
|
auto* const view__30 = createKnobCCBox(CRect(83, 5, 153, 95), kTagSetCCPan, "Pan", kCenterText, 12);
|
||||||
panCCKnob_ = view__30;
|
panCCKnob_ = view__30;
|
||||||
view__26->addView(view__30);
|
view__26->addView(view__30);
|
||||||
auto* const view__31 = createVMeter(CRect(159, 5, 168, 95), -1, "", kCenterText, 14);
|
auto* const view__31 = createVMeter(CRect(159, 5, 182, 95), -1, "", kCenterText, 14);
|
||||||
leftMeter_ = view__31;
|
meters_[0] = view__31;
|
||||||
view__26->addView(view__31);
|
view__26->addView(view__31);
|
||||||
auto* const view__32 = createVMeter(CRect(171, 5, 180, 95), -1, "", kCenterText, 14);
|
auto* const view__32 = createVMeter(CRect(183, 5, 206, 95), -1, "", kCenterText, 14);
|
||||||
rightMeter_ = view__32;
|
meters_[1] = view__32;
|
||||||
view__26->addView(view__32);
|
view__26->addView(view__32);
|
||||||
enterPalette(defaultPalette);
|
enterPalette(defaultPalette);
|
||||||
auto* const view__33 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
|
auto* const view__33 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@
|
||||||
lv2:binary <Contents/Binary/@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
|
lv2:binary <Contents/Binary/@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
|
||||||
rdfs:seeAlso <@PROJECT_NAME@.ttl>, <controllers.ttl> .
|
rdfs:seeAlso <@PROJECT_NAME@.ttl>, <controllers.ttl> .
|
||||||
|
|
||||||
|
<@LV2PLUGIN_URI@-multi>
|
||||||
|
a lv2:Plugin ;
|
||||||
|
lv2:binary <Contents/Binary/@PROJECT_NAME@@CMAKE_SHARED_MODULE_SUFFIX@> ;
|
||||||
|
rdfs:seeAlso <@PROJECT_NAME@.ttl>, <controllers.ttl> .
|
||||||
|
|
||||||
@LV2PLUGIN_IF_ENABLE_UI@<@LV2PLUGIN_URI@#ui>
|
@LV2PLUGIN_IF_ENABLE_UI@<@LV2PLUGIN_URI@#ui>
|
||||||
@LV2PLUGIN_IF_ENABLE_UI@ a ui:@LV2_UI_TYPE@ ;
|
@LV2PLUGIN_IF_ENABLE_UI@ a ui:@LV2_UI_TYPE@ ;
|
||||||
@LV2PLUGIN_IF_ENABLE_UI@ ui:binary <Contents/Binary/@PROJECT_NAME@_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
|
@LV2PLUGIN_IF_ENABLE_UI@ ui:binary <Contents/Binary/@PROJECT_NAME@_ui@CMAKE_SHARED_MODULE_SUFFIX@> ;
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ sfizz_atom_extract_integer(sfizz_plugin_t *self, const LV2_Atom *atom, int64_t *
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
connect_port(LV2_Handle instance,
|
connect_port_stereo(LV2_Handle instance,
|
||||||
uint32_t port,
|
uint32_t port,
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
|
|
@ -264,6 +264,133 @@ connect_port(LV2_Handle instance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
connect_port_multi(LV2_Handle instance,
|
||||||
|
uint32_t port,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
||||||
|
switch (port)
|
||||||
|
{
|
||||||
|
case SFIZZ_MULTI_CONTROL:
|
||||||
|
self->control_port = (const LV2_Atom_Sequence *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_AUTOMATE:
|
||||||
|
self->automate_port = (LV2_Atom_Sequence *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT1L:
|
||||||
|
self->output_buffers[0] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT1R:
|
||||||
|
self->output_buffers[1] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT2L:
|
||||||
|
self->output_buffers[2] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT2R:
|
||||||
|
self->output_buffers[3] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT3L:
|
||||||
|
self->output_buffers[4] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT3R:
|
||||||
|
self->output_buffers[5] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT4L:
|
||||||
|
self->output_buffers[6] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT4R:
|
||||||
|
self->output_buffers[7] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT5L:
|
||||||
|
self->output_buffers[8] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT5R:
|
||||||
|
self->output_buffers[9] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT6L:
|
||||||
|
self->output_buffers[10] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT6R:
|
||||||
|
self->output_buffers[11] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT7L:
|
||||||
|
self->output_buffers[12] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT7R:
|
||||||
|
self->output_buffers[13] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT8L:
|
||||||
|
self->output_buffers[14] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OUT8R:
|
||||||
|
self->output_buffers[15] = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_VOLUME:
|
||||||
|
self->volume_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_POLYPHONY:
|
||||||
|
self->polyphony_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OVERSAMPLING:
|
||||||
|
self->oversampling_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_PRELOAD:
|
||||||
|
self->preload_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_FREEWHEELING:
|
||||||
|
self->freewheel_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_SCALA_ROOT_KEY:
|
||||||
|
self->scala_root_key_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_TUNING_FREQUENCY:
|
||||||
|
self->tuning_frequency_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_STRETCH_TUNING:
|
||||||
|
self->stretch_tuning_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_SAMPLE_QUALITY:
|
||||||
|
self->sample_quality_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OSCILLATOR_QUALITY:
|
||||||
|
self->oscillator_quality_port = (const float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_ACTIVE_VOICES:
|
||||||
|
self->active_voices_port = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_CURVES:
|
||||||
|
self->num_curves_port = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_MASTERS:
|
||||||
|
self->num_masters_port = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_GROUPS:
|
||||||
|
self->num_groups_port = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_REGIONS:
|
||||||
|
self->num_regions_port = (float *)data;
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_SAMPLES:
|
||||||
|
self->num_samples_port = (float *)data;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
connect_port(LV2_Handle instance,
|
||||||
|
uint32_t port,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
|
||||||
|
if (self->multi_out)
|
||||||
|
connect_port_multi(instance, port, data);
|
||||||
|
else
|
||||||
|
connect_port_stereo(instance, port, data);
|
||||||
|
}
|
||||||
|
|
||||||
// This function sets the sample rate in the self parameter but does not update it.
|
// This function sets the sample rate in the self parameter but does not update it.
|
||||||
static void
|
static void
|
||||||
sfizz_lv2_parse_sample_rate(sfizz_plugin_t* self, const LV2_Options_Option* opt)
|
sfizz_lv2_parse_sample_rate(sfizz_plugin_t* self, const LV2_Options_Option* opt)
|
||||||
|
|
@ -370,6 +497,7 @@ instantiate(const LV2_Descriptor *descriptor,
|
||||||
self->bundle_path[MAX_BUNDLE_PATH_SIZE - 1] = '\0';
|
self->bundle_path[MAX_BUNDLE_PATH_SIZE - 1] = '\0';
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
|
self->multi_out = !strcmp(descriptor->URI, SFIZZ_MULTI_URI);
|
||||||
self->max_block_size = MAX_BLOCK_SIZE;
|
self->max_block_size = MAX_BLOCK_SIZE;
|
||||||
self->sample_rate = (float)rate;
|
self->sample_rate = (float)rate;
|
||||||
self->expect_nominal_block_length = false;
|
self->expect_nominal_block_length = false;
|
||||||
|
|
@ -513,6 +641,13 @@ instantiate(const LV2_Descriptor *descriptor,
|
||||||
|
|
||||||
sfizz_lv2_update_timeinfo(self, 0, ~0);
|
sfizz_lv2_update_timeinfo(self, 0, ~0);
|
||||||
|
|
||||||
|
#if defined(SFIZZ_LV2_UI)
|
||||||
|
if (self->multi_out)
|
||||||
|
self->rms_follower.setNumOutputs(MULTI_OUTPUT_COUNT);
|
||||||
|
else
|
||||||
|
self->rms_follower.setNumOutputs(2);
|
||||||
|
#endif
|
||||||
|
|
||||||
return (LV2_Handle)self;
|
return (LV2_Handle)self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -589,11 +724,8 @@ sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_URID verb_uri, unsigned cc,
|
||||||
|
|
||||||
#if defined(SFIZZ_LV2_UI)
|
#if defined(SFIZZ_LV2_UI)
|
||||||
static void
|
static void
|
||||||
sfizz_lv2_send_levels(sfizz_plugin_t *self, float left, float right)
|
sfizz_lv2_send_levels(sfizz_plugin_t *self, const float* levels, uint32_t num_levels)
|
||||||
{
|
{
|
||||||
const float levels[] = {left, right};
|
|
||||||
uint32_t num_levels = sizeof(levels) / sizeof(levels[0]);
|
|
||||||
|
|
||||||
LV2_Atom_Forge* forge = &self->forge_automate;
|
LV2_Atom_Forge* forge = &self->forge_automate;
|
||||||
bool write_ok = lv2_atom_forge_frame_time(forge, 0);
|
bool write_ok = lv2_atom_forge_frame_time(forge, 0);
|
||||||
|
|
||||||
|
|
@ -1083,7 +1215,10 @@ run(LV2_Handle instance, uint32_t sample_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the block
|
// Render the block
|
||||||
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
|
if (self->multi_out)
|
||||||
|
sfizz_render_block(self->synth, self->output_buffers, MULTI_OUTPUT_COUNT, (int)sample_count);
|
||||||
|
else
|
||||||
|
sfizz_render_block(self->synth, self->output_buffers, 2, (int)sample_count);
|
||||||
|
|
||||||
// Request OSC updates
|
// Request OSC updates
|
||||||
sfizz_send_message(self->synth, self->client, 0, "/sw/last/current", "", nullptr);
|
sfizz_send_message(self->synth, self->client, 0, "/sw/last/current", "", nullptr);
|
||||||
|
|
@ -1110,10 +1245,19 @@ run(LV2_Handle instance, uint32_t sample_count)
|
||||||
#if defined(SFIZZ_LV2_UI)
|
#if defined(SFIZZ_LV2_UI)
|
||||||
if (self->ui_active)
|
if (self->ui_active)
|
||||||
{
|
{
|
||||||
self->rms_follower.process(self->output_buffers[0], self->output_buffers[1], sample_count);
|
if (self->multi_out) {
|
||||||
const simde__m128 rms = self->rms_follower.getRMS();
|
self->rms_follower.process((const float**)self->output_buffers,
|
||||||
const float *levels = (const float *)&rms;
|
sample_count, MULTI_OUTPUT_COUNT);
|
||||||
sfizz_lv2_send_levels(self, levels[0], levels[1]);
|
float levels[MULTI_OUTPUT_COUNT];
|
||||||
|
self->rms_follower.getRMS(levels, MULTI_OUTPUT_COUNT);
|
||||||
|
sfizz_lv2_send_levels(self, levels, MULTI_OUTPUT_COUNT);
|
||||||
|
} else {
|
||||||
|
self->rms_follower.process((const float**)self->output_buffers,
|
||||||
|
sample_count, 2);
|
||||||
|
float levels[2];
|
||||||
|
self->rms_follower.getRMS(levels, 2);
|
||||||
|
sfizz_lv2_send_levels(self, levels, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1244,10 +1388,10 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
|
||||||
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
|
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
|
||||||
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
|
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
|
||||||
// Mark all the used CCs for automation with default values
|
// Mark all the used CCs for automation with default values
|
||||||
self->ccauto[cc] = desc.ccDefault[cc];
|
self->ccauto[cc] = desc.ccValue[cc];
|
||||||
self->have_ccauto = true;
|
self->have_ccauto = true;
|
||||||
// Update the current CCs
|
// Update the current CCs
|
||||||
self->cc_current[cc] = desc.ccDefault[cc];
|
self->cc_current[cc] = desc.ccValue[cc];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1332,7 +1476,7 @@ restore(LV2_Handle instance,
|
||||||
|
|
||||||
if (path)
|
if (path)
|
||||||
{
|
{
|
||||||
strncpy(self->sfz_file_path, path, MAX_PATH_SIZE);
|
strncpy(self->sfz_file_path, path, MAX_PATH_SIZE - 1);
|
||||||
self->sfz_file_path[MAX_PATH_SIZE - 1] = '\0';
|
self->sfz_file_path[MAX_PATH_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (map_path)
|
if (map_path)
|
||||||
|
|
@ -1353,7 +1497,7 @@ restore(LV2_Handle instance,
|
||||||
|
|
||||||
if (path)
|
if (path)
|
||||||
{
|
{
|
||||||
strncpy(self->scala_file_path, path, MAX_PATH_SIZE);
|
strncpy(self->scala_file_path, path, MAX_PATH_SIZE - 1);
|
||||||
self->scala_file_path[MAX_PATH_SIZE - 1] = '\0';
|
self->scala_file_path[MAX_PATH_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (map_path)
|
if (map_path)
|
||||||
|
|
@ -1769,6 +1913,16 @@ static const LV2_Descriptor descriptor = {
|
||||||
cleanup,
|
cleanup,
|
||||||
extension_data};
|
extension_data};
|
||||||
|
|
||||||
|
static const LV2_Descriptor descriptor_multi = {
|
||||||
|
SFIZZ_MULTI_URI,
|
||||||
|
instantiate,
|
||||||
|
connect_port,
|
||||||
|
activate,
|
||||||
|
run,
|
||||||
|
deactivate,
|
||||||
|
cleanup,
|
||||||
|
extension_data};
|
||||||
|
|
||||||
LV2_SYMBOL_EXPORT
|
LV2_SYMBOL_EXPORT
|
||||||
const LV2_Descriptor *
|
const LV2_Descriptor *
|
||||||
lv2_descriptor(uint32_t index)
|
lv2_descriptor(uint32_t index)
|
||||||
|
|
@ -1777,6 +1931,8 @@ lv2_descriptor(uint32_t index)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return &descriptor;
|
return &descriptor;
|
||||||
|
case 1:
|
||||||
|
return &descriptor_multi;
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -409,3 +409,442 @@ midnam:update a lv2:Feature .
|
||||||
] ;
|
] ;
|
||||||
|
|
||||||
rdfs:seeAlso <controllers.ttl> .
|
rdfs:seeAlso <controllers.ttl> .
|
||||||
|
|
||||||
|
<@LV2PLUGIN_URI@-multi>
|
||||||
|
a doap:Project, lv2:Plugin, lv2:InstrumentPlugin ;
|
||||||
|
|
||||||
|
doap:name "@LV2PLUGIN_NAME@" ;
|
||||||
|
doap:license <https://spdx.org/licenses/@LV2PLUGIN_SPDX_LICENSE_ID@> ;
|
||||||
|
doap:maintainer [
|
||||||
|
foaf:name "@LV2PLUGIN_AUTHOR@" ;
|
||||||
|
foaf:homepage <@LV2PLUGIN_URI@> ;
|
||||||
|
foaf:mbox <mailto:@LV2PLUGIN_EMAIL@> ;
|
||||||
|
] ;
|
||||||
|
rdfs:comment "@LV2PLUGIN_COMMENT@",
|
||||||
|
"Échantillonneur SFZ"@fr ,
|
||||||
|
"Campionatore SFZ"@it ;
|
||||||
|
|
||||||
|
lv2:minorVersion @LV2PLUGIN_VERSION_MINOR@ ;
|
||||||
|
lv2:microVersion @LV2PLUGIN_VERSION_MICRO@ ;
|
||||||
|
|
||||||
|
lv2:requiredFeature urid:map, bufsize:boundedBlockLength, work:schedule ;
|
||||||
|
lv2:optionalFeature lv2:hardRTCapable, opts:options, state:mapPath, state:freePath ;
|
||||||
|
lv2:extensionData opts:interface, state:interface, work:interface ;
|
||||||
|
|
||||||
|
lv2:optionalFeature midnam:update ;
|
||||||
|
lv2:extensionData midnam:interface ;
|
||||||
|
|
||||||
|
opts:supportedOption param:sampleRate ;
|
||||||
|
opts:supportedOption bufsize:maxBlockLength, bufsize:nominalBlockLength ;
|
||||||
|
|
||||||
|
@LV2PLUGIN_IF_ENABLE_UI@ui:ui <@LV2PLUGIN_URI@#ui> ;
|
||||||
|
|
||||||
|
patch:writable <@LV2PLUGIN_URI@:sfzfile> ,
|
||||||
|
<@LV2PLUGIN_URI@:tuningfile> ;
|
||||||
|
|
||||||
|
pg:mainOutput <@LV2PLUGIN_URI@#stereo_output> ;
|
||||||
|
|
||||||
|
lv2:port [
|
||||||
|
a lv2:InputPort, atom:AtomPort ;
|
||||||
|
atom:bufferType atom:Sequence ;
|
||||||
|
atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob>, <@LV2PLUGIN_URI@:Notify> ;
|
||||||
|
lv2:designation lv2:control ;
|
||||||
|
lv2:index 0 ;
|
||||||
|
lv2:symbol "control" ;
|
||||||
|
lv2:name "Control",
|
||||||
|
"Contrôle"@fr ;
|
||||||
|
rsz:minimumSize 524288 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, atom:AtomPort ;
|
||||||
|
atom:bufferType atom:Sequence ;
|
||||||
|
atom:supports patch:Message, <@LV2PLUGIN_URI@:OSCBlob> ;
|
||||||
|
lv2:designation lv2:control ;
|
||||||
|
lv2:index 1 ;
|
||||||
|
lv2:symbol "automate" ;
|
||||||
|
lv2:name "Automate",
|
||||||
|
"Automatisation"@fr ;
|
||||||
|
rsz:minimumSize 524288 ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 2 ;
|
||||||
|
lv2:symbol "out1L" ;
|
||||||
|
lv2:name "Output 1 Left",
|
||||||
|
"Sortie gauche 1"@fr ,
|
||||||
|
"Uscita Sinistra 1"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 3 ;
|
||||||
|
lv2:symbol "out1R" ;
|
||||||
|
lv2:name "Output 1 Right",
|
||||||
|
"Sortie droite 1"@fr ,
|
||||||
|
"Uscita Destra 1"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 4 ;
|
||||||
|
lv2:symbol "out2L" ;
|
||||||
|
lv2:name "Output 2 Left",
|
||||||
|
"Sortie gauche 2"@fr ,
|
||||||
|
"Uscita Sinistra 2"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 5 ;
|
||||||
|
lv2:symbol "out2R" ;
|
||||||
|
lv2:name "Output 2 Right",
|
||||||
|
"Sortie droite 2"@fr ,
|
||||||
|
"Uscita Destra 2"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 6 ;
|
||||||
|
lv2:symbol "out3L" ;
|
||||||
|
lv2:name "Output 3 Left",
|
||||||
|
"Sortie gauche 3"@fr ,
|
||||||
|
"Uscita Sinistra 3"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 7 ;
|
||||||
|
lv2:symbol "out3R" ;
|
||||||
|
lv2:name "Output 3 Right",
|
||||||
|
"Sortie droite 3"@fr ,
|
||||||
|
"Uscita Destra 3"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 8 ;
|
||||||
|
lv2:symbol "out4L" ;
|
||||||
|
lv2:name "Output 4 Left",
|
||||||
|
"Sortie gauche 4"@fr ,
|
||||||
|
"Uscita Sinistra 4"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 9 ;
|
||||||
|
lv2:symbol "out4R" ;
|
||||||
|
lv2:name "Output 4 Right",
|
||||||
|
"Sortie droite 4"@fr ,
|
||||||
|
"Uscita Destra 4"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 10 ;
|
||||||
|
lv2:symbol "out5L" ;
|
||||||
|
lv2:name "Output 5 Left",
|
||||||
|
"Sortie gauche 5"@fr ,
|
||||||
|
"Uscita Sinistra 5"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 11 ;
|
||||||
|
lv2:symbol "out5R" ;
|
||||||
|
lv2:name "Output 5 Right",
|
||||||
|
"Sortie droite 5"@fr ,
|
||||||
|
"Uscita Destra 5"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 12 ;
|
||||||
|
lv2:symbol "out6L" ;
|
||||||
|
lv2:name "Output 6 Left",
|
||||||
|
"Sortie gauche 6"@fr ,
|
||||||
|
"Uscita Sinistra 6"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 13 ;
|
||||||
|
lv2:symbol "out6R" ;
|
||||||
|
lv2:name "Output 6 Right",
|
||||||
|
"Sortie droite 6"@fr ,
|
||||||
|
"Uscita Destra 6"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 14 ;
|
||||||
|
lv2:symbol "out7L" ;
|
||||||
|
lv2:name "Output 7 Left",
|
||||||
|
"Sortie gauche 7"@fr ,
|
||||||
|
"Uscita Sinistra 7"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 15 ;
|
||||||
|
lv2:symbol "out7R" ;
|
||||||
|
lv2:name "Output 7 Right",
|
||||||
|
"Sortie droite 7"@fr ,
|
||||||
|
"Uscita Destra 7"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 16 ;
|
||||||
|
lv2:symbol "out8L" ;
|
||||||
|
lv2:name "Output 8 Left",
|
||||||
|
"Sortie gauche 8"@fr ,
|
||||||
|
"Uscita Sinistra 8"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:AudioPort, lv2:OutputPort ;
|
||||||
|
lv2:index 17 ;
|
||||||
|
lv2:symbol "out8R" ;
|
||||||
|
lv2:name "Output 8 Right",
|
||||||
|
"Sortie droite 8"@fr ,
|
||||||
|
"Uscita Destra 8"@it ;
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 18 ;
|
||||||
|
lv2:symbol "volume" ;
|
||||||
|
lv2:name "Volume" ;
|
||||||
|
lv2:default 0.0 ;
|
||||||
|
lv2:minimum -80.0 ;
|
||||||
|
lv2:maximum 6.0 ;
|
||||||
|
units:unit units:db
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 19 ;
|
||||||
|
lv2:symbol "num_voices" ;
|
||||||
|
lv2:name "Polyphony",
|
||||||
|
"Polyphonie"@fr ,
|
||||||
|
"Polifonia"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#config> ;
|
||||||
|
lv2:portProperty pprop:notAutomatic ;
|
||||||
|
lv2:portProperty pprop:expensive ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:portProperty lv2:enumeration ;
|
||||||
|
lv2:default 64 ;
|
||||||
|
lv2:minimum 8 ;
|
||||||
|
lv2:maximum 256 ;
|
||||||
|
lv2:scalePoint [ rdfs:label "8 voices",
|
||||||
|
"8 voix"@fr ,
|
||||||
|
"8 Voci"@it;
|
||||||
|
rdf:value 8
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "16 voices",
|
||||||
|
"16 voix"@fr ,
|
||||||
|
"16 Voci"@it;
|
||||||
|
rdf:value 16
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "32 voices",
|
||||||
|
"32 voix"@fr ,
|
||||||
|
"32 Voci"@it;
|
||||||
|
rdf:value 32
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "64 voices",
|
||||||
|
"64 voix"@fr ,
|
||||||
|
"64 Voci"@it;
|
||||||
|
rdf:value 64
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "128 voices",
|
||||||
|
"128 voix"@fr ,
|
||||||
|
"128 Voci"@it;
|
||||||
|
rdf:value 128
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "256 voices",
|
||||||
|
"256 voix"@fr ,
|
||||||
|
"256 Voci"@it;
|
||||||
|
rdf:value 256
|
||||||
|
] ;
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 20 ;
|
||||||
|
lv2:symbol "oversampling" ;
|
||||||
|
lv2:name "Oversampling factor",
|
||||||
|
"Facteur de suréchantillonnage"@fr ,
|
||||||
|
"Fattore Sovracampionamento"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#config> ;
|
||||||
|
lv2:portProperty pprop:notAutomatic ;
|
||||||
|
lv2:portProperty pprop:expensive ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:portProperty lv2:enumeration ;
|
||||||
|
lv2:default 1 ;
|
||||||
|
lv2:minimum 1 ;
|
||||||
|
lv2:maximum 8 ;
|
||||||
|
lv2:scalePoint [ rdfs:label "x1 oversampling",
|
||||||
|
"suréchantillonnage x1"@fr ,
|
||||||
|
"x1 Sovracampionamento"@it;
|
||||||
|
rdf:value 1
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "x2 oversampling",
|
||||||
|
"suréchantillonnage x2"@fr ,
|
||||||
|
"x2 Sovracampionamento"@it;
|
||||||
|
rdf:value 2
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "x4 oversampling",
|
||||||
|
"suréchantillonnage x4"@fr ,
|
||||||
|
"x4 Sovracampionamento"@it;
|
||||||
|
rdf:value 4
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "x8 oversampling",
|
||||||
|
"suréchantillonnage x8"@fr ,
|
||||||
|
"x8 Sovracampionamento"@it;
|
||||||
|
rdf:value 8
|
||||||
|
] ;
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 21 ;
|
||||||
|
lv2:symbol "preload_size" ;
|
||||||
|
lv2:name "Preload size",
|
||||||
|
"Taille préchargée"@fr ,
|
||||||
|
"Grandezza Precaricamento"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#config> ;
|
||||||
|
lv2:portProperty pprop:notAutomatic ;
|
||||||
|
lv2:portProperty pprop:expensive ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:portProperty lv2:enumeration ;
|
||||||
|
lv2:default 8192 ;
|
||||||
|
lv2:minimum 1024 ;
|
||||||
|
lv2:maximum 65536 ;
|
||||||
|
lv2:scalePoint [ rdfs:label "4 KB",
|
||||||
|
"4 Ko"@fr;
|
||||||
|
rdf:value 1024
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "8 KB",
|
||||||
|
"8 Ko"@fr;
|
||||||
|
rdf:value 2048
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "16 KB",
|
||||||
|
"16 Ko"@fr;
|
||||||
|
rdf:value 4096
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "32 KB",
|
||||||
|
"32 Ko"@fr;
|
||||||
|
rdf:value 8192
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "64 KB",
|
||||||
|
"64 Ko"@fr;
|
||||||
|
rdf:value 16384
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "128 KB",
|
||||||
|
"128 Ko"@fr;
|
||||||
|
rdf:value 32768
|
||||||
|
] ;
|
||||||
|
lv2:scalePoint [ rdfs:label "256 KB",
|
||||||
|
"256 Ko"@fr;
|
||||||
|
rdf:value 65536
|
||||||
|
] ;
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 22 ;
|
||||||
|
lv2:symbol "freewheeling" ;
|
||||||
|
lv2:name "Freewheeling",
|
||||||
|
"En roue libre (freewheeling)"@fr ,
|
||||||
|
"A Ruota Libera"@it ;
|
||||||
|
lv2:designation lv2:freeWheeling ;
|
||||||
|
lv2:portProperty lv2:toggled ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 1 ;
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 23 ;
|
||||||
|
lv2:symbol "scala_root_key" ;
|
||||||
|
lv2:name "Scala root key",
|
||||||
|
"Tonalité de base Scala"@fr ,
|
||||||
|
"Tonalità di base Scala"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 60 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 127 ;
|
||||||
|
units:unit units:midiNote
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 24 ;
|
||||||
|
lv2:symbol "tuning_frequency" ;
|
||||||
|
lv2:name "Tuning frequency",
|
||||||
|
"Fréquence d'accordage"@fr ,
|
||||||
|
"Frequenza di accordatura"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||||
|
lv2:default 440.0 ;
|
||||||
|
lv2:minimum 300.0 ;
|
||||||
|
lv2:maximum 500.0 ;
|
||||||
|
units:unit units:hz
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 25 ;
|
||||||
|
lv2:symbol "stretched_tuning" ;
|
||||||
|
lv2:name "Stretched tuning",
|
||||||
|
"Accordage étiré"@fr ,
|
||||||
|
"Tensione di accordatura"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||||
|
lv2:default 0.0 ;
|
||||||
|
lv2:minimum 0.0 ;
|
||||||
|
lv2:maximum 1.0 ;
|
||||||
|
units:unit units:coef
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 26 ;
|
||||||
|
lv2:symbol "sample_quality" ;
|
||||||
|
lv2:name "Sample quality",
|
||||||
|
"Qualité des échantillons"@fr ,
|
||||||
|
"Qualità del campione"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||||
|
lv2:default 2.0 ;
|
||||||
|
lv2:minimum 0.0 ;
|
||||||
|
lv2:maximum 10.0
|
||||||
|
] , [
|
||||||
|
a lv2:InputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 27 ;
|
||||||
|
lv2:symbol "oscillator_quality" ;
|
||||||
|
lv2:name "Oscillator quality",
|
||||||
|
"Qualité des oscillateurs"@fr ,
|
||||||
|
"Qualità dell'oscillatore"@it ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||||
|
lv2:default 1.0 ;
|
||||||
|
lv2:minimum 0.0 ;
|
||||||
|
lv2:maximum 3.0
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 28 ;
|
||||||
|
lv2:symbol "active_voices" ;
|
||||||
|
lv2:name "Active voices",
|
||||||
|
"Voix utilisées"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 256 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 29 ;
|
||||||
|
lv2:symbol "num_curves" ;
|
||||||
|
lv2:name "Number of curves",
|
||||||
|
"Nombre de courbes"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 65535 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 30 ;
|
||||||
|
lv2:symbol "num_masters" ;
|
||||||
|
lv2:name "Number of masters",
|
||||||
|
"Nombre de maîtres"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 65535 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 31 ;
|
||||||
|
lv2:symbol "num_groups" ;
|
||||||
|
lv2:name "Number of groups",
|
||||||
|
"Nombre de groupes"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 65535 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 32 ;
|
||||||
|
lv2:symbol "num_regions" ;
|
||||||
|
lv2:name "Number of regions",
|
||||||
|
"Nombre de régions"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 65535 ;
|
||||||
|
] , [
|
||||||
|
a lv2:OutputPort, lv2:ControlPort ;
|
||||||
|
lv2:index 33 ;
|
||||||
|
lv2:symbol "num_samples" ;
|
||||||
|
lv2:name "Number of samples",
|
||||||
|
"Nombre d'échantillons"@fr ;
|
||||||
|
pg:group <@LV2PLUGIN_URI@#status> ;
|
||||||
|
lv2:portProperty lv2:integer ;
|
||||||
|
lv2:default 0 ;
|
||||||
|
lv2:minimum 0 ;
|
||||||
|
lv2:maximum 65535 ;
|
||||||
|
] ;
|
||||||
|
|
||||||
|
rdfs:seeAlso <controllers.ttl> .
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@
|
||||||
#define MAX_PATH_SIZE 1024
|
#define MAX_PATH_SIZE 1024
|
||||||
#define ATOM_TEMP_SIZE 8192
|
#define ATOM_TEMP_SIZE 8192
|
||||||
#define OSC_TEMP_SIZE 8192
|
#define OSC_TEMP_SIZE 8192
|
||||||
|
#define MULTI_OUTPUT_COUNT 16
|
||||||
|
|
||||||
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
|
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
|
||||||
|
#define SFIZZ_MULTI_URI SFIZZ_URI "-multi"
|
||||||
#define SFIZZ_UI_URI "http://sfztools.github.io/sfizz#ui"
|
#define SFIZZ_UI_URI "http://sfztools.github.io/sfizz#ui"
|
||||||
#define SFIZZ_PREFIX SFIZZ_URI "#"
|
#define SFIZZ_PREFIX SFIZZ_URI "#"
|
||||||
#define SFIZZ__sfzFile SFIZZ_URI ":" "sfzfile"
|
#define SFIZZ__sfzFile SFIZZ_URI ":" "sfzfile"
|
||||||
|
|
@ -73,6 +75,44 @@ enum
|
||||||
SFIZZ_NUM_SAMPLES = 19,
|
SFIZZ_NUM_SAMPLES = 19,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SFIZZ_MULTI_CONTROL = 0,
|
||||||
|
SFIZZ_MULTI_AUTOMATE = 1,
|
||||||
|
SFIZZ_MULTI_OUT1L = 2,
|
||||||
|
SFIZZ_MULTI_OUT1R = 3,
|
||||||
|
SFIZZ_MULTI_OUT2L = 4,
|
||||||
|
SFIZZ_MULTI_OUT2R = 5,
|
||||||
|
SFIZZ_MULTI_OUT3L = 6,
|
||||||
|
SFIZZ_MULTI_OUT3R = 7,
|
||||||
|
SFIZZ_MULTI_OUT4L = 8,
|
||||||
|
SFIZZ_MULTI_OUT4R = 9,
|
||||||
|
SFIZZ_MULTI_OUT5L = 10,
|
||||||
|
SFIZZ_MULTI_OUT5R = 11,
|
||||||
|
SFIZZ_MULTI_OUT6L = 12,
|
||||||
|
SFIZZ_MULTI_OUT6R = 13,
|
||||||
|
SFIZZ_MULTI_OUT7L = 14,
|
||||||
|
SFIZZ_MULTI_OUT7R = 15,
|
||||||
|
SFIZZ_MULTI_OUT8L = 16,
|
||||||
|
SFIZZ_MULTI_OUT8R = 17,
|
||||||
|
SFIZZ_MULTI_VOLUME = 18,
|
||||||
|
SFIZZ_MULTI_POLYPHONY = 19,
|
||||||
|
SFIZZ_MULTI_OVERSAMPLING = 20,
|
||||||
|
SFIZZ_MULTI_PRELOAD = 21,
|
||||||
|
SFIZZ_MULTI_FREEWHEELING = 22,
|
||||||
|
SFIZZ_MULTI_SCALA_ROOT_KEY = 23,
|
||||||
|
SFIZZ_MULTI_TUNING_FREQUENCY = 24,
|
||||||
|
SFIZZ_MULTI_STRETCH_TUNING = 25,
|
||||||
|
SFIZZ_MULTI_SAMPLE_QUALITY = 26,
|
||||||
|
SFIZZ_MULTI_OSCILLATOR_QUALITY = 27,
|
||||||
|
SFIZZ_MULTI_ACTIVE_VOICES = 28,
|
||||||
|
SFIZZ_MULTI_NUM_CURVES = 29,
|
||||||
|
SFIZZ_MULTI_NUM_MASTERS = 30,
|
||||||
|
SFIZZ_MULTI_NUM_GROUPS = 31,
|
||||||
|
SFIZZ_MULTI_NUM_REGIONS = 32,
|
||||||
|
SFIZZ_MULTI_NUM_SAMPLES = 33,
|
||||||
|
};
|
||||||
|
|
||||||
// For use with instance-access
|
// For use with instance-access
|
||||||
struct sfizz_plugin_t;
|
struct sfizz_plugin_t;
|
||||||
|
|
||||||
|
|
@ -92,6 +132,14 @@ bool sfizz_lv2_fetch_description(
|
||||||
sfizz_plugin_t *self, const int *serial,
|
sfizz_plugin_t *self, const int *serial,
|
||||||
uint8_t **descp, uint32_t *sizep, int *serialp);
|
uint8_t **descp, uint32_t *sizep, int *serialp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of outputs of the plugin
|
||||||
|
*
|
||||||
|
* @param self
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int sfizz_lv2_get_num_outputs(sfizz_plugin_t *self);
|
||||||
|
|
||||||
#if defined(SFIZZ_LV2_UI)
|
#if defined(SFIZZ_LV2_UI)
|
||||||
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active);
|
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ bool sfizz_lv2_fetch_description(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sfizz_lv2_get_num_outputs(sfizz_plugin_t *self)
|
||||||
|
{
|
||||||
|
if (self->multi_out)
|
||||||
|
return MULTI_OUTPUT_COUNT;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(SFIZZ_LV2_UI)
|
#if defined(SFIZZ_LV2_UI)
|
||||||
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active)
|
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,10 @@ struct sfizz_plugin_t
|
||||||
LV2_Midnam *midnam {};
|
LV2_Midnam *midnam {};
|
||||||
|
|
||||||
// Ports
|
// Ports
|
||||||
|
bool multi_out { false };
|
||||||
const LV2_Atom_Sequence *control_port {};
|
const LV2_Atom_Sequence *control_port {};
|
||||||
LV2_Atom_Sequence *automate_port {};
|
LV2_Atom_Sequence *automate_port {};
|
||||||
float *output_buffers[2] {};
|
float *output_buffers[MULTI_OUTPUT_COUNT] {};
|
||||||
const float *volume_port {};
|
const float *volume_port {};
|
||||||
const float *polyphony_port {};
|
const float *polyphony_port {};
|
||||||
const float *oversampling_port {};
|
const float *oversampling_port {};
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
|
||||||
|
|
||||||
int sfz_serial = 0;
|
int sfz_serial = 0;
|
||||||
bool valid_sfz_serial = false;
|
bool valid_sfz_serial = false;
|
||||||
|
bool multi_out = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void uiSendValue(EditId id, const EditValue& v) override;
|
void uiSendValue(EditId id, const EditValue& v) override;
|
||||||
|
|
@ -271,6 +272,13 @@ instantiate(const LV2UI_Descriptor *descriptor,
|
||||||
// LV2 has its own path management mechanism
|
// LV2 has its own path management mechanism
|
||||||
self->uiReceiveValue(EditId::CanEditUserFilesDir, 0);
|
self->uiReceiveValue(EditId::CanEditUserFilesDir, 0);
|
||||||
|
|
||||||
|
// Send the number of outputs if necessary
|
||||||
|
int numOutputs = sfizz_lv2_get_num_outputs(self->plugin);
|
||||||
|
if (numOutputs > 2) {
|
||||||
|
self->multi_out = true;
|
||||||
|
self->uiReceiveValue(EditId::PluginOutputs, numOutputs);
|
||||||
|
}
|
||||||
|
|
||||||
*widget = reinterpret_cast<LV2UI_Widget>(uiFrame->getPlatformFrame()->getPlatformRepresentation());
|
*widget = reinterpret_cast<LV2UI_Widget>(uiFrame->getPlatformFrame()->getPlatformRepresentation());
|
||||||
|
|
||||||
if (self->resize)
|
if (self->resize)
|
||||||
|
|
@ -295,6 +303,114 @@ cleanup(LV2UI_Handle ui)
|
||||||
delete self;
|
delete self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
port_event_stereo(sfizz_ui_t *self, uint32_t port_index, const void *buffer)
|
||||||
|
{
|
||||||
|
const float v = *reinterpret_cast<const float*>(buffer);
|
||||||
|
|
||||||
|
switch (port_index) {
|
||||||
|
case SFIZZ_VOLUME:
|
||||||
|
self->uiReceiveValue(EditId::Volume, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_POLYPHONY:
|
||||||
|
self->uiReceiveValue(EditId::Polyphony, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_OVERSAMPLING:
|
||||||
|
self->uiReceiveValue(EditId::Oversampling, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_PRELOAD:
|
||||||
|
self->uiReceiveValue(EditId::PreloadSize, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_SCALA_ROOT_KEY:
|
||||||
|
self->uiReceiveValue(EditId::ScalaRootKey, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_TUNING_FREQUENCY:
|
||||||
|
self->uiReceiveValue(EditId::TuningFrequency, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_STRETCH_TUNING:
|
||||||
|
self->uiReceiveValue(EditId::StretchTuning, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_SAMPLE_QUALITY:
|
||||||
|
self->uiReceiveValue(EditId::SampleQuality, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_OSCILLATOR_QUALITY:
|
||||||
|
self->uiReceiveValue(EditId::OscillatorQuality, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_ACTIVE_VOICES:
|
||||||
|
self->uiReceiveValue(EditId::UINumActiveVoices, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_NUM_CURVES:
|
||||||
|
self->uiReceiveValue(EditId::UINumCurves, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_NUM_MASTERS:
|
||||||
|
self->uiReceiveValue(EditId::UINumMasters, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_NUM_GROUPS:
|
||||||
|
self->uiReceiveValue(EditId::UINumGroups, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_NUM_REGIONS:
|
||||||
|
self->uiReceiveValue(EditId::UINumRegions, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_NUM_SAMPLES:
|
||||||
|
self->uiReceiveValue(EditId::UINumPreloadedSamples, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
port_event_multi(sfizz_ui_t *self, uint32_t port_index, const void *buffer)
|
||||||
|
{
|
||||||
|
const float v = *reinterpret_cast<const float*>(buffer);
|
||||||
|
|
||||||
|
switch (port_index) {
|
||||||
|
case SFIZZ_MULTI_VOLUME:
|
||||||
|
self->uiReceiveValue(EditId::Volume, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_POLYPHONY:
|
||||||
|
self->uiReceiveValue(EditId::Polyphony, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OVERSAMPLING:
|
||||||
|
self->uiReceiveValue(EditId::Oversampling, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_PRELOAD:
|
||||||
|
self->uiReceiveValue(EditId::PreloadSize, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_SCALA_ROOT_KEY:
|
||||||
|
self->uiReceiveValue(EditId::ScalaRootKey, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_TUNING_FREQUENCY:
|
||||||
|
self->uiReceiveValue(EditId::TuningFrequency, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_STRETCH_TUNING:
|
||||||
|
self->uiReceiveValue(EditId::StretchTuning, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_SAMPLE_QUALITY:
|
||||||
|
self->uiReceiveValue(EditId::SampleQuality, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_OSCILLATOR_QUALITY:
|
||||||
|
self->uiReceiveValue(EditId::OscillatorQuality, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_ACTIVE_VOICES:
|
||||||
|
self->uiReceiveValue(EditId::UINumActiveVoices, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_CURVES:
|
||||||
|
self->uiReceiveValue(EditId::UINumCurves, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_MASTERS:
|
||||||
|
self->uiReceiveValue(EditId::UINumMasters, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_GROUPS:
|
||||||
|
self->uiReceiveValue(EditId::UINumGroups, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_REGIONS:
|
||||||
|
self->uiReceiveValue(EditId::UINumRegions, v);
|
||||||
|
break;
|
||||||
|
case SFIZZ_MULTI_NUM_SAMPLES:
|
||||||
|
self->uiReceiveValue(EditId::UINumPreloadedSamples, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
port_event(LV2UI_Handle ui,
|
port_event(LV2UI_Handle ui,
|
||||||
uint32_t port_index,
|
uint32_t port_index,
|
||||||
|
|
@ -303,57 +419,11 @@ port_event(LV2UI_Handle ui,
|
||||||
const void *buffer)
|
const void *buffer)
|
||||||
{
|
{
|
||||||
sfizz_ui_t *self = (sfizz_ui_t *)ui;
|
sfizz_ui_t *self = (sfizz_ui_t *)ui;
|
||||||
|
|
||||||
if (format == 0) {
|
if (format == 0) {
|
||||||
const float v = *reinterpret_cast<const float*>(buffer);
|
if (self->multi_out)
|
||||||
|
port_event_multi(self, port_index, buffer);
|
||||||
switch (port_index) {
|
else
|
||||||
case SFIZZ_VOLUME:
|
port_event_stereo(self, port_index, buffer);
|
||||||
self->uiReceiveValue(EditId::Volume, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_POLYPHONY:
|
|
||||||
self->uiReceiveValue(EditId::Polyphony, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_OVERSAMPLING:
|
|
||||||
self->uiReceiveValue(EditId::Oversampling, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_PRELOAD:
|
|
||||||
self->uiReceiveValue(EditId::PreloadSize, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_SCALA_ROOT_KEY:
|
|
||||||
self->uiReceiveValue(EditId::ScalaRootKey, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_TUNING_FREQUENCY:
|
|
||||||
self->uiReceiveValue(EditId::TuningFrequency, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_STRETCH_TUNING:
|
|
||||||
self->uiReceiveValue(EditId::StretchTuning, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_SAMPLE_QUALITY:
|
|
||||||
self->uiReceiveValue(EditId::SampleQuality, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_OSCILLATOR_QUALITY:
|
|
||||||
self->uiReceiveValue(EditId::OscillatorQuality, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_ACTIVE_VOICES:
|
|
||||||
self->uiReceiveValue(EditId::UINumActiveVoices, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_NUM_CURVES:
|
|
||||||
self->uiReceiveValue(EditId::UINumCurves, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_NUM_MASTERS:
|
|
||||||
self->uiReceiveValue(EditId::UINumMasters, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_NUM_GROUPS:
|
|
||||||
self->uiReceiveValue(EditId::UINumGroups, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_NUM_REGIONS:
|
|
||||||
self->uiReceiveValue(EditId::UINumRegions, v);
|
|
||||||
break;
|
|
||||||
case SFIZZ_NUM_SAMPLES:
|
|
||||||
self->uiReceiveValue(EditId::UINumPreloadedSamples, v);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (format == self->atom_event_transfer_uri) {
|
else if (format == self->atom_event_transfer_uri) {
|
||||||
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
|
auto *atom = reinterpret_cast<const LV2_Atom *>(buffer);
|
||||||
|
|
@ -410,11 +480,8 @@ port_event(LV2UI_Handle ui,
|
||||||
const float *levels = reinterpret_cast<const float *>(vec_body);
|
const float *levels = reinterpret_cast<const float *>(vec_body);
|
||||||
uint32_t count = static_cast<uint32_t>((vec_end - vec_body) / sizeof(float));
|
uint32_t count = static_cast<uint32_t>((vec_end - vec_body) / sizeof(float));
|
||||||
|
|
||||||
float left = (count > 0) ? levels[0] : 0.0f;
|
for (uint32_t i = 0; i < count; ++i)
|
||||||
float right = (count > 1) ? levels[1] : 0.0f;
|
self->uiReceiveValue(editIdForLevel(i), levels[i]);
|
||||||
|
|
||||||
self->uiReceiveValue(EditId::LeftLevel, left);
|
|
||||||
self->uiReceiveValue(EditId::RightLevel, right);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,14 +105,15 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume levels
|
// Volume levels
|
||||||
parameters.addParameter(
|
for (unsigned i = 0; i < 16; ++i) {
|
||||||
SfizzRange::getForParameter(kPidLeftLevel).createParameter(
|
Steinberg::String title;
|
||||||
Steinberg::String("Left level"), pid++, nullptr,
|
title.printf("Level %u", i);
|
||||||
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
|
|
||||||
parameters.addParameter(
|
parameters.addParameter(
|
||||||
SfizzRange::getForParameter(kPidRightLevel).createParameter(
|
SfizzRange::getForParameter(kPidLevel0 + i).createParameter(
|
||||||
Steinberg::String("Right level"), pid++, nullptr,
|
title, pid++, nullptr, 0,
|
||||||
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
|
Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
|
||||||
|
}
|
||||||
|
|
||||||
// Editor status
|
// Editor status
|
||||||
parameters.addParameter(
|
parameters.addParameter(
|
||||||
|
|
|
||||||
|
|
@ -343,16 +343,16 @@ void SfizzVstEditor::updateParameter(Vst::Parameter* parameterToUpdate)
|
||||||
case kPidOscillatorQuality:
|
case kPidOscillatorQuality:
|
||||||
uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value));
|
uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value));
|
||||||
break;
|
break;
|
||||||
case kPidLeftLevel:
|
case kPidNumOutputs:
|
||||||
uiReceiveValue(EditId::LeftLevel, range.denormalize(value));
|
uiReceiveValue(EditId::PluginOutputs, (int32)range.denormalize(value));
|
||||||
break;
|
|
||||||
case kPidRightLevel:
|
|
||||||
uiReceiveValue(EditId::RightLevel, range.denormalize(value));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (id >= kPidCC0 && id <= kPidCCLast) {
|
if (id >= kPidCC0 && id <= kPidCCLast) {
|
||||||
int cc = int(id - kPidCC0);
|
int cc = int(id - kPidCC0);
|
||||||
uiReceiveValue(editIdForCC(cc), range.denormalize(value));
|
uiReceiveValue(editIdForCC(cc), range.denormalize(value));
|
||||||
|
} else if (id >= kPidLevel0 && id <= kPidLevelLast) {
|
||||||
|
int levelId = int(id - kPidLevel0);
|
||||||
|
uiReceiveValue(editIdForLevel(levelId), range.denormalize(value));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -517,6 +517,8 @@ Vst::ParamID SfizzVstEditor::parameterOfEditId(EditId id)
|
||||||
default:
|
default:
|
||||||
if (editIdIsCC(id))
|
if (editIdIsCC(id))
|
||||||
return kPidCC0 + ccForEditId(id);
|
return kPidCC0 + ccForEditId(id);
|
||||||
|
else if (editIdIsLevel(id))
|
||||||
|
return kPidLevel0 + levelForEditId(id);
|
||||||
return Vst::kNoParamId;
|
return Vst::kNoParamId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
*/
|
*/
|
||||||
#define SfizzVstProcessor_cid \
|
#define SfizzVstProcessor_cid \
|
||||||
Steinberg::FUID(0xe8fab718, 0x15ed46e3, 0x8b598310, 0x1e12993f)
|
Steinberg::FUID(0xe8fab718, 0x15ed46e3, 0x8b598310, 0x1e12993f)
|
||||||
|
#define SfizzVstProcessorMulti_cid \
|
||||||
|
Steinberg::FUID(0xc9da9274, 0x43794873, 0xa900ed81, 0xd1946115)
|
||||||
#define SfizzVstController_cid \
|
#define SfizzVstController_cid \
|
||||||
Steinberg::FUID(0x7129736c, 0xbc784134, 0xbb899d56, 0x2ebafe4f)
|
Steinberg::FUID(0x7129736c, 0xbc784134, 0xbb899d56, 0x2ebafe4f)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ enum {
|
||||||
kPidPitchBend,
|
kPidPitchBend,
|
||||||
kPidCC0,
|
kPidCC0,
|
||||||
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
|
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
|
||||||
kPidLeftLevel,
|
kPidNumOutputs,
|
||||||
kPidRightLevel,
|
kPidLevel0,
|
||||||
|
kPidLevelLast = kPidLevel0 + 16,
|
||||||
kPidEditorOpen,
|
kPidEditorOpen,
|
||||||
/* Reserved */
|
/* Reserved */
|
||||||
kNumParameters,
|
kNumParameters,
|
||||||
|
|
@ -81,15 +82,15 @@ struct SfizzRange {
|
||||||
return {0.0, 0.0, 1.0};
|
return {0.0, 0.0, 1.0};
|
||||||
case kPidPitchBend:
|
case kPidPitchBend:
|
||||||
return {0.0, -1.0, 1.0};
|
return {0.0, -1.0, 1.0};
|
||||||
case kPidLeftLevel:
|
|
||||||
return {0.0, 0.0, 1.0};
|
|
||||||
case kPidRightLevel:
|
|
||||||
return {0.0, 0.0, 1.0};
|
|
||||||
case kPidEditorOpen:
|
case kPidEditorOpen:
|
||||||
return {0.0, 0.0, 1.0};
|
return {0.0, 0.0, 1.0};
|
||||||
|
case kPidNumOutputs:
|
||||||
|
return {2.0, 2.0, 16.0};
|
||||||
default:
|
default:
|
||||||
if (id >= kPidCC0 && id <= kPidCCLast)
|
if (id >= kPidCC0 && id <= kPidCCLast)
|
||||||
return {0.0, 0.0, 1.0};
|
return {0.0, 0.0, 1.0};
|
||||||
|
else if (id >= kPidLevel0 && id <= kPidLevelLast)
|
||||||
|
return {0.0, 0.0, 1.0};
|
||||||
throw std::runtime_error("Bad parameter ID");
|
throw std::runtime_error("Bad parameter ID");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
|
||||||
_scalaUpdate->addDependent(this);
|
_scalaUpdate->addDependent(this);
|
||||||
_automationUpdate->addDependent(this);
|
_automationUpdate->addDependent(this);
|
||||||
|
|
||||||
addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo);
|
addAudioOutput(STR16("Audio Output 1"), Vst::SpeakerArr::kStereo);
|
||||||
addEventInput(STR16("Event Input"), 1);
|
addEventInput(STR16("Event Input"), 1);
|
||||||
|
|
||||||
_state = SfizzVstState();
|
_state = SfizzVstState();
|
||||||
|
|
@ -113,7 +113,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
|
||||||
_synth->setBroadcastCallback(onMessage, this);
|
_synth->setBroadcastCallback(onMessage, this);
|
||||||
|
|
||||||
_currentStretchedTuning = 0.0;
|
_currentStretchedTuning = 0.0;
|
||||||
loadSfzFileOrDefault({}, false);
|
loadSfzFileOrDefault({});
|
||||||
|
|
||||||
_synth->bpmTempo(0, 120);
|
_synth->bpmTempo(0, 120);
|
||||||
_timeSigNumerator = 4;
|
_timeSigNumerator = 4;
|
||||||
|
|
@ -143,9 +143,11 @@ tresult PLUGIN_API SfizzVstProcessor::terminate()
|
||||||
|
|
||||||
tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts)
|
tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts)
|
||||||
{
|
{
|
||||||
bool isStereo = numIns == 0 && numOuts == 1 && outputs[0] == Vst::SpeakerArr::kStereo;
|
bool allStereo { true };
|
||||||
|
for (unsigned o = 0; o < numOuts; ++o)
|
||||||
|
allStereo &= (outputs[o] == Vst::SpeakerArr::kStereo);
|
||||||
|
|
||||||
if (!isStereo)
|
if (!allStereo)
|
||||||
return kResultFalse;
|
return kResultFalse;
|
||||||
|
|
||||||
return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts);
|
return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts);
|
||||||
|
|
@ -218,7 +220,7 @@ void SfizzVstProcessor::syncStateToSynth()
|
||||||
if (!synth)
|
if (!synth)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
loadSfzFileOrDefault(_state.sfzFile, true);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
synth->setVolume(_state.volume);
|
synth->setVolume(_state.volume);
|
||||||
synth->setNumVoices(_state.numVoices);
|
synth->setNumVoices(_state.numVoices);
|
||||||
synth->setOversamplingFactor(1 << _state.oversamplingLog2);
|
synth->setOversamplingFactor(1 << _state.oversamplingLog2);
|
||||||
|
|
@ -274,30 +276,45 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
if (data.processMode == Vst::kOffline)
|
if (data.processMode == Vst::kOffline)
|
||||||
lock.lock();
|
lock.lock();
|
||||||
else
|
else
|
||||||
lock.try_lock();
|
(void)lock.try_lock();
|
||||||
|
|
||||||
if (data.processContext)
|
if (data.processContext)
|
||||||
updateTimeInfo(*data.processContext);
|
updateTimeInfo(*data.processContext);
|
||||||
|
|
||||||
const uint32 numFrames = data.numSamples;
|
const uint32 numFrames = data.numSamples;
|
||||||
_canPerformEventsAndParameters = lock.owns_lock();
|
_canPerformEventsAndParameters = lock.owns_lock();
|
||||||
|
bool editorWasOpen = _editorIsOpen;
|
||||||
processUnorderedEvents(numFrames, data.inputParameterChanges, data.inputEvents);
|
processUnorderedEvents(numFrames, data.inputParameterChanges, data.inputEvents);
|
||||||
|
|
||||||
if (data.numOutputs < 1) // flush mode
|
if (data.numOutputs < 1) // flush mode
|
||||||
return kResultTrue;
|
return kResultTrue;
|
||||||
|
|
||||||
constexpr uint32 numChannels = 2;
|
constexpr uint32 numChannels = 2;
|
||||||
float* outputs[numChannels];
|
constexpr uint32 maxChannels = 16;
|
||||||
|
float* outputs[maxChannels];
|
||||||
|
const auto numMonoChannels = data.numOutputs * numChannels;
|
||||||
|
|
||||||
assert(numChannels == data.outputs[0].numChannels);
|
for (unsigned o = 0; o < data.numOutputs; ++o) {
|
||||||
|
assert(data.outputs[o].numChannels == numChannels);
|
||||||
|
for (unsigned c = 0; c < numChannels; ++c)
|
||||||
|
outputs[numChannels * o + c] = data.outputs[o].channelBuffers32[c];
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned c = 0; c < numChannels; ++c)
|
if (!editorWasOpen && _editorIsOpen) {
|
||||||
outputs[c] = data.outputs[0].channelBuffers32[c];
|
if (Vst::IParameterChanges* pcs = data.outputParameterChanges) {
|
||||||
|
int32 index;
|
||||||
|
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidNumOutputs, index))
|
||||||
|
vq->addPoint(0, SfizzRange::getForParameter(kPidNumOutputs).normalize(numMonoChannels), index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!lock.owns_lock()) {
|
if (!lock.owns_lock()) {
|
||||||
for (unsigned c = 0; c < numChannels; ++c)
|
for (unsigned c = 0; c < numChannels; ++c)
|
||||||
std::memset(outputs[c], 0, numFrames * sizeof(float));
|
std::memset(outputs[c], 0, numFrames * sizeof(float));
|
||||||
data.outputs[0].silenceFlags = 3;
|
|
||||||
|
for (unsigned o = 0; o < data.numOutputs; ++o)
|
||||||
|
data.outputs[o].silenceFlags = 3;
|
||||||
|
|
||||||
return kResultTrue;
|
return kResultTrue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,21 +335,20 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
|
||||||
synth.setSampleQuality(sfz::Sfizz::ProcessLive, _state.sampleQuality);
|
synth.setSampleQuality(sfz::Sfizz::ProcessLive, _state.sampleQuality);
|
||||||
synth.setOscillatorQuality(sfz::Sfizz::ProcessLive, _state.oscillatorQuality);
|
synth.setOscillatorQuality(sfz::Sfizz::ProcessLive, _state.oscillatorQuality);
|
||||||
|
|
||||||
synth.renderBlock(outputs, numFrames, numChannels);
|
synth.renderBlock(outputs, numFrames, data.numOutputs);
|
||||||
|
|
||||||
// Update levels, if editor is open, otherwise skip
|
// Update levels, if editor is open, otherwise skip
|
||||||
RMSFollower& rmsFollower = _rmsFollower;
|
RMSFollower& rmsFollower = _rmsFollower;
|
||||||
if (_editorIsOpen) {
|
if (_editorIsOpen) {
|
||||||
rmsFollower.process(outputs[0], outputs[1], numFrames);
|
rmsFollower.process((const float**)outputs, numFrames, numMonoChannels);
|
||||||
simde__m128 rms = _rmsFollower.getRMS();
|
float levels[maxChannels];
|
||||||
float left = reinterpret_cast<float*>(&rms)[0];
|
rmsFollower.getRMS(levels, numMonoChannels);
|
||||||
float right = reinterpret_cast<float*>(&rms)[1];
|
|
||||||
if (Vst::IParameterChanges* pcs = data.outputParameterChanges) {
|
if (Vst::IParameterChanges* pcs = data.outputParameterChanges) {
|
||||||
int32 index;
|
int32 index;
|
||||||
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidLeftLevel, index))
|
for (int c = 0; c < numMonoChannels; ++c) {
|
||||||
vq->addPoint(0, left, index);
|
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidLevel0 + c, index))
|
||||||
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidRightLevel, index))
|
vq->addPoint(0, levels[c], index);
|
||||||
vq->addPoint(0, right, index);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -574,7 +590,7 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
|
||||||
|
|
||||||
std::unique_lock<SpinMutex> lock(_processMutex);
|
std::unique_lock<SpinMutex> lock(_processMutex);
|
||||||
_state.sfzFile.assign(static_cast<const char *>(data), size);
|
_state.sfzFile.assign(static_cast<const char *>(data), size);
|
||||||
loadSfzFileOrDefault(_state.sfzFile, false);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
else if (!std::strcmp(id, "LoadScala")) {
|
else if (!std::strcmp(id, "LoadScala")) {
|
||||||
|
|
@ -689,7 +705,7 @@ void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState)
|
void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath)
|
||||||
{
|
{
|
||||||
sfz::Sfizz& synth = *_synth;
|
sfz::Sfizz& synth = *_synth;
|
||||||
|
|
||||||
|
|
@ -709,16 +725,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
|
||||||
const InstrumentDescription desc = parseDescriptionBlob(descBlob);
|
const InstrumentDescription desc = parseDescriptionBlob(descBlob);
|
||||||
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
||||||
if (desc.ccUsed.test(cc))
|
if (desc.ccUsed.test(cc))
|
||||||
newControllers[cc] = desc.ccDefault[cc];
|
newControllers[cc] = desc.ccValue[cc];
|
||||||
}
|
|
||||||
// set CC from existing state
|
|
||||||
if (initParametersFromState) {
|
|
||||||
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
|
|
||||||
if (absl::optional<float> value = oldControllers[cc]) {
|
|
||||||
newControllers[cc] = *value;
|
|
||||||
synth.automateHdcc(0, int(cc), *value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_state.controllers = std::move(newControllers);
|
_state.controllers = std::move(newControllers);
|
||||||
}
|
}
|
||||||
|
|
@ -822,7 +829,7 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
|
||||||
if (_synth->shouldReloadFile()) {
|
if (_synth->shouldReloadFile()) {
|
||||||
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
|
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
|
||||||
std::lock_guard<SpinMutex> lock(_processMutex);
|
std::lock_guard<SpinMutex> lock(_processMutex);
|
||||||
loadSfzFileOrDefault(_state.sfzFile, false);
|
loadSfzFileOrDefault(_state.sfzFile);
|
||||||
}
|
}
|
||||||
if (_synth->shouldReloadScala()) {
|
if (_synth->shouldReloadScala()) {
|
||||||
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
|
fprintf(stderr, "[Sfizz] scala file has changed, reloading\n");
|
||||||
|
|
@ -916,10 +923,39 @@ FUnknown* SfizzVstProcessor::createInstance(void*)
|
||||||
return static_cast<Vst::IAudioProcessor*>(new SfizzVstProcessor);
|
return static_cast<Vst::IAudioProcessor*>(new SfizzVstProcessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FUnknown* SfizzVstProcessorMulti::createInstance(void*)
|
||||||
|
{
|
||||||
|
return static_cast<Vst::IAudioProcessor*>(new SfizzVstProcessorMulti);
|
||||||
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
FUnknown* createInstance<SfizzVstProcessor>(void* context)
|
FUnknown* createInstance<SfizzVstProcessor>(void* context)
|
||||||
{
|
{
|
||||||
return SfizzVstProcessor::createInstance(context);
|
return SfizzVstProcessor::createInstance(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tresult PLUGIN_API SfizzVstProcessorMulti::initialize(FUnknown* context)
|
||||||
|
{
|
||||||
|
tresult res = SfizzVstProcessor::initialize(context);
|
||||||
|
if (res != kResultFalse) {
|
||||||
|
addAudioOutput(STR16("Audio Output 2"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 3"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 4"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 5"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 6"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 7"), Vst::SpeakerArr::kStereo);
|
||||||
|
addAudioOutput(STR16("Audio Output 8"), Vst::SpeakerArr::kStereo);
|
||||||
|
}
|
||||||
|
_multi = true;
|
||||||
|
_rmsFollower.setNumOutputs(16);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
FUnknown* createInstance<SfizzVstProcessorMulti>(void* context)
|
||||||
|
{
|
||||||
|
return SfizzVstProcessorMulti::createInstance(context);
|
||||||
|
}
|
||||||
|
|
||||||
FUID SfizzVstProcessor::cid = SfizzVstProcessor_cid;
|
FUID SfizzVstProcessor::cid = SfizzVstProcessor_cid;
|
||||||
|
FUID SfizzVstProcessorMulti::cid = SfizzVstProcessor_cid;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ public:
|
||||||
static FUID cid;
|
static FUID cid;
|
||||||
|
|
||||||
// --- Sfizz stuff here below ---
|
// --- Sfizz stuff here below ---
|
||||||
|
protected:
|
||||||
|
RMSFollower _rmsFollower {};
|
||||||
|
bool _multi { false };
|
||||||
private:
|
private:
|
||||||
// synth state. acquire processMutex before accessing
|
// synth state. acquire processMutex before accessing
|
||||||
std::unique_ptr<sfz::Sfizz> _synth;
|
std::unique_ptr<sfz::Sfizz> _synth;
|
||||||
|
|
@ -66,7 +69,6 @@ private:
|
||||||
bool _canPerformEventsAndParameters {};
|
bool _canPerformEventsAndParameters {};
|
||||||
|
|
||||||
// level meters
|
// level meters
|
||||||
RMSFollower _rmsFollower;
|
|
||||||
bool _editorIsOpen = false;
|
bool _editorIsOpen = false;
|
||||||
|
|
||||||
// updates
|
// updates
|
||||||
|
|
@ -84,7 +86,7 @@ private:
|
||||||
void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
|
void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState);
|
void loadSfzFileOrDefault(const std::string& filePath);
|
||||||
|
|
||||||
// note event tracking
|
// note event tracking
|
||||||
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change
|
std::array<float, 128> _noteEventsCurrentCycle; // 0: off, >0: on, <0: no change
|
||||||
|
|
@ -129,6 +131,12 @@ private:
|
||||||
static bool writeMessage(Ring_Buffer& fifo, const char* type, const void* data, uintptr_t size);
|
static bool writeMessage(Ring_Buffer& fifo, const char* type, const void* data, uintptr_t size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SfizzVstProcessorMulti : public SfizzVstProcessor {
|
||||||
|
public:
|
||||||
|
tresult PLUGIN_API initialize(FUnknown* context) override;
|
||||||
|
static FUnknown* createInstance(void*);
|
||||||
|
static FUID cid;
|
||||||
|
};
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
template <class T> const T* SfizzVstProcessor::RTMessage::payload() const
|
template <class T> const T* SfizzVstProcessor::RTMessage::payload() const
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
||||||
|
|
||||||
class SfizzVstProcessor;
|
class SfizzVstProcessor;
|
||||||
|
class SfizzVstProcessorMulti;
|
||||||
class SfizzVstController;
|
class SfizzVstController;
|
||||||
|
|
||||||
BEGIN_FACTORY_DEF(VSTPLUGIN_VENDOR,
|
BEGIN_FACTORY_DEF(VSTPLUGIN_VENDOR,
|
||||||
|
|
@ -28,6 +29,16 @@ DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstProcessor_cid),
|
||||||
kVstVersionString,
|
kVstVersionString,
|
||||||
createInstance<SfizzVstProcessor>)
|
createInstance<SfizzVstProcessor>)
|
||||||
|
|
||||||
|
DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstProcessorMulti_cid),
|
||||||
|
PClassInfo::kManyInstances,
|
||||||
|
kVstAudioEffectClass,
|
||||||
|
VSTPLUGIN_NAME,
|
||||||
|
Vst::kDistributable,
|
||||||
|
Vst::PlugType::kInstrumentSynth,
|
||||||
|
VSTPLUGIN_VERSION,
|
||||||
|
kVstVersionString,
|
||||||
|
createInstance<SfizzVstProcessorMulti>)
|
||||||
|
|
||||||
DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstController_cid),
|
DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstController_cid),
|
||||||
PClassInfo::kManyInstances,
|
PClassInfo::kManyInstances,
|
||||||
kVstComponentControllerClass,
|
kVstComponentControllerClass,
|
||||||
|
|
|
||||||
|
|
@ -699,7 +699,8 @@ SFIZZ_EXPORTED_API void sfizz_send_playback_state(sfizz_synth_t* synth, int dela
|
||||||
*
|
*
|
||||||
* @param synth The synth.
|
* @param synth The synth.
|
||||||
* @param channels Pointers to the left and right channel of the output.
|
* @param channels Pointers to the left and right channel of the output.
|
||||||
* @param num_channels Should be equal to 2 for the time being.
|
* @param num_channels Number of output channels; should be a multiple of 2 as
|
||||||
|
* sfizz only handles stereo outputs.
|
||||||
* @param num_frames Number of frames to fill. This should be less than
|
* @param num_frames Number of frames to fill. This should be less than
|
||||||
* or equal to the expected samples_per_block.
|
* or equal to the expected samples_per_block.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace sfz
|
||||||
* @tparam MaxChannels the maximum number of channels in the buffer
|
* @tparam MaxChannels the maximum number of channels in the buffer
|
||||||
* @tparam Alignment the alignment for the buffers
|
* @tparam Alignment the alignment for the buffers
|
||||||
*/
|
*/
|
||||||
template <class Type, size_t MaxChannels = config::numChannels,
|
template <class Type, size_t MaxChannels = config::maxChannels,
|
||||||
unsigned int Alignment = config::defaultAlignment,
|
unsigned int Alignment = config::defaultAlignment,
|
||||||
size_t PaddingLeft_ = 0, size_t PaddingRight_ = 0>
|
size_t PaddingLeft_ = 0, size_t PaddingRight_ = 0>
|
||||||
class AudioBuffer {
|
class AudioBuffer {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ namespace sfz
|
||||||
* @tparam Type the underlying buffer type
|
* @tparam Type the underlying buffer type
|
||||||
* @tparam MaxChannels the maximum number of channels. Defaults to sfz::config::numChannels
|
* @tparam MaxChannels the maximum number of channels. Defaults to sfz::config::numChannels
|
||||||
*/
|
*/
|
||||||
template <class Type, size_t MaxChannels = sfz::config::numChannels>
|
template <class Type, size_t MaxChannels = sfz::config::maxChannels>
|
||||||
class AudioSpan {
|
class AudioSpan {
|
||||||
public:
|
public:
|
||||||
using size_type = size_t;
|
using size_type = size_t;
|
||||||
|
|
@ -95,6 +95,23 @@ public:
|
||||||
this->spans[i] = spans[i] + offset;
|
this->spans[i] = spans[i] + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Audio Span object
|
||||||
|
*
|
||||||
|
* @param spans an array of pointers to buffers.
|
||||||
|
* @param numChannels the number of spans to take in from the array
|
||||||
|
* @param offset starting offset for the AudioSpan
|
||||||
|
* @param numFrames size of the AudioSpan
|
||||||
|
*/
|
||||||
|
AudioSpan(Type** spans, size_type numChannels, size_type offset, size_type numFrames)
|
||||||
|
: numFrames(numFrames)
|
||||||
|
, numChannels(numChannels)
|
||||||
|
{
|
||||||
|
ASSERT(numChannels <= MaxChannels);
|
||||||
|
for (size_t i = 0; i < numChannels; ++i)
|
||||||
|
this->spans[i] = spans[i] + offset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new Audio Span object from initializer lists
|
* @brief Construct a new Audio Span object from initializer lists
|
||||||
*
|
*
|
||||||
|
|
@ -440,12 +457,18 @@ public:
|
||||||
*
|
*
|
||||||
* @param length the number of elements to take on each channel
|
* @param length the number of elements to take on each channel
|
||||||
*/
|
*/
|
||||||
AudioSpan<Type> subspan(size_type offset) const
|
AudioSpan<Type, MaxChannels> subspan(size_type offset) const
|
||||||
{
|
{
|
||||||
ASSERT(offset <= numFrames);
|
ASSERT(offset <= numFrames);
|
||||||
return { spans, numChannels, offset, numFrames - offset };
|
return { spans, numChannels, offset, numFrames - offset };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioSpan<Type, 2> getStereoSpan(size_type start) const
|
||||||
|
{
|
||||||
|
ASSERT(start + 1 < numChannels);
|
||||||
|
return { { spans[start], spans[start+1] }, 2, 0, numFrames };
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static_assert(MaxChannels > 0, "Need a positive number of channels");
|
static_assert(MaxChannels > 0, "Need a positive number of channels");
|
||||||
std::array<Type*, MaxChannels> spans;
|
std::array<Type*, MaxChannels> spans;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ namespace config {
|
||||||
constexpr int loggerQueueSize { 256 };
|
constexpr int loggerQueueSize { 256 };
|
||||||
constexpr int voiceLoggerQueueSize { 256 };
|
constexpr int voiceLoggerQueueSize { 256 };
|
||||||
constexpr bool loggingEnabled { false };
|
constexpr bool loggingEnabled { false };
|
||||||
constexpr size_t numChannels { 2 };
|
constexpr size_t maxChannels { 32 };
|
||||||
constexpr int numBackgroundThreads { 4 };
|
constexpr int numBackgroundThreads { 4 };
|
||||||
constexpr unsigned fileClearingPeriod { 5 }; // in seconds
|
constexpr unsigned fileClearingPeriod { 5 }; // in seconds
|
||||||
constexpr int numVoices { 64 };
|
constexpr int numVoices { 64 };
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ FloatSpec oscillatorModDepth { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermis
|
||||||
FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
||||||
Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
|
Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
|
||||||
Int64Spec group { 0, {-int32_t_max, uint32_t_max}, 0 };
|
Int64Spec group { 0, {-int32_t_max, uint32_t_max}, 0 };
|
||||||
|
UInt16Spec output { 0, {0, config::maxChannels / 2 - 1}, kEnforceBounds };
|
||||||
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
|
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
|
||||||
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
|
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
|
||||||
UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
|
UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> oscillatorModDepthMod;
|
extern const OpcodeSpec<float> oscillatorModDepthMod;
|
||||||
extern const OpcodeSpec<int32_t> oscillatorQuality;
|
extern const OpcodeSpec<int32_t> oscillatorQuality;
|
||||||
extern const OpcodeSpec<int64_t> group;
|
extern const OpcodeSpec<int64_t> group;
|
||||||
|
extern const OpcodeSpec<uint16_t> output;
|
||||||
extern const OpcodeSpec<float> offTime;
|
extern const OpcodeSpec<float> offTime;
|
||||||
extern const OpcodeSpec<uint32_t> polyphony;
|
extern const OpcodeSpec<uint32_t> polyphony;
|
||||||
extern const OpcodeSpec<uint32_t> notePolyphony;
|
extern const OpcodeSpec<uint32_t> notePolyphony;
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ void EffectBus::addEffect(std::unique_ptr<Effect> fx)
|
||||||
_effects.emplace_back(std::move(fx));
|
_effects.emplace_back(std::move(fx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Effect* EffectBus::effectView(unsigned index) const
|
||||||
|
{
|
||||||
|
if (index > _effects.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return _effects[index].get();
|
||||||
|
}
|
||||||
|
|
||||||
void EffectBus::clearInputs(unsigned nframes)
|
void EffectBus::clearInputs(unsigned nframes)
|
||||||
{
|
{
|
||||||
AudioSpan<float>(_inputs).first(nframes).fill(0.0f);
|
AudioSpan<float>(_inputs).first(nframes).fill(0.0f);
|
||||||
|
|
@ -111,6 +119,8 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign
|
||||||
if (addGain == 0)
|
if (addGain == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_hasSignal = true;
|
||||||
|
|
||||||
for (unsigned c = 0; c < EffectChannels; ++c) {
|
for (unsigned c = 0; c < EffectChannels; ++c) {
|
||||||
absl::Span<const float> addIn { addInput[c], nframes };
|
absl::Span<const float> addIn { addInput[c], nframes };
|
||||||
sfz::multiplyAdd1(addGain, addIn, _inputs.getSpan(c).first(nframes));
|
sfz::multiplyAdd1(addGain, addIn, _inputs.getSpan(c).first(nframes));
|
||||||
|
|
@ -144,15 +154,19 @@ void EffectBus::process(unsigned nframes)
|
||||||
{
|
{
|
||||||
size_t numEffects = _effects.size();
|
size_t numEffects = _effects.size();
|
||||||
|
|
||||||
|
// TODO: Can we have only one buffer and pass stuff without copies?
|
||||||
if (numEffects > 0 && hasNonZeroOutput()) {
|
if (numEffects > 0 && hasNonZeroOutput()) {
|
||||||
_effects[0]->process(
|
_effects[0]->process(
|
||||||
AudioSpan<float>(_inputs), AudioSpan<float>(_outputs), nframes);
|
AudioSpan<float>(_inputs), AudioSpan<float>(_outputs), nframes);
|
||||||
for (size_t i = 1; i < numEffects; ++i)
|
for (size_t i = 1; i < numEffects; ++i)
|
||||||
_effects[i]->process(
|
_effects[i]->process(
|
||||||
AudioSpan<float>(_outputs), AudioSpan<float>(_outputs), nframes);
|
AudioSpan<float>(_outputs), AudioSpan<float>(_outputs), nframes);
|
||||||
} else
|
} else {
|
||||||
fx::Nothing().process(
|
fx::Nothing().process(
|
||||||
AudioSpan<float>(_inputs), AudioSpan<float>(_outputs), nframes);
|
AudioSpan<float>(_inputs), AudioSpan<float>(_outputs), nframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasSignal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes)
|
void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes)
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,18 @@ public:
|
||||||
*/
|
*/
|
||||||
void addEffect(std::unique_ptr<Effect> fx);
|
void addEffect(std::unique_ptr<Effect> fx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a view into an effect in the chain
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @return const Effect*
|
||||||
|
*/
|
||||||
|
const Effect* effectView(unsigned index) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Checks whether this bus can produce output.
|
@brief Checks whether this bus can produce output.
|
||||||
*/
|
*/
|
||||||
bool hasNonZeroOutput() const { return _gainToMain != 0 || _gainToMix != 0; }
|
bool hasNonZeroOutput() const { return _hasSignal && (_gainToMain != 0 || _gainToMix != 0); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief Sets the amount of effect output going to the main.
|
@brief Sets the amount of effect output going to the main.
|
||||||
|
|
@ -181,6 +189,7 @@ private:
|
||||||
AudioBuffer<float> _outputs { EffectChannels, config::defaultSamplesPerBlock };
|
AudioBuffer<float> _outputs { EffectChannels, config::defaultSamplesPerBlock };
|
||||||
float _gainToMain { Default::effect };
|
float _gainToMain { Default::effect };
|
||||||
float _gainToMix { Default::effect };
|
float _gainToMix { Default::effect };
|
||||||
|
bool _hasSignal { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -320,6 +320,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
preloadedFiles[fileId].information.maxOffset = maxOffset;
|
preloadedFiles[fileId].information.maxOffset = maxOffset;
|
||||||
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad);
|
preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad);
|
||||||
}
|
}
|
||||||
|
existingFile->second.preloadCallCount++;
|
||||||
} else {
|
} else {
|
||||||
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
fileInformation->sampleRate = static_cast<double>(reader->sampleRate());
|
||||||
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
auto insertedPair = preloadedFiles.insert_or_assign(fileId, {
|
||||||
|
|
@ -327,14 +328,30 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce
|
||||||
*fileInformation
|
*fileInformation
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!insertedPair.second)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
insertedPair.first->second.status = FileData::Status::Preloaded;
|
insertedPair.first->second.status = FileData::Status::Preloaded;
|
||||||
|
insertedPair.first->second.preloadCallCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::FilePool::resetPreloadCallCounts() noexcept
|
||||||
|
{
|
||||||
|
for (auto& preloadedFile: preloadedFiles)
|
||||||
|
preloadedFile.second.preloadCallCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::FilePool::removeUnusedPreloadedData() noexcept
|
||||||
|
{
|
||||||
|
for (auto it = preloadedFiles.begin(), end = preloadedFiles.end(); it != end; ) {
|
||||||
|
auto copyIt = it++;
|
||||||
|
if (copyIt->second.preloadCallCount == 0) {
|
||||||
|
DBG("[sfizz] Removing unused preloaded data: " << copyIt->first.filename());
|
||||||
|
preloadedFiles.erase(copyIt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept
|
||||||
{
|
{
|
||||||
auto fileInformation = getFileInformation(fileId);
|
auto fileInformation = getFileInformation(fileId);
|
||||||
|
|
@ -363,7 +380,7 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr<FileId>&
|
||||||
{
|
{
|
||||||
const auto preloaded = preloadedFiles.find(*fileId);
|
const auto preloaded = preloadedFiles.find(*fileId);
|
||||||
if (preloaded == preloadedFiles.end()) {
|
if (preloaded == preloadedFiles.end()) {
|
||||||
DBG("[sfizz] File not found in the preloaded files: " << fileId);
|
DBG("[sfizz] File not found in the preloaded files: " << fileId->filename());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
QueuedFileData queuedData { fileId, &preloaded->second, std::chrono::high_resolution_clock::now() };
|
QueuedFileData queuedData { fileId, &preloaded->second, std::chrono::high_resolution_clock::now() };
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ struct FileData
|
||||||
FileAudioBuffer preloadedData;
|
FileAudioBuffer preloadedData;
|
||||||
FileInformation information;
|
FileInformation information;
|
||||||
FileAudioBuffer fileData {};
|
FileAudioBuffer fileData {};
|
||||||
|
int preloadCallCount { 0 };
|
||||||
std::atomic<Status> status { Status::Invalid };
|
std::atomic<Status> status { Status::Invalid };
|
||||||
std::atomic<size_t> availableFrames { 0 };
|
std::atomic<size_t> availableFrames { 0 };
|
||||||
std::atomic<int> readerCount { 0 };
|
std::atomic<int> readerCount { 0 };
|
||||||
|
|
@ -258,6 +259,17 @@ public:
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the number of preloadFile counts for each sample.
|
||||||
|
*/
|
||||||
|
void resetPreloadCallCounts() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all files with a preload call count of 0.
|
||||||
|
*/
|
||||||
|
void removeUnusedPreloadedData() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a handle on a file, which triggers background loading
|
* @brief Get a handle on a file, which triggers background loading
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
sfz::MidiState::MidiState()
|
sfz::MidiState::MidiState()
|
||||||
{
|
{
|
||||||
reset();
|
resetEventStates();
|
||||||
|
resetNoteStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
||||||
|
|
@ -221,11 +222,36 @@ float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept
|
||||||
return ccEvents[ccNumber].back().value;
|
return ccEvents[ccNumber].back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::reset() noexcept
|
void sfz::MidiState::resetNoteStates() noexcept
|
||||||
{
|
{
|
||||||
for (auto& velocity: lastNoteVelocities)
|
for (auto& velocity: lastNoteVelocities)
|
||||||
velocity = 0.0f;
|
velocity = 0.0f;
|
||||||
|
|
||||||
|
velocityOverride = 0.0f;
|
||||||
|
activeNotes = 0;
|
||||||
|
internalClock = 0;
|
||||||
|
lastNotePlayed = -1;
|
||||||
|
alternate = 0.0f;
|
||||||
|
|
||||||
|
auto setEvents = [] (EventVector& events, float value) {
|
||||||
|
events.clear();
|
||||||
|
events.push_back({ 0, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
setEvents(ccEvents[ExtendedCCs::noteOnVelocity], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::keyboardNoteNumber], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::unipolarRandom], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::bipolarRandom], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::keyboardNoteGate], 0.0f);
|
||||||
|
setEvents(ccEvents[ExtendedCCs::alternate], 0.0f);
|
||||||
|
|
||||||
|
noteStates.reset();
|
||||||
|
absl::c_fill(noteOnTimes, 0);
|
||||||
|
absl::c_fill(noteOffTimes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::MidiState::resetEventStates() noexcept
|
||||||
|
{
|
||||||
auto clearEvents = [] (EventVector& events) {
|
auto clearEvents = [] (EventVector& events) {
|
||||||
events.clear();
|
events.clear();
|
||||||
events.push_back({ 0, 0.0f });
|
events.push_back({ 0, 0.0f });
|
||||||
|
|
@ -239,14 +265,6 @@ void sfz::MidiState::reset() noexcept
|
||||||
|
|
||||||
clearEvents(pitchEvents);
|
clearEvents(pitchEvents);
|
||||||
clearEvents(channelAftertouchEvents);
|
clearEvents(channelAftertouchEvents);
|
||||||
|
|
||||||
velocityOverride = 0.0f;
|
|
||||||
activeNotes = 0;
|
|
||||||
internalClock = 0;
|
|
||||||
lastNotePlayed = -1;
|
|
||||||
noteStates.reset();
|
|
||||||
absl::c_fill(noteOnTimes, 0);
|
|
||||||
absl::c_fill(noteOffTimes, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
||||||
|
|
|
||||||
|
|
@ -184,15 +184,20 @@ public:
|
||||||
float getCCValueAt(int ccNumber, int delay) const noexcept;
|
float getCCValueAt(int ccNumber, int delay) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset the midi state (does not impact the last note on time)
|
* @brief Reset the midi note states
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void reset() noexcept;
|
void resetNoteStates() noexcept;
|
||||||
|
|
||||||
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||||
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept;
|
||||||
const EventVector& getPitchEvents() const noexcept;
|
const EventVector& getPitchEvents() const noexcept;
|
||||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Reset the midi event states (CC, AT, and pitch bend)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void resetEventStates() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,9 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
|
||||||
case hash("group"): // also polyphony_group
|
case hash("group"): // also polyphony_group
|
||||||
group = opcode.read(Default::group);
|
group = opcode.read(Default::group);
|
||||||
break;
|
break;
|
||||||
|
case hash("output"):
|
||||||
|
output = opcode.read(Default::output);
|
||||||
|
break;
|
||||||
case hash("off_by"): // also offby
|
case hash("off_by"): // also offby
|
||||||
offBy = opcode.readOptional(Default::group);
|
offBy = opcode.readOptional(Default::group);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ struct Region {
|
||||||
|
|
||||||
// Instrument settings: voice lifecycle
|
// Instrument settings: voice lifecycle
|
||||||
int64_t group { Default::group }; // group
|
int64_t group { Default::group }; // group
|
||||||
|
uint16_t output { Default::output }; // output
|
||||||
absl::optional<int64_t> offBy {}; // off_by
|
absl::optional<int64_t> offBy {}; // off_by
|
||||||
OffMode offMode { Default::offMode }; // off_mode
|
OffMode offMode { Default::offMode }; // off_mode
|
||||||
float offTime { Default::offTime }; // off_mode
|
float offTime { Default::offTime }; // off_mode
|
||||||
|
|
|
||||||
|
|
@ -61,19 +61,25 @@ void Resources::setSamplesPerBlock(int samplesPerBlock)
|
||||||
impl.beatClock.setSamplesPerBlock(samplesPerBlock);
|
impl.beatClock.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resources::clear()
|
void Resources::clearNonState()
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
impl.curves = CurveSet::createPredefined();
|
impl.curves = CurveSet::createPredefined();
|
||||||
impl.filePool.clear();
|
impl.filePool.clear();
|
||||||
impl.wavePool.clearFileWaves();
|
impl.wavePool.clearFileWaves();
|
||||||
impl.logger.clear();
|
impl.logger.clear();
|
||||||
impl.midiState.reset();
|
|
||||||
impl.modMatrix.clear();
|
impl.modMatrix.clear();
|
||||||
impl.beatClock.clear();
|
|
||||||
impl.metronome.clear();
|
impl.metronome.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Resources::clearState()
|
||||||
|
{
|
||||||
|
Impl& impl = *impl_;
|
||||||
|
impl.midiState.resetNoteStates();
|
||||||
|
impl.midiState.resetEventStates();
|
||||||
|
impl.beatClock.clear();
|
||||||
|
}
|
||||||
|
|
||||||
const SynthConfig& Resources::getSynthConfig() const noexcept
|
const SynthConfig& Resources::getSynthConfig() const noexcept
|
||||||
{
|
{
|
||||||
return impl_->synthConfig;
|
return impl_->synthConfig;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,17 @@ public:
|
||||||
|
|
||||||
void setSampleRate(float samplerate);
|
void setSampleRate(float samplerate);
|
||||||
void setSamplesPerBlock(int samplesPerBlock);
|
void setSamplesPerBlock(int samplesPerBlock);
|
||||||
void clear();
|
/**
|
||||||
|
* @brief Clear resources that are related to a currently loaded SFZ file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void clearNonState();
|
||||||
|
/**
|
||||||
|
* @brief Clear resources that are unrelated to the currently loaded SFZ file,
|
||||||
|
* i.e. midi state and beat clock.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void clearState();
|
||||||
|
|
||||||
#define ACCESSOR_RW(Accessor, RetTy) \
|
#define ACCESSOR_RW(Accessor, RetTy) \
|
||||||
RetTy const& Accessor() const noexcept; \
|
RetTy const& Accessor() const noexcept; \
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,10 @@ Synth::Impl::Impl()
|
||||||
|
|
||||||
parser_.setListener(this);
|
parser_.setListener(this);
|
||||||
effectFactory_.registerStandardEffectTypes();
|
effectFactory_.registerStandardEffectTypes();
|
||||||
effectBuses_.reserve(5); // sufficient room for main and fx1-4
|
initEffectBuses();
|
||||||
resetVoices(config::numVoices);
|
resetVoices(config::numVoices);
|
||||||
|
resetDefaultCCValues();
|
||||||
|
resetAllControllers(0);
|
||||||
|
|
||||||
// modulation sources
|
// modulation sources
|
||||||
MidiState& midiState = resources_.getMidiState();
|
MidiState& midiState = resources_.getMidiState();
|
||||||
|
|
@ -229,6 +231,27 @@ void Synth::Impl::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
lastLayer->initializeActivations();
|
lastLayer->initializeActivations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::addEffectBusesIfNecessary(uint16_t output)
|
||||||
|
{
|
||||||
|
while (effectBuses_.size() <= output) {
|
||||||
|
// Add output
|
||||||
|
effectBuses_.emplace_back();
|
||||||
|
auto& buses = effectBuses_.back();
|
||||||
|
// Add an empty main bus on output
|
||||||
|
buses.emplace_back(new EffectBus);
|
||||||
|
buses[0]->setGainToMain(1.0);
|
||||||
|
buses[0]->setSamplesPerBlock(samplesPerBlock_);
|
||||||
|
buses[0]->setSampleRate(sampleRate_);
|
||||||
|
buses[0]->clearInputs(samplesPerBlock_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::initEffectBuses()
|
||||||
|
{
|
||||||
|
effectBuses_.clear();
|
||||||
|
addEffectBusesIfNecessary(0);
|
||||||
|
}
|
||||||
|
|
||||||
void Synth::Impl::clear()
|
void Synth::Impl::clear()
|
||||||
{
|
{
|
||||||
FilePool& filePool = resources_.getFilePool();
|
FilePool& filePool = resources_.getFilePool();
|
||||||
|
|
@ -253,23 +276,18 @@ void Synth::Impl::clear()
|
||||||
currentSet_ = nullptr;
|
currentSet_ = nullptr;
|
||||||
sets_.clear();
|
sets_.clear();
|
||||||
layers_.clear();
|
layers_.clear();
|
||||||
effectBuses_.clear();
|
resources_.clearNonState();
|
||||||
effectBuses_.emplace_back(new EffectBus);
|
|
||||||
effectBuses_[0]->setGainToMain(1.0);
|
|
||||||
effectBuses_[0]->setSamplesPerBlock(samplesPerBlock_);
|
|
||||||
effectBuses_[0]->setSampleRate(sampleRate_);
|
|
||||||
effectBuses_[0]->clearInputs(samplesPerBlock_);
|
|
||||||
resources_.clear();
|
|
||||||
rootPath_.clear();
|
rootPath_.clear();
|
||||||
numGroups_ = 0;
|
numGroups_ = 0;
|
||||||
numMasters_ = 0;
|
numMasters_ = 0;
|
||||||
|
numOutputs_ = 1;
|
||||||
noteOffset_ = 0;
|
noteOffset_ = 0;
|
||||||
octaveOffset_ = 0;
|
octaveOffset_ = 0;
|
||||||
currentSwitch_ = absl::nullopt;
|
currentSwitch_ = absl::nullopt;
|
||||||
defaultPath_ = "";
|
defaultPath_ = "";
|
||||||
image_ = "";
|
image_ = "";
|
||||||
midiState.reset();
|
midiState.resetNoteStates();
|
||||||
filePool.clear();
|
midiState.flushEvents();
|
||||||
filePool.setRamLoading(config::loadInRam);
|
filePool.setRamLoading(config::loadInRam);
|
||||||
clearCCLabels();
|
clearCCLabels();
|
||||||
currentUsedCCs_.clear();
|
currentUsedCCs_.clear();
|
||||||
|
|
@ -287,17 +305,7 @@ void Synth::Impl::clear()
|
||||||
modificationTime_ = absl::nullopt;
|
modificationTime_ = absl::nullopt;
|
||||||
playheadMoved_ = false;
|
playheadMoved_ = false;
|
||||||
|
|
||||||
// set default controllers
|
initEffectBuses();
|
||||||
// midistate is reset above
|
|
||||||
fill(absl::MakeSpan(defaultCCValues_), 0.0f);
|
|
||||||
setDefaultHdcc(7, normalizeCC(100));
|
|
||||||
setDefaultHdcc(10, 0.5f);
|
|
||||||
setDefaultHdcc(11, 1.0f);
|
|
||||||
|
|
||||||
// set default controller labels
|
|
||||||
setCCLabel(7, "Volume");
|
|
||||||
setCCLabel(10, "Pan");
|
|
||||||
setCCLabel(11, "Expression");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::Impl::handleMasterOpcodes(const std::vector<Opcode>& members)
|
void Synth::Impl::handleMasterOpcodes(const std::vector<Opcode>& members)
|
||||||
|
|
@ -383,12 +391,20 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
switch (member.lettersOnlyHash) {
|
switch (member.lettersOnlyHash) {
|
||||||
case hash("set_cc&"):
|
case hash("set_cc&"):
|
||||||
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
||||||
setDefaultHdcc(member.parameters.back(), member.read(Default::loCC));
|
const auto ccNumber = member.parameters.back();
|
||||||
|
const auto value = member.read(Default::loCC);
|
||||||
|
setDefaultHdcc(ccNumber, value);
|
||||||
|
if (!reloading)
|
||||||
|
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case hash("set_hdcc&"):
|
case hash("set_hdcc&"):
|
||||||
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
if (Default::ccNumber.bounds.containsWithEnd(member.parameters.back())) {
|
||||||
setDefaultHdcc(member.parameters.back(), member.read(Default::loNormalized));
|
const auto ccNumber = member.parameters.back();
|
||||||
|
const auto value = member.read(Default::loNormalized);
|
||||||
|
setDefaultHdcc(ccNumber, value);
|
||||||
|
if (!reloading)
|
||||||
|
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case hash("label_cc&"):
|
case hash("label_cc&"):
|
||||||
|
|
@ -453,14 +469,27 @@ void Synth::Impl::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
||||||
{
|
{
|
||||||
absl::string_view busName = "main";
|
absl::string_view busName { "main" };
|
||||||
|
uint16_t output { Default::output };
|
||||||
|
|
||||||
auto getOrCreateBus = [this](unsigned index) -> EffectBus& {
|
std::vector<Opcode> members;
|
||||||
if (index + 1 > effectBuses_.size())
|
members.reserve(rawMembers.size());
|
||||||
effectBuses_.resize(index + 1);
|
for (const Opcode& opcode : rawMembers) {
|
||||||
EffectBusPtr& bus = effectBuses_[index];
|
if (opcode.lettersOnlyHash == hash("output"))
|
||||||
|
output = opcode.read(Default::output);
|
||||||
|
|
||||||
|
members.push_back(opcode.cleanUp(kOpcodeScopeEffect));
|
||||||
|
}
|
||||||
|
|
||||||
|
addEffectBusesIfNecessary(output);
|
||||||
|
|
||||||
|
auto getOrCreateBus = [this, output](unsigned index) -> EffectBus& {
|
||||||
|
if (index + 1 > effectBuses_[output].size())
|
||||||
|
effectBuses_[output].resize(index + 1);
|
||||||
|
EffectBusPtr& bus = effectBuses_[output][index];
|
||||||
if (!bus) {
|
if (!bus) {
|
||||||
bus.reset(new EffectBus);
|
bus.reset(new EffectBus);
|
||||||
bus->setSampleRate(sampleRate_);
|
bus->setSampleRate(sampleRate_);
|
||||||
|
|
@ -470,11 +499,6 @@ void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
||||||
return *bus;
|
return *bus;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Opcode> members;
|
|
||||||
members.reserve(rawMembers.size());
|
|
||||||
for (const Opcode& opcode : rawMembers)
|
|
||||||
members.push_back(opcode.cleanUp(kOpcodeScopeEffect));
|
|
||||||
|
|
||||||
for (const Opcode& opcode : members) {
|
for (const Opcode& opcode : members) {
|
||||||
switch (opcode.lettersOnlyHash) {
|
switch (opcode.lettersOnlyHash) {
|
||||||
case hash("bus"):
|
case hash("bus"):
|
||||||
|
|
@ -488,15 +512,21 @@ void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case hash("fx&tomain"): // fx&tomain
|
case hash("fx&tomain"): // fx&tomain
|
||||||
if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses)
|
{
|
||||||
break;
|
const auto busIndex = opcode.parameters.front();
|
||||||
getOrCreateBus(opcode.parameters.front()).setGainToMain(opcode.read(Default::effect));
|
if (busIndex < 1 || busIndex > config::maxEffectBuses)
|
||||||
|
break;
|
||||||
|
getOrCreateBus(busIndex).setGainToMain(opcode.read(Default::effect));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case hash("fx&tomix"): // fx&tomix
|
case hash("fx&tomix"): // fx&tomix
|
||||||
if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses)
|
{
|
||||||
break;
|
const auto busIndex = opcode.parameters.front();
|
||||||
getOrCreateBus(opcode.parameters.front()).setGainToMix(opcode.read(Default::effect));
|
if (busIndex < 1 || busIndex > config::maxEffectBuses)
|
||||||
|
break;
|
||||||
|
getOrCreateBus(busIndex).setGainToMix(opcode.read(Default::effect));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,22 +542,59 @@ void Synth::Impl::handleEffectOpcodes(const std::vector<Opcode>& rawMembers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the effect and add it
|
// create the effect and add it
|
||||||
EffectBus& bus = getOrCreateBus(busIndex);
|
|
||||||
auto fx = effectFactory_.makeEffect(members);
|
auto fx = effectFactory_.makeEffect(members);
|
||||||
fx->setSampleRate(sampleRate_);
|
fx->setSampleRate(sampleRate_);
|
||||||
fx->setSamplesPerBlock(samplesPerBlock_);
|
fx->setSamplesPerBlock(samplesPerBlock_);
|
||||||
bus.addEffect(std::move(fx));
|
getOrCreateBus(busIndex).addEffect(std::move(fx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::resetDefaultCCValues() noexcept
|
||||||
|
{
|
||||||
|
fill(absl::MakeSpan(defaultCCValues_), 0.0f);
|
||||||
|
setDefaultHdcc(7, normalizeCC(100));
|
||||||
|
setDefaultHdcc(10, 0.5f);
|
||||||
|
setDefaultHdcc(11, 1.0f);
|
||||||
|
|
||||||
|
setCCLabel(7, "Volume");
|
||||||
|
setCCLabel(10, "Pan");
|
||||||
|
setCCLabel(11, "Expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::Impl::prepareSfzLoad(const fs::path& path)
|
||||||
|
{
|
||||||
|
auto newPath_ = path.string();
|
||||||
|
reloading = (lastPath_ == newPath_);
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (reloading) {
|
||||||
|
DBG("[sfizz] Reloading the current file");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!reloading) {
|
||||||
|
|
||||||
|
// Clear the background queues and clear the filePool
|
||||||
|
auto& filePool = resources_.getFilePool();
|
||||||
|
filePool.waitForBackgroundLoading();
|
||||||
|
filePool.clear();
|
||||||
|
|
||||||
|
// Set the default hdcc to their default
|
||||||
|
resetDefaultCCValues();
|
||||||
|
|
||||||
|
// Store the new path
|
||||||
|
lastPath_ = std::move(newPath_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Synth::loadSfzFile(const fs::path& file)
|
bool Synth::loadSfzFile(const fs::path& file)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
impl.prepareSfzLoad(file);
|
||||||
impl.clear();
|
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::path realFile = fs::canonical(file, ec);
|
fs::path realFile = fs::canonical(file, ec);
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Parser& parser = impl.parser_;
|
Parser& parser = impl.parser_;
|
||||||
parser.parseFile(ec ? file : realFile);
|
parser.parseFile(ec ? file : realFile);
|
||||||
|
|
@ -539,7 +606,10 @@ bool Synth::loadSfzFile(const fs::path& file)
|
||||||
success = success && !impl.layers_.empty();
|
success = success && !impl.layers_.empty();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
DBG("[sfizz] Loading failed");
|
||||||
|
auto& filePool = impl.resources_.getFilePool();
|
||||||
parser.clear();
|
parser.clear();
|
||||||
|
filePool.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -550,8 +620,7 @@ bool Synth::loadSfzFile(const fs::path& file)
|
||||||
bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
|
impl.prepareSfzLoad(path);
|
||||||
impl.clear();
|
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
Parser& parser = impl.parser_;
|
Parser& parser = impl.parser_;
|
||||||
|
|
@ -564,7 +633,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
success = success && !impl.layers_.empty();
|
success = success && !impl.layers_.empty();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
auto& filePool = impl.resources_.getFilePool();
|
||||||
|
DBG("[sfizz] Loading failed");
|
||||||
parser.clear();
|
parser.clear();
|
||||||
|
filePool.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -574,8 +646,10 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text)
|
||||||
|
|
||||||
void Synth::Impl::finalizeSfzLoad()
|
void Synth::Impl::finalizeSfzLoad()
|
||||||
{
|
{
|
||||||
const fs::path& rootDirectory = parser_.originalDirectory();
|
|
||||||
FilePool& filePool = resources_.getFilePool();
|
FilePool& filePool = resources_.getFilePool();
|
||||||
|
WavetablePool& wavePool = resources_.getWavePool();
|
||||||
|
|
||||||
|
const fs::path& rootDirectory = parser_.originalDirectory();
|
||||||
filePool.setRootDirectory(rootDirectory);
|
filePool.setRootDirectory(rootDirectory);
|
||||||
|
|
||||||
// a string representation used for OSC purposes
|
// a string representation used for OSC purposes
|
||||||
|
|
@ -611,9 +685,6 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
|
|
||||||
absl::optional<FileInformation> fileInformation;
|
absl::optional<FileInformation> fileInformation;
|
||||||
|
|
||||||
FilePool& filePool = resources_.getFilePool();
|
|
||||||
WavetablePool& wavePool = resources_.getWavePool();
|
|
||||||
|
|
||||||
if (!region.isGenerator()) {
|
if (!region.isGenerator()) {
|
||||||
if (!filePool.checkSampleId(*region.sampleId)) {
|
if (!filePool.checkSampleId(*region.sampleId)) {
|
||||||
removeCurrentRegion();
|
removeCurrentRegion();
|
||||||
|
|
@ -761,14 +832,24 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
haveAmplitudeLFO = haveAmplitudeLFO || region.amplitudeLFO != absl::nullopt;
|
haveAmplitudeLFO = haveAmplitudeLFO || region.amplitudeLFO != absl::nullopt;
|
||||||
havePitchLFO = havePitchLFO || region.pitchLFO != absl::nullopt;
|
havePitchLFO = havePitchLFO || region.pitchLFO != absl::nullopt;
|
||||||
haveFilterLFO = haveFilterLFO || region.filterLFO != absl::nullopt;
|
haveFilterLFO = haveFilterLFO || region.filterLFO != absl::nullopt;
|
||||||
|
numOutputs_ = max(region.output + 1, numOutputs_);
|
||||||
|
|
||||||
++currentRegionIndex;
|
++currentRegionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& toLoad: filesToLoad) {
|
// Reset the preload call count to check for unused preloaded samples
|
||||||
filePool.preloadFile(toLoad.first, toLoad.second);
|
// when reloading
|
||||||
}
|
if (reloading)
|
||||||
|
filePool.resetPreloadCallCounts();
|
||||||
|
|
||||||
|
for (const auto& toLoad: filesToLoad)
|
||||||
|
filePool.preloadFile(toLoad.first, toLoad.second);
|
||||||
|
|
||||||
|
// Remove preloaded data with no linked regions
|
||||||
|
if (reloading)
|
||||||
|
filePool.removeUnusedPreloadedData();
|
||||||
|
|
||||||
|
// Remove bad regions with unknown files
|
||||||
if (currentRegionCount < layers_.size()) {
|
if (currentRegionCount < layers_.size()) {
|
||||||
DBG("Removing " << (layers_.size() - currentRegionCount)
|
DBG("Removing " << (layers_.size() - currentRegionCount)
|
||||||
<< " out of " << layers_.size() << " regions");
|
<< " out of " << layers_.size() << " regions");
|
||||||
|
|
@ -819,7 +900,7 @@ void Synth::Impl::finalizeSfzLoad()
|
||||||
settingsPerVoice_.haveFilterLFO = haveFilterLFO;
|
settingsPerVoice_.haveFilterLFO = haveFilterLFO;
|
||||||
|
|
||||||
applySettingsPerVoice();
|
applySettingsPerVoice();
|
||||||
|
addEffectBusesIfNecessary(numOutputs_);
|
||||||
setupModMatrix();
|
setupModMatrix();
|
||||||
|
|
||||||
// cache the set of used CCs for future access
|
// cache the set of used CCs for future access
|
||||||
|
|
@ -920,9 +1001,11 @@ void Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
|
||||||
impl.resources_.setSamplesPerBlock(samplesPerBlock);
|
impl.resources_.setSamplesPerBlock(samplesPerBlock);
|
||||||
|
|
||||||
for (auto& bus : impl.effectBuses_) {
|
for (int i = 0; i < impl.numOutputs_; ++i) {
|
||||||
if (bus)
|
for (auto& bus : impl.getEffectBusesForOutput(i)) {
|
||||||
bus->setSamplesPerBlock(samplesPerBlock);
|
if (bus)
|
||||||
|
bus->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -942,9 +1025,11 @@ void Synth::setSampleRate(float sampleRate) noexcept
|
||||||
|
|
||||||
impl.resources_.setSampleRate(sampleRate);
|
impl.resources_.setSampleRate(sampleRate);
|
||||||
|
|
||||||
for (auto& bus : impl.effectBuses_) {
|
for (int i = 0; i < impl.numOutputs_; ++i) {
|
||||||
if (bus)
|
for (auto& bus : impl.getEffectBusesForOutput(i)) {
|
||||||
bus->setSampleRate(sampleRate);
|
if (bus)
|
||||||
|
bus->setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1005,9 +1090,11 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
|
|
||||||
{ // Clear effect busses
|
{ // Clear effect busses
|
||||||
ScopedTiming logger { callbackBreakdown.effects };
|
ScopedTiming logger { callbackBreakdown.effects };
|
||||||
for (auto& bus : impl.effectBuses_) {
|
for (int i = 0; i < impl.numOutputs_; ++i) {
|
||||||
if (bus)
|
for (auto& bus : impl.getEffectBusesForOutput(i)) {
|
||||||
bus->clearInputs(numFrames);
|
if (bus)
|
||||||
|
bus->clearInputs(numFrames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1023,10 +1110,11 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
|
|
||||||
const Region* region = voice.getRegion();
|
const Region* region = voice.getRegion();
|
||||||
ASSERT(region != nullptr);
|
ASSERT(region != nullptr);
|
||||||
|
const auto& effectBuses = impl.getEffectBusesForOutput(region->output);
|
||||||
|
|
||||||
voice.renderBlock(*tempSpan);
|
voice.renderBlock(*tempSpan);
|
||||||
for (size_t i = 0, n = impl.effectBuses_.size(); i < n; ++i) {
|
for (size_t i = 0, n = effectBuses.size(); i < n; ++i) {
|
||||||
if (auto& bus = impl.effectBuses_[i]) {
|
if (auto& bus = effectBuses[i]) {
|
||||||
float addGain = region->getGainToEffectBus(i);
|
float addGain = region->getGainToEffectBus(i);
|
||||||
bus->addToInputs(*tempSpan, addGain, numFrames);
|
bus->addToInputs(*tempSpan, addGain, numFrames);
|
||||||
}
|
}
|
||||||
|
|
@ -1048,20 +1136,26 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
// without any <effect>, the signal is just going to flow through it.
|
// without any <effect>, the signal is just going to flow through it.
|
||||||
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
||||||
|
|
||||||
for (auto& bus : impl.effectBuses_) {
|
const int numChannels = static_cast<int>(buffer.getNumChannels());
|
||||||
if (bus) {
|
for (int i = 0; i < impl.numOutputs_; ++i) {
|
||||||
bus->process(numFrames);
|
const auto outputStart = numChannels == 0 ? 0 : (2 * i) % numChannels;
|
||||||
bus->mixOutputsTo(buffer, *tempMixSpan, numFrames);
|
auto outputSpan = buffer.getStereoSpan(outputStart);
|
||||||
|
const auto& effectBuses = impl.getEffectBusesForOutput(i);
|
||||||
|
for (auto& bus : effectBuses) {
|
||||||
|
if (bus) {
|
||||||
|
bus->process(numFrames);
|
||||||
|
bus->mixOutputsTo(outputSpan, *tempMixSpan, numFrames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the Mix output (fxNtomix opcodes)
|
||||||
|
// -- note(jpc) the purpose of the Mix output is not known.
|
||||||
|
// perhaps it's designed as extension point for custom processing?
|
||||||
|
// as default behavior, it adds itself to the Main signal.
|
||||||
|
outputSpan.add(*tempMixSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the Mix output (fxNtomix opcodes)
|
|
||||||
// -- note(jpc) the purpose of the Mix output is not known.
|
|
||||||
// perhaps it's designed as extension point for custom processing?
|
|
||||||
// as default behavior, it adds itself to the Main signal.
|
|
||||||
buffer.add(*tempMixSpan);
|
|
||||||
|
|
||||||
// Apply the master volume
|
// Apply the master volume
|
||||||
buffer.applyGain(db2mag(impl.volume_));
|
buffer.applyGain(db2mag(impl.volume_));
|
||||||
|
|
||||||
|
|
@ -1348,7 +1442,6 @@ void Synth::Impl::setDefaultHdcc(int ccNumber, float value)
|
||||||
ASSERT(ccNumber >= 0);
|
ASSERT(ccNumber >= 0);
|
||||||
ASSERT(ccNumber < config::numCCs);
|
ASSERT(ccNumber < config::numCCs);
|
||||||
defaultCCValues_[ccNumber] = value;
|
defaultCCValues_[ccNumber] = value;
|
||||||
resources_.getMidiState().ccEvent(0, ccNumber, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float Synth::getHdcc(int ccNumber)
|
float Synth::getHdcc(int ccNumber)
|
||||||
|
|
@ -1624,10 +1717,10 @@ const Region* Synth::getRegionView(int idx) const noexcept
|
||||||
return layer ? &layer->getRegion() : nullptr;
|
return layer ? &layer->getRegion() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EffectBus* Synth::getEffectBusView(int idx) const noexcept
|
const EffectBus* Synth::getEffectBusView(int idx, int output) const noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
return (size_t)idx < impl.effectBuses_.size() ? impl.effectBuses_[idx].get() : nullptr;
|
return (size_t)idx < impl.effectBuses_[output].size() ? impl.effectBuses_[output][idx].get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RegionSet* Synth::getRegionSetView(int idx) const noexcept
|
const RegionSet* Synth::getRegionSetView(int idx) const noexcept
|
||||||
|
|
@ -2010,8 +2103,11 @@ void Synth::allSoundOff() noexcept
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
for (auto& voice : impl.voiceManager_)
|
for (auto& voice : impl.voiceManager_)
|
||||||
voice.reset();
|
voice.reset();
|
||||||
for (auto& effectBus : impl.effectBuses_)
|
for (int i = 0; i < impl.numOutputs_; ++i) {
|
||||||
effectBus->clear();
|
for (auto& effectBus : impl.getEffectBusesForOutput(i))
|
||||||
|
if (effectBus)
|
||||||
|
effectBus->clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::addExternalDefinition(const std::string& id, const std::string& value)
|
void Synth::addExternalDefinition(const std::string& id, const std::string& value)
|
||||||
|
|
|
||||||
|
|
@ -237,9 +237,10 @@ public:
|
||||||
* You'll need to include "Effects.h" to resolve the forward declaration.
|
* You'll need to include "Effects.h" to resolve the forward declaration.
|
||||||
*
|
*
|
||||||
* @param idx
|
* @param idx
|
||||||
|
* @param output
|
||||||
* @return const EffectBus*
|
* @return const EffectBus*
|
||||||
*/
|
*/
|
||||||
const EffectBus* getEffectBusView(int idx) const noexcept;
|
const EffectBus* getEffectBusView(int idx, int output = 0) const noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get a raw view into a specific set of regions. This is mostly used
|
* @brief Get a raw view into a specific set of regions. This is mostly used
|
||||||
* for testing.
|
* for testing.
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'i'>(delay, path, impl.noteOffset_);
|
client.receive<'i'>(delay, path, impl.noteOffset_);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/num_outputs", "") {
|
||||||
|
client.receive<'i'>(delay, path, impl.numOutputs_);
|
||||||
|
} break;
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
MATCH("/key/slots", "") {
|
MATCH("/key/slots", "") {
|
||||||
|
|
@ -195,6 +199,22 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'b'>(delay, path, &blob);
|
client.receive<'b'>(delay, path, &blob);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/aftertouch", "") {
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getChannelAftertouch());
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/poly_aftertouch/&", "") {
|
||||||
|
if (indices[0] > 127)
|
||||||
|
break;
|
||||||
|
// Note: result value is not frame-exact
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPolyAftertouch(indices[0]));
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/pitch_bend", "") {
|
||||||
|
// Note: result value is not frame-exact
|
||||||
|
client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPitchBend());
|
||||||
|
} break;
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
MATCH("/mem/buffers", "") {
|
MATCH("/mem/buffers", "") {
|
||||||
|
|
@ -346,6 +366,11 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'N'>(delay, path, {});
|
client.receive<'N'>(delay, path, {});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/output", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
client.receive<'i'>(delay, path, region.output);
|
||||||
|
} break;
|
||||||
|
|
||||||
MATCH("/region&/group", "") {
|
MATCH("/region&/group", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
client.receive<'h'>(delay, path, region.group);
|
client.receive<'h'>(delay, path, region.group);
|
||||||
|
|
|
||||||
|
|
@ -176,9 +176,24 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
*/
|
*/
|
||||||
void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept;
|
void startDelayedSostenutoReleases(Layer* layer, int delay, SisterVoiceRingBuilder& ring) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the default CCs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void resetDefaultCCValues() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prepare before loading a new SFZ file. The behavior of this function
|
||||||
|
* is changed by the reloading state.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
|
void prepareSfzLoad(const fs::path& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Finalize SFZ loading, following a successful execution of the
|
* @brief Finalize SFZ loading, following a successful execution of the
|
||||||
* parsing step.
|
* parsing step. The behavior of this function is changed by the reloading
|
||||||
|
* state.
|
||||||
*/
|
*/
|
||||||
void finalizeSfzLoad();
|
void finalizeSfzLoad();
|
||||||
|
|
||||||
|
|
@ -234,6 +249,7 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
|
|
||||||
int numGroups_ { 0 };
|
int numGroups_ { 0 };
|
||||||
int numMasters_ { 0 };
|
int numMasters_ { 0 };
|
||||||
|
int numOutputs_ { 1 };
|
||||||
|
|
||||||
// Opcode memory; these are used to build regions, as a new region
|
// Opcode memory; these are used to build regions, as a new region
|
||||||
// will integrate opcodes from the group, master and global block
|
// will integrate opcodes from the group, master and global block
|
||||||
|
|
@ -278,7 +294,10 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
// Effect factory and buses
|
// Effect factory and buses
|
||||||
EffectFactory effectFactory_;
|
EffectFactory effectFactory_;
|
||||||
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
||||||
std::vector<EffectBusPtr> effectBuses_; // 0 is "main", 1-N are "fx1"-"fxN"
|
std::vector<std::vector<EffectBusPtr>> effectBuses_; // first index is the output, then 0 is "main", 1-N are "fx1"-"fxN"
|
||||||
|
const std::vector<EffectBusPtr>& getEffectBusesForOutput(uint16_t numOutput) { return effectBuses_[numOutput]; }
|
||||||
|
void initEffectBuses();
|
||||||
|
void addEffectBusesIfNecessary(uint16_t output);
|
||||||
|
|
||||||
int samplesPerBlock_ { config::defaultSamplesPerBlock };
|
int samplesPerBlock_ { config::defaultSamplesPerBlock };
|
||||||
float sampleRate_ { config::defaultSampleRate };
|
float sampleRate_ { config::defaultSampleRate };
|
||||||
|
|
@ -326,7 +345,9 @@ struct Synth::Impl final: public Parser::Listener {
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> lastGarbageCollection_;
|
std::chrono::time_point<std::chrono::high_resolution_clock> lastGarbageCollection_;
|
||||||
|
|
||||||
Parser parser_;
|
Parser parser_;
|
||||||
|
std::string lastPath_;
|
||||||
absl::optional<fs::file_time_type> modificationTime_ { };
|
absl::optional<fs::file_time_type> modificationTime_ { };
|
||||||
|
bool reloading { false };
|
||||||
|
|
||||||
std::array<float, config::numCCs> defaultCCValues_ { };
|
std::array<float, config::numCCs> defaultCCValues_ { };
|
||||||
BitArray<config::numCCs> currentUsedCCs_;
|
BitArray<config::numCCs> currentUsedCCs_;
|
||||||
|
|
|
||||||
|
|
@ -766,7 +766,7 @@ void Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
impl.powerFollower_.setSamplesPerBlock(samplesPerBlock);
|
impl.powerFollower_.setSamplesPerBlock(samplesPerBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
void Voice::renderBlock(AudioSpan<float, 2> buffer) noexcept
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
ASSERT(static_cast<int>(buffer.getNumFrames()) <= impl.samplesPerBlock_);
|
ASSERT(static_cast<int>(buffer.getNumFrames()) <= impl.samplesPerBlock_);
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,5 @@ std::unique_ptr<Effect> Disto::makeInstance(absl::Span<const Opcode> members)
|
||||||
|
|
||||||
return fx;
|
return fx;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
} // namespace fx
|
} // namespace fx
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,5 @@ namespace fx {
|
||||||
fPhase = phase;
|
fPhase = phase;
|
||||||
fLastValue = lastValue;
|
fLastValue = lastValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fx
|
} // namespace fx
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,5 @@ namespace fx {
|
||||||
|
|
||||||
return fx;
|
return fx;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fx
|
} // namespace fx
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -255,9 +255,10 @@ void sfz::Sfizz::playbackState(int delay, int playbackState)
|
||||||
synth->synth.playbackState(delay, playbackState);
|
synth->synth.playbackState(delay, playbackState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Sfizz::renderBlock(float** buffers, size_t numSamples, int /*numOutputs*/) noexcept
|
void sfz::Sfizz::renderBlock(float** buffers, size_t numSamples, int numOutputs) noexcept
|
||||||
{
|
{
|
||||||
synth->synth.renderBlock({{buffers[0], buffers[1]}, numSamples});
|
sfz::AudioSpan<float> bufferSpan { buffers, static_cast<size_t>(numOutputs * 2), 0, numSamples };
|
||||||
|
synth->synth.renderBlock(bufferSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
int sfz::Sfizz::getNumActiveVoices() const noexcept
|
int sfz::Sfizz::getNumActiveVoices() const noexcept
|
||||||
|
|
|
||||||
|
|
@ -193,10 +193,8 @@ void sfizz_send_playback_state(sfizz_synth_t* synth, int delay, int playback_sta
|
||||||
|
|
||||||
void sfizz_render_block(sfizz_synth_t* synth, float** channels, int num_channels, int num_frames)
|
void sfizz_render_block(sfizz_synth_t* synth, float** channels, int num_channels, int num_frames)
|
||||||
{
|
{
|
||||||
// Only stereo output is supported for now
|
sfz::AudioSpan<float> channelSpan { channels, static_cast<size_t>(num_channels), 0, static_cast<size_t>(num_frames) };
|
||||||
ASSERT(num_channels == 2);
|
synth->synth.renderBlock(channelSpan);
|
||||||
UNUSED(num_channels);
|
|
||||||
synth->synth.renderBlock({{channels[0], channels[1]}, static_cast<size_t>(num_frames)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int sfizz_get_preload_size(sfizz_synth_t* synth)
|
unsigned int sfizz_get_preload_size(sfizz_synth_t* synth)
|
||||||
|
|
|
||||||
|
|
@ -699,3 +699,19 @@ TEST_CASE("[Files] Key center from audio file")
|
||||||
REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10);
|
REQUIRE(synth.getRegionView(4)->pitchKeycenter == 10);
|
||||||
REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62);
|
REQUIRE(synth.getRegionView(5)->pitchKeycenter == 62);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Unused samples are cleared on reloading")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
<region> sample=kick.wav
|
||||||
|
)");
|
||||||
|
REQUIRE(synth.getNumPreloadedSamples() == 1);
|
||||||
|
|
||||||
|
// Same file path to reload
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/unused_samples.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
REQUIRE(synth.getNumPreloadedSamples() == 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,16 +45,67 @@ TEST_CASE("[MidiState] Set and get pitch bends")
|
||||||
REQUIRE(state.getPitchBend() == 0.0f);
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Reset")
|
TEST_CASE("[MidiState] Resetting things")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
state.pitchBendEvent(0, 0.7f);
|
state.pitchBendEvent(0, 0.7f);
|
||||||
state.noteOnEvent(0, 64, 24_norm);
|
state.noteOnEvent(0, 64, 24_norm);
|
||||||
state.ccEvent(0, 123, 124_norm);
|
state.ccEvent(0, 123, 124_norm);
|
||||||
state.reset();
|
state.channelAftertouchEvent(0, 56_norm);
|
||||||
REQUIRE(state.getPitchBend() == 0.0f);
|
state.polyAftertouchEvent(0, 64, 43_norm);
|
||||||
|
state.advanceTime(1024);
|
||||||
|
|
||||||
|
// Only reset note stuff
|
||||||
|
state.resetNoteStates();
|
||||||
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
||||||
|
REQUIRE(state.getNoteDuration(64) == 0_norm);
|
||||||
|
REQUIRE(state.getActiveNotes() == 0);
|
||||||
|
|
||||||
|
// Extended CCs too
|
||||||
|
REQUIRE(state.getCCValue(131) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(132) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(133) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(134) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(135) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(136) == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(137) == 0.0f);
|
||||||
|
|
||||||
|
// State isn't reset
|
||||||
|
REQUIRE(state.getPitchBend() != 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(123) != 0_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() != 0_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) != 0_norm);
|
||||||
|
|
||||||
|
state.resetEventStates(); // But now it is
|
||||||
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
REQUIRE(state.getCCValue(123) == 0_norm);
|
REQUIRE(state.getCCValue(123) == 0_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() == 0_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) == 0_norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MidiState] Flushing state")
|
||||||
|
{
|
||||||
|
sfz::MidiState state;
|
||||||
|
state.pitchBendEvent(40, 0.7f);
|
||||||
|
state.ccEvent(100, 123, 124_norm);
|
||||||
|
state.channelAftertouchEvent(20, 56_norm);
|
||||||
|
state.polyAftertouchEvent(80, 64, 43_norm);
|
||||||
|
|
||||||
|
REQUIRE(state.getCCEvents(123).size() > 1);
|
||||||
|
REQUIRE(state.getChannelAftertouchEvents().size() > 1);
|
||||||
|
REQUIRE(state.getPolyAftertouchEvents(64).size() > 1);
|
||||||
|
REQUIRE(state.getPitchEvents().size() > 1);
|
||||||
|
|
||||||
|
state.flushEvents();
|
||||||
|
REQUIRE(state.getCCEvents(123).size() == 1);
|
||||||
|
REQUIRE(state.getChannelAftertouchEvents().size() == 1);
|
||||||
|
REQUIRE(state.getPolyAftertouchEvents(64).size() == 1);
|
||||||
|
REQUIRE(state.getPitchEvents().size() == 1);
|
||||||
|
|
||||||
|
REQUIRE(state.getCCValue(123) == 124_norm);
|
||||||
|
REQUIRE(state.getChannelAftertouch() == 56_norm);
|
||||||
|
REQUIRE(state.getPolyAftertouch(64) == 43_norm);
|
||||||
|
REQUIRE(state.getPitchBend() == 0.7f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Set and get note velocities")
|
TEST_CASE("[MidiState] Set and get note velocities")
|
||||||
|
|
|
||||||
|
|
@ -427,6 +427,64 @@ TEST_CASE("[Values] Loop count")
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Values] Output")
|
||||||
|
{
|
||||||
|
Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
|
||||||
|
SECTION("No special outputs") {
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
|
<region> sample=kick.wav
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/output,i : { 0 }",
|
||||||
|
"/num_outputs,i : { 1 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("1 output") {
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
|
<region> sample=kick.wav
|
||||||
|
<region> sample=kick.wav output=1
|
||||||
|
<region> sample=kick.wav output=-1
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region2/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/output,i : { 0 }",
|
||||||
|
"/region1/output,i : { 1 }",
|
||||||
|
"/region2/output,i : { 0 }",
|
||||||
|
"/num_outputs,i : { 2 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("More than 1 output") {
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
|
<region> sample=kick.wav
|
||||||
|
<region> sample=kick.wav output=1
|
||||||
|
<region> sample=kick.wav output=3
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region2/output", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/output,i : { 0 }",
|
||||||
|
"/region1/output,i : { 1 }",
|
||||||
|
"/region2/output,i : { 3 }",
|
||||||
|
"/num_outputs,i : { 4 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Values] Group")
|
TEST_CASE("[Values] Group")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
180
tests/SynthT.cpp
180
tests/SynthT.cpp
|
|
@ -271,7 +271,6 @@ TEST_CASE("[Synth] Number of effect buses and resetting behavior")
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
|
||||||
REQUIRE( synth.getEffectBusView(0) == nullptr); // No effects at first
|
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/Effects/base.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/Effects/base.sfz", R"(
|
||||||
<region> lokey=0 hikey=127 sample=*sine
|
<region> lokey=0 hikey=127 sample=*sine
|
||||||
)");
|
)");
|
||||||
|
|
@ -398,6 +397,60 @@ TEST_CASE("[Synth] Gain to mix")
|
||||||
REQUIRE( bus->gainToMix() == 0.5 );
|
REQUIRE( bus->gainToMix() == 0.5 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Effect with two outputs")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz", R"(
|
||||||
|
<region> lokey=0 hikey=127 sample=*sine effect1=100
|
||||||
|
<region> lokey=0 hikey=127 sample=*sine effect1=100 output=1
|
||||||
|
<effect> directtomain=50 fx1tomain=50 type=lofi bus=fx1 bitred=90 decim=10
|
||||||
|
)");
|
||||||
|
auto bus = synth.getEffectBusView(0);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 0 );
|
||||||
|
REQUIRE( bus->gainToMain() == 0.5 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
bus = synth.getEffectBusView(1);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 1 );
|
||||||
|
REQUIRE( bus->gainToMain() == 0.5 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
bus = synth.getEffectBusView(0, 1);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 0 );
|
||||||
|
REQUIRE( bus->gainToMain() == 1 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
bus = synth.getEffectBusView(1, 1);
|
||||||
|
REQUIRE( bus == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Effect on the second output, with two outputs")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz", R"(
|
||||||
|
<region> lokey=0 hikey=127 sample=*sine effect1=100
|
||||||
|
<region> lokey=0 hikey=127 sample=*sine effect1=100 output=1
|
||||||
|
<effect> directtomain=50 fx1tomain=50 type=lofi bus=fx1 bitred=90 decim=10 output=1
|
||||||
|
)");
|
||||||
|
auto bus = synth.getEffectBusView(0);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 0 );
|
||||||
|
REQUIRE( bus->gainToMain() == 1 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
bus = synth.getEffectBusView(1);
|
||||||
|
REQUIRE( bus == nullptr);
|
||||||
|
bus = synth.getEffectBusView(0, 1);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 0 );
|
||||||
|
REQUIRE( bus->gainToMain() == 0.5 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
bus = synth.getEffectBusView(1, 1);
|
||||||
|
REQUIRE( bus != nullptr);
|
||||||
|
REQUIRE( bus->numEffects() == 1 );
|
||||||
|
REQUIRE( bus->gainToMain() == 0.5 );
|
||||||
|
REQUIRE( bus->gainToMix() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Basic curves")
|
TEST_CASE("[Synth] Basic curves")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
|
@ -1705,7 +1758,7 @@ TEST_CASE("[Synth] Initial values of CC")
|
||||||
REQUIRE(synth.getHdcc(10) == 0.7f);
|
REQUIRE(synth.getHdcc(10) == 0.7f);
|
||||||
REQUIRE(synth.getDefaultHdcc(10) == 0.5f);
|
REQUIRE(synth.getDefaultHdcc(10) == 0.5f);
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "init_cc_new_file.sfz", R"(
|
||||||
<control> set_hdcc111=0.1234 set_cc112=77
|
<control> set_hdcc111=0.1234 set_cc112=77
|
||||||
<region> sample=*sine
|
<region> sample=*sine
|
||||||
)");
|
)");
|
||||||
|
|
@ -1964,3 +2017,126 @@ TEST_CASE("[Synth] Loading resets note and octave offsets")
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Default CC values")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/default.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc7/value,f : { 0.787402 }",
|
||||||
|
"/cc10/value,f : { 0.5 }",
|
||||||
|
"/cc11/value,f : { 1 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Loading a new file doesn't reset the midi state")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
|
||||||
|
synth.hdcc(10, 63, 0.4f);
|
||||||
|
synth.hdcc(10, 7, 0.41f);
|
||||||
|
synth.hdcc(10, 10, 0.42f);
|
||||||
|
synth.hdcc(10, 11, 0.43f);
|
||||||
|
synth.hdChannelAftertouch(20, 0.1f);
|
||||||
|
synth.hdPolyAftertouch(30, 64, 0.2f);
|
||||||
|
synth.hdPitchWheel(40, 0.3f);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr);
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"(
|
||||||
|
<region> sample=*saw
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc63/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc7/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc10/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc11/value", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/aftertouch", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/poly_aftertouch/64", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/pitch_bend", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc63/value,f : { 0.4 }",
|
||||||
|
"/cc7/value,f : { 0.41 }",
|
||||||
|
"/cc10/value,f : { 0.42 }",
|
||||||
|
"/cc11/value,f : { 0.43 }",
|
||||||
|
"/aftertouch,f : { 0.1 }",
|
||||||
|
"/poly_aftertouch/64,f : { 0.2 }",
|
||||||
|
"/pitch_bend,f : { 0.3 }",
|
||||||
|
"/cc63/value,f : { 0.4 }",
|
||||||
|
"/cc7/value,f : { 0.41 }",
|
||||||
|
"/cc10/value,f : { 0.42 }",
|
||||||
|
"/cc11/value,f : { 0.43 }",
|
||||||
|
"/aftertouch,f : { 0.1 }",
|
||||||
|
"/poly_aftertouch/64,f : { 0.2 }",
|
||||||
|
"/pitch_bend,f : { 0.3 }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Reloading a file ignores the `set_ccN` opcodes")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
sfz::Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
|
||||||
|
synth.hdcc(10, 1, 0.4f);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
// Same file
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sine.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*sine
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
// Different file
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/saw.sfz", R"(
|
||||||
|
<control> set_cc1=0
|
||||||
|
<region> sample=*saw
|
||||||
|
)");
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
synth.dispatchMessage(client, 0, "/cc1/value", "", nullptr);
|
||||||
|
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/cc1/value,f : { 0.4 }",
|
||||||
|
"/cc1/value,f : { 0.4 }",
|
||||||
|
"/cc1/value,f : { 0 }",
|
||||||
|
};
|
||||||
|
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue