Skip to content

Commit 32c49b2

Browse files
committed
0.13.0
1 parent dfafd1a commit 32c49b2

File tree

15 files changed

+138
-143
lines changed

15 files changed

+138
-143
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
- MIDI support (e.g. for using MIDI keyboard and a sampler plugin as soundboard)
1414
- etc
1515

16+
Plugin Host2 can interface directly with audio and MIDI hardware, OBS audio sources, and output audio as new OBS sources, allowing for complex audio processing setups. E.g. use ASIO interface as audio device, take additional audio from OBS sources, route monitoring to ASIO outputs and/or different audio drivers/hardware, use plugins and create final mix, and output the processed audio as a new OBS source for recording and streaming. Or just create a simple soundboard with a sampler plugin and a MIDI keyboard.
17+
1618
## Device I/O
1719
- Send and receive audio directly into and from audio devices
1820
- "Anything from/to anywhere" device routing

buildspec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@
4343
"uuids": {
4444
"windowsApp": "ad885c58-5ca9-44de-8f4f-1c12676626a9"
4545
},
46-
"version": "0.11.0",
46+
"version": "0.13.0",
4747
"website": "https://www.atkaudio.com"
4848
}

lib/atkaudio/cmake/cpack.cmake

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ file(TO_NATIVE_PATH "${ICON_PATH}" ICON_PATH)
3434
string(REPLACE "\\" "\\\\" ICON_PATH "${ICON_PATH}")
3535
set(CPACK_NSIS_MUI_ICON "${ICON_PATH}")
3636
set(CPACK_NSIS_MUI_UNIICON "${ICON_PATH}")
37+
set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_SOURCE_DIR}/lib/atkaudio/assets/icon.ico")
3738
set(CPACK_PACKAGE_ICON "${ICON_PATH}")
3839

3940
if(WIN32)
@@ -45,19 +46,20 @@ else()
4546
set(CPACK_PACKAGE_EXTENSION "pkg")
4647
endif()
4748

48-
if(NOT DEFINED ENV{CI})
49-
return()
50-
endif()
51-
if(NOT WIN32)
52-
return()
53-
endif()
5449

5550
set(CPACK_RELEASE_STAGING_DIRECTORY "${CMAKE_SOURCE_DIR}/release")
5651
set(BUILD_TYPE_FOR_CPACK "Release")
5752

5853
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
5954
set(CPACK_PACKAGE_ABSOLUTE_PATH ${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}.${CPACK_PACKAGE_EXTENSION})
6055

56+
if(NOT DEFINED ENV{CI})
57+
set(BUILD_TYPE_FOR_CPACK "Debug")
58+
endif()
59+
if(NOT WIN32)
60+
return()
61+
endif()
62+
6163
include(CPack)
6264

6365
if(NOT EXISTS ${CPACK_PACKAGE_ABSOLUTE_PATH})

lib/atkaudio/src/atkaudio/FifoBuffer2.h

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class FifoBuffer2 : public juce::Timer
3030
fifo.reset();
3131
auto freeSpace = fifo.getFreeSpace();
3232

33-
if (freeSpace < minBufferSize)
33+
if (freeSpace < (int)std::ceil(minBufferSize))
3434
freeSpace = 2 * freeSpace;
3535

3636
if (buffer.getNumChannels() < minNumChannels)
@@ -265,53 +265,70 @@ class SyncBuffer : public juce::Timer
265265
void prepareReader(double sampleRate, int newNumChannels, int bufferSize)
266266
{
267267
readerSampleRate.store(sampleRate, std::memory_order_release);
268-
readerBufferSize.store(bufferSize, std::memory_order_release);
269-
readerNumChannels.store(newNumChannels, std::memory_order_release);
270268
}
271269

272270
void prepareWriter(double sampleRate, int newNumChannels, int bufferSize)
273271
{
274272
writerSampleRate.store(sampleRate, std::memory_order_release);
275-
writerBufferSize.store(bufferSize, std::memory_order_release);
276-
writerNumChannels.store(newNumChannels, std::memory_order_release);
277273
}
278274

279275
int write(const float* const* src, int numChannels, int numSamples)
280276
{
277+
juce::ScopedLock lock(writeLock);
278+
279+
auto newMinBufferSize = numSamples;
280+
if (writerNumChannels.load(std::memory_order_acquire) < numChannels ||
281+
writerBufferSize.load(std::memory_order_acquire) < newMinBufferSize)
282+
{
283+
isPrepared.store(false, std::memory_order_release);
284+
285+
writerNumChannels.store(numChannels, std::memory_order_release);
286+
writerBufferSize.store(newMinBufferSize, std::memory_order_release);
287+
}
288+
281289
if (!isPrepared.load(std::memory_order_acquire))
282290
return 0;
283291

284-
juce::ScopedLock lock(writeLock);
285-
286292
fifoBuffer.write(src, numChannels, numSamples);
287293

288294
return numSamples;
289295
}
290296

291297
int read(float* const* dest, int numChannels, int numSamples, bool addToBuffer = false)
292298
{
293-
if (!isPrepared.load(std::memory_order_acquire))
294-
return 0;
295-
296299
juce::ScopedLock lock(readLock);
300+
auto readerMinBufferSizeOverhead = (int)std::ceil(
301+
(ATK_RATE_FACTOR * readerSampleRate.load(std::memory_order_acquire) -
302+
readerSampleRate.load(std::memory_order_acquire)) /
303+
2 * ATK_SMOOTHING_SPEED
304+
);
305+
306+
auto readerMinBufferSize = readerMinBufferSizeOverhead + numSamples;
297307

298308
auto ratio =
299309
writerSampleRate.load(std::memory_order_acquire) / readerSampleRate.load(std::memory_order_acquire);
300310

311+
readerMinBufferSize = (int)std::ceil(readerMinBufferSize * ratio);
312+
313+
if (readerNumChannels.load(std::memory_order_acquire) < numChannels ||
314+
readerBufferSize.load(std::memory_order_acquire) < readerMinBufferSize)
315+
{
316+
isPrepared.store(false, std::memory_order_release);
317+
318+
readerNumChannels.store(numChannels, std::memory_order_release);
319+
readerBufferSize.store(readerMinBufferSize, std::memory_order_release);
320+
}
321+
322+
if (!isPrepared.load(std::memory_order_acquire))
323+
return 0;
324+
301325
int maxAvailable = fifoBuffer.getNumReady();
302326

303327
auto factor = 1.0f;
304-
if (maxAvailable < std::ceil(ATK_RATE_FACTOR * numSamples))
305-
{
328+
if (maxAvailable < readerMinBufferSize)
306329
factor = factor / ATK_RATE_FACTOR;
307-
}
308-
else if (maxAvailable > 2 * std::max(
309-
std::ceil(ATK_RATE_FACTOR * readerBufferSize.load(std::memory_order_acquire)),
310-
(float)writerBufferSize.load(std::memory_order_acquire)
311-
))
312-
{
330+
else if (maxAvailable > 2 * std::max(readerMinBufferSize, writerBufferSize.load(std::memory_order_acquire)))
313331
factor = factor * ATK_RATE_FACTOR;
314-
}
315332

316333
rateSmoothing.setTargetValue(factor);
317334
factor = rateSmoothing.getNextValue();
@@ -394,8 +411,8 @@ class SyncBuffer : public juce::Timer
394411

395412
juce::AudioBuffer<float> tempBuffer;
396413

397-
std::atomic<int> readerBufferSize;
398-
std::atomic<int> writerBufferSize;
414+
std::atomic<int> readerBufferSize{0};
415+
std::atomic<int> writerBufferSize{0};
399416
std::atomic<int> readerNumChannels{0};
400417
std::atomic<int> writerNumChannels{0};
401418

lib/atkaudio/src/atkaudio/PluginHost/JuceHostPlugin.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class HostAudioProcessorImpl
3535
opt.applicationName = getName();
3636
opt.commonToAllUsers = false;
3737
opt.doNotSave = false;
38-
opt.filenameSuffix = ".props";
38+
opt.filenameSuffix = "settings";
3939
opt.ignoreCaseOfKeyNames = false;
4040
opt.storageFormat = PropertiesFile::StorageFormat::storeAsXML;
4141
opt.osxLibrarySubFolder = "Application Support";
@@ -158,7 +158,7 @@ class HostAudioProcessorImpl
158158

159159
const String getName() const final
160160
{
161-
return "atkAudioObsAudioPluginHost";
161+
return "atkAudio Plugin Host";
162162
}
163163

164164
bool acceptsMidi() const final

lib/atkaudio/src/atkaudio/PluginHost2/ObsOutput.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ class ObsOutputAudioProcessor : public juce::AudioProcessor
2626
if (!privateSource)
2727
{
2828
privateSource = obs_source_create(id.c_str(), name.c_str(), nullptr, nullptr);
29-
obs_source_set_audio_active(privateSource, true);
3029

3130
auto* sceneSource = obs_frontend_get_current_scene();
3231
auto* scene = obs_scene_from_source(sceneSource);
3332
obs_scene_add(scene, privateSource);
33+
34+
obs_source_set_audio_active(privateSource, true);
35+
obs_source_set_enabled(privateSource, true);
3436
}
3537
}
3638

lib/atkaudio/src/atkaudio/PluginHost2/ObsSource.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ static juce::StringArray GetObsAudioSources(obs_source_t* parentSource = nullptr
4646
if ((caps & OBS_SOURCE_AUDIO) == 0)
4747
return true;
4848

49+
if (name && juce::String(name).containsIgnoreCase("ph2out"))
50+
return true;
51+
4952
if (name)
5053
names->add(juce::String(name));
5154
return true;
@@ -179,6 +182,7 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
179182
if (source)
180183
{
181184
obs_source_add_audio_capture_callback(source, obs_capture_callback, this);
185+
obs_source_set_muted(source, true);
182186
currentObsSource = source;
183187
}
184188
}

lib/atkaudio/src/atkaudio/PluginHost2/PluginHost2.cpp

Lines changed: 41 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,52 +22,6 @@ struct atk::PluginHost2::Impl : public juce::Timer
2222
{
2323
}
2424

25-
void initialise(int numInputChannels, int numOutputChannels, double sampleRate, void* obs_parent_source)
26-
{
27-
auto& dm = mainHostWindow->getDeviceManager();
28-
dm.initialiseWithDefaultDevices(numInputChannels, numOutputChannels);
29-
dm.addAudioDeviceType(std::make_unique<VirtualAudioIODeviceType>());
30-
dm.setCurrentAudioDeviceType(IO_TYPE, true);
31-
juce::AudioDeviceManager::AudioDeviceSetup setup = dm.getAudioDeviceSetup();
32-
setup.inputDeviceName = IO_NAME;
33-
setup.outputDeviceName = IO_NAME;
34-
dm.setAudioDeviceSetup(setup, true);
35-
}
36-
37-
juce::AudioBuffer<float> outputData{MAX_AUDIO_CHANNELS, AUDIO_OUTPUT_FRAMES};
38-
39-
void process(float** buffer, int newNumChannels, int newNumSamples, double newSampleRate)
40-
{
41-
(void)newSampleRate;
42-
outputData.setSize(newNumChannels, newNumSamples, false, false, true);
43-
44-
auto& dm = mainHostWindow->getDeviceManager();
45-
46-
auto* currentDevice = dm.getCurrentAudioDevice();
47-
if (!currentDevice || currentDevice->getTypeName() != IO_TYPE)
48-
{
49-
for (int ch = 0; ch < newNumChannels; ++ch)
50-
std::fill(buffer[ch], buffer[ch] + newNumSamples, 0.0f);
51-
52-
return;
53-
}
54-
55-
juce::ScopedLock lock(dm.getAudioCallbackLock());
56-
currentDevice = dm.getCurrentAudioDevice();
57-
auto* vdev = static_cast<VirtualAudioIODevice*>(currentDevice);
58-
if (vdev && currentDevice && currentDevice->getTypeName() == IO_TYPE)
59-
{
60-
vdev->process(
61-
const_cast<const float**>(buffer),
62-
outputData.getArrayOfWritePointers(),
63-
newNumChannels,
64-
newNumSamples
65-
);
66-
for (int ch = 0; ch < newNumChannels; ++ch)
67-
std::memcpy(buffer[ch], outputData.getReadPointer(ch), newNumSamples * sizeof(float));
68-
}
69-
}
70-
7125
void setVisible(bool visible)
7226
{
7327
if (!mainHostWindow->isOnDesktop() && visible)
@@ -118,9 +72,50 @@ struct atk::PluginHost2::Impl : public juce::Timer
11872
);
11973
}
12074

75+
void process(float** buffer, int newNumChannels, int newNumSamples, double newSampleRate)
76+
{
77+
(void)newSampleRate;
78+
outputData.setSize(newNumChannels, newNumSamples, false, false, true);
79+
80+
auto& dm = mainHostWindow->getDeviceManager();
81+
82+
auto* currentDevice = dm.getCurrentAudioDevice();
83+
if (!currentDevice || currentDevice->getTypeName() != IO_TYPE)
84+
{
85+
for (int ch = 0; ch < newNumChannels; ++ch)
86+
std::fill(buffer[ch], buffer[ch] + newNumSamples, 0.0f);
87+
88+
return;
89+
}
90+
91+
juce::ScopedLock lock(dm.getAudioCallbackLock());
92+
hostTimeNs = juce::Time::getHighResolutionTicks();
93+
94+
currentDevice = dm.getCurrentAudioDevice();
95+
auto* vdev = static_cast<VirtualAudioIODevice*>(currentDevice);
96+
auto* cb = vdev->getAudioDeviceCallback();
97+
if (cb && vdev && currentDevice && currentDevice->getTypeName() == IO_TYPE)
98+
{
99+
cb->audioDeviceIOCallbackWithContext(
100+
const_cast<const float**>(buffer),
101+
newNumChannels,
102+
outputData.getArrayOfWritePointers(),
103+
newNumChannels,
104+
newNumSamples,
105+
context
106+
);
107+
for (int ch = 0; ch < newNumChannels; ++ch)
108+
std::memcpy(buffer[ch], outputData.getReadPointer(ch), newNumSamples * sizeof(float));
109+
}
110+
}
111+
121112
private:
122113
std::unique_ptr<MainHostWindow> mainHostWindow;
123114
std::unique_ptr<juce::AudioDeviceManager> deviceManager;
115+
116+
juce::AudioBuffer<float> outputData{MAX_AUDIO_CHANNELS, AUDIO_OUTPUT_FRAMES};
117+
uint64_t hostTimeNs;
118+
juce::AudioIODeviceCallbackContext context{.hostTimeNs = &hostTimeNs};
124119
};
125120

126121
void atk::PluginHost2::process(float** buffer, int numChannels, int numSamples, double sampleRate)
@@ -143,16 +138,6 @@ void atk::PluginHost2::setState(std::string& s)
143138
pImpl->setState(s);
144139
}
145140

146-
void atk::PluginHost2::initialise(
147-
int numInputChannels,
148-
int numOutputChannels,
149-
double sampleRate,
150-
void* obs_parent_source
151-
)
152-
{
153-
pImpl->initialise(numInputChannels, numOutputChannels, sampleRate, obs_parent_source);
154-
}
155-
156141
atk::PluginHost2::PluginHost2()
157142
: pImpl(new Impl())
158143
{

lib/atkaudio/src/atkaudio/PluginHost2/PluginHost2.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ class PluginHost2
1919
void getState(std::string& s);
2020
void setState(std::string& s);
2121

22-
void initialise(int numInputChannels, int numOutputChannels, double sampleRate, void* obs_parent_source = nullptr);
23-
2422
private:
2523
struct Impl;
2624
Impl* pImpl;

0 commit comments

Comments
 (0)