Skip to content

Commit dd33ab0

Browse files
committed
0.32.2
1 parent 4696e57 commit dd33ab0

35 files changed

Lines changed: 657 additions & 244 deletions

CMakePresets.json

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,18 @@
172172
"CMAKE_POSITION_INDEPENDENT_CODE": true
173173
}
174174
},
175+
{
176+
"name": "ubuntu-x86_64-debug",
177+
"inherits": [
178+
"ubuntu-x86_64"
179+
],
180+
"displayName": "Ubuntu x86_64 Debug",
181+
"description": "Local debug build for Ubuntu x86_64",
182+
"binaryDir": "${sourceDir}/build_x86_64_debug",
183+
"cacheVariables": {
184+
"CMAKE_BUILD_TYPE": "Debug"
185+
}
186+
},
175187
{
176188
"name": "ubuntu-ci-x86_64",
177189
"inherits": [
@@ -186,10 +198,17 @@
186198
{
187199
"name": "macos",
188200
"configurePreset": "macos",
189-
"displayName": "macOS Universal",
201+
"displayName": "macOS Universal Debug",
190202
"description": "macOS build for Universal architectures",
191203
"configuration": "Debug"
192204
},
205+
{
206+
"name": "macos-relwithdebinfo",
207+
"configurePreset": "macos",
208+
"displayName": "macOS Universal RelWithDebInfo",
209+
"description": "macOS build for Universal architectures (RelWithDebInfo configuration)",
210+
"configuration": "RelWithDebInfo"
211+
},
193212
{
194213
"name": "macos-ci",
195214
"configurePreset": "macos-ci",
@@ -204,6 +223,13 @@
204223
"description": "Windows build for x64",
205224
"configuration": "RelWithDebInfo"
206225
},
226+
{
227+
"name": "windows-x64-debug",
228+
"configurePreset": "windows-x64",
229+
"displayName": "Windows x64 Debug",
230+
"description": "Windows build for x64 (Debug configuration)",
231+
"configuration": "Debug"
232+
},
207233
{
208234
"name": "windows-x64-release",
209235
"configurePreset": "windows-x64",
@@ -239,6 +265,13 @@
239265
"description": "Ubuntu build for x86_64",
240266
"configuration": "RelWithDebInfo"
241267
},
268+
{
269+
"name": "ubuntu-x86_64-debug",
270+
"configurePreset": "ubuntu-x86_64-debug",
271+
"displayName": "Ubuntu x86_64 Debug",
272+
"description": "Ubuntu local debug build for x86_64",
273+
"configuration": "Debug"
274+
},
242275
{
243276
"name": "ubuntu-ci-x86_64",
244277
"configurePreset": "ubuntu-ci-x86_64",

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ git clone https://github.com/atkaudio/pluginforobsrelease
5757
cd pluginforobsrelease
5858
cmake --preset ubuntu-x86_64
5959
cmake --build --preset ubuntu-x86_64
60+
61+
# Local debug build
62+
cmake --preset ubuntu-x86_64-debug
63+
cmake --build --preset ubuntu-x86_64-debug
6064
```
6165

6266
Find `atkaudio-pluginforobs.so` and copy it to OBS plugins directory.

buildspec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@
4747
"uuids": {
4848
"windowsApp": "ad885c58-5ca9-44de-8f4f-1c12676626a9"
4949
},
50-
"version": "0.32.1",
50+
"version": "0.32.2",
5151
"website": "https://www.atkaudio.com"
5252
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Called from a post-build command to copy Qt6 debug DLLs for local Debug runs.
2+
# Variables expected:
3+
# CONFIG - build configuration (Debug/Release/RelWithDebInfo/...)
4+
# SRC_DIR - source directory containing Qt6 DLLs
5+
# DST_DIR - target output directory for copied DLLs
6+
7+
if(NOT CONFIG STREQUAL "Debug")
8+
return()
9+
endif()
10+
11+
file(GLOB _qt6_debug_dlls "${SRC_DIR}/Qt6*d.dll")
12+
13+
if(NOT _qt6_debug_dlls)
14+
message(WARNING "No Qt6 debug DLLs found under: ${SRC_DIR}")
15+
return()
16+
endif()
17+
18+
foreach(_src IN LISTS _qt6_debug_dlls)
19+
get_filename_component(_dll "${_src}" NAME)
20+
set(_dst "${DST_DIR}/${_dll}")
21+
22+
file(COPY_FILE "${_src}" "${_dst}" ONLY_IF_DIFFERENT)
23+
message(STATUS "Copied Qt6 debug DLL: ${_dll}")
24+
endforeach()

cmake/windows/helpers.cmake

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ function(set_target_properties_plugin target)
8484
VERBATIM
8585
)
8686

87+
# Copy Qt6 debug DLLs next to the plugin for local MSVC x64 Debug builds.
88+
# Skip this in CI environments where debug Qt runtime is not required.
89+
if(MSVC AND CMAKE_VS_PLATFORM_NAME STREQUAL "x64" AND NOT (DEFINED ENV{CI} OR DEFINED ENV{GITHUB_ACTIONS}))
90+
add_custom_command(
91+
TARGET ${target}
92+
POST_BUILD
93+
COMMAND
94+
"${CMAKE_COMMAND}"
95+
"-DCONFIG=$<CONFIG>"
96+
"-DSRC_DIR=${CMAKE_SOURCE_DIR}/_deps/x64/obs-deps-qt6-${QT6_VERSION}-x64/bin"
97+
"-DDST_DIR=$<TARGET_FILE_DIR:${target}>"
98+
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/copy_qt_debug_dlls.cmake"
99+
COMMENT "Copy Qt6 debug DLLs for Debug config"
100+
VERBATIM
101+
)
102+
endif()
103+
87104
target_install_resources(${target})
88105

89106
get_target_property(target_sources ${target} SOURCES)

src/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ target_compile_definitions(
4646
${PROJECT_NAME}
4747
PRIVATE
4848
JUCE_STRICT_REFCOUNTEDPOINTER=1
49-
JUCE_STANDALONE_APPLICATION=1
50-
JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1
5149
JUCE_WIN_PER_MONITOR_DPI_AWARE=1
5250
JUCE_PLUGINHOST_VST3=1
5351
JUCE_PLUGINHOST_VST=0

src/core/atkaudio.cpp

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
#include "core/atkaudio/atkaudio.h"
22

33
#include <atkaudio/AudioProcessorGraphMT/RealtimeThreadPool.h>
4-
#include <atkaudio/JuceApp.h>
54
#include <atkaudio/LookAndFeel.h>
6-
#include <atkaudio/MessagePump.h>
75
#include <atkaudio/ModuleInfrastructure/AudioServer/AudioServer.h>
86
#include <atkaudio/ModuleInfrastructure/MidiServer/MidiServer.h>
7+
#include <atkaudio/ObsJucePluginFormatLifecycle.h>
98
#include <atkaudio/UpdateCheck.h>
109

1110
#include <juce_audio_utils/juce_audio_utils.h>
@@ -18,31 +17,59 @@
1817
#include <QWindow>
1918
#endif
2019

20+
#include <obs-module.h>
2121
#include <obs-frontend-api.h>
2222

2323
UpdateCheck* updateCheck = nullptr;
24-
atk::MessagePump* g_messagePump = nullptr;
2524

2625
// Store Qt main window handle for per-instance parent creation (lazy-initialized)
2726
static void* g_qtMainWindowHandle = nullptr;
2827
static bool g_qtMainWindowInitialized = false;
2928

30-
void atk::create()
29+
static juce::File getFallbackSettingsFile(const juce::String& name)
3130
{
32-
// Set createInstance to make JUCE think this is a standalone app.
33-
// This enables per-monitor DPI awareness on Windows via setDPIAwareness().
34-
#ifndef JUCE_MAC
35-
juce::JUCEApplicationBase::createInstance = []() -> juce::JUCEApplicationBase* { return nullptr; };
36-
#endif
37-
juce::initialiseJuce_GUI();
31+
juce::PropertiesFile::Options opts;
32+
opts.applicationName = name;
33+
opts.filenameSuffix = "settings";
34+
opts.osxLibrarySubFolder = "Application Support";
35+
opts.folderName = "atkAudio Plugin";
36+
return opts.getDefaultFile();
37+
}
38+
39+
juce::File atk::getSettingsFile(const juce::String& name)
40+
{
41+
auto* module = obs_current_module();
42+
if (module == nullptr)
43+
return getFallbackSettingsFile(name);
44+
45+
auto filename = name + ".settings";
46+
char* obsPath = obs_module_get_config_path(module, filename.toRawUTF8());
47+
if (obsPath == nullptr)
48+
return getFallbackSettingsFile(name);
49+
50+
juce::File result(obsPath);
51+
bfree(obsPath);
52+
return result;
53+
}
3854

39-
juce::MessageManager::getInstance()->setCurrentThreadAsMessageThread();
55+
bool atk::create()
56+
{
57+
auto& lifecycle = atk::ObsJucePluginFormatLifecycle::getInstance();
58+
if (!lifecycle.initialize())
59+
{
60+
DBG("create: failed to initialize OBS JUCE format lifecycle");
61+
return false;
62+
}
4063

4164
// Initialize LookAndFeel singleton
4265
juce::SharedResourcePointer<atk::LookAndFeel> lookAndFeel;
4366

44-
// Sync OBS theme colors to JUCE LookAndFeel
67+
// Sync OBS theme colors to JUCE LookAndFeel.
68+
// Bypass only true Debug builds (_DEBUG without NDEBUG), but keep this enabled
69+
// in RelWithDebInfo where _DEBUG may still be defined by toolchain settings.
70+
#if !(defined(_DEBUG) && !defined(NDEBUG))
4571
getQtMainWindowHandle();
72+
#endif
4673

4774
// Initialize MIDI server
4875
if (auto* midiServer = atk::MidiServer::getInstance())
@@ -55,49 +82,40 @@ void atk::create()
5582
// Initialize RealtimeThreadPool synchronously so it's ready when filters are created
5683
if (auto* threadPool = atk::RealtimeThreadPool::getInstance())
5784
threadPool->initialize();
85+
86+
return true;
5887
}
5988

6089
void atk::pump()
6190
{
62-
#if JUCE_LINUX
63-
// On Linux, we need to pump the JUCE message loop
64-
// Use dispatchPendingMessages() instead of runDispatchLoopUntil() to avoid
65-
// conflicts with Qt's event loop - we just want to process pending JUCE messages
66-
// without polling/blocking for new ones
67-
if (auto* mm = juce::MessageManager::getInstanceWithoutCreating())
68-
{
69-
// Only dispatch if we're on the message thread
70-
if (mm->isThisTheMessageThread())
71-
{
72-
// Process any pending async callbacks
73-
mm->runDispatchLoopUntil(0); // 0ms = just process pending, don't wait
74-
}
75-
}
76-
#endif
91+
atk::ObsJucePluginFormatLifecycle::getInstance().pumpPendingMessages();
7792
}
7893

79-
void atk::startMessagePump(QObject* qtParent)
94+
bool atk::startMessagePump(QObject* qtParent)
8095
{
81-
#if JUCE_LINUX
82-
// On Linux, we need a Qt timer-based message pump for JUCE
83-
if (g_messagePump)
96+
auto& lifecycle = atk::ObsJucePluginFormatLifecycle::getInstance();
97+
if (!lifecycle.startMessagePump(qtParent))
8498
{
85-
DBG("startMessagePump: MessagePump already started");
86-
return;
99+
DBG("startMessagePump: failed to start OBS JUCE format message pump");
100+
return false;
87101
}
88102

89-
g_messagePump = new atk::MessagePump(qtParent);
90-
#else
91-
// On macOS and Windows, we don't need a message pump - JUCE integrates with native event loops
92-
(void)qtParent;
93-
#endif
103+
return true;
94104
}
95105

96-
void atk::destroy()
106+
bool atk::isReady()
97107
{
98-
juce::MessageManager::getInstance()->setCurrentThreadAsMessageThread();
108+
return atk::ObsJucePluginFormatLifecycle::getInstance().isReady();
109+
}
99110

100-
g_messagePump = nullptr;
111+
bool atk::isShuttingDown()
112+
{
113+
return atk::ObsJucePluginFormatLifecycle::getInstance().isShuttingDown();
114+
}
115+
116+
void atk::destroy()
117+
{
118+
auto& lifecycle = atk::ObsJucePluginFormatLifecycle::getInstance();
101119

102120
if (auto* midiServer = atk::MidiServer::getInstance())
103121
{
@@ -117,7 +135,7 @@ void atk::destroy()
117135
atk::RealtimeThreadPool::deleteInstance();
118136
}
119137

120-
juce::shutdownJuce_GUI();
138+
lifecycle.shutdown();
121139
}
122140

123141
void atk::update()

src/core/atkaudio/AudioProcessorGraphMT/AudioProcessorGraphMT.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,7 @@ class DelayLinePool
20712071
{
20722072
juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear> delayLine;
20732073
std::atomic_int delayAmount{0};
2074+
int preparedChannels = 0;
20742075
};
20752076

20762077
std::shared_ptr<PooledDelayLine>
@@ -2081,6 +2082,18 @@ class DelayLinePool
20812082
auto it = delayLines.find(key);
20822083
if (it != delayLines.end())
20832084
{
2085+
// Re-prepare if a graph rebuild now needs more channels than were originally prepared.
2086+
// Cached delay lines survive rebuilds to preserve delay state, but the prepared channel
2087+
// count must cover every destination channel index that will be routed through this line.
2088+
if (numChannels > it->second->preparedChannels)
2089+
{
2090+
it->second->delayLine.prepare(
2091+
juce::dsp::ProcessSpec{sampleRate, blockSize, static_cast<uint32>(numChannels)}
2092+
);
2093+
it->second->delayLine.reset();
2094+
it->second->delayLine.setMaximumDelayInSamples(MAX_DELAY_SAMPLES);
2095+
it->second->preparedChannels = numChannels;
2096+
}
20842097
it->second->delayAmount.store(delayNeeded);
20852098
return it->second;
20862099
}
@@ -2090,6 +2103,7 @@ class DelayLinePool
20902103
pooledLine->delayLine.reset();
20912104
pooledLine->delayLine.setMaximumDelayInSamples(MAX_DELAY_SAMPLES);
20922105
pooledLine->delayAmount.store(delayNeeded);
2106+
pooledLine->preparedChannels = numChannels;
20932107
delayLines[key] = pooledLine;
20942108
return pooledLine;
20952109
}
@@ -2163,6 +2177,16 @@ class ParallelRenderSequence
21632177

21642178
auto& pooledLine = it->second;
21652179
auto& delayLine = pooledLine->delayLine;
2180+
2181+
// Safety: if the caller asks for a channel beyond what the delay line was prepared for,
2182+
// skip delay compensation for this sample rather than triggering an OOB in JUCE.
2183+
// registerSource() should prepare enough channels; this guards against stale pool entries.
2184+
if (channel < 0 || channel >= pooledLine->preparedChannels)
2185+
{
2186+
FloatVectorOperations::add(dst, src, numSamples);
2187+
return;
2188+
}
2189+
21662190
int delay = pooledLine->delayAmount.load();
21672191

21682192
for (int i = 0; i < numSamples; ++i)
@@ -2736,7 +2760,7 @@ class ParallelRenderSequence
27362760
maxInputLatency,
27372761
s.sampleRate,
27382762
s.blockSize,
2739-
chain->sequence->getNumChannelsNeeded()
2763+
chain->getAudioBuffer().getNumChannels()
27402764
);
27412765
}
27422766

@@ -2748,7 +2772,7 @@ class ParallelRenderSequence
27482772
maxInputLatency,
27492773
s.sampleRate,
27502774
s.blockSize,
2751-
chain->sequence->getNumChannelsNeeded()
2775+
chain->getAudioBuffer().getNumChannels()
27522776
);
27532777
}
27542778
}

0 commit comments

Comments
 (0)