From fbaad89fbf835b254ae0a241702e2b02d797a638 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sat, 5 Jul 2025 17:48:14 +1000 Subject: [PATCH 01/10] enable vst3 support --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 156cd12..83182f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,8 @@ juce_add_plugin(NeuralDX7PatchGenerator COPY_PLUGIN_AFTER_BUILD TRUE PLUGIN_MANUFACTURER_CODE NinA PLUGIN_CODE NeuD - FORMATS Standalone AU + FORMATS Standalone VST3 AU + VST3_CATEGORIES "Generator Tools" PRODUCT_NAME "NeuralDX7PatchGenerator") # Override standalone binary location to be in ${CMAKE_BUILD_TYPE}/ instead of ${CMAKE_BUILD_TYPE}/Standalone/ @@ -197,7 +198,8 @@ endif() # Disable JUCE networking features to avoid curl dependency target_compile_definitions(NeuralDX7PatchGenerator PRIVATE JUCE_WEB_BROWSER=0 - JUCE_USE_CURL=0) + JUCE_USE_CURL=0 + JUCE_VST3_CAN_REPLACE_VST2=0) # Include directories target_include_directories(NeuralDX7PatchGenerator PRIVATE Source ${CMAKE_CURRENT_BINARY_DIR} ${GTK3_INCLUDE_DIRS} ${WEBKIT2GTK_INCLUDE_DIRS}) From c733a3255c1493a53e1db6c5fa12936381926932 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sat, 5 Jul 2025 19:50:12 +1000 Subject: [PATCH 02/10] added threaded inference worker to avoid blocking main threaed --- CMakeLists.txt | 1 + Source/PluginProcessor.cpp | 88 ++++++------- Source/PluginProcessor.h | 3 +- Source/ThreadedInferenceEngine.cpp | 202 +++++++++++++++++++++++++++++ Source/ThreadedInferenceEngine.h | 74 +++++++++++ 5 files changed, 322 insertions(+), 46 deletions(-) create mode 100644 Source/ThreadedInferenceEngine.cpp create mode 100644 Source/ThreadedInferenceEngine.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 83182f1..6c7a6b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ target_sources(NeuralDX7PatchGenerator Source/DX7BulkPacker.cpp Source/NeuralModelWrapper.cpp Source/EmbeddedModelLoader.cpp + Source/ThreadedInferenceEngine.cpp ${CMAKE_CURRENT_BINARY_DIR}/model_data.h) # Link libraries diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0cb6f3a..3f5af18 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -7,12 +7,17 @@ NeuralDX7PatchGeneratorProcessor::NeuralDX7PatchGeneratorProcessor() : AudioProcessor (BusesProperties()) { - latentVector.resize(NeuralModelWrapper::LATENT_DIM, 0.0f); + inferenceEngine = std::make_unique(); + inferenceEngine->startInferenceThread(); } NeuralDX7PatchGeneratorProcessor::~NeuralDX7PatchGeneratorProcessor() { + if (inferenceEngine) + { + inferenceEngine->stopInferenceThread(); + } } const juce::String NeuralDX7PatchGeneratorProcessor::getName() const @@ -150,13 +155,8 @@ void NeuralDX7PatchGeneratorProcessor::generateAndSendMidi() { std::cout << "generateAndSendMidi() called" << std::endl; - if (!neuralModel.isModelLoaded()) { - std::cout << "Neural model not loaded, attempting to load..." << std::endl; - if (!neuralModel.loadModelFromFile()) { - std::cout << "Failed to load neural model!" << std::endl; - return; - } - std::cout << "Neural model loaded successfully!" << std::endl; + if (!inferenceEngine->isModelLoaded()) { + std::cout << "Neural model not loaded yet, request queued" << std::endl; } std::cout << "Generating voices with latent vector: ["; @@ -166,53 +166,51 @@ void NeuralDX7PatchGeneratorProcessor::generateAndSendMidi() } std::cout << "]" << std::endl; - auto voices = neuralModel.generateVoices(latentVector); - if (voices.empty()) { - std::cout << "No voices generated!" << std::endl; - return; - } - - std::cout << "Generated " << voices.size() << " voices, sending first voice as single voice SysEx" << std::endl; - - // For customise functionality, send just the first voice as a single voice SysEx - auto sysexData = DX7VoicePacker::packSingleVoice(voices[0]); - if (!sysexData.empty()) { - std::cout << "Packed single voice SysEx data: " << sysexData.size() << " bytes" << std::endl; - addMidiSysEx(sysexData); - } else { - std::cout << "Failed to pack single voice SysEx data!" << std::endl; - } + // Request custom voices from inference engine + inferenceEngine->requestCustomVoices(latentVector, [this](std::vector voices) { + if (voices.empty()) { + std::cout << "No voices generated!" << std::endl; + return; + } + + std::cout << "Generated " << voices.size() << " voices, sending first voice as single voice SysEx" << std::endl; + + // For customise functionality, send just the first voice as a single voice SysEx + auto sysexData = DX7VoicePacker::packSingleVoice(voices[0]); + if (!sysexData.empty()) { + std::cout << "Packed single voice SysEx data: " << sysexData.size() << " bytes" << std::endl; + addMidiSysEx(sysexData); + } else { + std::cout << "Failed to pack single voice SysEx data!" << std::endl; + } + }); } void NeuralDX7PatchGeneratorProcessor::generateRandomVoicesAndSend() { std::cout << "generateRandomVoicesAndSend() called" << std::endl; - if (!neuralModel.isModelLoaded()) { - std::cout << "Neural model not loaded, attempting to load..." << std::endl; - if (!neuralModel.loadModelFromFile()) { - std::cout << "Failed to load neural model!" << std::endl; + // Try to use buffered voices first for instant response + if (inferenceEngine->hasBufferedRandomVoices()) { + std::cout << "Using buffered random voices for instant response" << std::endl; + auto voices = inferenceEngine->getBufferedRandomVoices(); + + if (!voices.empty()) { + std::cout << "Got " << voices.size() << " buffered voices, packing into SysEx..." << std::endl; + auto sysexData = DX7BulkPacker::packBulkDump(voices); + + if (!sysexData.empty()) { + std::cout << "SysEx data packed successfully, sending..." << std::endl; + addMidiSysEx(sysexData); + } else { + std::cout << "Failed to pack SysEx data!" << std::endl; + } return; } } - std::cout << "Generating 32 random voices..." << std::endl; - auto voices = neuralModel.generateMultipleRandomVoices(); - - if (voices.empty()) { - std::cout << "Failed to generate voices!" << std::endl; - return; - } - - std::cout << "Generated " << voices.size() << " voices, packing into SysEx..." << std::endl; - auto sysexData = DX7BulkPacker::packBulkDump(voices); - - if (!sysexData.empty()) { - std::cout << "SysEx data packed successfully, sending..." << std::endl; - addMidiSysEx(sysexData); - } else { - std::cout << "Failed to pack SysEx data!" << std::endl; - } + // If no buffer available, do nothing to prevent weird behavior on rapid clicks + std::cout << "No buffered voices available, ignoring request" << std::endl; } void NeuralDX7PatchGeneratorProcessor::setLatentValues(const std::vector& values) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 15ccb3f..8d5f412 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -4,6 +4,7 @@ #include #include "NeuralModelWrapper.h" #include "DX7VoicePacker.h" +#include "ThreadedInferenceEngine.h" class NeuralDX7PatchGeneratorProcessor : public juce::AudioProcessor { @@ -44,7 +45,7 @@ class NeuralDX7PatchGeneratorProcessor : public juce::AudioProcessor void setLatentValues(const std::vector& values); private: - NeuralModelWrapper neuralModel; + std::unique_ptr inferenceEngine; std::vector latentVector; juce::Random random; juce::MidiBuffer pendingMidiMessages; diff --git a/Source/ThreadedInferenceEngine.cpp b/Source/ThreadedInferenceEngine.cpp new file mode 100644 index 0000000..8174011 --- /dev/null +++ b/Source/ThreadedInferenceEngine.cpp @@ -0,0 +1,202 @@ +#include "ThreadedInferenceEngine.h" +#include + +ThreadedInferenceEngine::ThreadedInferenceEngine() + : juce::Thread("InferenceEngine") +{ + neuralModel = std::make_unique(); +} + +ThreadedInferenceEngine::~ThreadedInferenceEngine() +{ + stopInferenceThread(); +} + +void ThreadedInferenceEngine::startInferenceThread() +{ + if (!isThreadRunning()) + { + shouldStop.store(false); + startThread(juce::Thread::Priority::high); + std::cout << "ThreadedInferenceEngine: Started inference thread" << std::endl; + + // Don't pre-generate buffer here - let the thread handle it after model loads + } +} + +void ThreadedInferenceEngine::stopInferenceThread() +{ + if (isThreadRunning()) + { + shouldStop.store(true); + + // Wake up the thread + { + std::unique_lock lock(requestMutex); + requestCondition.notify_all(); + } + + // Wait for thread to finish + stopThread(2000); // 2 second timeout + std::cout << "ThreadedInferenceEngine: Stopped inference thread" << std::endl; + } +} + +void ThreadedInferenceEngine::run() +{ + std::cout << "ThreadedInferenceEngine: Thread started, loading model..." << std::endl; + + // Load model in background thread + if (neuralModel->loadModelFromFile()) + { + modelLoaded.store(true); + std::cout << "ThreadedInferenceEngine: Model loaded successfully" << std::endl; + + // Now that model is loaded, generate initial buffer + preGenerateRandomVoices(); + } + else + { + std::cout << "ThreadedInferenceEngine: Failed to load model" << std::endl; + return; + } + + // Main inference loop + while (!shouldStop.load()) + { + processInferenceRequests(); + + // Wait for new requests or stop signal + std::unique_lock lock(requestMutex); + requestCondition.wait_for(lock, std::chrono::milliseconds(100), [this]() { + return !requestQueue.empty() || shouldStop.load(); + }); + } + + std::cout << "ThreadedInferenceEngine: Thread exiting" << std::endl; +} + +void ThreadedInferenceEngine::processInferenceRequests() +{ + std::queue localQueue; + + // Copy requests to local queue to minimize lock time + { + std::unique_lock lock(requestMutex); + localQueue.swap(requestQueue); + } + + // Process all requests + while (!localQueue.empty() && !shouldStop.load()) + { + processInferenceRequest(localQueue.front()); + localQueue.pop(); + } +} + +void ThreadedInferenceEngine::processInferenceRequest(const InferenceRequest& request) +{ + if (!modelLoaded.load()) + { + std::cout << "ThreadedInferenceEngine: Model not loaded, skipping request" << std::endl; + return; + } + + std::vector voices; + + try + { + switch (request.type) + { + case InferenceRequest::RANDOM_VOICES: + { + std::cout << "ThreadedInferenceEngine: Processing random voices request" << std::endl; + voices = neuralModel->generateMultipleRandomVoices(); + + // Update buffer with new voices for next time + if (!voices.empty()) + { + std::unique_lock lock(bufferMutex); + bufferedRandomVoices = voices; + hasBufferedVoices.store(true); + isGeneratingBuffer.store(false); + } + break; + } + + case InferenceRequest::CUSTOM_VOICES: + { + std::cout << "ThreadedInferenceEngine: Processing custom voices request" << std::endl; + voices = neuralModel->generateVoices(request.latentVector); + break; + } + } + + // Call callback on main thread + if (request.callback && !voices.empty()) + { + juce::MessageManager::callAsync([callback = request.callback, voices]() { + callback(voices); + }); + } + } + catch (const std::exception& e) + { + std::cerr << "ThreadedInferenceEngine: Error processing request: " << e.what() << std::endl; + } +} + +void ThreadedInferenceEngine::requestRandomVoices(std::function)> callback) +{ + std::unique_lock lock(requestMutex); + requestQueue.emplace(InferenceRequest::RANDOM_VOICES, callback); + requestCondition.notify_one(); +} + +void ThreadedInferenceEngine::requestCustomVoices(const std::vector& latentVector, std::function)> callback) +{ + std::unique_lock lock(requestMutex); + requestQueue.emplace(InferenceRequest::CUSTOM_VOICES, latentVector, callback); + requestCondition.notify_one(); +} + +bool ThreadedInferenceEngine::hasBufferedRandomVoices() const +{ + return hasBufferedVoices.load(); +} + +std::vector ThreadedInferenceEngine::getBufferedRandomVoices() +{ + std::unique_lock lock(bufferMutex); + if (hasBufferedVoices.load()) + { + auto voices = bufferedRandomVoices; + hasBufferedVoices.store(false); + + // Trigger generation of new buffer + preGenerateRandomVoices(); + + return voices; + } + + return {}; +} + +void ThreadedInferenceEngine::preGenerateRandomVoices() +{ + if (!isGeneratingBuffer.load()) + { + isGeneratingBuffer.store(true); + + // Request new random voices for the buffer + requestRandomVoices([this](std::vector voices) { + // Callback is handled in processInferenceRequest + std::cout << "ThreadedInferenceEngine: Buffer regenerated with " << voices.size() << " voices" << std::endl; + }); + } +} + +bool ThreadedInferenceEngine::isModelLoaded() const +{ + return modelLoaded.load(); +} \ No newline at end of file diff --git a/Source/ThreadedInferenceEngine.h b/Source/ThreadedInferenceEngine.h new file mode 100644 index 0000000..ff79126 --- /dev/null +++ b/Source/ThreadedInferenceEngine.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "NeuralModelWrapper.h" +#include "DX7Voice.h" + +class ThreadedInferenceEngine : public juce::Thread +{ +public: + struct InferenceRequest + { + enum Type { RANDOM_VOICES, CUSTOM_VOICES }; + Type type; + std::vector latentVector; + std::function)> callback; + + InferenceRequest(Type t, std::function)> cb) + : type(t), callback(cb) {} + + InferenceRequest(Type t, const std::vector& latent, std::function)> cb) + : type(t), latentVector(latent), callback(cb) {} + }; + + ThreadedInferenceEngine(); + ~ThreadedInferenceEngine() override; + + // Thread management + void startInferenceThread(); + void stopInferenceThread(); + + // Request inference + void requestRandomVoices(std::function)> callback); + void requestCustomVoices(const std::vector& latentVector, std::function)> callback); + + // Double buffer management + bool hasBufferedRandomVoices() const; + std::vector getBufferedRandomVoices(); + void preGenerateRandomVoices(); + + // Thread safety + bool isModelLoaded() const; + +private: + void run() override; + void processInferenceRequests(); + void processInferenceRequest(const InferenceRequest& request); + + // Neural model wrapper + std::unique_ptr neuralModel; + + // Thread synchronization + std::mutex requestMutex; + std::condition_variable requestCondition; + std::queue requestQueue; + std::atomic shouldStop{false}; + + // Double buffer for random voices + std::mutex bufferMutex; + std::vector bufferedRandomVoices; + std::atomic hasBufferedVoices{false}; + std::atomic isGeneratingBuffer{false}; + + // Model loading state + std::atomic modelLoaded{false}; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ThreadedInferenceEngine) +}; \ No newline at end of file From d51bb54ba95e4bd0fb22edb79c770d454c4bd930 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sat, 5 Jul 2025 21:26:59 +1000 Subject: [PATCH 03/10] fix customise latency issues --- Source/NeuralModelWrapper.cpp | 35 +++--- Source/PluginProcessor.cpp | 58 ++++++++-- Source/PluginProcessor.h | 4 + Source/ThreadedInferenceEngine.cpp | 173 ++++++++++++++++++++++++++++- Source/ThreadedInferenceEngine.h | 33 +++++- 5 files changed, 267 insertions(+), 36 deletions(-) diff --git a/Source/NeuralModelWrapper.cpp b/Source/NeuralModelWrapper.cpp index 3a83372..87c8e68 100644 --- a/Source/NeuralModelWrapper.cpp +++ b/Source/NeuralModelWrapper.cpp @@ -85,13 +85,13 @@ std::vector NeuralModelWrapper::generateVoices(const std::vector inputs; @@ -99,11 +99,14 @@ std::vector NeuralModelWrapper::generateVoices(const std::vector voices; - voices.reserve(N_VOICES); + voices.reserve(numVoices); - for (int i = 0; i < N_VOICES; ++i) { + for (int i = 0; i < numVoices; ++i) { torch::Tensor voiceLogits = logits[i]; auto parameters = DX7Voice::logitsToParameters(voiceLogits); voices.push_back(DX7Voice::fromParameters(parameters)); @@ -146,26 +149,14 @@ std::vector NeuralModelWrapper::generateMultipleRandomVoices() std::mt19937 gen(rd()); std::normal_distribution dis(0.0f, 1.0f); - // Create tensor with different random latent vectors for each voice - torch::Tensor z = torch::randn({N_VOICES, LATENT_DIM}); - - // Generate parameters using the model - std::vector inputs; - inputs.push_back(z); - - torch::Tensor logits = model.forward(inputs).toTensor(); - - // Convert logits to parameters and then to voices - std::vector voices; - voices.reserve(N_VOICES); + // Create batched latent vectors for multiple voices + std::vector batchedLatent(N_VOICES * LATENT_DIM); - for (int i = 0; i < N_VOICES; ++i) { - torch::Tensor voiceLogits = logits[i]; - auto parameters = DX7Voice::logitsToParameters(voiceLogits); - voices.push_back(DX7Voice::fromParameters(parameters)); + for (int i = 0; i < N_VOICES * LATENT_DIM; ++i) { + batchedLatent[i] = dis(gen); } - return voices; + return generateVoices(batchedLatent); } catch (const std::exception& e) { std::cerr << "Error generating multiple random voices: " << e.what() << "\n"; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 3f5af18..2320e78 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -3,6 +3,28 @@ #include "DX7BulkPacker.h" #include "DX7VoicePacker.h" +// Simple debounce timer class +class DebounceTimer : public juce::Timer +{ +public: + DebounceTimer(std::function callback) : callback_(callback) {} + + void timerCallback() override + { + stopTimer(); + if (callback_) + callback_(); + } + + void debounce(int milliseconds) + { + startTimer(milliseconds); + } + +private: + std::function callback_; +}; + NeuralDX7PatchGeneratorProcessor::NeuralDX7PatchGeneratorProcessor() : AudioProcessor (BusesProperties()) @@ -10,6 +32,12 @@ NeuralDX7PatchGeneratorProcessor::NeuralDX7PatchGeneratorProcessor() latentVector.resize(NeuralModelWrapper::LATENT_DIM, 0.0f); inferenceEngine = std::make_unique(); inferenceEngine->startInferenceThread(); + + // Create debounce timer for slider changes + debounceTimer = std::make_unique([this]() { + // Pre-generate voice for current latent vector + inferenceEngine->preGenerateCustomVoice(latentVector); + }); } NeuralDX7PatchGeneratorProcessor::~NeuralDX7PatchGeneratorProcessor() @@ -156,27 +184,23 @@ void NeuralDX7PatchGeneratorProcessor::generateAndSendMidi() std::cout << "generateAndSendMidi() called" << std::endl; if (!inferenceEngine->isModelLoaded()) { - std::cout << "Neural model not loaded yet, request queued" << std::endl; + std::cout << "Neural model not loaded yet, request ignored" << std::endl; + return; } - std::cout << "Generating voices with latent vector: ["; + std::cout << "Generating voice with latent vector: ["; for (size_t i = 0; i < latentVector.size(); ++i) { std::cout << latentVector[i]; if (i < latentVector.size() - 1) std::cout << ", "; } std::cout << "]" << std::endl; - // Request custom voices from inference engine - inferenceEngine->requestCustomVoices(latentVector, [this](std::vector voices) { - if (voices.empty()) { - std::cout << "No voices generated!" << std::endl; - return; - } - - std::cout << "Generated " << voices.size() << " voices, sending first voice as single voice SysEx" << std::endl; + // Use cached request for instant response if available + inferenceEngine->requestCachedCustomVoice(latentVector, [this](DX7Voice voice) { + std::cout << "Got custom voice, sending as single voice SysEx" << std::endl; - // For customise functionality, send just the first voice as a single voice SysEx - auto sysexData = DX7VoicePacker::packSingleVoice(voices[0]); + // For customise functionality, send as single voice SysEx + auto sysexData = DX7VoicePacker::packSingleVoice(voice); if (!sysexData.empty()) { std::cout << "Packed single voice SysEx data: " << sysexData.size() << " bytes" << std::endl; addMidiSysEx(sysexData); @@ -217,6 +241,16 @@ void NeuralDX7PatchGeneratorProcessor::setLatentValues(const std::vector& { if (values.size() == NeuralModelWrapper::LATENT_DIM) { latentVector = values; + // Trigger debounced pre-generation + debouncedPreGeneration(); + } +} + +void NeuralDX7PatchGeneratorProcessor::debouncedPreGeneration() +{ + // Debounce slider changes with 150ms delay + if (debounceTimer) { + static_cast(debounceTimer.get())->debounce(150); } } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 8d5f412..2391ce3 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -43,6 +43,7 @@ class NeuralDX7PatchGeneratorProcessor : public juce::AudioProcessor void generateAndSendMidi(); void generateRandomVoicesAndSend(); void setLatentValues(const std::vector& values); + void debouncedPreGeneration(); // For slider changes private: std::unique_ptr inferenceEngine; @@ -50,6 +51,9 @@ class NeuralDX7PatchGeneratorProcessor : public juce::AudioProcessor juce::Random random; juce::MidiBuffer pendingMidiMessages; + // Debouncing for slider changes + std::unique_ptr debounceTimer; + void addMidiSysEx(const std::vector& sysexData); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralDX7PatchGeneratorProcessor) diff --git a/Source/ThreadedInferenceEngine.cpp b/Source/ThreadedInferenceEngine.cpp index 8174011..b62131d 100644 --- a/Source/ThreadedInferenceEngine.cpp +++ b/Source/ThreadedInferenceEngine.cpp @@ -130,9 +130,24 @@ void ThreadedInferenceEngine::processInferenceRequest(const InferenceRequest& re voices = neuralModel->generateVoices(request.latentVector); break; } + + case InferenceRequest::SINGLE_CUSTOM_VOICE: + { + std::cout << "ThreadedInferenceEngine: Processing single custom voice request" << std::endl; + voices = neuralModel->generateVoices(request.latentVector); + + // Call single voice callback with first voice + if (request.singleCallback && !voices.empty()) + { + juce::MessageManager::callAsync([callback = request.singleCallback, voice = voices[0]]() { + callback(voice); + }); + } + return; + } } - // Call callback on main thread + // Call multi-voice callback on main thread if (request.callback && !voices.empty()) { juce::MessageManager::callAsync([callback = request.callback, voices]() { @@ -160,6 +175,13 @@ void ThreadedInferenceEngine::requestCustomVoices(const std::vector& late requestCondition.notify_one(); } +void ThreadedInferenceEngine::requestSingleCustomVoice(const std::vector& latentVector, std::function callback) +{ + std::unique_lock lock(requestMutex); + requestQueue.emplace(InferenceRequest::SINGLE_CUSTOM_VOICE, latentVector, callback); + requestCondition.notify_one(); +} + bool ThreadedInferenceEngine::hasBufferedRandomVoices() const { return hasBufferedVoices.load(); @@ -196,6 +218,155 @@ void ThreadedInferenceEngine::preGenerateRandomVoices() } } +std::string ThreadedInferenceEngine::latentVectorToKey(const std::vector& latentVector) const +{ + std::string key; + key.reserve(latentVector.size() * 8); // Rough estimate for string size + + for (float value : latentVector) + { + // Round to 3 decimal places to create reasonable cache keys + int rounded = static_cast(value * 1000.0f); + key += std::to_string(rounded) + ","; + } + + return key; +} + +void ThreadedInferenceEngine::addToCache(const std::vector& latentVector, const DX7Voice& voice) +{ + std::unique_lock lock(cacheMutex); + + std::string key = latentVectorToKey(latentVector); + + // If cache is full, evict oldest entry + if (voiceCache.size() >= MAX_CACHE_SIZE) + { + evictOldestFromCache(); + } + + // Add new entry + voiceCache.emplace(key, voice); + cacheOrder.push(key); + + std::cout << "ThreadedInferenceEngine: Added voice to cache (size: " << voiceCache.size() << ")" << std::endl; +} + +void ThreadedInferenceEngine::evictOldestFromCache() +{ + if (!cacheOrder.empty()) + { + std::string oldestKey = cacheOrder.front(); + cacheOrder.pop(); + voiceCache.erase(oldestKey); + std::cout << "ThreadedInferenceEngine: Evicted oldest cache entry" << std::endl; + } +} + +bool ThreadedInferenceEngine::hasCachedVoice(const std::vector& latentVector) const +{ + std::unique_lock lock(cacheMutex); + std::string key = latentVectorToKey(latentVector); + return voiceCache.find(key) != voiceCache.end(); +} + +DX7Voice ThreadedInferenceEngine::getCachedVoice(const std::vector& latentVector) const +{ + std::unique_lock lock(cacheMutex); + std::string key = latentVectorToKey(latentVector); + auto it = voiceCache.find(key); + + if (it != voiceCache.end()) + { + std::cout << "ThreadedInferenceEngine: Cache hit for custom voice" << std::endl; + return it->second; + } + + std::cerr << "ThreadedInferenceEngine: Warning - requested voice not found in cache" << std::endl; + // Return voice with default oscillators and global data + std::array, 6> defaultOsc{}; + std::array defaultGlobal{}; + return DX7Voice(defaultOsc, defaultGlobal); +} + +void ThreadedInferenceEngine::requestCachedCustomVoice(const std::vector& latentVector, std::function callback) +{ + // Check cache first + if (hasCachedVoice(latentVector)) + { + DX7Voice cachedVoice = getCachedVoice(latentVector); + juce::MessageManager::callAsync([callback, cachedVoice]() { + callback(cachedVoice); + }); + return; + } + + // Not in cache, generate and cache + requestSingleCustomVoice(latentVector, [this, latentVector, callback](DX7Voice voice) { + // Add to cache + addToCache(latentVector, voice); + + // Call original callback + callback(voice); + }); +} + +void ThreadedInferenceEngine::preGenerateCustomVoice(const std::vector& latentVector) +{ + // Only generate if not already cached + if (hasCachedVoice(latentVector)) + { + return; + } + + std::unique_lock lock(scheduleMutex); + + // If no request is currently inflight, start one immediately + if (!isPreGenerating.load()) + { + isPreGenerating.store(true); + lock.unlock(); // Release lock before making request + + std::cout << "ThreadedInferenceEngine: Pre-generating custom voice for cache" << std::endl; + requestSingleCustomVoice(latentVector, [this, latentVector](DX7Voice voice) { + // Add to cache for future use + addToCache(latentVector, voice); + + // Check if there's a scheduled request to process + std::unique_lock scheduleLock(scheduleMutex); + if (scheduledRequest.has_value()) + { + auto nextRequest = std::move(scheduledRequest.value()); + scheduledRequest.reset(); + scheduleLock.unlock(); + + std::cout << "ThreadedInferenceEngine: Processing scheduled request" << std::endl; + // Process the scheduled request + requestSingleCustomVoice(nextRequest.latentVector, [this, nextRequest](DX7Voice scheduledVoice) { + addToCache(nextRequest.latentVector, scheduledVoice); + isPreGenerating.store(false); // Allow new pre-generation requests + std::cout << "ThreadedInferenceEngine: Scheduled request completed" << std::endl; + }); + } + else + { + isPreGenerating.store(false); // Allow new pre-generation requests + std::cout << "ThreadedInferenceEngine: Pre-generated voice cached" << std::endl; + } + }); + } + else + { + // Request is inflight, schedule this one (overwriting any previous scheduled request) + if (scheduledRequest.has_value()) { + std::cout << "ThreadedInferenceEngine: Overwriting previously scheduled request" << std::endl; + } else { + std::cout << "ThreadedInferenceEngine: Scheduling request to run after current completes" << std::endl; + } + scheduledRequest = InferenceRequest(InferenceRequest::SINGLE_CUSTOM_VOICE, latentVector, [](DX7Voice){}); + } +} + bool ThreadedInferenceEngine::isModelLoaded() const { return modelLoaded.load(); diff --git a/Source/ThreadedInferenceEngine.h b/Source/ThreadedInferenceEngine.h index ff79126..4421f71 100644 --- a/Source/ThreadedInferenceEngine.h +++ b/Source/ThreadedInferenceEngine.h @@ -8,6 +8,10 @@ #include #include #include +#include +#include +#include +#include #include "NeuralModelWrapper.h" #include "DX7Voice.h" @@ -16,16 +20,20 @@ class ThreadedInferenceEngine : public juce::Thread public: struct InferenceRequest { - enum Type { RANDOM_VOICES, CUSTOM_VOICES }; + enum Type { RANDOM_VOICES, CUSTOM_VOICES, SINGLE_CUSTOM_VOICE }; Type type; std::vector latentVector; std::function)> callback; + std::function singleCallback; InferenceRequest(Type t, std::function)> cb) : type(t), callback(cb) {} InferenceRequest(Type t, const std::vector& latent, std::function)> cb) : type(t), latentVector(latent), callback(cb) {} + + InferenceRequest(Type t, const std::vector& latent, std::function cb) + : type(t), latentVector(latent), singleCallback(cb) {} }; ThreadedInferenceEngine(); @@ -38,12 +46,19 @@ class ThreadedInferenceEngine : public juce::Thread // Request inference void requestRandomVoices(std::function)> callback); void requestCustomVoices(const std::vector& latentVector, std::function)> callback); + void requestSingleCustomVoice(const std::vector& latentVector, std::function callback); // Double buffer management bool hasBufferedRandomVoices() const; std::vector getBufferedRandomVoices(); void preGenerateRandomVoices(); + // Custom voice caching + bool hasCachedVoice(const std::vector& latentVector) const; + DX7Voice getCachedVoice(const std::vector& latentVector) const; + void requestCachedCustomVoice(const std::vector& latentVector, std::function callback); + void preGenerateCustomVoice(const std::vector& latentVector); // For debounced pre-generation + // Thread safety bool isModelLoaded() const; @@ -67,6 +82,22 @@ class ThreadedInferenceEngine : public juce::Thread std::atomic hasBufferedVoices{false}; std::atomic isGeneratingBuffer{false}; + // Custom voice caching + static constexpr size_t MAX_CACHE_SIZE = 1000; + mutable std::mutex cacheMutex; + std::unordered_map voiceCache; + std::queue cacheOrder; // For LRU eviction + std::atomic isPreGenerating{false}; // Prevent multiple inflight cache fills + + // Request scheduling for inflight handling + std::mutex scheduleMutex; + std::optional scheduledRequest; // Next request to run after current completes + + // Helper methods + std::string latentVectorToKey(const std::vector& latentVector) const; + void addToCache(const std::vector& latentVector, const DX7Voice& voice); + void evictOldestFromCache(); + // Model loading state std::atomic modelLoaded{false}; From be903b10545e24dbd38bce6d4b03a73e48a4be5e Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sun, 6 Jul 2025 12:40:19 +1000 Subject: [PATCH 04/10] fix issue where sometimes uninitialised patches would be sent --- Source/PluginProcessor.cpp | 9 +++-- Source/ThreadedInferenceEngine.cpp | 53 +++++++++++++++++------------- Source/ThreadedInferenceEngine.h | 10 +++--- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2320e78..7aa9771 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -196,11 +196,16 @@ void NeuralDX7PatchGeneratorProcessor::generateAndSendMidi() std::cout << "]" << std::endl; // Use cached request for instant response if available - inferenceEngine->requestCachedCustomVoice(latentVector, [this](DX7Voice voice) { + inferenceEngine->requestCachedCustomVoice(latentVector, [this](std::optional voiceOpt) { + if (!voiceOpt.has_value()) { + std::cout << "Warning: Attempted to send null voice - ignoring request" << std::endl; + return; + } + std::cout << "Got custom voice, sending as single voice SysEx" << std::endl; // For customise functionality, send as single voice SysEx - auto sysexData = DX7VoicePacker::packSingleVoice(voice); + auto sysexData = DX7VoicePacker::packSingleVoice(voiceOpt.value()); if (!sysexData.empty()) { std::cout << "Packed single voice SysEx data: " << sysexData.size() << " bytes" << std::endl; addMidiSysEx(sysexData); diff --git a/Source/ThreadedInferenceEngine.cpp b/Source/ThreadedInferenceEngine.cpp index b62131d..6cb6ecd 100644 --- a/Source/ThreadedInferenceEngine.cpp +++ b/Source/ThreadedInferenceEngine.cpp @@ -136,11 +136,12 @@ void ThreadedInferenceEngine::processInferenceRequest(const InferenceRequest& re std::cout << "ThreadedInferenceEngine: Processing single custom voice request" << std::endl; voices = neuralModel->generateVoices(request.latentVector); - // Call single voice callback with first voice - if (request.singleCallback && !voices.empty()) + // Call single voice callback with first voice (or nullopt if empty) + if (request.singleCallback) { - juce::MessageManager::callAsync([callback = request.singleCallback, voice = voices[0]]() { - callback(voice); + std::optional voiceOpt = voices.empty() ? std::nullopt : std::make_optional(voices[0]); + juce::MessageManager::callAsync([callback = request.singleCallback, voiceOpt]() { + callback(voiceOpt); }); } return; @@ -175,7 +176,7 @@ void ThreadedInferenceEngine::requestCustomVoices(const std::vector& late requestCondition.notify_one(); } -void ThreadedInferenceEngine::requestSingleCustomVoice(const std::vector& latentVector, std::function callback) +void ThreadedInferenceEngine::requestSingleCustomVoice(const std::vector& latentVector, std::function)> callback) { std::unique_lock lock(requestMutex); requestQueue.emplace(InferenceRequest::SINGLE_CUSTOM_VOICE, latentVector, callback); @@ -270,7 +271,7 @@ bool ThreadedInferenceEngine::hasCachedVoice(const std::vector& latentVec return voiceCache.find(key) != voiceCache.end(); } -DX7Voice ThreadedInferenceEngine::getCachedVoice(const std::vector& latentVector) const +std::optional ThreadedInferenceEngine::getCachedVoice(const std::vector& latentVector) const { std::unique_lock lock(cacheMutex); std::string key = latentVectorToKey(latentVector); @@ -282,19 +283,16 @@ DX7Voice ThreadedInferenceEngine::getCachedVoice(const std::vector& laten return it->second; } - std::cerr << "ThreadedInferenceEngine: Warning - requested voice not found in cache" << std::endl; - // Return voice with default oscillators and global data - std::array, 6> defaultOsc{}; - std::array defaultGlobal{}; - return DX7Voice(defaultOsc, defaultGlobal); + std::cout << "ThreadedInferenceEngine: Voice not found in cache, returning null" << std::endl; + return std::nullopt; } -void ThreadedInferenceEngine::requestCachedCustomVoice(const std::vector& latentVector, std::function callback) +void ThreadedInferenceEngine::requestCachedCustomVoice(const std::vector& latentVector, std::function)> callback) { // Check cache first if (hasCachedVoice(latentVector)) { - DX7Voice cachedVoice = getCachedVoice(latentVector); + std::optional cachedVoice = getCachedVoice(latentVector); juce::MessageManager::callAsync([callback, cachedVoice]() { callback(cachedVoice); }); @@ -302,12 +300,15 @@ void ThreadedInferenceEngine::requestCachedCustomVoice(const std::vector& } // Not in cache, generate and cache - requestSingleCustomVoice(latentVector, [this, latentVector, callback](DX7Voice voice) { - // Add to cache - addToCache(latentVector, voice); + requestSingleCustomVoice(latentVector, [this, latentVector, callback](std::optional voiceOpt) { + // Add to cache if voice was generated successfully + if (voiceOpt.has_value()) + { + addToCache(latentVector, voiceOpt.value()); + } // Call original callback - callback(voice); + callback(voiceOpt); }); } @@ -328,9 +329,12 @@ void ThreadedInferenceEngine::preGenerateCustomVoice(const std::vector& l lock.unlock(); // Release lock before making request std::cout << "ThreadedInferenceEngine: Pre-generating custom voice for cache" << std::endl; - requestSingleCustomVoice(latentVector, [this, latentVector](DX7Voice voice) { - // Add to cache for future use - addToCache(latentVector, voice); + requestSingleCustomVoice(latentVector, [this, latentVector](std::optional voiceOpt) { + // Add to cache for future use if voice was generated + if (voiceOpt.has_value()) + { + addToCache(latentVector, voiceOpt.value()); + } // Check if there's a scheduled request to process std::unique_lock scheduleLock(scheduleMutex); @@ -342,8 +346,11 @@ void ThreadedInferenceEngine::preGenerateCustomVoice(const std::vector& l std::cout << "ThreadedInferenceEngine: Processing scheduled request" << std::endl; // Process the scheduled request - requestSingleCustomVoice(nextRequest.latentVector, [this, nextRequest](DX7Voice scheduledVoice) { - addToCache(nextRequest.latentVector, scheduledVoice); + requestSingleCustomVoice(nextRequest.latentVector, [this, nextRequest](std::optional scheduledVoiceOpt) { + if (scheduledVoiceOpt.has_value()) + { + addToCache(nextRequest.latentVector, scheduledVoiceOpt.value()); + } isPreGenerating.store(false); // Allow new pre-generation requests std::cout << "ThreadedInferenceEngine: Scheduled request completed" << std::endl; }); @@ -363,7 +370,7 @@ void ThreadedInferenceEngine::preGenerateCustomVoice(const std::vector& l } else { std::cout << "ThreadedInferenceEngine: Scheduling request to run after current completes" << std::endl; } - scheduledRequest = InferenceRequest(InferenceRequest::SINGLE_CUSTOM_VOICE, latentVector, [](DX7Voice){}); + scheduledRequest = InferenceRequest(InferenceRequest::SINGLE_CUSTOM_VOICE, latentVector, [](std::optional){}); } } diff --git a/Source/ThreadedInferenceEngine.h b/Source/ThreadedInferenceEngine.h index 4421f71..7190afb 100644 --- a/Source/ThreadedInferenceEngine.h +++ b/Source/ThreadedInferenceEngine.h @@ -24,7 +24,7 @@ class ThreadedInferenceEngine : public juce::Thread Type type; std::vector latentVector; std::function)> callback; - std::function singleCallback; + std::function)> singleCallback; InferenceRequest(Type t, std::function)> cb) : type(t), callback(cb) {} @@ -32,7 +32,7 @@ class ThreadedInferenceEngine : public juce::Thread InferenceRequest(Type t, const std::vector& latent, std::function)> cb) : type(t), latentVector(latent), callback(cb) {} - InferenceRequest(Type t, const std::vector& latent, std::function cb) + InferenceRequest(Type t, const std::vector& latent, std::function)> cb) : type(t), latentVector(latent), singleCallback(cb) {} }; @@ -46,7 +46,7 @@ class ThreadedInferenceEngine : public juce::Thread // Request inference void requestRandomVoices(std::function)> callback); void requestCustomVoices(const std::vector& latentVector, std::function)> callback); - void requestSingleCustomVoice(const std::vector& latentVector, std::function callback); + void requestSingleCustomVoice(const std::vector& latentVector, std::function)> callback); // Double buffer management bool hasBufferedRandomVoices() const; @@ -55,8 +55,8 @@ class ThreadedInferenceEngine : public juce::Thread // Custom voice caching bool hasCachedVoice(const std::vector& latentVector) const; - DX7Voice getCachedVoice(const std::vector& latentVector) const; - void requestCachedCustomVoice(const std::vector& latentVector, std::function callback); + std::optional getCachedVoice(const std::vector& latentVector) const; + void requestCachedCustomVoice(const std::vector& latentVector, std::function)> callback); void preGenerateCustomVoice(const std::vector& latentVector); // For debounced pre-generation // Thread safety From ca22d8e155a960145551cb47524a78c3e6f37c21 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sun, 6 Jul 2025 13:52:31 +1000 Subject: [PATCH 05/10] add lv2 plugin support --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c7a6b6..3c0fb7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,8 +66,10 @@ juce_add_plugin(NeuralDX7PatchGenerator COPY_PLUGIN_AFTER_BUILD TRUE PLUGIN_MANUFACTURER_CODE NinA PLUGIN_CODE NeuD - FORMATS Standalone VST3 AU + FORMATS Standalone VST3 LV2 AU VST3_CATEGORIES "Generator Tools" + LV2_CATEGORIES "GeneratorPlugin" + LV2URI "urn:nintoracaudio:neuraldx7patchgenerator" PRODUCT_NAME "NeuralDX7PatchGenerator") # Override standalone binary location to be in ${CMAKE_BUILD_TYPE}/ instead of ${CMAKE_BUILD_TYPE}/Standalone/ From aa1cb169fa10d25b7b8ab5177d665b83b415e152 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sun, 6 Jul 2025 13:52:45 +1000 Subject: [PATCH 06/10] fix midi syx handling issues for plugin releases --- Source/PluginProcessor.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 7aa9771..c85ed4f 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -263,12 +263,39 @@ void NeuralDX7PatchGeneratorProcessor::addMidiSysEx(const std::vector& { std::cout << "addMidiSysEx() called with " << sysexData.size() << " bytes" << std::endl; +#if JucePlugin_Build_LV2 || JucePlugin_Build_VST3 + // For LV2 and VST3, strip SysEx start (0xF0) and end (0xF7) bytes + if (sysexData.size() >= 2 && sysexData[0] == 0xF0 && sysexData[sysexData.size() - 1] == 0xF7) + { + std::cout << "LV2/VST3 plugin detected - stripping SysEx start/end bytes" << std::endl; + std::vector strippedData(sysexData.begin() + 1, sysexData.end() - 1); + + juce::MidiMessage sysexMessage = juce::MidiMessage::createSysExMessage( + strippedData.data(), static_cast(strippedData.size()) + ); + + pendingMidiMessages.addEvent(sysexMessage, 0); + std::cout << "Added stripped SysEx message to pending MIDI buffer" << std::endl; + } + else + { + std::cout << "SysEx data doesn't have expected start/end bytes, sending as-is" << std::endl; + juce::MidiMessage sysexMessage = juce::MidiMessage::createSysExMessage( + sysexData.data(), static_cast(sysexData.size()) + ); + + pendingMidiMessages.addEvent(sysexMessage, 0); + std::cout << "Added SysEx message to pending MIDI buffer" << std::endl; + } +#else + // For other plugin formats, send SysEx data as-is juce::MidiMessage sysexMessage = juce::MidiMessage::createSysExMessage( sysexData.data(), static_cast(sysexData.size()) ); pendingMidiMessages.addEvent(sysexMessage, 0); std::cout << "Added SysEx message to pending MIDI buffer" << std::endl; +#endif } juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() From ab5853c0f60dcfc8740b0b242604d6d84423aa3a Mon Sep 17 00:00:00 2001 From: Nintorac Date: Mon, 7 Jul 2025 21:18:41 +1000 Subject: [PATCH 07/10] show less in show build output --- .github/workflows/project-pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/project-pipeline.yml b/.github/workflows/project-pipeline.yml index 86ad88b..9ed0e11 100644 --- a/.github/workflows/project-pipeline.yml +++ b/.github/workflows/project-pipeline.yml @@ -47,7 +47,7 @@ jobs: run: make build - name: Show Build Output - run: ls -lR ${{github.workspace}}/build + run: ls -lR ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts - name: Upload Artifacts uses: actions/upload-artifact@v4 @@ -96,7 +96,7 @@ jobs: run: make build - name: Show Build Output - run: ls -lR ${{github.workspace}}/build + run: ls -lR ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts - name: Upload Artifacts uses: actions/upload-artifact@v4 @@ -149,7 +149,7 @@ jobs: - name: Show shell: cmd - run: dir /s ${{github.workspace}}\build + run: dir /s ${{github.workspace}}\build\NeuralDX7PatchGenerator_artefacts - name: Upload Artifacts uses: actions/upload-artifact@v4 From 273d44dc25ce5a33ef0160924fff007394f9d47b Mon Sep 17 00:00:00 2001 From: Nintorac Date: Tue, 22 Jul 2025 23:17:55 +1000 Subject: [PATCH 08/10] fix windows build issues and save all plugin artifacts --- .github/workflows/project-pipeline.yml | 4 ++++ CMakeLists.txt | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/project-pipeline.yml b/.github/workflows/project-pipeline.yml index 9ed0e11..65e8d1a 100644 --- a/.github/workflows/project-pipeline.yml +++ b/.github/workflows/project-pipeline.yml @@ -55,6 +55,7 @@ jobs: name: linux-bin path: | ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/NeuralDX7PatchGenerator + ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/VST3/ ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/lib/ !**/*.a @@ -105,6 +106,8 @@ jobs: path: | ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/NeuralDX7PatchGenerator.app ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/NeuralDX7PatchGenerator.component + ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/AU/ + ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/VST3/ build-windows: runs-on: windows-latest @@ -157,6 +160,7 @@ jobs: name: windows-bin path: | ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/NeuralDX7PatchGenerator.exe + ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/VST3/ ${{github.workspace}}/build/NeuralDX7PatchGenerator_artefacts/${{env.BUILD_TYPE}}/*.dll release: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c0fb7c..e738aad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,8 +66,9 @@ juce_add_plugin(NeuralDX7PatchGenerator COPY_PLUGIN_AFTER_BUILD TRUE PLUGIN_MANUFACTURER_CODE NinA PLUGIN_CODE NeuD - FORMATS Standalone VST3 LV2 AU + FORMATS Standalone VST3 AU VST3_CATEGORIES "Generator Tools" + VST3_AUTO_MANIFEST FALSE LV2_CATEGORIES "GeneratorPlugin" LV2URI "urn:nintoracaudio:neuraldx7patchgenerator" PRODUCT_NAME "NeuralDX7PatchGenerator") From 7904c7d4189ffcc59255459915ac78d7299c189a Mon Sep 17 00:00:00 2001 From: Nintorac Date: Tue, 22 Jul 2025 23:25:59 +1000 Subject: [PATCH 09/10] produce seperate archives for vst/au/standalone releases --- .github/workflows/project-pipeline.yml | 40 ++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/project-pipeline.yml b/.github/workflows/project-pipeline.yml index 65e8d1a..376985a 100644 --- a/.github/workflows/project-pipeline.yml +++ b/.github/workflows/project-pipeline.yml @@ -189,24 +189,52 @@ jobs: - name: Create release packages run: | - # Create Windows ZIP + # Create Windows Standalone ZIP if [ -d "./artifacts/windows-bin" ]; then cd artifacts/windows-bin - zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-windows.zip . + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-windows-standalone.zip NeuralDX7PatchGenerator.exe *.dll cd ../.. fi - # Create Linux ZIP + # Create Windows VST ZIP + if [ -d "./artifacts/windows-bin" ]; then + cd artifacts/windows-bin + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-windows-vst.zip VST3/ + cd ../.. + fi + + # Create Linux Standalone ZIP + if [ -d "./artifacts/linux-bin" ]; then + cd artifacts/linux-bin + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-linux-standalone.zip NeuralDX7PatchGenerator lib/ + cd ../.. + fi + + # Create Linux VST ZIP if [ -d "./artifacts/linux-bin" ]; then cd artifacts/linux-bin - zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-linux.zip . + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-linux-vst.zip VST3/ + cd ../.. + fi + + # Create macOS Standalone ZIP + if [ -d "./artifacts/macos-bin" ]; then + cd artifacts/macos-bin + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-macos-standalone.zip NeuralDX7PatchGenerator.app + cd ../.. + fi + + # Create macOS VST ZIP + if [ -d "./artifacts/macos-bin" ]; then + cd artifacts/macos-bin + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-macos-vst.zip VST3/ cd ../.. fi - # Create macOS ZIP + # Create macOS AU ZIP if [ -d "./artifacts/macos-bin" ]; then cd artifacts/macos-bin - zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-macos.zip . + zip -r ../../NeuralDX7PatchGenerator-${{ steps.extract_tag.outputs.tag }}-macos-au.zip AU/ cd ../.. fi From 82e4284c0f5c65775fd93402dca86667383e6a91 Mon Sep 17 00:00:00 2001 From: Nintorac Date: Sat, 25 Oct 2025 13:24:20 +1000 Subject: [PATCH 10/10] disable osx build --- .github/workflows/project-pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/project-pipeline.yml b/.github/workflows/project-pipeline.yml index 376985a..f5aa0c7 100644 --- a/.github/workflows/project-pipeline.yml +++ b/.github/workflows/project-pipeline.yml @@ -61,6 +61,7 @@ jobs: build-macos: runs-on: macos-latest + if: false steps: - uses: actions/checkout@v4