commit df42e631d9a90be598e3383cb23972ac50c2d2e8
parent 8dcd3860b51e00ae18f0d15b96d026bab1d3f320
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Tue, 4 Feb 2025 22:39:27 +0100
initial version of plugin tester
Diffstat:
9 files changed, 374 insertions(+), 1 deletion(-)
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
@@ -62,7 +62,7 @@ if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN)
add_subdirectory(juceUiLib EXCLUDE_FROM_ALL)
add_subdirectory(jucePluginEditorLib EXCLUDE_FROM_ALL)
add_subdirectory(jucePluginData EXCLUDE_FROM_ALL)
-
+ add_subdirectory(pluginTester)
include(juce.cmake)
endif()
diff --git a/source/pluginTester/CMakeLists.txt b/source/pluginTester/CMakeLists.txt
@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(pluginTester VERSION ${CMAKE_PROJECT_VERSION})
+
+set(SOURCES
+ fakeAudioDevice.cpp fakeAudioDevice.h
+ logger.cpp logger.h
+ pluginTester.cpp
+ pluginHost.cpp
+ pluginHost.h
+)
+
+juce_add_console_app(pluginTester
+ COMPANY_NAME "The Usual Suspects" # Specify the name of the plugin's author
+ COMPANY_WEBSITE "https://dsp56300.com"
+ FORMATS VST VST3 AU LV2 CLAP # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 LV2
+ PRODUCT_NAME "pluginTester" # The name of the final executable, which can differ from the target name
+)
+
+juce_generate_juce_header(pluginTester)
+
+target_link_libraries(pluginTester PRIVATE juce::juce_audio_utils)
+
+target_sources(pluginTester PRIVATE ${SOURCES})
+source_group("source" FILES ${SOURCES})
+
+target_link_libraries(pluginTester PUBLIC baseLib)
+
+target_compile_definitions(pluginTester PRIVATE
+ JUCE_PLUGINHOST_VST=1
+ JUCE_PLUGINHOST_VST3=1
+ JUCE_PLUGINHOST_CLAP=1
+ JUCE_PLUGINHOST_LV2=1
+ JUCE_PLUGINHOST_AU=1
+ JUCE_USE_CURL=0
+ JUCE_WEB_BROWSER=0
+)
+
+set_property(TARGET pluginTester PROPERTY FOLDER "Gearmulator")
diff --git a/source/pluginTester/fakeAudioDevice.cpp b/source/pluginTester/fakeAudioDevice.cpp
@@ -0,0 +1,65 @@
+#include "fakeAudioDevice.h"
+
+Array<double> FakeAudioIODevice::getAvailableSampleRates()
+{
+ return {44100.0, 48000.0};
+}
+
+Array<int> FakeAudioIODevice::getAvailableBufferSizes()
+{
+ return {64, 128, 256, 512, 1024};
+}
+
+int FakeAudioIODevice::getDefaultBufferSize()
+{
+ return 512;
+}
+
+String FakeAudioIODevice::open(const uint32_t& _numInputChannels, const uint32_t& _numOutputChannels, double _sampleRate, int _bufferSizeSamples)
+{
+ m_numInputChannels = _numInputChannels;
+ m_numOutputChannels = _numOutputChannels;
+ return open(BigInteger((1<<_numInputChannels) - 1), BigInteger(((1<<_numOutputChannels)-1)), _sampleRate, _bufferSizeSamples);
+}
+
+String FakeAudioIODevice::open(const BigInteger& _inputChannels, const BigInteger& _outputChannels, const double _sampleRate, const int _bufferSizeSamples)
+{
+ m_activeInputChannels = _inputChannels;
+ m_activeOutputChannels = _outputChannels;
+
+ m_sampleRate = _sampleRate;
+ m_bufferSizeSamples = _bufferSizeSamples;
+ m_isOpenFlag = true;
+ return {};
+}
+
+void FakeAudioIODevice::start(AudioIODeviceCallback* _callback)
+{
+ m_callback = _callback;
+ m_isPlayingFlag = true;
+ if (m_callback)
+ m_callback->audioDeviceAboutToStart(this);
+}
+
+void FakeAudioIODevice::stop()
+{
+ m_isPlayingFlag = false;
+ if (m_callback)
+ m_callback->audioDeviceStopped();
+}
+
+void FakeAudioIODevice::processAudio() const
+{
+ if (!m_callback || !m_isPlayingFlag || !m_isOpenFlag)
+ return;
+
+ AudioBuffer<float> buffer(std::max(m_numInputChannels, m_numOutputChannels), m_bufferSizeSamples);
+ buffer.clear();
+
+ m_callback->audioDeviceIOCallbackWithContext(buffer.getArrayOfReadPointers(),
+ m_numInputChannels ? static_cast<int>(m_numInputChannels) : 2,
+ buffer.getArrayOfWritePointers(),
+ static_cast<int>(m_numOutputChannels),
+ buffer.getNumSamples(),
+ AudioIODeviceCallbackContext());
+}
diff --git a/source/pluginTester/fakeAudioDevice.h b/source/pluginTester/fakeAudioDevice.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "JuceHeader.h"
+
+class FakeAudioIODevice : public AudioIODevice
+{
+public:
+ FakeAudioIODevice()
+ : AudioIODevice("Fake Audio Device", "Fake Type"),
+ m_sampleRate(44100.0),
+ m_bufferSizeSamples(512),
+ m_isOpenFlag(false),
+ m_isPlayingFlag(false)
+ {
+ }
+
+ StringArray getOutputChannelNames() override { return {"Output"}; }
+ StringArray getInputChannelNames() override { return {"Input"}; }
+
+ std::optional<BigInteger> getDefaultOutputChannels() const override { return BigInteger(2); }
+ std::optional<BigInteger> getDefaultInputChannels() const override { return BigInteger(2); }
+
+ Array<double> getAvailableSampleRates() override;
+ Array<int> getAvailableBufferSizes() override;
+
+ int getDefaultBufferSize() override;
+
+ String open(const uint32_t& _numInputChannels,
+ const uint32_t& _numOutputChannels,
+ double _sampleRate,
+ int _bufferSizeSamples);
+
+ void start(AudioIODeviceCallback* _callback) override;
+
+private:
+ String open(const BigInteger& _inputChannels,
+ const BigInteger& _outputChannels,
+ double _sampleRate,
+ int _bufferSizeSamples) override;
+
+ void close() override { m_isOpenFlag = false; }
+ bool isOpen() override { return m_isOpenFlag; }
+
+ void stop() override;
+
+ bool isPlaying() override { return m_isPlayingFlag; }
+ String getLastError() override { return {}; }
+
+ int getCurrentBufferSizeSamples() override { return m_bufferSizeSamples; }
+ double getCurrentSampleRate() override { return m_sampleRate; }
+ int getCurrentBitDepth() override { return 32; }
+
+ BigInteger getActiveInputChannels() const override { return m_activeInputChannels; }
+ BigInteger getActiveOutputChannels() const override { return m_activeOutputChannels; }
+
+ int getOutputLatencyInSamples() override { return 0; }
+ int getInputLatencyInSamples() override { return 0; }
+
+ bool hasControlPanel() const override { return false; }
+ bool showControlPanel() override { return false; }
+ bool setAudioPreprocessingEnabled(bool) override { return false; }
+ int getXRunCount() const noexcept override { return 0; }
+
+public:
+ void processAudio() const;
+
+private:
+ double m_sampleRate;
+ int m_bufferSizeSamples;
+ bool m_isOpenFlag;
+ bool m_isPlayingFlag;
+ AudioIODeviceCallback* m_callback = nullptr;
+ uint32_t m_numInputChannels = 2;
+ uint32_t m_numOutputChannels = 2;
+ BigInteger m_activeInputChannels{0b11};
+ BigInteger m_activeOutputChannels{0b11};
+};
+\ No newline at end of file
diff --git a/source/pluginTester/logger.cpp b/source/pluginTester/logger.cpp
@@ -0,0 +1,16 @@
+#include "logger.h"
+
+StdoutLogger::StdoutLogger()
+{
+ setCurrentLogger(this);
+}
+
+StdoutLogger::~StdoutLogger()
+{
+ setCurrentLogger(nullptr);
+}
+
+void ::StdoutLogger::logMessage(const String& _message)
+{
+ puts(_message.toStdString().c_str());
+}
diff --git a/source/pluginTester/logger.h b/source/pluginTester/logger.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "JuceHeader.h"
+
+class StdoutLogger : public Logger
+{
+public:
+ StdoutLogger();
+ ~StdoutLogger() override;
+ void logMessage(const String& _message) override;
+private:
+ JUCE_DECLARE_NON_COPYABLE(StdoutLogger)
+ JUCE_DECLARE_NON_MOVEABLE(StdoutLogger)
+};
diff --git a/source/pluginTester/pluginHost.cpp b/source/pluginTester/pluginHost.cpp
@@ -0,0 +1,28 @@
+#include "pluginHost.h"
+
+CommandLinePluginHost::CommandLinePluginHost()
+{
+ m_formatManager.addDefaultFormats();
+}
+
+CommandLinePluginHost::~CommandLinePluginHost()
+{
+ setProcessor(nullptr);
+ m_pluginInstance.reset();
+}
+
+bool CommandLinePluginHost::loadPlugin(const PluginDescription& _plugin)
+{
+ String errorMessage;
+
+ m_pluginInstance = m_formatManager.createPluginInstance(_plugin, 48000.0f, 512, errorMessage);
+
+ if (!m_pluginInstance)
+ {
+ Logger::writeToLog("Failed to load plugin: " + errorMessage);
+ return false;
+ }
+
+ setProcessor(m_pluginInstance.get());
+ return true;
+}
diff --git a/source/pluginTester/pluginHost.h b/source/pluginTester/pluginHost.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "JuceHeader.h"
+
+class CommandLinePluginHost final : public AudioProcessorPlayer
+{
+public:
+ CommandLinePluginHost();
+
+ CommandLinePluginHost(const CommandLinePluginHost&) = delete;
+ CommandLinePluginHost(CommandLinePluginHost&&) = delete;
+
+ ~CommandLinePluginHost() override;
+
+ CommandLinePluginHost& operator=(const CommandLinePluginHost&) = delete;
+ CommandLinePluginHost& operator=(CommandLinePluginHost&&) = delete;
+
+ bool loadPlugin(const PluginDescription& _plugin);
+ const AudioPluginFormatManager& getFormatManager() { return m_formatManager; }
+
+private:
+ AudioPluginFormatManager m_formatManager;
+ std::unique_ptr<AudioPluginInstance> m_pluginInstance;
+};
diff --git a/source/pluginTester/pluginTester.cpp b/source/pluginTester/pluginTester.cpp
@@ -0,0 +1,109 @@
+#include "fakeAudioDevice.h"
+#include "pluginHost.h"
+#include "logger.h"
+#include "baseLib/filesystem.h"
+
+class JuceAppLifetimeObjects
+{
+public:
+ JuceAppLifetimeObjects()
+ {
+ MessageManager::getInstance();
+ }
+ ~JuceAppLifetimeObjects()
+ {
+ DeletedAtShutdown::deleteAll();
+ MessageManager::deleteInstance();
+ }
+private:
+ JUCE_DECLARE_NON_COPYABLE(JuceAppLifetimeObjects)
+ JUCE_DECLARE_NON_MOVEABLE(JuceAppLifetimeObjects)
+};
+
+int main(const int _argc, char* _argv[])
+{
+ StdoutLogger logger;
+
+ try
+ {
+ if (_argc < 2)
+ {
+ Logger::writeToLog("Usage: pluginTester <path_to_vst2/3_plugin>");
+ return 1;
+ }
+
+ ConsoleApplication app;
+
+ const String pluginPathName = _argv[1];
+ const String pluginPath = baseLib::filesystem::getPath(_argv[1]);
+ const String pluginFilename = baseLib::filesystem::getFilenameWithoutPath(_argv[1]);
+
+ JuceAppLifetimeObjects jalto;
+
+ CommandLinePluginHost pluginHost;
+
+ const auto& formatManager = pluginHost.getFormatManager();
+
+ PluginDescription desc;
+
+ for (int i = 0; i < formatManager.getNumFormats(); ++i)
+ {
+ auto* format = formatManager.getFormat(i);
+
+ if (!format)
+ continue;
+
+ KnownPluginList plugins;
+
+ OwnedArray<PluginDescription> typesFound;
+ plugins.scanAndAddFile(pluginPathName, true,typesFound, *format);
+
+ const auto types = plugins.getTypes();
+
+ if (types.isEmpty())
+ continue;
+
+ desc = types.getFirst();
+ break;
+ }
+
+ if (desc.fileOrIdentifier.isEmpty())
+ {
+ Logger::writeToLog("Failed to find plugin " + pluginPathName);
+ return 3;
+ }
+
+ if (!pluginHost.loadPlugin(desc))
+ {
+ Logger::writeToLog("Failed to load plugin " + pluginPathName);
+ return 1;
+ }
+
+ FakeAudioIODevice audioDevice;
+
+ const uint32_t numIns = pluginHost.getCurrentProcessor()->getTotalNumInputChannels();
+ const uint32_t numOuts = pluginHost.getCurrentProcessor()->getTotalNumOutputChannels();
+
+ auto res = audioDevice.open(numIns, numOuts, 48000, 512);
+
+ if (res.isNotEmpty())
+ {
+ Logger::writeToLog("Failed to open audio device: " + res);
+ return 2;
+ }
+
+ audioDevice.start(&pluginHost);
+
+ while (true)
+ {
+ audioDevice.processAudio();
+ }
+
+ return 0;
+ }
+ catch (const std::exception& e)
+ {
+ juce::Logger::writeToLog(e.what());
+ return 1;
+ }
+}