@@ -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