Volume meter for plugin editors

This commit is contained in:
Jean Pierre Cimalando 2021-04-14 06:36:17 +02:00
parent 9411bd458d
commit 9952d28b97
23 changed files with 514 additions and 134 deletions

View file

@ -45,6 +45,11 @@ if(WIN32)
add_compile_definitions(_WIN32_WINNT=0x601)
endif()
# Define the math constants everywhere
if(WIN32)
add_compile_definitions(_USE_MATH_DEFINES)
endif()
# Set macOS compatibility level
if(APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")

View file

@ -1,4 +1,5 @@
add_library(plugins-common STATIC EXCLUDE_FROM_ALL
"common/plugin/RMSFollower.h"
"common/plugin/MessageUtils.h"
"common/plugin/MessageUtils.cpp"
"common/plugin/InstrumentDescription.h"
@ -28,7 +29,7 @@ endif()
target_include_directories(plugins-common PUBLIC "common")
target_link_libraries(plugins-common
PUBLIC sfizz::spin_mutex
PUBLIC sfizz::filesystem absl::strings
PUBLIC sfizz::simde sfizz::filesystem absl::strings
PRIVATE sfizz::pugixml
PRIVATE sfizz::internal sfizz::sfizz)
add_library(sfizz::plugins-common ALIAS plugins-common)

View file

@ -0,0 +1,73 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include <simde/x86/sse.h>
#include <cstddef>
#include <cmath>
class RMSFollower {
public:
RMSFollower()
{
updatePole();
}
void clear()
{
mem_ = simde_mm_setzero_ps();
}
void init(float sampleRate)
{
sampleRate_ = sampleRate;
updatePole();
clear();
}
void setT60(float t60)
{
t60_ = t60;
updatePole();
}
void process(const float* leftBlock, const float* rightBlock, size_t numFrames)
{
simde__m128 mem = mem_;
const simde__m128 pole = simde_mm_load1_ps(&pole_);
for (size_t i = 0; i < numFrames; ++i) {
float left = leftBlock[i];
float right = rightBlock[i];
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 getMS() const
{
return mem_;
}
simde__m128 getRMS() const
{
return simde_mm_sqrt_ps(mem_);
}
private:
void updatePole()
{
pole_ = std::exp(float(-2.0 * M_PI) / (t60_ * sampleRate_));
}
private:
simde__m128 mem_ {};
float pole_ {};
float t60_ = 300e-3;
float sampleRate_ = 44100;
};

View file

@ -134,10 +134,6 @@ widget_class mainView {open
xywh {610 70 60 5} labelsize 12 hide
class ValueLabel
}
Fl_Box {} {
xywh {745 20 35 55} box BORDER_BOX
class VMeter
}
Fl_Box volumeCCKnob_ {
label Volume
comment {tag=kTagSetCCVolume}
@ -150,6 +146,14 @@ widget_class mainView {open
xywh {655 10 70 90} box BORDER_BOX labelsize 12 align 17
class KnobCCBox
}
Fl_Box leftMeter_ {
xywh {740 10 15 90} box BORDER_BOX
class VMeter
}
Fl_Box rightMeter_ {selected
xywh {760 10 15 90} box BORDER_BOX
class VMeter
}
}
}
Fl_Group {subPanels_[kPanelGeneral]} {

View file

@ -38,6 +38,9 @@ enum class EditId : int {
CC_RANGE(ControllerDefault),
CC_RANGE(ControllerLabel),
//
LeftLevel,
RightLevel,
//
UINumCurves,
UINumMasters,
UINumGroups,

View file

@ -153,6 +153,9 @@ struct Editor::Impl : EditorController::Receiver,
SKnobCCBox* volumeCCKnob_ = nullptr;
SKnobCCBox* panCCKnob_ = nullptr;
SLevelMeter* leftMeter_ = nullptr;
SLevelMeter* rightMeter_ = nullptr;
SAboutDialog* aboutDialog_ = nullptr;
SharedPointer<CBitmap> backgroundBitmap_;
@ -504,6 +507,20 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
updateBackgroundImage(value.c_str());
}
break;
case EditId::LeftLevel:
{
const float value = v.to_float();
if (SLevelMeter* meter = leftMeter_)
meter->setValue(value);
}
break;
case EditId::RightLevel:
{
const float value = v.to_float();
if (SLevelMeter* meter = rightMeter_)
meter->setValue(value);
}
break;
default:
if (editIdIsKey(id)) {
const int key = keyForEditId(id);
@ -703,11 +720,15 @@ void Editor::Impl::createFrameContents()
lbl->setFont(font);
return lbl;
};
auto createVMeter = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
// TODO the volume meter...
CViewContainer* container = new CViewContainer(bounds);
container->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00));
return container;
auto createVMeter = [this, &palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
SLevelMeter* meter = new SLevelMeter(bounds);
meter->setNormalFillColor(CColor(0x00, 0xaa, 0x11));
meter->setDangerFillColor(CColor(0xaa, 0x00, 0x00));
OnThemeChanged.push_back([meter, palette]() {
meter->setFrameColor(palette->inactiveText);
meter->setBackColor(palette->knobInactiveTrack);
});
return meter;
};
#if 0
auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) {

View file

@ -979,6 +979,75 @@ void SControlsPanel::ControlSlotListener::controlEndEdit(CControl* pControl)
panel_->EndEditFunction(pControl->getTag());
}
///
SLevelMeter::SLevelMeter(const CRect& size)
: CView(size)
{
}
void SLevelMeter::setValue(float value)
{
if (value_ == value)
return;
value_ = value;
invalid();
}
void SLevelMeter::draw(CDrawContext* dc)
{
float dbValue = 20.0f * std::log10(value_);
float fill = (dbValue - dbMin_) / (dbMax_ - dbMin_);
fill = (fill < 0.0f) ? 0.0f : fill;
fill = (fill > 1.0f) ? 1.0f : fill;
CRect largeBounds = getViewSize();
CRect fillBounds = largeBounds;
fillBounds.top = largeBounds.bottom - fill * largeBounds.getHeight();
const CColor safeColor = safeFillColor_;
const CColor dangerColor = dangerFillColor_;
CColor fillColor;
if (safeColor == dangerColor) {
fillColor = safeColor;
}
else {
float thres = dangerThreshold_;
float mix = (fill - thres) / (1.0f - thres);
mix = (mix < 0.0f) ? 0.0f : mix;
CCoord safeH, safeS, safeV, safeA;
CCoord dangerH, dangerS, dangerV, dangerA;
safeColor.toHSV(safeH, safeS, safeV);
dangerColor.toHSV(dangerH, dangerS, dangerV);
safeA = safeColor.alpha / 255.0;
dangerA = dangerColor.alpha / 255.0;
CCoord H, S, V, A;
H = safeH + mix * (dangerH - safeH);
S = safeS + mix * (dangerS - safeS);
V = safeV + mix * (dangerV - safeV);
A = safeA + mix * (dangerA - safeA);
fillColor.fromHSV(H, S, V);
fillColor.alpha = static_cast<uint8_t>(A * 255.0);
}
dc->setDrawMode(kAliasing);
if (backColor_.alpha > 0) {
dc->setFillColor(backColor_);
dc->drawRect(largeBounds, kDrawFilled);
}
dc->setFrameColor(frameColor_);
dc->setFillColor(fillColor);
dc->drawRect(fillBounds, kDrawFilled);
dc->drawRect(largeBounds);
}
///
SPlaceHolder::SPlaceHolder(const CRect& size, const CColor& color)
: CView(size), color_(color)

View file

@ -365,6 +365,41 @@ private:
SharedPointer<CVSTGUITimer> relayoutTrigger_;
};
///
class SLevelMeter : public CView {
public:
explicit SLevelMeter(const CRect& size);
float getValue() const { return value_; }
void setValue(float value);
float getDangerThreshold() const { return dangerThreshold_; }
void setDangerThreshold(float thres) { dangerThreshold_ = thres; invalid(); }
CColor getFrameColor() const { return frameColor_; }
void setFrameColor(CColor color) { frameColor_ = color; invalid(); }
CColor getBackColor() const { return backColor_; }
void setBackColor(CColor color) { backColor_ = color; invalid(); }
CColor getNormalFillColor() const { return safeFillColor_; }
void setNormalFillColor(CColor color) { safeFillColor_ = color; invalid(); }
CColor getDangerFillColor() const { return dangerFillColor_; }
void setDangerFillColor(CColor color) { dangerFillColor_ = color; invalid(); }
protected:
void draw(CDrawContext* dc) override;
private:
float value_ = 0;
float dangerThreshold_ = 0.5;
float dbMin_ = -40;
float dbMax_ = 0;
CColor frameColor_;
CColor safeFillColor_;
CColor dangerFillColor_;
CColor backColor_;
};
///
class SPlaceHolder : public CView {
public:

View file

@ -72,124 +72,128 @@ view__26->setVisible(false);
auto* const view__27 = createValueLabel(CRect(40, 65, 100, 70), -1, "Center", kCenterText, 12);
view__25->addView(view__27);
view__27->setVisible(false);
auto* const view__28 = createVMeter(CRect(175, 15, 210, 70), -1, "", kCenterText, 14);
auto* const view__28 = createKnobCCBox(CRect(10, 5, 80, 95), kTagSetCCVolume, "Volume", kCenterText, 12);
volumeCCKnob_ = view__28;
view__25->addView(view__28);
auto* const view__29 = createKnobCCBox(CRect(10, 5, 80, 95), kTagSetCCVolume, "Volume", kCenterText, 12);
volumeCCKnob_ = view__29;
auto* const view__29 = createKnobCCBox(CRect(85, 5, 155, 95), kTagSetCCPan, "Pan", kCenterText, 12);
panCCKnob_ = view__29;
view__25->addView(view__29);
auto* const view__30 = createKnobCCBox(CRect(85, 5, 155, 95), kTagSetCCPan, "Pan", kCenterText, 12);
panCCKnob_ = view__30;
auto* const view__30 = createVMeter(CRect(170, 5, 185, 95), -1, "", kCenterText, 14);
leftMeter_ = view__30;
view__25->addView(view__30);
auto* const view__31 = createVMeter(CRect(190, 5, 205, 95), -1, "", kCenterText, 14);
rightMeter_ = view__31;
view__25->addView(view__31);
enterPalette(defaultPalette);
auto* const view__31 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14);
subPanels_[kPanelGeneral] = view__31;
view__0->addView(view__31);
view__31->setVisible(false);
auto* const view__32 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14);
view__31->addView(view__32);
auto* const view__33 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
auto* const view__32 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14);
subPanels_[kPanelGeneral] = view__32;
view__0->addView(view__32);
view__32->setVisible(false);
auto* const view__33 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14);
view__32->addView(view__33);
auto* const view__34 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14);
view__32->addView(view__34);
auto* const view__35 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14);
view__32->addView(view__35);
auto* const view__36 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14);
view__32->addView(view__36);
auto* const view__37 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14);
view__32->addView(view__37);
auto* const view__38 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14);
infoCurvesLabel_ = view__38;
view__32->addView(view__38);
auto* const view__39 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14);
infoMastersLabel_ = view__39;
view__32->addView(view__39);
auto* const view__40 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14);
infoGroupsLabel_ = view__40;
view__32->addView(view__40);
auto* const view__41 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14);
infoRegionsLabel_ = view__41;
view__32->addView(view__41);
auto* const view__42 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14);
infoSamplesLabel_ = view__42;
view__32->addView(view__42);
auto* const view__43 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelControls] = view__43;
view__0->addView(view__43);
view__43->setVisible(false);
auto* const view__44 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14);
view__43->addView(view__44);
auto* const view__45 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12);
controlsPanel_ = view__45;
auto* const view__34 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
view__33->addView(view__34);
auto* const view__35 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14);
view__33->addView(view__35);
auto* const view__36 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14);
view__33->addView(view__36);
auto* const view__37 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14);
view__33->addView(view__37);
auto* const view__38 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14);
view__33->addView(view__38);
auto* const view__39 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14);
infoCurvesLabel_ = view__39;
view__33->addView(view__39);
auto* const view__40 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14);
infoMastersLabel_ = view__40;
view__33->addView(view__40);
auto* const view__41 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14);
infoGroupsLabel_ = view__41;
view__33->addView(view__41);
auto* const view__42 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14);
infoRegionsLabel_ = view__42;
view__33->addView(view__42);
auto* const view__43 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14);
infoSamplesLabel_ = view__43;
view__33->addView(view__43);
auto* const view__44 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelControls] = view__44;
view__0->addView(view__44);
view__44->setVisible(false);
auto* const view__45 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14);
view__44->addView(view__45);
auto* const view__46 = createLogicalGroup(CRect(5, 109, 795, 425), -1, "", kCenterText, 14);
subPanels_[kPanelSettings] = view__46;
view__0->addView(view__46);
auto* const view__47 = createTitleGroup(CRect(300, 26, 495, 126), -1, "Engine", kCenterText, 12);
view__46->addView(view__47);
auto* const view__48 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12);
oversamplingSlider_ = view__48;
auto* const view__46 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12);
controlsPanel_ = view__46;
view__45->addView(view__46);
auto* const view__47 = createLogicalGroup(CRect(5, 109, 795, 425), -1, "", kCenterText, 14);
subPanels_[kPanelSettings] = view__47;
view__0->addView(view__47);
auto* const view__48 = createTitleGroup(CRect(300, 26, 495, 126), -1, "Engine", kCenterText, 12);
view__47->addView(view__48);
auto* const view__49 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12);
view__47->addView(view__49);
auto* const view__50 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12);
view__47->addView(view__50);
auto* const view__51 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12);
preloadSizeSlider_ = view__51;
view__47->addView(view__51);
auto* const view__52 = createTitleGroup(CRect(170, 161, 585, 261), -1, "Tuning", kCenterText, 12);
view__46->addView(view__52);
auto* const view__53 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12);
view__52->addView(view__53);
auto* const view__54 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12);
tuningFrequencySlider_ = view__54;
view__52->addView(view__54);
auto* const view__55 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12);
view__52->addView(view__55);
auto* const view__56 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14);
stretchedTuningSlider_ = view__56;
view__52->addView(view__56);
auto* const view__57 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12);
view__52->addView(view__57);
auto* const view__58 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12);
view__52->addView(view__58);
auto* const view__59 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12);
scalaFileButton_ = view__59;
view__52->addView(view__59);
auto* const view__60 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootKeySlider_ = view__60;
view__52->addView(view__60);
auto* const view__61 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootOctaveSlider_ = view__61;
view__52->addView(view__61);
auto* const view__62 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12);
scalaResetButton_ = view__62;
view__52->addView(view__62);
auto* const view__63 = createTitleGroup(CRect(615, 161, 754, 261), -1, "Files", kCenterText, 12);
userFilesGroup_ = view__63;
view__46->addView(view__63);
auto* const view__64 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12);
view__63->addView(view__64);
auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12);
userFilesDirButton_ = view__65;
view__63->addView(view__65);
auto* const view__66 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12);
view__46->addView(view__66);
auto* const view__67 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12);
sampleQualitySlider_ = view__67;
view__66->addView(view__67);
auto* const view__68 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12);
view__66->addView(view__68);
auto* const view__69 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12);
view__66->addView(view__69);
auto* const view__70 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12);
oscillatorQualitySlider_ = view__70;
view__66->addView(view__70);
auto* const view__71 = createTitleGroup(CRect(35, 161, 140, 261), -1, "Theme", kCenterText, 12);
view__46->addView(view__71);
view__71->setVisible(false);
auto* const view__72 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12);
themeMenu_ = view__72;
view__71->addView(view__72);
auto* const view__73 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12);
piano_ = view__73;
view__0->addView(view__73);
auto* const view__49 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12);
oversamplingSlider_ = view__49;
view__48->addView(view__49);
auto* const view__50 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12);
view__48->addView(view__50);
auto* const view__51 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12);
view__48->addView(view__51);
auto* const view__52 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12);
preloadSizeSlider_ = view__52;
view__48->addView(view__52);
auto* const view__53 = createTitleGroup(CRect(170, 161, 585, 261), -1, "Tuning", kCenterText, 12);
view__47->addView(view__53);
auto* const view__54 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12);
view__53->addView(view__54);
auto* const view__55 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12);
tuningFrequencySlider_ = view__55;
view__53->addView(view__55);
auto* const view__56 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12);
view__53->addView(view__56);
auto* const view__57 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14);
stretchedTuningSlider_ = view__57;
view__53->addView(view__57);
auto* const view__58 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12);
view__53->addView(view__58);
auto* const view__59 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12);
view__53->addView(view__59);
auto* const view__60 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12);
scalaFileButton_ = view__60;
view__53->addView(view__60);
auto* const view__61 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootKeySlider_ = view__61;
view__53->addView(view__61);
auto* const view__62 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootOctaveSlider_ = view__62;
view__53->addView(view__62);
auto* const view__63 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12);
scalaResetButton_ = view__63;
view__53->addView(view__63);
auto* const view__64 = createTitleGroup(CRect(615, 161, 754, 261), -1, "Files", kCenterText, 12);
userFilesGroup_ = view__64;
view__47->addView(view__64);
auto* const view__65 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12);
view__64->addView(view__65);
auto* const view__66 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12);
userFilesDirButton_ = view__66;
view__64->addView(view__66);
auto* const view__67 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12);
view__47->addView(view__67);
auto* const view__68 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12);
sampleQualitySlider_ = view__68;
view__67->addView(view__68);
auto* const view__69 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12);
view__67->addView(view__69);
auto* const view__70 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12);
view__67->addView(view__70);
auto* const view__71 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12);
oscillatorQualitySlider_ = view__71;
view__67->addView(view__71);
auto* const view__72 = createTitleGroup(CRect(35, 161, 140, 261), -1, "Theme", kCenterText, 12);
view__47->addView(view__72);
view__72->setVisible(false);
auto* const view__73 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12);
themeMenu_ = view__73;
view__72->addView(view__73);
auto* const view__74 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12);
piano_ = view__74;
view__0->addView(view__74);

View file

@ -61,6 +61,12 @@ if(SFIZZ_LV2_UI)
endif()
endif()
# Define a preprocessor variable to indicate a build with UI enabled
if(SFIZZ_LV2_UI)
target_compile_definitions(${LV2PLUGIN_PRJ_NAME} PRIVATE "SFIZZ_LV2_UI=1")
target_compile_definitions(${LV2PLUGIN_PRJ_NAME}_ui PRIVATE "SFIZZ_LV2_UI=1")
endif()
# Remove the "lib" prefix, rename the target name and build it in the .lv build dir
# <build_dir>/lv2/<plugin_name>_lv2.<ext> to
# <build_dir>/lv2/<plugin_name>.lv2/<plugin_name>.<ext>

View file

@ -121,9 +121,9 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self)
self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize);
self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling);
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel);
self->time_position_uri = map->map(map->handle, LV2_TIME__Position);
self->time_bar_uri = map->map(map->handle, LV2_TIME__bar);
self->time_bar_beat_uri = map->map(map->handle, LV2_TIME__barBeat);
@ -532,6 +532,9 @@ activate(LV2_Handle instance)
sfizz_set_samples_per_block(self->synth, self->max_block_size);
sfizz_set_sample_rate(self->synth, self->sample_rate);
self->must_update_midnam.store(1);
#if defined(SFIZZ_LV2_UI)
self->rms_follower.init(self->sample_rate);
#endif
}
static void
@ -576,6 +579,30 @@ sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned
lv2_atom_forge_pop(forge, &frame);
}
#if defined(SFIZZ_LV2_UI)
static void
sfizz_lv2_send_levels(sfizz_plugin_t *self, LV2_Atom_Forge* forge, float left, float right)
{
const float levels[] = {left, right};
uint32_t num_levels = sizeof(levels) / sizeof(levels[0]);
bool write_ok = lv2_atom_forge_frame_time(forge, 0);
LV2_Atom_Vector *vector = nullptr;
if (write_ok) {
LV2_Atom_Forge_Ref ref = lv2_atom_forge_vector(forge, sizeof(float), self->atom_float_uri, num_levels, levels);
write_ok = ref;
if (write_ok)
vector = (LV2_Atom_Vector *)lv2_atom_forge_deref(forge, ref);
}
if (write_ok)
vector->atom.type = self->sfizz_audio_level_uri;
(void)write_ok;
}
#endif
static void
sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Object *obj)
{
@ -1046,6 +1073,20 @@ run(LV2_Handle instance, uint32_t sample_count)
spin_mutex_unlock(self->synth_mutex);
#if defined(SFIZZ_LV2_UI)
if (self->ui_active)
{
self->rms_follower.process(self->output_buffers[0], self->output_buffers[1], sample_count);
const simde__m128 rms = self->rms_follower.getRMS();
const float *levels = (const float *)&rms;
sfizz_lv2_send_levels(self, &self->forge_notify, levels[0], levels[1]);
}
else
{
self->rms_follower.clear();
}
#endif
lv2_atom_forge_pop(&self->forge_notify, &notify_frame);
lv2_atom_forge_pop(&self->forge_automate, &automate_frame);
}

View file

@ -44,6 +44,8 @@
#define SFIZZ__checkModification SFIZZ_URI ":" "check_modification"
// OSC atoms
#define SFIZZ__OSCBlob SFIZZ_URI ":" "OSCBlob"
// Level atoms
#define SFIZZ__AudioLevel SFIZZ_URI ":" "AudioLevel"
enum
{
@ -89,6 +91,10 @@ bool sfizz_lv2_fetch_description(
sfizz_plugin_t *self, const int *serial,
uint8_t **descp, uint32_t *sizep, int *serialp);
#if defined(SFIZZ_LV2_UI)
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active);
#endif
// Mapping URID to CC and vice-versa
struct sfizz_lv2_ccmap;
sfizz_lv2_ccmap *sfizz_lv2_ccmap_create(LV2_URID_Map* map);

View file

@ -31,6 +31,13 @@ bool sfizz_lv2_fetch_description(
return true;
}
#if defined(SFIZZ_LV2_UI)
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active)
{
self->ui_active = ui_active;
}
#endif
struct sfizz_lv2_ccmap {
LV2_URID *cc_to_urid;
int *urid_to_cc;

View file

@ -7,6 +7,9 @@
#pragma once
#include "sfizz_lv2.h"
#if defined(SFIZZ_LV2_UI)
#include "plugin/RMSFollower.h"
#endif
#include <spin_mutex.h>
#include <sfizz.h>
#include <stdbool.h>
@ -89,6 +92,7 @@ struct sfizz_plugin_t
LV2_URID sfizz_check_modification_uri {};
LV2_URID sfizz_active_voices_uri {};
LV2_URID sfizz_osc_blob_uri {};
LV2_URID sfizz_audio_level_uri {};
LV2_URID time_position_uri {};
LV2_URID time_bar_uri {};
LV2_URID time_bar_beat_uri {};
@ -145,4 +149,10 @@ struct sfizz_plugin_t
// OSC
uint8_t osc_temp[OSC_TEMP_SIZE] {};
#if defined(SFIZZ_LV2_UI)
// UI
volatile bool ui_active = false;
RMSFollower rms_follower;
#endif
};

View file

@ -116,6 +116,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_scala_file_uri;
LV2_URID sfizz_osc_blob_uri;
LV2_URID sfizz_audio_level_uri;
std::unique_ptr<sfizz_lv2_ccmap, sfizz_lv2_ccmap_delete> ccmap;
uint8_t osc_temp[OSC_TEMP_SIZE];
@ -217,6 +218,7 @@ instantiate(const LV2UI_Descriptor *descriptor,
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel);
self->ccmap.reset(sfizz_lv2_ccmap_create(map));
// set up the resource path
@ -287,6 +289,7 @@ static void
cleanup(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
sfizz_lv2_set_ui_active(self->plugin, false);
delete self;
}
@ -391,6 +394,27 @@ port_event(LV2UI_Handle ui,
if (sfizz_extract_message(LV2_ATOM_BODY_CONST(atom), atom->size, buffer, sizeof(buffer), &path, &sig, &args) > 0)
self->uiReceiveMessage(path, sig, args);
}
else if (atom->type == self->sfizz_audio_level_uri) {
const LV2_Atom_Vector *vector = reinterpret_cast<const LV2_Atom_Vector *>(atom);
if (vector->body.child_type == self->atom_float_uri &&
vector->body.child_size == sizeof(float))
{
const uint8_t *vec_body = reinterpret_cast<const uint8_t*>(
LV2_ATOM_CONTENTS_CONST(LV2_Atom_Vector, vector));
const uint8_t *vec_end = reinterpret_cast<const uint8_t*>(
LV2_ATOM_BODY_CONST(&vector->atom)) + vector->atom.size;
const float *levels = reinterpret_cast<const float *>(vec_body);
uint32_t count = static_cast<uint32_t>((vec_end - vec_body) / sizeof(float));
float left = (count > 0) ? levels[0] : 0.0f;
float right = (count > 1) ? levels[1] : 0.0f;
self->uiReceiveValue(EditId::LeftLevel, left);
self->uiReceiveValue(EditId::RightLevel, right);
}
}
}
(void)buffer_size;
@ -466,6 +490,8 @@ idle(LV2UI_Handle ui)
(void)self;
#endif
sfizz_lv2_set_ui_active(self->plugin, self->uiFrame->isVisible());
return 0;
}

View file

@ -12,4 +12,4 @@
lv2:optionalFeature ui:touch ;
lv2:requiredFeature urid:map ;
lv2:requiredFeature urid:unmap ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/instance-access> .
lv2:requiredFeature <http://lv2plug.in/ns/ext/instance-access> .

View file

@ -92,6 +92,22 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
Vst::kRootUnitId, shortTitle));
}
// Volume levels
parameters.addParameter(
SfizzRange::getForParameter(kPidLeftLevel).createParameter(
Steinberg::String("Left level"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
parameters.addParameter(
SfizzRange::getForParameter(kPidRightLevel).createParameter(
Steinberg::String("Right level"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
// Editor status
parameters.addParameter(
SfizzRange::getForParameter(kPidEditorOpen).createParameter(
Steinberg::String("Editor open"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
// Initial MIDI mapping
for (int32 i = 0; i < Vst::kCountCtrlNumber; ++i) {
Vst::ParamID id = Vst::kNoParamId;

View file

@ -122,6 +122,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
uiReceiveValue(EditId::UserFilesDir, userFilesDir.value_or(fs::path()).u8string());
uiReceiveValue(EditId::FallbackFilesDir, SfizzPaths::getSfzFallbackDefaultPath().u8string());
updateEditorIsOpenParameter();
return true;
}
@ -153,6 +155,16 @@ void PLUGIN_API SfizzVstEditor::close()
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
noteEventQueue_.reset();
}
updateEditorIsOpenParameter();
}
void SfizzVstEditor::updateEditorIsOpenParameter()
{
SfizzVstController* ctrl = getController();
bool editorIsOpen = frame && frame->isVisible();
ctrl->setParamNormalized(kPidEditorOpen, editorIsOpen);
ctrl->performEdit(kPidEditorOpen, editorIsOpen);
}
///
@ -180,6 +192,7 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
if (message == CVSTGUITimer::kMsgTimer) {
processOscQueue();
processNoteEventQueue();
updateEditorIsOpenParameter(); // Note(jpc) for Reaper, it can fail at open time
}
return result;
@ -296,6 +309,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
case kPidOscillatorQuality:
uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value));
break;
case kPidLeftLevel:
uiReceiveValue(EditId::LeftLevel, range.denormalize(value));
break;
case kPidRightLevel:
uiReceiveValue(EditId::RightLevel, range.denormalize(value));
break;
default:
if (id >= kPidCC0 && id <= kPidCCLast) {
int cc = int(id - kPidCC0);

View file

@ -37,15 +37,14 @@ public:
return static_cast<SfizzVstController*>(Vst::VSTGUIEditor::getController());
}
void updateEditorIsOpenParameter();
// VSTGUIEditor
CMessageResult notify(CBaseObject* sender, const char* message) override;
// FObject
void PLUGIN_API update(FUnknown* changedUnknown, int32 message) override;
//
void updateState(const SfizzVstState& state);
void updatePlayState(const SfizzPlayState& playState);
private:
void processOscQueue();
void processNoteEventQueue();

View file

@ -26,6 +26,9 @@ enum {
kPidPitchBend,
kPidCC0,
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
kPidLeftLevel,
kPidRightLevel,
kPidEditorOpen,
/* Reserved */
kNumParameters,
};
@ -78,6 +81,12 @@ struct SfizzRange {
return {0.0, 0.0, 1.0};
case kPidPitchBend:
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:
return {0.0, 0.0, 1.0};
default:
if (id >= kPidCC0 && id <= kPidCCLast)
return {0.0, 0.0, 1.0};

View file

@ -37,7 +37,7 @@ static const char* kMsgIdSetPreloadSize = "SetPreloadSize";
static const char* kMsgIdReceiveMessage = "ReceiveMessage";
static const char* kMsgIdNoteEvents = "NoteEvents";
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 50 };
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 20 };
SfizzVstProcessor::SfizzVstProcessor()
: _oscTemp(new uint8_t[kOscTempSize]),
@ -106,6 +106,8 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
_noteEventsCurrentCycle.fill(-1.0f);
_editorIsOpen = false;
return result;
}
@ -221,6 +223,7 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state)
if (state) {
synth->setSampleRate(processSetup.sampleRate);
synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock);
_rmsFollower.init(processSetup.sampleRate);
initializeEventProcessor(processSetup, kNumParameters);
startBackgroundWork();
} else {
@ -286,6 +289,24 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
synth.renderBlock(outputs, numFrames, numChannels);
// Update levels, if editor is open, otherwise skip
RMSFollower& rmsFollower = _rmsFollower;
if (_editorIsOpen) {
rmsFollower.process(outputs[0], outputs[1], numFrames);
simde__m128 rms = _rmsFollower.getRMS();
float left = reinterpret_cast<float*>(&rms)[0];
float right = reinterpret_cast<float*>(&rms)[1];
if (Vst::IParameterChanges* pcs = data.outputParameterChanges) {
int32 index;
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidLeftLevel, index))
vq->addPoint(0, left, index);
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidRightLevel, index))
vq->addPoint(0, right, index);
}
}
else
rmsFollower.clear();
// Request OSC updates
sfz::Client& client = *_client;
synth.sendMessage(client, 0, "/sw/last/current", "", nullptr);
@ -388,6 +409,9 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id
case kPidPitchBend:
synth.hdPitchWheel(sampleOffset, range.denormalize(value));
break;
case kPidEditorOpen:
_editorIsOpen = value != 0;
break;
default:
if (id >= kPidCC0 && id <= kPidCCLast) {
int32 ccNumber = static_cast<int32>(id - kPidCC0);
@ -713,7 +737,7 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
sendMessage(notification);
}
if (idleCounter % 10 == 0) {
if (idleCounter % 25 == 0) {
if (_synth->shouldReloadFile()) {
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);

View file

@ -7,6 +7,7 @@
#pragma once
#include "SfizzVstState.h"
#include "OrderedEventProcessor.h"
#include "plugin/RMSFollower.h"
#include "sfizz/RTSemaphore.h"
#include "ring_buffer/ring_buffer.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
@ -63,6 +64,10 @@ private:
// whether allowed to perform events (owns the processing lock)
bool _canPerformEventsAndParameters {};
// level meters
RMSFollower _rmsFollower;
bool _editorIsOpen = false;
// client
sfz::ClientPtr _client;
std::unique_ptr<uint8_t[]> _oscTemp;

View file

@ -296,9 +296,6 @@ if(SFIZZ_USE_SNDFILE)
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1")
target_link_libraries(sfizz_internal PUBLIC st_audiofile)
endif()
if(WIN32)
target_compile_definitions(sfizz_internal PRIVATE _USE_MATH_DEFINES)
endif()
if(SFIZZ_RELEASE_ASSERTS)
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_ENABLE_RELEASE_ASSERT=1")
endif()