Author Topic: How to play a short sound consistently in WASAPI Exclusive  (Read 591 times)

PascalB

  • Posts: 3
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.

Code: [Select]
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:

Code: [Select]
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?
« Last Edit: 5 Jun '24 - 00:35 by PascalB »

Ian @ un4seen

  • Administrator
  • Posts: 26266
I don't think the timing of the metronome sound will be very good with the code above, eg. the timing resolution is determined by the WASAPIPROC "length" value. I would recommend using a mixer (BASSmix add-on) to play the metronome sound for exact timing. I think that'll also fix your sound issue. You can find some example code in this thread:

   www.un4seen.com/forum/?topic=18314.msg128665#msg128665

To simplify things a bit, you can pass the mixer handle to BASS_WASAPI_Init instead of using a WASAPIPROC callback, like this:

Code: [Select]
BASS_WASAPI_Init(-1, 0, 0, BASS_WASAPI_EXCLUSIVE | BASS_WASAPI_EVENT, 0.05, 0, WASAPIPROC_BASS, (void*)mixer);

Please see the BASS_WASAPI_Init documentation for details on that.

PascalB

  • Posts: 3
The timing is indeed not as good as I would like it to be. I'll try out the mixer and let you know.

Thanks.

PascalB

  • Posts: 3
Using the mixer helped, thanks.