AudioDeviceManager tutorial crash/bug

Hi, it’s about the tutorial project downloaded here: https://docs.juce.com/master/tutorial_audio_device_manager.html
I built it without issue on both Mac and Windows, but if I tried to switch on and off the Input/Output channels of the device, after a few attempt it would always crash.

I traced the issue to something about channel number that the AudioBuffer class doesn’t like:
At line 88, the number of audio i/o channels seems to be hard coded to setAudioChannels( 2, 2), but at the audio block line 104~line 110, the process involves getting new channel counts from deviceManager and use that directly to modify audio buffer.

Out of curiosity I tried to move setAudioChannels from line 88 to line 111 and changed the channel number to setAudioChannels(maxInputChannels, maxOnputChannels), this seemed to fix the crash problem, but then I don’t know if doing it at buffer process is a good idea or not.

Thanks for reporting. The issue is to do with how the tutorial code is calculating the number of input/output channels of the device. Changing lines 109 and 110 from:

auto maxInputChannels  = activeInputChannels .getHighestBit() + 1;
auto maxOutputChannels = activeOutputChannels.getHighestBit() + 1;

to

auto maxInputChannels  = activeInputChannels.countNumberOfSetBits();
auto maxOutputChannels = activeOutputChannels.countNumberOfSetBits();

fixes the issue. I’ve pushed this change but it might take a while for the tutorials to update on the website.

Thank you for checking it so quick.
Does that mean the setting in setAudioChannels is actually irrelevant when we are getting channel numbers per audio block like this? With the new fix I tried setAudioChannels(0, 0) or even omit the line completely and was still able to send test tone to channel 3~6 on my interface.

edit: I found the test tone is irrelevant to the channel setting/block process.
But I was still able to send white noise generated with buffer process to multiple channels with setAudioChannels(0, 0).

Since the code has the AudioDeviceSelectorComponent, I guess the hardware channel counts are actually set by that.

Another issue is about changing getHighestBit() to countNumberOfSetBits(), doesn’t it conflicts with the empty-channels clearing process in line 114~125?

If for example the channel setting mask from getActiveOutputChannels() is 1001, we get a “2” from countNumberOfSetBits( ), wouldn’t the line 112 channel for loop only count to 1 like this and the 4th channel will never get the signal?

I also don’t understand why getHighestBit()+1 is bad, if again the mask is 1001, getHighestBit()+1 gives 4, and the line 112 for loop would only count to 3, this looks like it’ll alway be within the channel range of the device and should be safe, how would that cause crash in the first place?

Got it working, with this I’m able to send white noise to any selected channel in the device manager UI without issue.

    auto maxOutputChannels = activeOutputChannels.countNumberOfSetBits();

    for (auto channel = 0; channel < maxOutputChannels; ++channel)
    {
        auto* outBuffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);

        for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
            outBuffer[sample] = random.nextFloat() * 0.1f;
    }

I think it appears that the audio buffer class automatically aligns channel number to only the active channels, so for example a selection on device ch.1 and ch.4 will be assigned to ch0, ch1 at the audio buffer, and the total channel count in the buffer class will be 2, that’s why the crash occur when it compares device channel number to buffer channel number.

Can anybody confirm this?
If so, then why the tutorial code does this manual clearing at line 114~117?

    if ((! activeOutputChannels[channel]) || maxInputChannels == 0)
    {
        bufferToFill.buffer->clear (channel, bufferToFill.startSample, bufferToFill.numSamples);
    }

Thank you, I think at least in this content the setting at setAudioChannels merely provides a startup selection to the device. Further changes done with device manager overrides that.

Could you review another tutorial as well?
https://docs.juce.com/master/tutorial_processing_audio_input.html
After some further research across JUCE examples I found it bizarre that only these two tutorials use the getHighestBit()+1 method to count i/o channels at audio block; this one particularly promote it in the article along with the “manual skipping / clearing channels” operation that I think seems to conflict with the actual channel count in audio buffer (see my previous comments.)

Here is a simple test to see what I’m talking about, using the default tutorial code from https://docs.juce.com/master/tutorial_audio_device_manager.html.

  1. Plug a mono sound source to Input-2 of a audio interface.
  2. Open the tutorial app, enable In/Out 1+2 and hear the sound from Output-2.
  3. Disable Output-1, you’ll find that Output-2 is also cut.
  4. Enable Output-4 (if it’s multi-channel), you’ll find the sound from Input-2 is routed to Output-4.

What I’m trying to convey is: the switching logic in line114~125 is part of the getHighestBit()+1 paradigm introduced in the Processing Audio Input tutorial, if getHighestBit()+1 crashed this Audio Device Manager tutorial and must be changed, these lines should be adjusted accordingly too.
Secondly, the Processing Audio Input tutorial should be reviewed, because the paradigm it introduced for dealing with individual channels crashed when it was actually applied in this Audio Device Manager tutorial.

Using the getHighestBit() method in this tutorial is fine as the input/output channels are not configurable. It was only causing issues in the AudioDeviceManager tutorial because the AudioDeviceSelectorComponent allows the user to change the enabled input/output channels.

Yes, it work fine only if the lower channels are not skipped, but the article made it sound like it is able to deal with individual on/off:

Individual output channels may also be inactive, so we check the state of the channel and also output silence for that channel if it is inactive:

[2]: Individual input channels may be inactive so we output silence in this case too.

Condition like (! activeOutputChannels[channel]) will never trigger in that tutorial (because the channels are not skipped,) and doesn’t work as indented if we use countNumberOfSetBits( ) ; as a begginer who depends on the tutorials to get an idea of the inner working of JUCE, this is misleading, for example I would never ask the question in post-5 of this thread if the articles clearly states that the audio buffer will align its channel count to only the active channels of the device and that’s why the countNumberOfSetBits() method will work, instead it teaches getHighestBit()+1 and follows it with (! activeOutputChannels[channel]) that gave the opposite impression that audio buffer always contain all channels lower than highest selection.