gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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:
Msource/CMakeLists.txt | 2+-
Asource/pluginTester/CMakeLists.txt | 39+++++++++++++++++++++++++++++++++++++++
Asource/pluginTester/fakeAudioDevice.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/pluginTester/fakeAudioDevice.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/pluginTester/logger.cpp | 16++++++++++++++++
Asource/pluginTester/logger.h | 14++++++++++++++
Asource/pluginTester/pluginHost.cpp | 28++++++++++++++++++++++++++++
Asource/pluginTester/pluginHost.h | 24++++++++++++++++++++++++
Asource/pluginTester/pluginTester.cpp | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; + } +}