Skip to content

Commit 8b004c9

Browse files
committed
0.31.6
1 parent 9e74e49 commit 8b004c9

17 files changed

+1150
-351
lines changed

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.31.5",
50+
"version": "0.31.6",
5151
"website": "https://www.atkaudio.com"
5252
}

lib/atkaudio/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ target_link_libraries(
3333
${PROJECT_NAME}
3434
PRIVATE
3535
juce::juce_audio_utils
36+
juce::juce_gui_extra
3637
juce::juce_dsp
3738
juce::juce_recommended_config_flags
3839
juce::juce_recommended_lto_flags
@@ -109,6 +110,9 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE JUCE_ASIO=1)
109110

110111
target_compile_definitions(${PROJECT_NAME} PRIVATE JUCE_ASIO=1)
111112

113+
# Enable Qt integration for dockable windows
114+
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_QT)
115+
112116
file(COPY_FILE ${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/INSTALLER-LICENSE)
113117

114118
# Build the sandboxed plugin scanner executable

lib/atkaudio/src/atkaudio/ModuleInfrastructure/AudioServer/AudioServerSettingsComponent.cpp

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,22 +236,68 @@ void AudioServerSettingsComponent::ChannelMappingMatrix::resized()
236236

237237
void AudioServerSettingsComponent::ChannelMappingMatrix::setSubscribedChannels(const std::vector<MappingRow>& rows)
238238
{
239-
// Save ALL current row mappings before resizing
240-
std::vector<std::vector<bool>> savedMappings = mappingGrid;
241-
std::vector<MappingRow> oldSubscribedChannels = subscribedChannels;
239+
// Save current mappings with channel identity (not just row index)
240+
std::vector<std::vector<bool>> savedFixedRowMappings;
241+
for (int row = 0; row < numFixedTopRows && row < (int)mappingGrid.size(); ++row)
242+
savedFixedRowMappings.push_back(mappingGrid[row]);
243+
244+
// Save subscribed channel mappings keyed by channel identity
245+
struct ChannelIdentity
246+
{
247+
juce::String deviceName;
248+
int deviceChannel;
249+
bool isInput;
250+
251+
bool operator==(const ChannelIdentity& other) const
252+
{
253+
return deviceName == other.deviceName && deviceChannel == other.deviceChannel && isInput == other.isInput;
254+
}
255+
};
256+
257+
std::vector<std::pair<ChannelIdentity, std::vector<bool>>> savedSubscribedMappings;
258+
for (size_t i = 0; i < subscribedChannels.size(); ++i)
259+
{
260+
int gridRow = numFixedTopRows + static_cast<int>(i);
261+
if (gridRow < (int)mappingGrid.size())
262+
{
263+
ChannelIdentity id{
264+
subscribedChannels[i].deviceName,
265+
subscribedChannels[i].deviceChannel,
266+
subscribedChannels[i].isInput
267+
};
268+
savedSubscribedMappings.push_back({id, mappingGrid[gridRow]});
269+
}
270+
}
242271

243272
subscribedChannels = rows;
244273

245274
// Resize grid to include fixed top rows + subscribed rows
246-
int totalRows = numFixedTopRows + rows.size();
275+
int totalRows = numFixedTopRows + static_cast<int>(rows.size());
247276
mappingGrid.clear();
248277
mappingGrid.resize(totalRows, std::vector<bool>(numClientChannels, false));
249278

250-
// Restore saved mappings for all rows that existed before
251-
int rowsToRestore = juce::jmin((int)savedMappings.size(), totalRows);
252-
for (int row = 0; row < rowsToRestore; ++row)
253-
for (int col = 0; col < numClientChannels && col < (int)savedMappings[row].size(); ++col)
254-
mappingGrid[row][col] = savedMappings[row][col];
279+
// Restore fixed row mappings
280+
for (int row = 0; row < numFixedTopRows && row < (int)savedFixedRowMappings.size(); ++row)
281+
for (int col = 0; col < numClientChannels && col < (int)savedFixedRowMappings[row].size(); ++col)
282+
mappingGrid[row][col] = savedFixedRowMappings[row][col];
283+
284+
// Restore subscribed channel mappings by matching channel identity
285+
for (size_t i = 0; i < rows.size(); ++i)
286+
{
287+
ChannelIdentity newId{rows[i].deviceName, rows[i].deviceChannel, rows[i].isInput};
288+
int gridRow = numFixedTopRows + static_cast<int>(i);
289+
290+
// Find matching saved mapping by channel identity
291+
for (const auto& saved : savedSubscribedMappings)
292+
{
293+
if (saved.first == newId)
294+
{
295+
for (int col = 0; col < numClientChannels && col < (int)saved.second.size(); ++col)
296+
mappingGrid[gridRow][col] = saved.second[col];
297+
break;
298+
}
299+
}
300+
}
255301

256302
table.updateContent();
257303
table.repaint();

lib/atkaudio/src/atkaudio/ModuleInfrastructure/Bridge/ModuleAudioServerDevice.h

Lines changed: 76 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,13 @@ class ModuleAudioServerDevice
283283

284284
juce::BigInteger getActiveInputChannels() const override
285285
{
286+
const juce::ScopedLock sl(lock);
286287
return activeInputChannels;
287288
}
288289

289290
juce::BigInteger getActiveOutputChannels() const override
290291
{
292+
const juce::ScopedLock sl(lock);
291293
return activeOutputChannels;
292294
}
293295

@@ -311,100 +313,76 @@ class ModuleAudioServerDevice
311313
const juce::AudioIODeviceCallbackContext& context
312314
) override
313315
{
314-
// Early exit if destroying to prevent use-after-free
316+
// Early exit if destroying
315317
if (isDestroying.load(std::memory_order_acquire))
316318
return;
317319

318-
// Track active callbacks with RAII guard
320+
// Track active callbacks for safe destruction
319321
activeCallbackCount.fetch_add(1, std::memory_order_acquire);
320322

321323
struct Guard
322324
{
323-
std::atomic<int>& count;
325+
std::atomic<int>& c;
324326

325327
~Guard()
326328
{
327-
count.fetch_sub(1, std::memory_order_release);
329+
c.fetch_sub(1, std::memory_order_release);
328330
}
329331
} guard{activeCallbackCount};
330332

331-
// Double-check after incrementing
332333
if (isDestroying.load(std::memory_order_acquire))
333334
return;
334335

336+
auto clearOutputs = [&]()
337+
{
338+
if (outputChannelData != nullptr)
339+
for (int ch = 0; ch < numOutputChannels; ++ch)
340+
if (outputChannelData[ch] != nullptr)
341+
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
342+
};
343+
335344
// Check if we're the active device
336345
if (!coordinator || !coordinator->isActive(this))
337-
{
338-
for (int ch = 0; ch < numOutputChannels; ++ch)
339-
if (outputChannelData[ch] != nullptr)
340-
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
341-
return;
342-
}
346+
return clearOutputs();
343347

344348
const juce::ScopedLock sl(lock);
345349

346-
if (!isOpen_ || userCallback == nullptr || !isPlaying_)
347-
{
348-
for (int ch = 0; ch < numOutputChannels; ++ch)
349-
if (outputChannelData[ch] != nullptr)
350-
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
351-
return;
352-
}
350+
if (!isOpen_ || !isPlaying_ || userCallback == nullptr || numSamples <= 0)
351+
return clearOutputs();
353352

354-
// AudioServer always passes ALL hardware channels
355-
// We need to filter to only the active channels before passing to JUCE's CallbackMaxSizeEnforcer
356-
// because it sizes its arrays based on getActiveInputChannels().countNumberOfSetBits()
353+
// Count active channels within available range
354+
int numActiveInputs = 0;
355+
for (int ch = 0; ch < numInputChannels; ++ch)
356+
if (activeInputChannels[ch])
357+
++numActiveInputs;
357358

358-
int numActiveInputs = activeInputChannels.countNumberOfSetBits();
359-
int numActiveOutputs = activeOutputChannels.countNumberOfSetBits();
359+
int numActiveOutputs = 0;
360+
for (int ch = 0; ch < numOutputChannels; ++ch)
361+
if (activeOutputChannels[ch])
362+
++numActiveOutputs;
360363

361-
// Resize temp output buffer if needed (for channel filtering)
362-
// We receive ALL channels from AudioServer but need to pass only active channels to user callback
364+
// Resize temp output buffer if needed
363365
if (tempOutputBuffer.getNumChannels() < numActiveOutputs || tempOutputBuffer.getNumSamples() < numSamples)
364366
tempOutputBuffer.setSize(numActiveOutputs, numSamples, false, false, true);
365367

366-
// Safety check: ensure temp buffer is valid after resize
367-
if (tempOutputBuffer.getNumChannels() < numActiveOutputs || tempOutputBuffer.getNumSamples() < numSamples)
368-
{
369-
// Failed to allocate - clear outputs and return
370-
for (int ch = 0; ch < numOutputChannels; ++ch)
371-
if (outputChannelData[ch] != nullptr)
372-
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
373-
return;
374-
}
375-
376-
// Build filtered input channel pointers (inputs are read-only, so we can use direct pointers)
377-
activeInputPtrs.clear();
378-
int activeIdx = 0;
379-
for (int ch = 0; ch < numInputChannels && activeIdx < numActiveInputs; ++ch)
380-
{
381-
if (activeInputChannels[ch] && inputChannelData[ch] != nullptr)
382-
{
368+
// Build filtered input pointers
369+
activeInputPtrs.clearQuick();
370+
for (int ch = 0; ch < numInputChannels; ++ch)
371+
if (activeInputChannels[ch] && inputChannelData != nullptr && inputChannelData[ch] != nullptr)
383372
activeInputPtrs.add(inputChannelData[ch]);
384-
activeIdx++;
385-
}
386-
}
373+
374+
// Channel count mismatch - skip (will resync on next audioDeviceAboutToStart)
375+
if (activeInputPtrs.size() != numActiveInputs)
376+
return clearOutputs();
387377

388378
// Build output pointers from temp buffer
389-
// Ensure vector is large enough for the number of active outputs
390379
if (static_cast<int>(activeOutputPtrs.size()) < numActiveOutputs)
391380
activeOutputPtrs.resize(numActiveOutputs);
392381

393382
for (int i = 0; i < numActiveOutputs; ++i)
394-
{
395-
auto* ptr = tempOutputBuffer.getWritePointer(i);
396-
if (ptr == nullptr)
397-
{
398-
// Invalid buffer pointer - clear outputs and return
399-
for (int ch = 0; ch < numOutputChannels; ++ch)
400-
if (outputChannelData[ch] != nullptr)
401-
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
402-
return;
403-
}
404-
activeOutputPtrs[i] = ptr;
405-
}
383+
activeOutputPtrs[i] = tempOutputBuffer.getWritePointer(i);
406384

407-
// Call user callback with filtered channels
385+
// Call user callback
408386
userCallback->audioDeviceIOCallbackWithContext(
409387
activeInputPtrs.getRawDataPointer(),
410388
numActiveInputs,
@@ -414,39 +392,56 @@ class ModuleAudioServerDevice
414392
context
415393
);
416394

417-
// Copy filtered output back to hardware channels
418-
activeIdx = 0;
395+
// Copy output back to hardware channels
396+
int activeIdx = 0;
419397
for (int ch = 0; ch < numOutputChannels; ++ch)
420398
{
421-
if (activeOutputChannels[ch] && outputChannelData[ch] != nullptr && activeIdx < numActiveOutputs)
422-
{
423-
// Safety check: ensure we have valid buffer pointers
424-
const float* srcPtr = tempOutputBuffer.getReadPointer(activeIdx);
425-
if (srcPtr != nullptr && activeIdx < tempOutputBuffer.getNumChannels())
426-
{
427-
std::copy(srcPtr, srcPtr + numSamples, outputChannelData[ch]);
428-
}
429-
else
430-
{
431-
// Invalid source pointer - clear this channel
432-
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
433-
}
434-
activeIdx++;
435-
}
436-
else if (outputChannelData[ch] != nullptr)
437-
{
438-
// Clear non-active output channels
399+
if (outputChannelData == nullptr || outputChannelData[ch] == nullptr)
400+
continue;
401+
402+
if (activeOutputChannels[ch] && activeIdx < numActiveOutputs)
403+
std::copy_n(tempOutputBuffer.getReadPointer(activeIdx++), numSamples, outputChannelData[ch]);
404+
else
439405
juce::FloatVectorOperations::clear(outputChannelData[ch], numSamples);
440-
}
441406
}
442407
}
443408

444-
void audioDeviceAboutToStart(juce::AudioIODevice*) override
409+
void audioDeviceAboutToStart(juce::AudioIODevice* device) override
445410
{
411+
juce::AudioIODeviceCallback* callbackToNotify = nullptr;
412+
413+
{
414+
const juce::ScopedLock sl(lock);
415+
416+
if (device != nullptr)
417+
{
418+
// Clamp active channels to device capabilities
419+
auto deviceInputs = device->getActiveInputChannels();
420+
auto deviceOutputs = device->getActiveOutputChannels();
421+
activeInputChannels &= deviceInputs;
422+
activeOutputChannels &= deviceOutputs;
423+
424+
currentSampleRate = device->getCurrentSampleRate();
425+
currentBufferSize = device->getCurrentBufferSizeSamples();
426+
}
427+
428+
callbackToNotify = userCallback;
429+
}
430+
431+
if (callbackToNotify != nullptr)
432+
callbackToNotify->audioDeviceAboutToStart(this);
446433
}
447434

448435
void audioDeviceStopped() override
449436
{
437+
juce::AudioIODeviceCallback* callbackToNotify = nullptr;
438+
{
439+
const juce::ScopedLock sl(lock);
440+
callbackToNotify = userCallback;
441+
}
442+
443+
if (callbackToNotify != nullptr)
444+
callbackToNotify->audioDeviceStopped();
450445
}
451446

452447
juce::String actualDeviceName;

0 commit comments

Comments
 (0)