BASS.NET API for the Un4seen BASS Audio Library

ASIOPROC Delegate

BASS.NET API for the Un4seen BASS Audio Library
User defined ASIO channel callback function (to be used with BASS_ASIO_ChannelEnable(Boolean, Int32, ASIOPROC, IntPtr)).

Namespace:  Un4seen.BassAsio
Assembly:  Bass.Net (in Bass.Net.dll) Version: 2.4.17.5
Syntax

public delegate int ASIOPROC(
	bool input,
	int channel,
	IntPtr buffer,
	int length,
	IntPtr user
)

Parameters

input
Type: SystemBoolean
Dealing with an input channel? = an output channel.
channel
Type: SystemInt32
The input/output channel number... 0 = first.
buffer
Type: SystemIntPtr
The pointer to the buffer containing the recorded data (input channel), or in which to put the data to output (output channel).
length
Type: SystemInt32
The number of bytes to process.
user
Type: SystemIntPtr
The user instance data given when BASS_ASIO_ChannelEnable(Boolean, Int32, ASIOPROC, IntPtr) was called.

Return Value

Type: Int32
In the case of an output channel, the number of bytes written to the buffer (a negative number will be treated as 0). The return value is ignored with input channels.
Remarks

ASIO is a low latency system, so a channel callback function should obviously be as quick as possible. BASS_ASIO_GetCPU can be used to monitor that.

When multiple channels are joined together, the sample data of the channels is interleaved; the channel that was enabled via BASS_ASIO_ChannelEnable(Boolean, Int32, ASIOPROC, IntPtr) comes first, followed by the channels that have been joined to it. The order of the joined channels defaults to numerically ascending order unless the BASS_ASIO_JOINORDER flag was used in the BASS_ASIO_Init(Int32, BASSASIOInit) call, in which case they will be in the order in which BASS_ASIO_ChannelJoin(Boolean, Int32, Int32) was called to join then.

When an output channel's function returns less data than requested, the remainder of the buffer is filled with silence, and some processing is saved by that. When 0 is returned, the level of processing is the same as if the channel had been paused with BASS_ASIO_ChannelPause(Boolean, Int32), ie. the ASIO buffer is simply filled with silence and all additional processing (resampling/etc) is bypassed.

ASIO is a low latency system, so a channel callback function should obviously be as quick as possible. BASS_ASIO_GetCPU can be used to monitor that. Do not call the BASS_ASIO_Stop or BASS_ASIO_Free functions from within an ASIO callback. Also, if it is an output channel, BASS_ASIO_ChannelSetFormat(Boolean, Int32, BASSASIOFormat) and BASS_ASIO_ChannelSetRate(Boolean, Int32, Double) should not be used on the channel being processed by the callback.

Prior to calling this function, BASSASIO will set the thread's device context to the device that the channel belongs to. So when using multiple devices, BASS_ASIO_GetDevice can be used to determine which device the channel is on.

NOTE: When you pass an instance of a callback delegate to one of the BASS functions, this delegate object will not be reference counted. This means .NET would not know, that it might still being used by BASS. The Garbage Collector might (re)move the delegate instance, if the variable holding the delegate is not declared as global. So make sure to always keep your delegate instance in a variable which lives as long as BASS needs it, e.g. use a global variable or member.

Examples

Streaming a file via BASS to ASIO:
int stream = Bass.BASS_StreamCreateFile(fileName, 0, 0, 
                  BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
if (stream != 0)
{
  // now setup ASIO
  _myAsioProc = new ASIOPROC(AsioCallback);
  // get the stream channel info
  BASS_CHANNELINFO info = Bass.BASS_ChannelGetInfo(stream);
  // enable 1st output channel...(0=first)
  BassAsio.BASS_ASIO_ChannelEnable(false, 0, _myAsioProc, new IntPtr(stream));
  // and join the next channels to it
  for (int a=1; a<info.chans; a++)
    BassAsio.BASS_ASIO_ChannelJoin(false, a, 0);
  // since we joined the channels, the next commands will apply to all channles joined
  // so setting the values to the first channels changes them all automatically
  // set the source format (float, as the decoding channel is)
  BassAsio.BASS_ASIO_ChannelSetFormat(false, 0, BASSASIOFormat.BASS_ASIO_FORMAT_FLOAT);
  // set the source rate
  BassAsio.BASS_ASIO_ChannelSetRate(false, 0, (double)info.freq);
  // try to set the device rate too (saves resampling)
  BassAsio.BASS_ASIO_SetRate( (double)info.freq );
  // and start playing it...start output using default buffer/latency
  BassAsio.BASS_ASIO_Start(0);
}
...
private ASIOPROC _myAsioProc; // make it global, so that it can not be removed by the GC
private int AsioCallback(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(user.ToInt32(), buffer, length);
}
Setting up a full-duplex input to output handler:
private byte[] _fullDuplexAsioBuffer;
private int _numchans = 2;

private void SetFullDuplex()
{
    BASS_ASIO_INFO info = BassAsio.BASS_ASIO_GetInfo();
    _fullDuplexAsioBuffer = new byte[info.bufmax * _numchans * 4];
    ...
}

private int AsioToAsioFullDuplexCallback(bool input, int channel, IntPtr buffer, int length, IntPtr user)
{
    if (input)
    {
        if (_fullDuplexAsioBuffer.Length < length)
            _fullDuplexAsioBuffer = new byte[length];
        Marshal.Copy(buffer, _fullDuplexAsioBuffer, 0, length);
        return 0;
    }
    else
    {
        if (length > _fullDuplexAsioBuffer.Length)
            length = _fullDuplexAsioBuffer.Length;
        Marshal.Copy(_fullDuplexAsioBuffer, 0, buffer, length);
        return length;
    }
}
See Also

Reference