unit UAudioInput_Bass;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
Classes,
SysUtils,
URecord,
UMusic;
implementation
uses
UMain,
UIni,
ULog,
UAudioCore_Bass,
Windows,
bass;
type
TAudioInput_Bass = class(TAudioInputBase)
public
function GetName: String; override;
function InitializeRecord: boolean; override;
destructor Destroy; override;
end;
TBassInputDevice = class(TAudioInputDevice)
public
DeviceIndex: integer; // index in TAudioInputProcessor.Device[]
BassDeviceID: integer; // DeviceID used by BASS
RecordStream: HSTREAM;
function Init(): boolean;
function Start(): boolean; override;
procedure Stop(); override;
end;
var
singleton_AudioInputBass : IAudioInput;
{ Global }
{*
* Bass input capture callback.
* Params:
* stream - BASS input stream
* buffer - buffer of captured samples
* len - size of buffer in bytes
* user - players associated with left/right channels
*}
function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
len: Cardinal; Card: Cardinal): boolean; stdcall;
begin
AudioInputProcessor.HandleMicrophoneData(buffer, len,
AudioInputProcessor.Device[Card]);
Result := true;
end;
{ TBassInputDevice }
function TBassInputDevice.Init(): boolean;
begin
Result := false;
// TODO: Call once. Otherwise it's to slow
if not BASS_RecordInit(BassDeviceID) then
begin
Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
TAudioCore_Bass.ErrorGetString());
Exit;
end;
Result := true;
end;
{*
* Start input-capturing on this device.
* TODO: call BASS_RecordInit only once
*}
function TBassInputDevice.Start(): boolean;
var
flags: Word;
const
latency = 20; // 20ms callback period (= latency)
begin
Result := false;
// recording already started -> stop first
if (RecordStream <> 0) then
Stop();
if not Init() then
Exit;
case AudioFormat.Format of
asfS16: flags := 0;
asfFloat: flags := BASS_SAMPLE_FLOAT;
asfU8: flags := BASS_SAMPLE_8BITS;
else begin
Log.LogError('Unhandled sample-format', 'TBassInputDevice.Start');
Exit;
end;
end;
// start capturing
RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels,
MakeLong(flags, latency),
@MicrophoneCallback, DeviceIndex);
if (RecordStream = 0) then
begin
BASS_RecordFree;
Exit;
end;
Result := true;
end;
{*
* Stop input-capturing on this device.
*}
procedure TBassInputDevice.Stop();
begin
if (RecordStream = 0) then
Exit;
// TODO: Don't free the device. Do this on close
if (BASS_RecordSetDevice(BassDeviceID)) then
BASS_RecordFree;
RecordStream := 0;
end;
{ TAudioInput_Bass }
function TAudioInput_Bass.GetName: String;
begin
result := 'BASS_Input';
end;
function TAudioInput_Bass.InitializeRecord(): boolean;
var
Descr: PChar;
SourceName: PChar;
Flags: integer;
BassDeviceID: integer;
BassDevice: TBassInputDevice;
DeviceIndex: integer;
SourceIndex: integer;
RecordInfo: BASS_RECORDINFO;
begin
result := false;
DeviceIndex := 0;
BassDeviceID := 0;
SetLength(AudioInputProcessor.Device, 0);
// checks for recording devices and puts them into an array
while true do
begin
Descr := BASS_RecordGetDeviceDescription(BassDeviceID);
if (Descr = nil) then
break;
// try to intialize the device
if not BASS_RecordInit(BassDeviceID) then
begin
Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
'TAudioInput_Bass.InitializeRecord');
end
else
begin
SetLength(AudioInputProcessor.Device, DeviceIndex+1);
// TODO: free object on termination
BassDevice := TBassInputDevice.Create();
AudioInputProcessor.Device[DeviceIndex] := BassDevice;
BassDevice.DeviceIndex := DeviceIndex;
BassDevice.BassDeviceID := BassDeviceID;
BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
// retrieve recording device info
BASS_RecordGetInfo(RecordInfo);
// FIXME: does BASS use LSB/MSB or system integer values for 16bit?
// check if BASS has capture-freq. info
if (RecordInfo.freq > 0) then
begin
// use current input sample rate (available only on Windows Vista and OSX).
// Recording at this rate will give the best quality and performance, as no resampling is required.
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
end
else
begin
// BASS does not provide an explizit input channel count (except BASS_RECORDINFO.formats)
// but it doesn't fail if we use stereo input on a mono device
// -> use stereo by default
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
end;
SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
// get input sources
SourceIndex := 0;
BassDevice.MicSource := 0;
// process each input
while true do
begin
SourceName := BASS_RecordGetInputName(SourceIndex);
{$IFDEF DARWIN}
// Patch for SingStar USB-Microphones:
if ((SourceName = nil) and (SourceIndex = 0) and (Pos('Serial#', Descr) > 0)) then
SourceName := 'Microphone'
else
break;
{$ELSE}
if (SourceName = nil) then
break;
{$ENDIF}
SetLength(BassDevice.Source, SourceIndex+1);
BassDevice.Source[SourceIndex].Name :=
UnifyDeviceSourceName(SourceName, BassDevice.Description);
// set mic index
Flags := BASS_RecordGetInput(SourceIndex);
if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
begin
BassDevice.MicSource := SourceIndex;
end;
Inc(SourceIndex);
end;
// FIXME: this call hangs in FPC (windows) every 2nd time USDX is called.
// Maybe because the sound-device was not released properly?
BASS_RecordFree;
Inc(DeviceIndex);
end;
Inc(BassDeviceID);
end;
result := true;
end;
destructor TAudioInput_Bass.Destroy;
begin
inherited;
end;
initialization
singleton_AudioInputBass := TAudioInput_Bass.create();
AudioManager.add( singleton_AudioInputBass );
finalization
AudioManager.Remove( singleton_AudioInputBass );
end.