Author Topic: Mixer and decoding channels for ASIO/WASAPI output  (Read 2035 times)

3delite

  • Posts: 895
Hi!

My current implementation is the following:

Code: [Select]
                 StereoSplitter -----> MainStereoMixer
                /                                     \
  SourceChannel                                        MainMixer
                \AllSplitter ------------------------->

This is my solution so I can apply WinAmp and VST DSP plugins for the stereo part (L/R channel) for a stream or for all playing channels's stereo part (as WinAmp DSPs are only stereo and BASS_VST isn't working on more then stereo channels, like I posted, and WinAmp DSPs also can be only applied to 1 channel at a time but I need cross-fade).
These plugins are applied to:
1. Globaly: to MainStereoMixer
2. Per source: to StereoSplitter
AllSplitter is the remaining (above stereo) channels with the stereo part muted with a mixing matrix.

The plugins get a stream record, which has the SourceChannel, StereoSplitter and AllSplitter handles.
The 1st record is a special item, it is the MainMixer record where SourceChannel = MainMixer.
So the plugin doesn't know it has to process a SourceChannel or a MainMixer channel handle, it just processes the channel handle.
The plugin can be a DSP plugin so it just sets up a DSP proc on the SourceChannel (when applied globaly it is applied to MainMixer because then it is the SourceChannel) and BASS_SFX the same way just gets a channel handle for visuals.

All this is working fine, but I'd like to add ASIO and WASAPI output mode to my application.
In ASIO mode the MainMixer channel is now a decoding channel and works fine, and that's why I am posting if I apply a visualisation plugin for example BASS_SFX it starts stealing data from the MainMixer channel and audio speeds up.

What would be the solution if there's one?
I tried some things but closest I got is making 2 splits of MainMixer one for ASIO one for visualisation, making the visualisation channel a slave channel but the visualisation was it seemed to me that couldn't get enough data to display properly because it was like the FFT was displaying a shadow of himself like there was a good frame (FPS is 30) then a frame where the FFT was like 50% lower. Basically there is 2 phases of visual, one good, and one half of the good one.
I don't know if this is the problem but what I think is that as now the channel is a decoding slave channel the visual consumes (and advances) more then there is in the buffer for the slave split!? I am just guessing, don't understand what is happening really.

Please help, how could I convert all this to support ASIO and WASAPI output?

Ian @ un4seen

  • Administrator
  • Posts: 20401
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #1 on: 22 Jan '13 - 15:53 »
I tried some things but closest I got is making 2 splits of MainMixer one for ASIO one for visualisation, making the visualisation channel a slave channel but the visualisation was it seemed to me that couldn't get enough data to display properly because it was like the FFT was displaying a shadow of himself like there was a good frame (FPS is 30) then a frame where the FFT was like 50% lower. Basically there is 2 phases of visual, one good, and one half of the good one.
I don't know if this is the problem but what I think is that as now the channel is a decoding slave channel the visual consumes (and advances) more then there is in the buffer for the slave split!? I am just guessing, don't understand what is happening really.

Yep, your vis's slave splitter is probably running out of data, ie. it's processing data faster than it's being decoded/played. Instead of that, you could try having the ASIOPROC just copy the data to a buffer, and then have the vis process that buffered data via a "push" stream. It could look something like this...

Code: [Select]
// initialise vis stuff
vischan=BASS_StreamCreate(rate, chans, BASS_SAMPLE_FLOAT|BASS_STREAM_DECODE, STREAMPROC_PUSH, 0); // create push stream for vis processing
visbuf=malloc(asiobuflen*chans*sizeof(float)); // allocate vis buffer (asiobuflen as set in BASS_ASIO_Start)
visbuflen=0;

...

// vis processing
float fft[512];
DWORD needed=1024*chans*sizeof(float); // amount of sample data needed for FFT
EnterCriticalSection(&visbuflock); // synchronise vis buffer access
BASS_StreamPutData(vischan, visbuf, min(needed, visbuflen)); // pass buffered data to vis stream
LeaveCriticalSection(&visbuflock);
BASS_ChannelGetData(vischan, fft, BASS_DATA_FFT1024); // get back FFT data

...

DWORD CALLBACK AsioProc(BOOL input, DWORD channel, void *buffer, DWORD length, void *user)
{
int c=BASS_ChannelGetData(decoder, buffer, length); // decode some data
if (c==-1) c=0; // an error, no data
EnterCriticalSection(&visbuflock); // synchronise vis buffer access
memcpy(visbuf, buffer, c); // copy data to vis buffer
visbuflen=c;
LeaveCriticalSection(&visbuflock);
return c;
}

Note that this is assuming that the sample format is floating-point, eg. BASS_ASIO_ChannelSetFormat called with BASS_ASIO_FORMAT_FLOAT.

3delite

  • Posts: 895
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #2 on: 22 Jan '13 - 18:21 »
Hmmm... still the same problem. The FFT visualisation is like there are 2 FFT graphs exchanging from frame to frame. And no clue why but running the old non-ASIO version it's clearly visible that some frequencies are constantly at 0 but now it's jumping up and down there. :-\

One thing I noticed is that in the Min() the values are:

Needed = 32768
visbuflen = 4096

Isn't this too much difference? Isn't this causing this double vision behavior that still too less data?
Any ideas? :)

Ian @ un4seen

  • Administrator
  • Posts: 20401
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #3 on: 23 Jan '13 - 16:48 »
Yes, that is quite a big difference; I guess you're using a small ASIO buffer and large FFT :) ... In that case, you will need to keep more data buffered, ie. have the ASIOPROC add to the buffer rather than replace it. Something like this...

Code: [Select]
// initialise vis stuff
vischan=BASS_StreamCreate(rate, chans, BASS_SAMPLE_FLOAT|BASS_STREAM_DECODE, STREAMPROC_PUSH, 0); // create push stream for vis processing
visbuf=(BYTE*)malloc(visbufmax); // allocate vis buffer (visbufmax = max size, ie. enough for FFT, must be at least as big as ASIO buffer)
visbuflen=0;

...

DWORD CALLBACK AsioProc(BOOL input, DWORD channel, void *buffer, DWORD length, void *user)
{
int c=BASS_ChannelGetData(decoder, buffer, length); // decode some data
if (c==-1) c=0; // an error, no data
EnterCriticalSection(&visbuflock); // synchronise vis buffer access
if (visbuflen+c>visbufmax) { // vis buffer is full, remove some old data...
memmove(visbuf, visbuf+visbuflen-(visbufmax-c), visbufmax-c);
visbuflen=visbufmax-c;
}
memcpy(visbuf+visbuflen, buffer, c); // add data to vis buffer
visbuflen+=c;
LeaveCriticalSection(&visbuflock);
return c;
}

3delite

  • Posts: 895
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #4 on: 24 Jan '13 - 15:18 »
Okay, getting better, but still not perfect. Now the visualisation is happening a little before the sound (maybe ASIO buffer length value).
But this whole way is wrong as I think, how all this will work with for example BASS_SFX? If it uses other than BASS_DATA_FFT4096 then it won't work. And it adds for developing visualisation plugins redundant code also.

What I think. I thought about this for some time now and didn't come up with a perfect solution but: All this BASS_ChannelGetData() things works prefectly in DirectSound mode. But DirectSound is getting outdated, everybody will start using ASIO and/or WASAPI in the future, and if they want to use visualisation they will all run into this problem that from decoding channels BASS_ChannelGetData() will steal data.

My best idea is the following:
1. Add a BASS_STREAM_BUFFER flag for StreamCreate() functions to be used together with BASS_STREAM_DECODE and implement that perfect buffering that is with DirectSound.
2. Add a flag for BASS_ChannelGetData() to take the values directly from the stream (this way all existing add-ons, like BASS_SFX would still work without modification).
3. You said that BASSMix will be incorporated into BASS. This would be a great opportunity to do so and for the BASS_ChannelGetData() without the 'directly from stream' flag use the BASS_Mixer_ChannelGetPosition() return value if it is plugged into a mixer to position when requesting BASS_ChannelGetData() without the 'directly from stream' flag.

I am using a lot of BASS add-ons in my program, so I would need a solution that will keep all existing add-ons still working.

This was the best I could think of so far, probably there are some issues that I didn't take into consideration, but please think about it, and tell me what do you think about it!? :-\

radio42

  • Posts: 4574
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #5 on: 24 Jan '13 - 16:20 »
Well such kind of 'BASS_STREAM_BUFFER' flag already exists - but not for regular streams, but rather only for mixer streams - see the BASSmix add-on, the flag is called BASS_MIXER_BUFFER.

BASS_MIXER_BUFFER: Buffer the sample data, for use by 'BASS_Mixer_ChannelGetData' and 'BASS_Mixer_ChannelGetLevel'. This increases memory requirements, so should not be enabled needlessly. The size of the buffer can be controlled via the 'BASS_CONFIG_MIXER_BUFFER' config option.

When using WASAPI and/or ASIO it is pretty much likely, that you almost always also want to use the BASSmix add-on, and create for example a decoding mixer stream which is used to feed the ASIO/WASAPI output. You sources might are then again decoding streams added to that mixer.
Why?
WASAPI as well as ASIO doesn't support any internal sample rate conversion within their drivers, which means you must always provide the smaple data having the same exact same sample rate. As a mixer stream handels resampling internally very well - using a mixer offers a care-free solution for that.
And when using a mixer stream you already have the above mentioned flag handy whenever needed.
 

3delite

  • Posts: 895
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #6 on: 24 Jan '13 - 18:24 »
Okay. I'll try that. And see how it works.

But then BASS_SFX still remains a problem.

...

Well it works fine, added a mixer before the final mixer so it is plugged into a mixer and I can use BASS_Mixer_ChannelGetData() and much more simpler and cleaner, but it has got the same problem, the vis is a little bit before the sound.

If I could fix this delay and if, I don't know, is BASS_SFX's sources available? Could it be re-compiled to use BASS_Mixer_ChannelGetData()? Then pretty much I would be satisfied. ;D

Maybe a little modification for BASS_Mixer_ChannelGetData() or a global variable to specify sample count offset that I get from BASS_ASIO_GetInfo() buffer size would be enough? ::)
« Last Edit: 24 Jan '13 - 18:59 by 3delite »

3delite

  • Posts: 895
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #7 on: 27 Jan '13 - 14:57 »
In the mean time I successfully modified BASS_SFX (see the BASS_SFX thread) to use BASS_Mixer_ChannelGetData() instead of BASS_ChannelGetData(). So this issue is ok. Thank you radio42!

I successfully implemented WASAPI output mode too, but it has the same problem as ASIO, the visualisation is happening about 1 secs before the audio.

I see that there is BASS_WASAPI_GetData() function, that will probably work fine, but I ask, is there an ultimate solution so I can use BASS_ChannelGetData() or BASS_Mixer_ChannelGetData() or do I must modify all code to choose and use the proper function for it!?

Please, please come up with something, that's more straightforward and simpler than this!!! :-\

3delite

  • Posts: 895
Re: Mixer and decoding channels for ASIO/WASAPI output
« Reply #8 on: 29 Jan '13 - 12:58 »
So to sum it all up: everything is working fine now except ASIO visualization as it has no according function like BASS_ASIO_GetData() for example. Having such a function would be the second best solution.