Hello,
Thank you for your BASS libraries. I have been using them for a cross-platform metronome application where low-latency is key, but I'm having trouble with WASAPI in exclusive mode.
Below, here's FreePascal code for a sound player that is a wrapper around the BASS and BASSWASAPI libraries. This code uses WASAPI in shared mode.
type
TWASAPISoundPlayer = class(TAbstractSoundPlayer)
protected
function GetDurationMs: Integer; override;
public
constructor Create(const FilePath: String);
destructor Destroy; override;
procedure Play; override;
end;
implementation
var
ClickChannel: HStream;
function WasapiProc(Buffer: Pointer; Length: DWORD; User: Pointer): DWORD; stdcall;
var
C: Integer;
begin
C := BASS_ChannelGetData(ClickChannel, buffer, length);
if (C < 0) then
begin
if (BASS_WASAPI_GetData(nil, BASS_DATA_AVAILABLE) <= 0) then
BASS_WASAPI_Stop(true);
Result := 0;
end else Result := C;
end;
constructor TWASAPISoundPlayer.Create(const FilePath: String);
begin
inherited Create;
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then
raise ENoSoundPlayer.Create('An incorrect version of BASS.DLL was loaded.');
BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 0);
BASS_SetConfig(BASS_CONFIG_BUFFER, 10);
BASS_Init(0, 48000, 0, 0, nil);
if (not(BASS_WASAPI_Init(-1, 48000, 2, 0, 0.1, 0.05, @WasapiProc, nil))) then
begin
BASS_Free;
raise ENoSoundPlayer.Create('Can''t initialize WASAPI.');
end;
ClickChannel := BASS_StreamCreateFile(false, PChar(FilePath), 0, 0, BASS_STREAM_DECODE or BASS_SAMPLE_FLOAT);
end;
destructor TWASAPISoundPlayer.Destroy;
begin
BASS_WASAPI_Free;
BASS_Free;
inherited Destroy;
end;
procedure TWASAPISoundPlayer.Play;
begin
BASS_ChannelSetPosition(ClickChannel, 0, BASS_POS_BYTE);
BASS_WASAPI_Start;
end;
function TWASAPISoundPlayer.GetDurationMs: Integer;
var
LengthBytes: Integer;
begin
LengthBytes := BASS_ChannelGetLength(ClickChannel, BASS_POS_BYTE);
Result := Round(BASS_ChannelBytes2Seconds(ClickChannel, LengthBytes) * MILLISECONDS_IN_SECONDS);
end;
Each time the Play method is called, a very short (44 ms) clicking sound is played. I have attached it to this post (click.wav).
So, if I wanted, say, to play the clicking sound every 750 ms, the code would look more-or-less like this:
SoundPlayer := TWASAPISoundPlayer.Create('click.wav');
while (true) do
begin
SoundPlayer.Play;
Sleep(750);
end;
The code above works fine with WASAPI in shared mode, as heard in wasapi-shared.mp3. (The echo is because it was recorded with a cheap microphone.) However, to achieve maximum latency, I want to use WASAPI in exclusive mode instead.
If I call BASS_WASAPI_Init with the BASS_WASAPI_EXCLUSIVE flag, the click is sometimes garbled, as heard in wasapi-exclusive-reset.mp3. In this example, the fifth click is garbled.
I can fix this problem by setting the reset flag of the BASS_WASAPI_Stop method to false, but then, I get buffering issues, as heard in wasapi-exclusive-noreset.mp3. Increasing the buffer size and the update period somewhat helps, but it doesn't fully eliminate the issue, and I need to raise them to a point where WASAPI is no longer low-latency.
What am I missing?