BASS.NET API for the Un4seen BASS Audio Library

STREAMPROC Delegate

BASS.NET API for the Un4seen BASS Audio Library
User stream writing callback delegate (to be used with BASS_StreamCreate(Int32, Int32, BASSFlag, STREAMPROC, IntPtr)).

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

public delegate int STREAMPROC(
	int handle,
	IntPtr buffer,
	int length,
	IntPtr user
)

Parameters

handle
Type: SystemInt32
The stream that needs writing.
buffer
Type: SystemIntPtr
The pointer to the buffer to write the sample data in. The sample data must be written in standard Windows PCM format - 8-bit samples are unsigned, 16-bit samples are signed, 32-bit floating-point samples range from -1 to 1.
length
Type: SystemInt32
The number of bytes to write.
user
Type: SystemIntPtr
The user instance data given when BASS_StreamCreate(Int32, Int32, BASSFlag, STREAMPROC, IntPtr) was called.

Return Value

Type: Int32
The number of bytes written by the function, optionally using the BASS_STREAMPROC_END (BASSStreamProc) flag to signify that the end of the stream is reached.
Remarks

A stream writing function should obviously be as quick as possible, because other streams (and MOD musics) can't be updated until it's finished.

It is better to return less data quickly, rather than spending a long time delivering the amount BASS requested.

Although a STREAMPROC may return less data than BASS requests, be careful not to do so by too much, too often. If the buffer level gets too low, BASS will automatically stall playback of the stream, until the whole buffer has refilled.

BASS_ChannelGetData(Int32, IntPtr, Int32) (BASS_DATA_AVAILABLE) can be used to check the buffer level, and BASS_ChannelIsActive(Int32) can be used to check if playback has stalled.

A BASS_SYNC_STALL sync can also be set via BASS_ChannelSetSync(Int32, BASSSync, Int64, SYNCPROC, IntPtr), to be triggered upon playback stalling or resuming.

If you do return less than the requested amount of data, the number of bytes should still equate to a whole number of samples.

Some functions can cause problems if called from within a stream (or DSP) function. Do not call these functions from within a stream callback:

BASS_Stop, BASS_Free, BASS_MusicLoad(String, Int64, Int32, BASSFlag, Int32), BASS_StreamCreate(Int32, Int32, BASSFlag, STREAMPROC, IntPtr) or any other stream creation functions.

Also, do not call BASS_StreamFree(Int32) or BASS_ChannelStop(Int32) with the same handle as received by the callback.

When streaming multi-channel sample data, the channel order of each sample is as follows:

3 channels: left-front, right-front, center.

4 channels: left-front, right-front, left-rear/side, right-rear/side.

6 channels(5.1): left-front, right-front, center, LFE, left-rear/side, right-rear/side.

8 channels(7.1): left-front, right-front, center, LFE, left-rear/side, right-rear/side, left-rear center, right-rear center.

It is clever to NOT alloc buffer data (e.g. a float[]) everytime within the callback method, since ALL callbacks should be really fast! And if you would do a 'float[] data = new float[]' every time here...the GarbageCollector would never really clean up that memory. Sideeffects might occure, due to the fact, that BASS will call this callback too fast and too often... However, this is not always the case, so in most examples it'll work just fine - but if you got problems - try moving any memory allocation things outside any callbacks.

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

A callback function to stream a file, in 44100hz 16-bit stereo:
private STREAMPROC _myStreamCreate; // make it global, so that the GC can not remove it
private byte[] _data; // local data buffer
...
_myStreamCreate = new STREAMPROC(MyFileProc);
FileStream _fs = File.OpenRead("test.raw");
int channel = Bass.BASS_StreamCreate(44100, 2, BASSFlag.BASS_DEFAULT, _myStreamCreate, IntPtr.Zero);
Bass.BASS_ChannelPlay(channel, false);
...
private int MyFileProc(int handle, IntPtr buffer, int length, IntPtr user)
{
    // implementing the callback for BASS_StreamCreate...
    // here we need to deliver PCM sample data
    // increase the data buffer as needed
    if (_data == null || _data.Length < length)
        _data = new byte[length];
    int bytesread = _fs.Read( _data, 0, length ); 
    Marshal.Copy( _data, 0, buffer, bytesread );
    if ( bytesread < length )
    {
        bytesread |= (int)BASSStreamProc.BASS_STREAMPROC_END; // set indicator flag
        _fs.Close();
    }
    return bytesread;
}
If you're into C# you might also make use of native pointers in an unsafe code block:
C#
unsafe private int MyFileProcUnsafe(int handle, IntPtr buffer, int length, IntPtr user)
{
    // simply cast the given IntPtr to a native pointer to byte values
    byte *data = (byte*)buffer;
    // read the file into the data pointer directly
    int bytesread = length;
    for (int a=0; a < length; a++)
    {
        int val = _fs.ReadByte();
        if (val != -1)
        {
            data[a] = (byte)val;   // set the value
        }
        else
        {
            bytesread = a;
            break;
        }
    }
    // end of the file/stream?
    if ( bytesread < length )
    {
        bytesread |= (int)BASSStreamProc.BASS_STREAMPROC_END; // set indicator flag
        _fs.Close();
    }
    return bytesread;
}
However, even if we directly access memory here, this is not really faster in that case, since we read the file byte per byte and also in a way perform a copy (we just use a single int buffer called val). So in essence when you need to read from a file you should take care, that the file access and read operation is fast an additional Marshal.Copy wouldn't count much in terms of performance. The only advantage we get here is actually, that we use less memory.
See Also

Reference