From 0d952f80af49e999b945bf376c73db40f38a8cb7 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 1 May 2022 06:33:14 +0100 Subject: [PATCH] Add Audio to CV Pitch Cardinal module Signed-off-by: falkTX --- Makefile | 15 +- plugins/Cardinal/orig/AudioToCVPitch.svg | 140 +++++++++ plugins/Cardinal/plugin.json | 9 + plugins/Cardinal/res/AudioToCVPitch.svg | 179 +++++++++++ plugins/Cardinal/src/.kdev_include_paths | 1 + plugins/Cardinal/src/AudioToCVPitch.cpp | 362 +++++++++++++++++++++++ plugins/Cardinal/src/HostAudio.cpp | 4 +- plugins/Cardinal/src/HostTime.cpp | 2 +- plugins/Cardinal/src/Widgets.hpp | 38 ++- plugins/Cardinal/src/plugin.hpp | 1 + plugins/Fundamental | 2 +- plugins/Makefile | 6 + plugins/plugins.cpp | 5 + src/Makefile.cardinal.mk | 6 + 14 files changed, 750 insertions(+), 20 deletions(-) create mode 100644 plugins/Cardinal/orig/AudioToCVPitch.svg create mode 100644 plugins/Cardinal/res/AudioToCVPitch.svg create mode 100644 plugins/Cardinal/src/AudioToCVPitch.cpp 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 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + ToCV Pitch + + 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 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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