diff --git a/Makefile b/Makefile
index 73a7383..a54098b 100644
--- a/Makefile
+++ b/Makefile
@@ -67,7 +67,7 @@ DGL_EXTRA_ARGS = \
WINDOWS_ICON_ID=401
# --------------------------------------------------------------
-# Check for system-wide dependencies
+# Check for required system-wide dependencies
ifeq ($(SYSDEPS),true)
@@ -125,6 +125,15 @@ endif
endif
endif
+# --------------------------------------------------------------
+# Check for optional system-wide dependencies
+
+ifeq ($(shell pkg-config --exists fftw3f && echo true),true)
+HAVE_FFTW3F = true
+else
+$(warning fftw3f dependency not installed/available)
+endif
+
# --------------------------------------------------------------
# MOD builds
@@ -202,6 +211,9 @@ ifeq ($(SYSDEPS),true)
else
$(MAKE) all -C deps
endif
+ifeq ($(HAVE_FFTW3F),true)
+ $(MAKE) all -C deps/aubio
+endif
dgl:
ifneq ($(HEADLESS),true)
@@ -250,6 +262,7 @@ deps/unzipfx/unzipfx2cat.exe:
clean:
$(MAKE) distclean -C carla $(CARLA_EXTRA_ARGS) CAN_GENERATE_LV2_TTL=false STATIC_PLUGIN_TARGET=true
$(MAKE) clean -C deps
+ $(MAKE) clean -C deps/aubio
$(MAKE) clean -C dpf/dgl
$(MAKE) clean -C dpf/utils/lv2-ttl-generator
$(MAKE) clean -C plugins
diff --git a/plugins/Cardinal/orig/AudioToCVPitch.svg b/plugins/Cardinal/orig/AudioToCVPitch.svg
new file mode 100644
index 0000000..26214ff
--- /dev/null
+++ b/plugins/Cardinal/orig/AudioToCVPitch.svg
@@ -0,0 +1,140 @@
+
+
+
+
diff --git a/plugins/Cardinal/plugin.json b/plugins/Cardinal/plugin.json
index bc72c41..357f2bd 100644
--- a/plugins/Cardinal/plugin.json
+++ b/plugins/Cardinal/plugin.json
@@ -146,6 +146,15 @@
"Utility"
]
},
+ {
+ "slug": "AudioToCVPitch",
+ "name": "Audio To CV Pitch",
+ "description": "Converts a monophonic audio signal to CV pitch",
+ "manualUrl": "https://github.com/DISTRHO/Cardinal/blob/main/docs/CARDINAL-MODULES.md#audio-to-cv-pitch",
+ "tags": [
+ "Utility"
+ ]
+ },
{
"slug": "Carla",
"name": "Carla Plugin Host",
diff --git a/plugins/Cardinal/res/AudioToCVPitch.svg b/plugins/Cardinal/res/AudioToCVPitch.svg
new file mode 100644
index 0000000..9612a7b
--- /dev/null
+++ b/plugins/Cardinal/res/AudioToCVPitch.svg
@@ -0,0 +1,179 @@
+
+
+
+
diff --git a/plugins/Cardinal/src/.kdev_include_paths b/plugins/Cardinal/src/.kdev_include_paths
index 41c154a..268be4e 100644
--- a/plugins/Cardinal/src/.kdev_include_paths
+++ b/plugins/Cardinal/src/.kdev_include_paths
@@ -1,3 +1,4 @@
+../../../deps/aubio/src
../../../include
../../../carla/source/backend
../../../carla/source/includes
diff --git a/plugins/Cardinal/src/AudioToCVPitch.cpp b/plugins/Cardinal/src/AudioToCVPitch.cpp
new file mode 100644
index 0000000..cca3b8b
--- /dev/null
+++ b/plugins/Cardinal/src/AudioToCVPitch.cpp
@@ -0,0 +1,362 @@
+/*
+ * DISTRHO Cardinal Plugin
+ * Copyright (C) 2021-2022 Bram Giesen
+ * Copyright (C) 2022 Filipe Coelho
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3 of
+ * the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * For a full copy of the GNU General Public License see the LICENSE file.
+ */
+
+#include "plugincontext.hpp"
+#include "ModuleWidgets.hpp"
+#include "Widgets.hpp"
+
+extern "C" {
+#include "aubio.h"
+}
+
+USE_NAMESPACE_DISTRHO;
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// aubio setup values (tested under 48 kHz sample rate)
+static constexpr const uint32_t kAubioHopSize = 1;
+static constexpr const uint32_t kAubioBufferSize = (1024 + 256 + 128) / kAubioHopSize;
+
+// default values
+static constexpr const float kDefaultSensitivity = 50.f;
+static constexpr const float kDefaultTolerance = 6.25f;
+static constexpr const float kDefaultThreshold = 12.5f;
+
+// static checks
+static_assert(sizeof(smpl_t) == sizeof(float), "smpl_t is float");
+static_assert(kAubioBufferSize % kAubioHopSize == 0, "kAubioBufferSize / kAubioHopSize has no remainder");
+
+// --------------------------------------------------------------------------------------------------------------------
+
+struct AudioToCVPitch : Module {
+ enum ParamIds {
+ PARAM_SENSITIVITY,
+ PARAM_CONFIDENCETHRESHOLD,
+ PARAM_TOLERANCE,
+ PARAM_OCTAVE,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ AUDIO_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ CV_PITCH,
+ CV_GATE,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ NUM_LIGHTS
+ };
+
+ bool holdOutputPitch = true;
+ bool smooth = true;
+ int octave = 0;
+
+ float lastKnownPitchInHz = 0.f;
+ float lastKnownPitchConfidence = 0.f;
+
+ float lastUsedTolerance = kDefaultTolerance;
+ float lastUsedOutputPitch = 0.f;
+ float lastUsedOutputSignal = 0.f;
+
+ fvec_t* const detectedPitch = new_fvec(1);
+ fvec_t* const inputBuffer = new_fvec(kAubioBufferSize);
+ uint32_t inputBufferPos = 0;
+
+ aubio_pitch_t* pitchDetector = nullptr;
+
+ dsp::SlewLimiter smoothOutputSignal;
+
+ AudioToCVPitch()
+ {
+ config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
+
+ configInput(AUDIO_INPUT, "Audio");
+ configOutput(CV_PITCH, "Pitch");
+ configOutput(CV_GATE, "Gate");
+ configParam(PARAM_SENSITIVITY, 0.1f, 99.f, kDefaultSensitivity, "Sensitivity", " %");
+ configParam(PARAM_CONFIDENCETHRESHOLD, 0.f, 99.f, kDefaultThreshold, "Confidence Threshold", " %");
+ configParam(PARAM_TOLERANCE, 0.f, 99.f, kDefaultTolerance, "Tolerance", " %");
+ }
+
+ void process(const ProcessArgs& args) override
+ {
+ float cvPitch = lastUsedOutputPitch;
+ float cvSignal = lastUsedOutputSignal;
+
+ inputBuffer->data[inputBufferPos] = inputs[AUDIO_INPUT].getVoltage() * 0.1f
+ * params[PARAM_SENSITIVITY].getValue();
+
+ if (++inputBufferPos == kAubioBufferSize)
+ {
+ inputBufferPos = 0;
+
+ const float tolerance = params[PARAM_TOLERANCE].getValue();
+ if (d_isNotEqual(lastUsedTolerance, tolerance))
+ {
+ lastUsedTolerance = tolerance;
+ aubio_pitch_set_tolerance(pitchDetector, tolerance * 0.01f);
+ }
+
+ aubio_pitch_do(pitchDetector, inputBuffer, detectedPitch);
+ const float detectedPitchInHz = fvec_get_sample(detectedPitch, 0);
+ const float pitchConfidence = aubio_pitch_get_confidence(pitchDetector);
+
+ if (detectedPitchInHz > 0.f && pitchConfidence >= params[PARAM_CONFIDENCETHRESHOLD].getValue() * 0.01f)
+ {
+ const float linearPitch = 12.f * (log2f(detectedPitchInHz / 440.f) + octave - 1) + 69.f;
+ cvPitch = std::max(-10.f, std::min(10.f, linearPitch * (1.f/12.f)));
+ lastKnownPitchInHz = detectedPitchInHz;
+ cvSignal = 10.f;
+ }
+ else
+ {
+ if (! holdOutputPitch)
+ lastKnownPitchInHz = cvPitch = 0.0f;
+
+ cvSignal = 0.f;
+ }
+
+ lastKnownPitchConfidence = pitchConfidence;
+ lastUsedOutputPitch = cvPitch;
+ lastUsedOutputSignal = cvSignal;
+ }
+
+ outputs[CV_PITCH].setVoltage(smooth ? smoothOutputSignal.process(args.sampleTime, cvPitch) : cvPitch);
+ outputs[CV_GATE].setVoltage(cvSignal);
+ }
+
+ void onReset() override
+ {
+ inputBufferPos = 0;
+ smooth = true;
+ holdOutputPitch = true;
+ octave = 0;
+ }
+
+ void onSampleRateChange(const SampleRateChangeEvent& e) override
+ {
+ float tolerance;
+
+ if (pitchDetector != nullptr)
+ {
+ tolerance = aubio_pitch_get_tolerance(pitchDetector);
+ del_aubio_pitch(pitchDetector);
+ }
+ else
+ {
+ tolerance = kDefaultTolerance * 0.01f;
+ }
+
+ pitchDetector = new_aubio_pitch("yinfast", kAubioBufferSize, kAubioHopSize, e.sampleRate);
+ DISTRHO_SAFE_ASSERT_RETURN(pitchDetector != nullptr,);
+
+ aubio_pitch_set_silence(pitchDetector, -30.0f);
+ aubio_pitch_set_tolerance(pitchDetector, tolerance);
+ aubio_pitch_set_unit(pitchDetector, "Hz");
+
+ const double fall = 1.0 / (double(kAubioBufferSize) / e.sampleRate);
+ smoothOutputSignal.reset();
+ smoothOutputSignal.setRiseFall(fall, fall);
+ }
+
+ json_t* dataToJson() override
+ {
+ json_t* const rootJ = json_object();
+ DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr);
+
+ json_object_set_new(rootJ, "holdOutputPitch", json_boolean(holdOutputPitch));
+ json_object_set_new(rootJ, "smooth", json_boolean(smooth));
+ json_object_set_new(rootJ, "octave", json_integer(octave));
+
+ return rootJ;
+ }
+
+ void dataFromJson(json_t* const rootJ) override
+ {
+ if (json_t* const holdOutputPitchJ = json_object_get(rootJ, "holdOutputPitch"))
+ holdOutputPitch = json_boolean_value(holdOutputPitchJ);
+
+ if (json_t* const smoothJ = json_object_get(rootJ, "smooth"))
+ smooth = json_boolean_value(smoothJ);
+
+ if (json_t* const octaveJ = json_object_get(rootJ, "octave"))
+ octave = json_integer_value(octaveJ);
+ }
+};
+
+struct SmallPercentageNanoKnob : NanoKnob<2, 0> {
+ SmallPercentageNanoKnob() {
+ box.size = Vec(32, 32);
+ displayLabel = "";
+ }
+
+ void onChange(const ChangeEvent&) override
+ {
+ engine::ParamQuantity* const pq = getParamQuantity();
+ DISTRHO_SAFE_ASSERT_RETURN(pq != nullptr,);
+
+ displayString = string::f("%.1f %%", pq->getDisplayValue());
+ }
+};
+
+struct AudioToCVPitchWidget : ModuleWidgetWith9HP {
+ static constexpr const float startX = 10.0f;
+ static constexpr const float startY_top = 71.0f;
+ static constexpr const float startY_cv1 = 115.0f;
+ static constexpr const float startY_cv2 = 145.0f;
+ static constexpr const float padding = 32.0f;
+
+ AudioToCVPitch* const module;
+ std::string monoFontPath;
+
+ AudioToCVPitchWidget(AudioToCVPitch* const m)
+ : module(m)
+ {
+ setModule(m);
+ setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/AudioToCVPitch.svg")));
+ monoFontPath = asset::system("res/fonts/ShareTechMono-Regular.ttf");
+
+ createAndAddScrews();
+
+ addInput(createInput(Vec(startX, startY_cv1 + 0 * padding), m, AudioToCVPitch::AUDIO_INPUT));
+ addOutput(createOutput(Vec(startX, startY_cv2 + 0 * padding), m, AudioToCVPitch::CV_PITCH));
+ addOutput(createOutput(Vec(startX, startY_cv2 + 1 * padding), m, AudioToCVPitch::CV_GATE));
+
+ SmallPercentageNanoKnob* knobSens = createParamCentered(Vec(box.size.x * 0.5f, startY_cv2 + 85.f),
+ module, AudioToCVPitch::PARAM_SENSITIVITY);
+ knobSens->displayString = "50 %";
+ addChild(knobSens);
+
+ SmallPercentageNanoKnob* knobTolerance = createParamCentered(Vec(box.size.x * 0.5f, startY_cv2 + 135.f),
+ module, AudioToCVPitch::PARAM_TOLERANCE);
+ knobTolerance->displayString = "6.25 %";
+ addChild(knobTolerance);
+
+ SmallPercentageNanoKnob* knobThres = createParamCentered(Vec(box.size.x * 0.5f, startY_cv2 + 185.f),
+ module, AudioToCVPitch::PARAM_CONFIDENCETHRESHOLD);
+ knobThres->displayString = "12.5 %";
+ addChild(knobThres);
+ }
+
+ void drawInputLine(NVGcontext* const vg, const uint offset, const char* const text)
+ {
+ const float y = startY_cv1 + offset * padding;
+ nvgBeginPath(vg);
+ nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0));
+ nvgText(vg, startX + 28, y + 16, text, nullptr);
+ }
+
+ void drawOutputLine(NVGcontext* const vg, const uint offset, const char* const text)
+ {
+ const float y = startY_cv2 + offset * padding;
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, startX - 1.f, y - 2.f, box.size.x - startX * 2 + 2.f, 28.f, 4);
+ nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0));
+ nvgFill(vg);
+ nvgBeginPath(vg);
+ nvgFillColor(vg, color::BLACK);
+ nvgText(vg, startX + 28, y + 16, text, nullptr);
+ }
+
+ void draw(const DrawArgs& args) override
+ {
+ drawBackground(args.vg);
+
+ nvgFontFaceId(args.vg, 0);
+ nvgFontSize(args.vg, 14);
+
+ drawInputLine(args.vg, 0, "Input");
+ drawOutputLine(args.vg, 0, "Pitch");
+ drawOutputLine(args.vg, 1, "Gate");
+
+ nvgFontSize(args.vg, 11);
+ nvgBeginPath(args.vg);
+ nvgFillColor(args.vg, nvgRGB(0xd0, 0xd0, 0xd0));
+ nvgTextLineHeight(args.vg, 0.8f);
+ nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
+ nvgTextBox(args.vg, startX + 6.f, startY_cv2 + 75.f, 11.f, "S\ne\nn\ns", nullptr);
+ nvgTextBox(args.vg, box.size.x - startX - 16.f, startY_cv2 + 130.f, 11.f, "T\no\nl", nullptr);
+ nvgTextBox(args.vg, startX + 6.f, startY_cv2 + 175.f, 11.f, "T\nh\nr\ne\ns", nullptr);
+
+ nvgBeginPath(args.vg);
+ nvgRoundedRect(args.vg, 10.0f, startY_top, box.size.x - 20.f, 38.0f, 4);
+ nvgFillColor(args.vg, color::BLACK);
+ nvgFill(args.vg);
+
+ ModuleWidgetWith9HP::draw(args);
+ }
+
+ void drawLayer(const DrawArgs& args, int layer) override
+ {
+ if (layer == 1)
+ {
+ nvgFontSize(args.vg, 17);
+ nvgFillColor(args.vg, nvgRGBf(0.76f, 0.11f, 0.22f));
+
+ char pitchConfString[24];
+ char pitchFreqString[24];
+
+ std::shared_ptr monoFont = APP->window->loadFont(monoFontPath);
+
+ if (module != nullptr && monoFont != nullptr)
+ {
+ nvgFontFaceId(args.vg, monoFont->handle);
+
+ std::snprintf(pitchConfString, sizeof(pitchConfString), "%5.1f %%", module->lastKnownPitchConfidence * 100.f);
+ std::snprintf(pitchFreqString, sizeof(pitchFreqString), "%5.0f Hz", module->lastKnownPitchInHz);
+ }
+ else
+ {
+ std::strcpy(pitchConfString, "0.0 %");
+ std::strcpy(pitchFreqString, "0 Hz");
+ }
+
+ nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
+ nvgText(args.vg, box.size.x * 0.5f, startY_top + 15.0f, pitchConfString, nullptr);
+ nvgText(args.vg, box.size.x * 0.5f, startY_top + 33.0f, pitchFreqString, nullptr);
+ }
+
+ ModuleWidgetWith9HP::drawLayer(args, layer);
+ }
+
+ void appendContextMenu(Menu* const menu) override
+ {
+ menu->addChild(new MenuSeparator);
+
+ menu->addChild(createBoolPtrMenuItem("Hold Output Pitch", "", &module->holdOutputPitch));
+ menu->addChild(createBoolPtrMenuItem("Smooth Output Pitch", "", &module->smooth));
+
+ static const std::vector octaves = {-4, -3, -2, -1, 0, 1, 2, 3, 4};
+ menu->addChild(createSubmenuItem("Octave", string::f("%d", module->octave), [=](Menu* menu) {
+ for (size_t i = 0; i < octaves.size(); i++) {
+ menu->addChild(createCheckMenuItem(string::f("%d", octaves[i]), "",
+ [=]() {return module->octave == octaves[i];},
+ [=]() {module->octave = octaves[i];}
+ ));
+ }
+ }));
+ }
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+Model* modelAudioToCVPitch = createModel("AudioToCVPitch");
+
+// --------------------------------------------------------------------------------------------------------------------
diff --git a/plugins/Cardinal/src/HostAudio.cpp b/plugins/Cardinal/src/HostAudio.cpp
index d5f4c64..3555963 100644
--- a/plugins/Cardinal/src/HostAudio.cpp
+++ b/plugins/Cardinal/src/HostAudio.cpp
@@ -329,12 +329,14 @@ struct HostAudioNanoMeter : NanoMeter {
// --------------------------------------------------------------------------------------------------------------------
struct HostAudioWidget2 : HostAudioWidget<2> {
+ typedef NanoKnob<> Knob;
+
HostAudioWidget2(HostAudio2* const m)
: HostAudioWidget<2>(m)
{
// FIXME
const float middleX = box.size.x * 0.5f;
- addParam(createParamCentered(Vec(middleX, 310.0f), m, 0));
+ addParam(createParamCentered(Vec(middleX, 310.0f), m, 0));
HostAudioNanoMeter* const meter = new HostAudioNanoMeter(m);
meter->box.pos = Vec(middleX - padding + 2.75f, startY + padding * 2);
diff --git a/plugins/Cardinal/src/HostTime.cpp b/plugins/Cardinal/src/HostTime.cpp
index da3fd5a..ec9b8b4 100644
--- a/plugins/Cardinal/src/HostTime.cpp
+++ b/plugins/Cardinal/src/HostTime.cpp
@@ -214,7 +214,7 @@ struct HostTimeWidget : ModuleWidget {
{
const float y = startY_cv + offset * padding;
nvgBeginPath(vg);
- nvgRoundedRect(vg, startX - 1.0f, y - 2.0f, box.size.x - (startX + 1) * 2, 28.0f, 4);
+ nvgRoundedRect(vg, startX - 1.0f, y - 2.f, box.size.x - startX * 2 + 2.f, 28.f, 4);
nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0));
nvgFill(vg);
nvgBeginPath(vg);
diff --git a/plugins/Cardinal/src/Widgets.hpp b/plugins/Cardinal/src/Widgets.hpp
index 730262a..b573246 100644
--- a/plugins/Cardinal/src/Widgets.hpp
+++ b/plugins/Cardinal/src/Widgets.hpp
@@ -61,9 +61,8 @@ struct CardinalLedDisplayChoice : LedDisplayChoice {
}
};
+template
struct NanoKnob : Knob {
- static const int ringSize = 4;
-
std::string displayLabel = "Level";
std::string displayString = "0 dB";
float normalizedValue = 0.5f;
@@ -81,14 +80,14 @@ struct NanoKnob : Knob {
const float w = box.size.x;
const float h = box.size.y;
- const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * 2) - ringSize;
+ const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * heightPadding) - ringSize;
const int knobStartX = w / 2 - knobSize / 2;
const int knobStartY = ringSize;
const int knobCenterX = knobStartX + knobSize / 2;
const int knobCenterY = knobStartY + knobSize / 2;
- const NVGcolor testing = nvgRGBf(0.76f, 0.11f, 0.22f);
+ const NVGcolor cardinalColor = nvgRGBf(0.76f, 0.11f, 0.22f);
nvgLineCap(args.vg, NVG_ROUND);
@@ -102,7 +101,7 @@ struct NanoKnob : Knob {
nvgDegToRad(135.0f) + nvgDegToRad(270.0f * normalizedValue),
NVG_CW);
nvgStrokeWidth(args.vg, ringSize);
- nvgStrokeColor(args.vg, testing);
+ nvgStrokeColor(args.vg, cardinalColor);
nvgStroke(args.vg);
// simulate color bleeding
@@ -115,7 +114,7 @@ struct NanoKnob : Knob {
nvgDegToRad(135.0f) + nvgDegToRad(270.0f * normalizedValue),
NVG_CW);
nvgStrokeWidth(args.vg, 5);
- nvgStrokeColor(args.vg, nvgRGBAf(testing.r, testing.g, testing.b, 0.1f));
+ nvgStrokeColor(args.vg, nvgRGBAf(cardinalColor.r, cardinalColor.g, cardinalColor.b, 0.1f));
nvgStroke(args.vg);
// line indicator
@@ -136,7 +135,7 @@ struct NanoKnob : Knob {
float radius = knobSize * 0.5f;
float oradius = radius + std::min(radius * 4.f, 15.f);
- NVGcolor icol = color::mult(nvgRGBAf(testing.r, testing.g, testing.b, 0.2f), halo);
+ NVGcolor icol = color::mult(nvgRGBAf(cardinalColor.r, cardinalColor.g, cardinalColor.b, 0.2f), halo);
NVGcolor ocol = nvgRGBA(0, 0, 0, 0);
NVGpaint paint = nvgRadialGradient(args.vg, knobCenterX, knobCenterY, radius, oradius, icol, ocol);
@@ -146,10 +145,13 @@ struct NanoKnob : Knob {
nvgFill(args.vg);
}
- // bottom label (value)
- bndIconLabelValue(args.vg, 0, knobSize + ringSize, w, BND_WIDGET_HEIGHT, -1,
- testing, BND_CENTER,
- BND_LABEL_FONT_SIZE, displayString.c_str(), nullptr);
+ if (! displayString.empty())
+ {
+ // bottom label (value)
+ bndIconLabelValue(args.vg, -w, knobSize + ringSize, w*3, BND_WIDGET_HEIGHT, -1,
+ cardinalColor, BND_CENTER,
+ BND_LABEL_FONT_SIZE, displayString.c_str(), nullptr);
+ }
Knob::drawLayer(args, layer);
}
@@ -162,7 +164,7 @@ struct NanoKnob : Knob {
const float w = box.size.x;
const float h = box.size.y;
- const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * 2) - ringSize;
+ const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * heightPadding) - ringSize;
const int knobStartX = w / 2 - knobSize / 2;
const int knobStartY = ringSize;
@@ -224,10 +226,14 @@ struct NanoKnob : Knob {
nvgStrokeColor(args.vg, nvgRGBf(0.5f, 0.5f, 0.5f));
nvgStroke(args.vg);
- // bottom label (name)
- bndIconLabelValue(args.vg, 0, knobStartY + knobSize + BND_WIDGET_HEIGHT * 0.75f, w, BND_WIDGET_HEIGHT, -1,
- SCHEME_WHITE, BND_CENTER,
- BND_LABEL_FONT_SIZE, displayLabel.c_str(), nullptr);
+ if (! displayLabel.empty())
+ {
+ // bottom label (name)
+ bndIconLabelValue(args.vg, -w, knobStartY + knobSize + BND_WIDGET_HEIGHT * 0.75f,
+ w*3, BND_WIDGET_HEIGHT, -1,
+ SCHEME_WHITE, BND_CENTER,
+ BND_LABEL_FONT_SIZE, displayLabel.c_str(), nullptr);
+ }
Knob::draw(args);
}
diff --git a/plugins/Cardinal/src/plugin.hpp b/plugins/Cardinal/src/plugin.hpp
index 710d31d..230e957 100644
--- a/plugins/Cardinal/src/plugin.hpp
+++ b/plugins/Cardinal/src/plugin.hpp
@@ -29,6 +29,7 @@ using namespace rack;
extern Plugin* pluginInstance;
extern Model* modelAudioFile;
+extern Model* modelAudioToCVPitch;
extern Model* modelCarla;
extern Model* modelCardinalBlank;
extern Model* modelExpanderInputMIDI;
diff --git a/plugins/Fundamental b/plugins/Fundamental
index 71f4c72..ec6d477 160000
--- a/plugins/Fundamental
+++ b/plugins/Fundamental
@@ -1 +1 @@
-Subproject commit 71f4c72ded3560639b4ff1df3bb827e31c7d61a0
+Subproject commit ec6d477cedc9a00b7dde0f2713d263c4778162d2
diff --git a/plugins/Makefile b/plugins/Makefile
index 9176d69..070df55 100644
--- a/plugins/Makefile
+++ b/plugins/Makefile
@@ -222,6 +222,11 @@ PLUGIN_FILES += $(wildcard Cardinal/src/DearImGui/*.cpp)
PLUGIN_FILES += $(wildcard Cardinal/src/DearImGuiColorTextEditor/*.cpp)
endif
+ifeq ($(shell pkg-config --exists fftw3f && echo true),true)
+PLUGIN_FILES += Cardinal/src/AudioToCVPitch.cpp
+BASE_FLAGS += -DHAVE_FFTW3F
+endif
+
# --------------------------------------------------------------
# Fundamental (always enabled)
@@ -1215,6 +1220,7 @@ $(BUILD_DIR)/Cardinal/%.cpp.o: Cardinal/%.cpp
-DCARLA_BACKEND_NAMESPACE=Cardinal \
-DREAL_BUILD \
-DSTATIC_PLUGIN_TARGET \
+ -I../deps/aubio/src \
-I../carla/source/backend \
-I../carla/source/includes \
-I../carla/source/modules \
diff --git a/plugins/plugins.cpp b/plugins/plugins.cpp
index 49016c5..d6aed24 100644
--- a/plugins/plugins.cpp
+++ b/plugins/plugins.cpp
@@ -882,6 +882,11 @@ static void initStatic__Cardinal()
#else
spl.removeModule("MPV");
#endif
+ #ifdef HAVE_FFTW3F
+ p->addModel(modelAudioToCVPitch);
+ #else
+ spl.removeModule("AudioToCVPitch");
+ #endif
hostTerminalModels = {
modelHostAudio2,
diff --git a/src/Makefile.cardinal.mk b/src/Makefile.cardinal.mk
index 45661a2..5fc11d9 100644
--- a/src/Makefile.cardinal.mk
+++ b/src/Makefile.cardinal.mk
@@ -113,6 +113,12 @@ endif
EXTRA_DEPENDENCIES = $(RACK_EXTRA_LIBS) $(CARLA_EXTRA_LIBS)
EXTRA_LIBS = $(RACK_EXTRA_LIBS) $(CARLA_EXTRA_LIBS) $(STATIC_CARLA_PLUGIN_LIBS)
+ifeq ($(shell pkg-config --exists fftw3f && echo true),true)
+EXTRA_DEPENDENCIES += ../../deps/aubio/libaubio.a
+EXTRA_LIBS += ../../deps/aubio/libaubio.a
+EXTRA_LIBS += $(shell $(PKG_CONFIG) --libs fftw3f)
+endif
+
# --------------------------------------------------------------
# Do some magic