Author Topic: Switching ASIO Output Channel  (Read 379 times)

findjammer

  • Posts: 34
Switching ASIO Output Channel
« on: 2 Sep '21 - 16:17 »
Hi,

I have an odd situation going on that I cannot figure out.

Start the app with ASIO outputs 1-2 set and everything is fine. The following code works:

Code: [Select]
var mynewSync = new ASIOPROC(AsioCallback);
_myAsioSyncs.Add(mynewSync);

var enabled = BassAsio.BASS_ASIO_ChannelEnable(false, _activeDeviceChannelIndex, mynewSync, new IntPtr(newStream));

var formatSet = BassAsio.BASS_ASIO_ChannelSetFormat(false, _activeDeviceChannelIndex, BASSASIOFormat.BASS_ASIO_FORMAT_FLOAT);

var joined = BassAsio.BASS_ASIO_ChannelJoin(false, _activeDeviceChannelIndex+1, _activeDeviceChannelIndex);

var started = BassAsio.BASS_ASIO_Start(0);

The parameter _activeDeviceChannelIndex is the user selected output.

If I change the selected _activeDeviceChannelIndex to outputs 3-4 whilst the application is running, everything stops. "enabled" returns false. If I switch back to the first output used 1-2, audio starts again.

If I restart the application with the outputs set to 3-4, the audio plays fine through 3-4. Switch back to 1-2 and it all stops again.

Basically, whichever output gets used first is the only one that will work until a restart.

It suggests something isn't getting freed up or released but I'm struggling to figure out what. Any ideas would be very appreciated.

Thanks,

J

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #1 on: 2 Sep '21 - 16:37 »
Ahhhhhhh ...

Code: [Select]
BassAsio.BASS_ASIO_Stop();

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #2 on: 3 Sep '21 - 12:45 »
I thought I'd gotten this licked but unfortunately not.

When my app starts up with an ASIO output set to 1-2 or 3-4 etc. Everything is fine. The audio plays over the correct output and all is well.

The issues start when the output is changed from 1-2 to 3-4 whilst the app is running. The audio starts playing over both sets of outputs and is crackly and broken (as you'd expect).

Within my Play method the code is:


Code: [Select]
                    if (ActiveOutput.IsAsio)
                    {
                        if (_activeDeviceChannelIndexChanged)
                        {
                            _asioStarted = !BassAsio.BASS_ASIO_Stop();
                            _activeDeviceChannelIndexChanged = false;
                        }

                        var mynewMixSync = new ASIOPROC(AsioMixerCallback);
                        _myAsioSyncs.Add(mynewMixSync);

                        if (!BassAsio.BASS_ASIO_ChannelEnable(false, _activeDeviceChannelIndex, mynewMixSync, new IntPtr(newStream)))
                        {
                            Log.Error($"BASS: Error BASS_ASIO_ChannelEnable: {BassAsio.BASS_ASIO_ErrorGetCode()}");
                        }

                        if (!BassAsio.BASS_ASIO_ChannelSetFormat(false, _activeDeviceChannelIndex, BASSASIOFormat.BASS_ASIO_FORMAT_FLOAT))
                        {
                            Log.Error($"BASS: Error BASS_ASIO_ChannelSetFormat: {BassAsio.BASS_ASIO_ErrorGetCode()}");
                        }

                        if (!BassAsio.BASS_ASIO_ChannelJoin(false, _activeDeviceChannelIndex + 1, _activeDeviceChannelIndex))
                        {
                            Log.Error($"BASS: Error BASS_ASIO_ChannelJoin: {BassAsio.BASS_ASIO_ErrorGetCode()}");
                        }

                        if (_mixerHandle == 0)
                        {
                            var deviceSampleRate = BassAsio.BASS_ASIO_GetRate();
                            _mixerHandle = BassMix.BASS_Mixer_StreamCreate((int)deviceSampleRate, 2, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_DOWNMIX);
                        }

                        if (!BassMix.BASS_Mixer_StreamAddChannel(_mixerHandle, newStream, BASSFlag.BASS_SPEAKER_PAIR1))
                        {
                            Log.Error($"BASS: Error BASS_Mixer_StreamAddChannel: {BassAsio.BASS_ASIO_ErrorGetCode()}");
                        }

                        ActiveStreamHandle = newStream;
                        _myStreams.Add(newStream);

                        if (!_asioStarted)
                        {
                            _asioStarted = BassAsio.BASS_ASIO_Start(0);
                            if (!_asioStarted)
                            {
                                Log.Error($"BASS: Error BASS_ASIO_Start: {BassAsio.BASS_ASIO_ErrorGetCode()}");
                            }
                        }
                    }

The callback code is:


Code: [Select]
private int AsioMixerCallback(bool input, int channel, IntPtr buffer, int length, IntPtr user)
{
    // Note: 'user' contains the underlying stream channel (see above)
    // We can simply use the bass method to get some data from a decoding channel
    // and store it to the asio buffer in the same moment...
    return Bass.BASS_ChannelGetData(_mixerHandle, buffer, length);
}

I've tried removing all channels from the existing mixer and setting the _mixerHandle  to 0 in order to create a new mixer for the newly selected outputs but that doesn't seem to work. I know there are other issues with the code as I keep seeing:

Code: [Select]
BASS: Error BASS_ASIO_ChannelJoin: BASS_ERROR_START
In my logs. Are these issues related? I'm a bit stuck if I'm honest. Any pointers would be appreciated.

I've also started getting crashes with 0xc0000374 which seems to be STATUS_HEAP_CORRUPTION, not ideal!

Thanks,
J
« Last Edit: 3 Sep '21 - 14:37 by findjammer »

Ian @ un4seen

  • Administrator
  • Posts: 23924
Re: Switching ASIO Output Channel
« Reply #3 on: 3 Sep '21 - 16:52 »
When you switch ASIO output channels, do you want to stop using the old channels? If so, when you enable the new channels, you should also disable the old channels by calling BASS_ASIO_ChannelEnable with proc=NULL. Note you will need to stop the ASIO device (with BASS_ASIO_Stop) before making such changes. If you would like to switch outputs without stopping the device then you could instead enable all 4 ASIO channels at the start and create a 4 channel mixer to feed them.

Regarding the crashing, it may be that your ASIOPROC delegate is getting garbage collected. To prevent that, it shouldn't be stored in a local variable (please see the "Callbacks and Delegates" section of the "Interoperating with Unmanaged Code" page in the BASS.Net docs for details). Another option is to use BASS_ASIO_ChannelEnableBASS instead of BASS_ASIO_ChannelEnable, to remove the need for a callback.

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #4 on: 3 Sep '21 - 17:47 »
Thanks Ian. I actually just saw your message after having implemented all your suggests over the few hours of fiddling I've done since asking. Fantastic to have it confirmed too. Still some more refactoring but looking much more stable now.

The only issue I have at the moment is with my particular hardware I think.

I have an old MOTU Traveler MkI. It does 192Khz but in order to do that it requires 4 inputs. I have that working by just looping over the required outputs with

Code: [Select]
BassAsio.BASS_ASIO_ChannelJoin(...)
The issue is the audio plays back at the wrong speed.

My mixer is created with:

Code: [Select]
_mixerHandle = BassMix.BASS_Mixer_StreamCreate((int)deviceSampleRate, 2, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE);
Now ... can you spot the obvious mistake ... ? I just fixed this issue too ... hard coded "2" for the required channel count ... sheesh ...

Have a good weekend!

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #5 on: 3 Sep '21 - 18:58 »
Yeah I spoke too soon ... Struggling to get it working with the 4 channel 192Khz settings on the interface.

Seems like all the joining is happening correctly. Enabled 0 and then joined 1 to 0, 2 to 0, 3 to 0 but it then the audio comes out of 5-6. Very odd.

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #6 on: 3 Sep '21 - 19:57 »
There's something really screwy going on with my ASIO setup.

If I set the Traveler to 96Kz using it's control panel and run Bass.BASS_Init(-1, 96000, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero); all my streams start returning a 0 handle.

Something is also setting my Traveler back to 44100 (I'm not executing any BASS_ASIO_SetRate calls) then the next run of my app works again as the sample rate is set back to 44100.

This is really weird. All my other audio apps seem unphased.

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #7 on: 4 Sep '21 - 09:28 »
I've gotten further with this issue and solved the playback and 0 handle issues.

Seems the MOTU driver is actually pretty flakey in some respects. If I change the sample rate in the control panel and run my app, streams sometimes come back with a 0 handle. If I close my app, power cycle the Traveler and restart my app, streams start coming back with a non-zero handle again!

The original problem all stems from mismatched Sample Rates between the hardware and BassAsio. If I check the hardware SR and re-initialise BASS using this value all is well.

But trying to set the device rate using BassAsio is completely failing.

Code: [Select]
var deviceSampleRate = BassAsio.BASS_ASIO_GetRate();
if (SetDeviceToSelectedSampleRate)
{
    var can = BassAsio.BASS_ASIO_CheckRate(ActiveSampleRate);
    //BassAsio.BASS_ASIO_ControlPanel();
    if (!BassAsio.BASS_ASIO_SetRate(ActiveSampleRate))
    {
        Log.Error($"BASS: BASS_ASIO_SetRate: {BassAsio.BASS_ASIO_ErrorGetCode()}");
    }
}

No matter what I try, the hardware device never sets the Sample Rate to the value provided in the BASS_ASIO_SetRate call. The only way I can get it to change is by closing my app, opening the MOTU control panel, setting the Sample Rate and then restarting my app.

Code: [Select]
var can = BassAsio.BASS_ASIO_CheckRate(ActiveSampleRate);
Returns true.

Code: [Select]
BassAsio.BASS_ASIO_SetRate(ActiveSampleRate)
Also returns true, but never actually sets the devices sample rate.

Later in the process, before calling BASS_ASIO_Start I've also tried calling

Code: [Select]
BassAsio.BASS_ASIO_ChannelSetRate(false, _activeDeviceChannelIndex, deviceSampleRate)
This also doesn't produce any audio since Bass & BassAsio are/were still initialised with a different sample rate to the device since the BASS_ASIO_SetRate is failing.

Think I might also need a newer ASIO device :)

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #8 on: 4 Sep '21 - 09:50 »
Another observation.

I've added a button in my GUI to open the control panel. If I open the panel and set a new SampleRate on the hardware the ASIONOTIFYPROC never gets fired either. So I can't then respond to this change and re-initialise Bass.

It seems all I can do with my particular hardware is initialise Bass to whatever the hardware is set to.

If I start my DAW, it sets the device SR fine so the driver is certainly capable of responding to a Sample Rate change instruction from software.


findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #9 on: 4 Sep '21 - 12:20 »
Sorry for the spamming but some more observations.

I honestly think I'm crashing headlong into issues with my Traveler. It's now a 16 year old Firewire interface. The last driver release was in 2015.

I'm finding I have to power cycle it to get it to behave sometimes. Using the Wave drivers I see this:

  • Open the control panel - set it to 44100
  • Launch my app and it switches to 48000 (Nothing in my app tries to set this)
  • Bass Init with 44100 fails = no audio.

  • Open the control panel again - set it to 44100
  • Power cycle the Traveler
  • launch my app and it sticks to 44100
  • Bass Init with 44100 works

Hmm ...

Still odd that my DAW can set the control panel sample rate but my app using BASS ASIO cannot.

findjammer

  • Posts: 34
Re: Switching ASIO Output Channel
« Reply #10 on: 6 Sep '21 - 12:23 »
Just a final message really.

My main development box has been rock solid for 3 years now since I built it.

Over the last few days I'm been poking the Traveler as I've been working on this and it's caused 5 BSOD in that time. The last one just happened without any config changes, not working with BASS and not even playing any audio from any source.

BOOM The MOTU driver just threw a wobbly and ditched Windows.

I've just ordered a Presonus 24c ... :)

Ian @ un4seen

  • Administrator
  • Posts: 23924
Re: Switching ASIO Output Channel
« Reply #11 on: 6 Sep '21 - 13:04 »
There's something really screwy going on with my ASIO setup.

If I set the Traveler to 96Kz using it's control panel and run Bass.BASS_Init(-1, 96000, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero); all my streams start returning a 0 handle.

Something is also setting my Traveler back to 44100 (I'm not executing any BASS_ASIO_SetRate calls) then the next run of my app works again as the sample rate is set back to 44100.

This is really weird. All my other audio apps seem unphased.

That sounds like the BASS_Init call may have failed. What is the return value and error code from that? If that call is successful then please also check the error code from the stream creation calls to find out why they failed (returned 0).

If you're also using ASIO, note that some ASIO drivers don't allow a device to be accessed by other means (eg. WASAPI/DirectSound) at the same time. If you're only using BASS for decoding purposes (and BASSASIO for output) then you can use device=0 in the BASS_Init call for the "No Sound" device to avoid any such conflicts.