Skip to content

Commit e7db7e0

Browse files
committed
0.23.1
1 parent ac21ef2 commit e7db7e0

File tree

6 files changed

+187
-47
lines changed

6 files changed

+187
-47
lines changed

buildspec.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222
"qt6": {
2323
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
2424
"debugSymbols": {
25-
"windows-x64": "11b7be92cf66a273299b8f3515c07a5cfb61614b59a4e67f7fc5ecba5e2bdf21"
25+
"windows-x64": "aae88a17e0211cb37db6a8602f2e20d69255be1f9700c699008ca5adbce1dde2"
2626
},
2727
"hashes": {
28-
"macos": "d3f5f04b6ea486e032530bdf0187cbda9a54e0a49621a4c8ba984c5023998867",
29-
"windows-x64": "0e76bf0555dd5382838850b748d3dcfab44a1e1058441309ab54e1a65b156d0a"
28+
"macos": "990f11638b80a4509e14e8c315f6e4caa0861e37fcd3113a256fbff835ffca29",
29+
"windows-x64": "c62e82483bc7c0bf199e8ac3220c66a85a6e8a0cd69a05b6d44f873b830e415f"
3030
},
3131
"label": "Pre-Built Qt6",
32-
"version": "2025-07-11"
32+
"version": "2025-08-23"
3333
}
3434
},
3535
"displayName": "atkAudio Plugin",
@@ -43,6 +43,6 @@
4343
"uuids": {
4444
"windowsApp": "ad885c58-5ca9-44de-8f4f-1c12676626a9"
4545
},
46-
"version": "0.20.1",
46+
"version": "0.23.1",
4747
"website": "https://www.atkaudio.com"
4848
}

lib/atkaudio/src/atkaudio/DeviceIo/DeviceIoApp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ class AudioAppMainWindow final : public DocumentWindow
118118
{
119119
setContentOwned(&demo, true);
120120
setResizable(true, false);
121+
122+
// Position title bar buttons on the right (Windows-style), like Plugin Host
123+
setTitleBarButtonsRequired(DocumentWindow::minimiseButton | DocumentWindow::closeButton, false);
124+
121125
centreWithSize(demo.getWidth(), demo.getHeight());
122126
setVisible(false);
123127
}

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

Lines changed: 137 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
#define PROPERTY_NAME "source"
1111
#define CHILD_NAME "SelectedSource"
12+
#define FOLLOW_VOLUME_PROPERTY "followVolume"
13+
#define FOLLOW_VOLUME_CHILD "FollowVolumeSettings"
14+
#define FOLLOW_MUTE_PROPERTY "followMute"
15+
#define FOLLOW_MUTE_CHILD "FollowMuteSettings"
1216

1317
static void drawTextLayout(
1418
juce::Graphics& g,
@@ -32,25 +36,25 @@ static void drawTextLayout(
3236
textLayout.draw(g, textBounds.toFloat());
3337
}
3438

35-
static juce::StringArray GetObsAudioSources(obs_source_t* parentSource = nullptr)
39+
static std::vector<std::string> GetObsAudioSources(obs_source_t* parentSource = nullptr)
3640
{
37-
juce::StringArray sourceNames;
41+
std::vector<std::string> sourceNames;
3842

3943
obs_enum_sources(
4044
[](void* param, obs_source_t* src)
4145
{
42-
auto* names = static_cast<juce::StringArray*>(param);
46+
auto* names = static_cast<std::vector<std::string>*>(param);
4347
const char* name = obs_source_get_name(src);
4448
uint32_t caps = obs_source_get_output_flags(src);
4549

4650
if ((caps & OBS_SOURCE_AUDIO) == 0)
4751
return true;
4852

49-
if (name && juce::String(name).containsIgnoreCase("ph2out"))
53+
if (name && std::string(name).find("ph2out") != std::string::npos)
5054
return true;
5155

5256
if (name)
53-
names->add(juce::String(name));
57+
names->push_back(std::string(name));
5458
return true;
5559
},
5660
&sourceNames
@@ -152,14 +156,19 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
152156
return apvts;
153157
}
154158

159+
obs_source_t* getCurrentObsSource() const
160+
{
161+
return currentObsSource;
162+
}
163+
155164
void removeObsAudioCaptureCallback()
156165
{
157-
auto sourceName =
158-
apvts.state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString();
159-
if (sourceName.isEmpty())
166+
auto sourceNameRaw =
167+
apvts.state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString().toRawUTF8();
168+
if (strlen(sourceNameRaw) == 0)
160169
return;
161170

162-
obs_source_t* source = obs_get_source_by_name(sourceName.toRawUTF8());
171+
obs_source_t* source = obs_get_source_by_name(sourceNameRaw);
163172
if (source)
164173
{
165174
obs_source_remove_audio_capture_callback(source, obs_capture_callback, this);
@@ -175,14 +184,21 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
175184
void addObsAudioCaptureCallback()
176185
{
177186
removeObsAudioCaptureCallback();
178-
auto sourceName =
179-
apvts.state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString();
187+
auto sourceNameRaw = apvts.state.getOrCreateChildWithName(CHILD_NAME, nullptr)
188+
.getProperty(PROPERTY_NAME)
189+
.toString()
190+
.toStdString();
180191

181-
obs_source_t* source = obs_get_source_by_name(sourceName.toRawUTF8());
192+
obs_source_t* source = obs_get_source_by_name(sourceNameRaw.c_str());
182193
if (source)
183194
{
184195
obs_source_add_audio_capture_callback(source, obs_capture_callback, this);
185-
obs_source_set_muted(source, true);
196+
197+
auto followVolume = apvts.state.getOrCreateChildWithName(FOLLOW_VOLUME_CHILD, nullptr)
198+
.getProperty(FOLLOW_VOLUME_PROPERTY, false);
199+
if (!followVolume)
200+
obs_source_set_muted(source, true);
201+
186202
currentObsSource = source;
187203
}
188204
}
@@ -195,6 +211,26 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
195211
buffer.getNumSamples(),
196212
getSampleRate()
197213
);
214+
215+
auto followVolume = apvts.state.getOrCreateChildWithName(FOLLOW_VOLUME_CHILD, nullptr)
216+
.getProperty(FOLLOW_VOLUME_PROPERTY, false);
217+
218+
if (followVolume && currentObsSource)
219+
{
220+
float obsVolume = obs_source_get_volume(currentObsSource);
221+
if (obsVolume != 1.0f)
222+
buffer.applyGain(obsVolume);
223+
}
224+
225+
auto followMute =
226+
apvts.state.getOrCreateChildWithName(FOLLOW_MUTE_CHILD, nullptr).getProperty(FOLLOW_MUTE_PROPERTY, false);
227+
228+
if (followMute && currentObsSource)
229+
{
230+
bool obsMuted = obs_source_muted(currentObsSource);
231+
if (obsMuted)
232+
buffer.clear();
233+
}
198234
}
199235

200236
private:
@@ -237,13 +273,67 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
237273
: juce::AudioProcessorEditor(&p)
238274
, processor(p)
239275
, listBox(p)
276+
, followVolumeToggle("Follow Source Volume")
277+
, followMuteToggle("Follow Source Mute")
240278
{
241279
auto sources = GetObsAudioSources();
242-
auto savedSource =
243-
processor.getApvts().state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME);
280+
auto savedSourceRaw = processor.getApvts()
281+
.state.getOrCreateChildWithName(CHILD_NAME, nullptr)
282+
.getProperty(PROPERTY_NAME)
283+
.toString()
284+
.toRawUTF8();
285+
286+
followVolumeToggle.setButtonText("Follow Source Volume");
287+
auto followVolumeState = processor.getApvts()
288+
.state.getOrCreateChildWithName(FOLLOW_VOLUME_CHILD, nullptr)
289+
.getProperty(FOLLOW_VOLUME_PROPERTY, false);
290+
followVolumeToggle.setToggleState(followVolumeState, juce::dontSendNotification);
291+
292+
followVolumeToggle.onClick = [this]()
293+
{
294+
auto& state = processor.getApvts().state;
295+
bool newState = followVolumeToggle.getToggleState();
296+
state.getOrCreateChildWithName(FOLLOW_VOLUME_CHILD, nullptr)
297+
.setProperty(FOLLOW_VOLUME_PROPERTY, newState, nullptr);
298+
299+
obs_source_t* currentSource = processor.getCurrentObsSource();
300+
if (currentSource)
301+
{
302+
if (newState)
303+
obs_source_set_muted(currentSource, false);
304+
else
305+
obs_source_set_muted(currentSource, true);
306+
}
307+
};
308+
309+
// Setup follow mute toggle checkbox
310+
followMuteToggle.setButtonText("Follow Source Mute");
311+
auto followMuteState = processor.getApvts()
312+
.state.getOrCreateChildWithName(FOLLOW_MUTE_CHILD, nullptr)
313+
.getProperty(FOLLOW_MUTE_PROPERTY, false);
314+
followMuteToggle.setToggleState(followMuteState, juce::dontSendNotification);
315+
316+
followMuteToggle.onClick = [this]()
317+
{
318+
auto& state = processor.getApvts().state;
319+
bool newState = followMuteToggle.getToggleState();
320+
state.getOrCreateChildWithName(FOLLOW_MUTE_CHILD, nullptr)
321+
.setProperty(FOLLOW_MUTE_PROPERTY, newState, nullptr);
322+
};
323+
324+
addAndMakeVisible(followVolumeToggle);
325+
addAndMakeVisible(followMuteToggle);
244326
addAndMakeVisible(listBox);
245327

246-
auto selectedIdx = sources.indexOf(savedSource.toString(), false);
328+
auto selectedIdx = -1;
329+
for (size_t i = 0; i < sources.size(); ++i)
330+
{
331+
if (sources[i] == savedSourceRaw)
332+
{
333+
selectedIdx = static_cast<int>(i);
334+
break;
335+
}
336+
}
247337

248338
setSize(300, 200);
249339
setResizable(true, true);
@@ -254,6 +344,15 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
254344
{
255345
auto area = getLocalBounds().reduced(8);
256346

347+
auto checkboxHeight = 24;
348+
followVolumeToggle.setBounds(area.removeFromTop(checkboxHeight));
349+
350+
area.removeFromTop(4);
351+
352+
followMuteToggle.setBounds(area.removeFromTop(checkboxHeight));
353+
354+
area.removeFromTop(8);
355+
257356
listBox.setBounds(area);
258357
}
259358

@@ -269,28 +368,28 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
269368
, processor(p)
270369
{
271370
for (const auto& item : GetObsAudioSources())
272-
items.add(item);
371+
items.push_back(item);
273372
setModel(this);
274373
setOutlineThickness(1);
275374
}
276375

277376
int getNumRows() override
278377
{
279-
return items.size();
378+
return static_cast<int>(items.size());
280379
}
281380

282381
void paintListBoxItem(int row, Graphics& g, int width, int height, bool rowIsSelected) override
283382
{
284383
auto& state = processor.getApvts().state;
285-
auto selectedSource =
286-
state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString();
287-
if (isPositiveAndBelow(row, items.size()))
384+
auto selectedSourceRaw =
385+
state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString().toRawUTF8();
386+
if (isPositiveAndBelow(row, static_cast<int>(items.size())))
288387
{
289388
if (rowIsSelected)
290389
g.fillAll(findColour(TextEditor::highlightColourId).withMultipliedAlpha(0.3f));
291390

292-
auto item = items[row];
293-
bool enabled = (item == selectedSource);
391+
auto itemRaw = items[row];
392+
bool enabled = (itemRaw == selectedSourceRaw);
294393

295394
auto x = getTickX();
296395
auto tickW = (float)height * 0.75f;
@@ -308,7 +407,8 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
308407
false
309408
);
310409

311-
drawTextLayout(g, *this, item, {x + 5, 0, width - x - 5, height}, enabled);
410+
juce::String displayText = juce::String::fromUTF8(itemRaw.c_str());
411+
drawTextLayout(g, *this, displayText, {x + 5, 0, width - x - 5, height}, enabled);
312412
}
313413
}
314414

@@ -334,7 +434,7 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
334434
{
335435
ListBox::paint(g);
336436

337-
if (items.isEmpty())
437+
if (items.empty())
338438
{
339439
g.setColour(Colours::grey);
340440
g.setFont(0.5f * (float)getRowHeight());
@@ -351,28 +451,30 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
351451

352452
private:
353453
ObsSourceAudioProcessor& processor;
354-
juce::Array<juce::String> items;
454+
std::vector<std::string> items;
355455

356456
void flipEnablement(const int row)
357457
{
358-
if (isPositiveAndBelow(row, items.size()))
458+
if (isPositiveAndBelow(row, static_cast<int>(items.size())))
359459
{
360-
auto sourceName = items[row];
460+
auto sourceNameRaw = items[row];
361461
auto& state = processor.getApvts().state;
362-
auto currentSelected =
363-
state.getOrCreateChildWithName(CHILD_NAME, nullptr).getProperty(PROPERTY_NAME).toString();
462+
auto currentSelectedRaw = state.getOrCreateChildWithName(CHILD_NAME, nullptr)
463+
.getProperty(PROPERTY_NAME)
464+
.toString()
465+
.toRawUTF8();
364466

365-
if (sourceName.isNotEmpty())
467+
if (!sourceNameRaw.empty())
366468
{
367-
if (currentSelected == sourceName)
469+
if (currentSelectedRaw == sourceNameRaw)
368470
{
369471
processor.removeObsAudioCaptureCallback();
370472
state.getOrCreateChildWithName(CHILD_NAME, nullptr).removeProperty(PROPERTY_NAME, nullptr);
371473
}
372474
else
373475
{
374476
state.getOrCreateChildWithName(CHILD_NAME, nullptr)
375-
.setProperty(PROPERTY_NAME, sourceName, nullptr);
477+
.setProperty(PROPERTY_NAME, juce::String(sourceNameRaw), nullptr);
376478
processor.addObsAudioCaptureCallback();
377479
}
378480
}
@@ -390,9 +492,10 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
390492

391493
ObsSourceAudioProcessor& processor;
392494
MidiInputSelectorComponentListBox listBox;
495+
juce::ToggleButton followVolumeToggle;
496+
juce::ToggleButton followMuteToggle;
393497
};
394498

395-
// Implementation of createEditor
396499
inline juce::AudioProcessorEditor* ObsSourceAudioProcessor::createEditor()
397500
{
398501
return new ObsSourceAudioProcessorEditor(*this);

lib/atkaudio/src/atkaudio/PluginHost2/UI/MainHostWindow.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ MainHostWindow::MainHostWindow()
329329

330330
setContentNonOwned(graphHolder.get(), false);
331331

332-
// setUsingNativeTitleBar(true);
332+
// Position title bar buttons on the right (Windows-style), like Plugin Host
333+
setTitleBarButtonsRequired(DocumentWindow::allButtons, false);
333334

334335
restoreWindowStateFromString(getAppProperties().getUserSettings()->getValue("mainWindowPos"));
335336

0 commit comments

Comments
 (0)