aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioInput_Bass.pas
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-05-09 19:19:28 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-05-09 19:19:28 +0000
commitb5a738fa52c8b0f2212deb5febd2d7f0b8f6544f (patch)
tree3c2812cffdd035b385d5b0f0f8f5ea0702973739 /Game/Code/Classes/UAudioInput_Bass.pas
parent37744cee627605db0675efd3a6e0c42bd51c48d6 (diff)
downloadusdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.tar.gz
usdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.tar.xz
usdx-b5a738fa52c8b0f2212deb5febd2d7f0b8f6544f.zip
- input-source selection works now (with bass or portaudio with portmixer)
- audio-effects (DSP) interface for audio-playback plus a simple voice removal example (does not sound that good) - FFMpeg support for BASS - audio-clock for FFMpeg for GetPosition and synchronisation - more compatible seeking in FFMpeg - clean termination of the audio interfaces/streams (especially ffmpeg) - Audio output device enumeration (selection will be added later to the sounds option screen) - display of threshold and volume in the record-options screen - threshold and volume can be changed with the 'T' (threshold) and '+'/'-' (source volume) keys - added a FadeIn() method to the IAudioPlayback interface - some minor changes to the audio classes/screens - new base-class for audio-playback classes (used by bass, portaudio and sdl) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1078 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'Game/Code/Classes/UAudioInput_Bass.pas')
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas329
1 files changed, 256 insertions, 73 deletions
diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas
index a62ff22e..db49749e 100644
--- a/Game/Code/Classes/UAudioInput_Bass.pas
+++ b/Game/Code/Classes/UAudioInput_Bass.pas
@@ -27,21 +27,32 @@ uses
type
TAudioInput_Bass = class(TAudioInputBase)
+ private
+ function EnumDevices(): boolean;
public
function GetName: String; override;
function InitializeRecord: boolean; override;
- destructor Destroy; override;
+ function FinalizeRecord: boolean; override;
end;
TBassInputDevice = class(TAudioInputDevice)
- public
- DeviceIndex: integer; // index in TAudioInputProcessor.Device[]
- BassDeviceID: integer; // DeviceID used by BASS
+ private
RecordStream: HSTREAM;
+ BassDeviceID: DWORD; // DeviceID used by BASS
+ SingleIn: boolean;
+
+ DeviceIndex: integer; // index in TAudioInputProcessor.Device[]
- function Init(): boolean;
+ function SetInputSource(SourceIndex: integer): boolean;
+ function GetInputSource(): integer;
+ public
+ function Open(): boolean;
+ function Close(): boolean;
function Start(): boolean; override;
- procedure Stop(); override;
+ function Stop(): boolean; override;
+
+ function GetVolume(): integer; override;
+ procedure SetVolume(Volume: integer); override;
end;
var
@@ -62,83 +73,226 @@ function MicrophoneCallback(stream: HSTREAM; buffer: Pointer;
len: Cardinal; Card: Cardinal): boolean; stdcall;
begin
AudioInputProcessor.HandleMicrophoneData(buffer, len,
- AudioInputProcessor.Device[Card]);
+ AudioInputProcessor.DeviceList[Card]);
Result := true;
end;
{ TBassInputDevice }
-function TBassInputDevice.Init(): boolean;
+function TBassInputDevice.GetInputSource(): integer;
+var
+ SourceCnt: integer;
+ i: integer;
+begin
+ // get input-source config (subtract virtual device to get BASS indices)
+ SourceCnt := Length(Source)-1;
+
+ // find source
+ Result := -1;
+ for i := 0 to SourceCnt-1 do
+ begin
+ // check if current source is selected
+ if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+ begin
+ // selected source found
+ Result := i;
+ Exit;
+ end;
+ end;
+end;
+
+function TBassInputDevice.SetInputSource(SourceIndex: integer): boolean;
+var
+ SourceCnt: integer;
+ i: integer;
begin
Result := false;
- // TODO: Call once. Otherwise it's to slow
- if not BASS_RecordInit(BassDeviceID) then
+ // check for invalid source index
+ if (SourceIndex < 0) then
+ Exit;
+
+ // get input-source config (subtract virtual device to get BASS indices)
+ SourceCnt := Length(Source)-1;
+
+ // turn on selected source (turns off the others for single-in devices)
+ if (not BASS_RecordSetInput(SourceIndex, BASS_INPUT_ON)) then
begin
- Log.LogError('TBassInputDevice.Start: Error initializing device['+IntToStr(DeviceIndex)+']: ' +
- TAudioCore_Bass.ErrorGetString());
+ Log.LogError('BASS_RecordSetInput: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
Exit;
end;
+ // turn off all other sources (not needed for single-in devices)
+ if (not SingleIn) then
+ begin
+ for i := 0 to SourceCnt-1 do
+ begin
+ if (i = SourceIndex) then
+ continue;
+ // deselect source if selected
+ if ((BASS_RecordGetInput(i) and BASS_INPUT_OFF) = 0) then
+ BASS_RecordSetInput(i, BASS_INPUT_OFF);
+ end;
+ end;
+
Result := true;
end;
-{*
- * Start input-capturing on this device.
- * TODO: call BASS_RecordInit only once
- *}
-function TBassInputDevice.Start(): boolean;
+function TBassInputDevice.Open(): boolean;
var
- flags: Word;
+ FormatFlags: DWORD;
+ SourceIndex: integer;
const
latency = 20; // 20ms callback period (= latency)
begin
Result := false;
- // recording already started -> stop first
- if (RecordStream <> 0) then
- Stop();
-
- if not Init() then
+ if not BASS_RecordInit(BassDeviceID) then
+ begin
+ Log.LogError('BASS_RecordInit[device:'+IntToStr(DeviceIndex)+']: ' +
+ TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
Exit;
+ end;
- 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;
+ if (not TAudioCore_Bass.ConvertAudioFormatToBASSFlags(AudioFormat.Format, FormatFlags)) then
+ begin
+ Log.LogError('Unhandled sample-format', 'TBassInputDevice.Open');
+ Exit;
end;
- // start capturing
+ // start capturing in paused state
RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels,
- MakeLong(flags, latency),
+ MakeLong(FormatFlags or BASS_RECORD_PAUSE, latency),
@MicrophoneCallback, DeviceIndex);
if (RecordStream = 0) then
begin
+ Log.LogError('BASS_RecordStart: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Open');
BASS_RecordFree;
Exit;
end;
+ // save current source selection and select new source
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // nothing to do if default source is used
+ SourceRestore := -1;
+ end
+ else
+ begin
+ // store current source-index and select new source
+ SourceRestore := GetInputSource();
+ SetInputSource(SourceIndex);
+ end;
+
Result := true;
end;
-{*
- * Stop input-capturing on this device.
- *}
-procedure TBassInputDevice.Stop();
+{* Start input-capturing on this device. *}
+function TBassInputDevice.Start(): boolean;
+begin
+ Result := false;
+
+ // recording already started -> stop first
+ if (RecordStream <> 0) then
+ Stop();
+
+ // TODO: Do not open the device here (takes too much time).
+ if not Open() then
+ Exit;
+
+ if (not BASS_ChannelPlay(RecordStream, true)) then
+ begin
+ Log.LogError('BASS_ChannelPlay: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Start');
+ Exit;
+ end;
+
+ Result := true;
+end;
+
+{* Stop input-capturing on this device. *}
+function TBassInputDevice.Stop(): boolean;
begin
+ Result := false;
+
if (RecordStream = 0) then
Exit;
- // TODO: Don't free the device. Do this on close
- if (BASS_RecordSetDevice(BassDeviceID)) then
- BASS_RecordFree;
+ if (not BASS_RecordSetDevice(BassDeviceID)) then
+ Exit;
+
+ if (not BASS_ChannelStop(RecordStream)) then
+ begin
+ Log.LogError('BASS_ChannelStop: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Stop');
+ end;
+
+ // TODO: Do not close the device here (takes too much time).
+ Result := Close();
+end;
+
+function TBassInputDevice.Close(): boolean;
+begin
+ // restore source selection
+ if (SourceRestore >= 0) then
+ begin
+ SetInputSource(SourceRestore);
+ end;
+
+ // free data
+ if (not BASS_RecordFree()) then
+ begin
+ Log.LogError('BASS_RecordFree: ' + TAudioCore_Bass.ErrorGetString(), 'TBassInputDevice.Close');
+ Result := false;
+ end
+ else
+ begin
+ Result := true;
+ end;
+
RecordStream := 0;
end;
+function TBassInputDevice.GetVolume(): integer;
+var
+ SourceIndex: integer;
+begin
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // if default source used find selected source
+ SourceIndex := GetInputSource();
+ if (SourceIndex = -1) then
+ begin
+ Result := 0;
+ Exit;
+ end;
+ end;
+
+ Result := LOWORD(BASS_RecordGetInput(SourceIndex));
+end;
+
+procedure TBassInputDevice.SetVolume(Volume: integer);
+var
+ SourceIndex: integer;
+begin
+ SourceIndex := Ini.InputDeviceConfig[CfgIndex].Input-1;
+ if (SourceIndex = -1) then
+ begin
+ // if default source used find selected source
+ SourceIndex := GetInputSource();
+ if (SourceIndex = -1) then
+ Exit;
+ end;
+
+ // clip volume to valid range
+ if (Volume > 100) then
+ Volume := 100
+ else if (Volume < 0) then
+ Volume := 0;
+
+ BASS_RecordSetInput(SourceIndex, BASS_INPUT_LEVEL or Volume);
+end;
+
{ TAudioInput_Bass }
@@ -147,7 +301,7 @@ begin
result := 'BASS_Input';
end;
-function TAudioInput_Bass.InitializeRecord(): boolean;
+function TAudioInput_Bass.EnumDevices(): boolean;
var
Descr: PChar;
SourceName: PChar;
@@ -157,12 +311,13 @@ var
DeviceIndex: integer;
SourceIndex: integer;
RecordInfo: BASS_RECORDINFO;
+ SelectedSourceIndex: integer;
begin
result := false;
DeviceIndex := 0;
BassDeviceID := 0;
- SetLength(AudioInputProcessor.Device, 0);
+ SetLength(AudioInputProcessor.DeviceList, 0);
// checks for recording devices and puts them into an array
while true do
@@ -171,7 +326,7 @@ begin
if (Descr = nil) then
break;
- // try to intialize the device
+ // try to initialize the device
if not BASS_RecordInit(BassDeviceID) then
begin
Log.LogStatus('Failed to initialize BASS Capture-Device['+inttostr(BassDeviceID)+']',
@@ -179,26 +334,25 @@ begin
end
else
begin
- SetLength(AudioInputProcessor.Device, DeviceIndex+1);
+ SetLength(AudioInputProcessor.DeviceList, DeviceIndex+1);
// TODO: free object on termination
BassDevice := TBassInputDevice.Create();
- AudioInputProcessor.Device[DeviceIndex] := BassDevice;
+ AudioInputProcessor.DeviceList[DeviceIndex] := BassDevice;
BassDevice.DeviceIndex := DeviceIndex;
BassDevice.BassDeviceID := BassDeviceID;
- BassDevice.Description := UnifyDeviceName(Descr, DeviceIndex);
+ BassDevice.Name := 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.
+ // FIXME: does BASS use LSB/MSB or system integer values for 16bit?
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, RecordInfo.freq, asfS16)
end
else
@@ -209,36 +363,59 @@ begin
BassDevice.AudioFormat := TAudioFormatInfo.Create(2, 44100, asfS16)
end;
+ // get info if multiple input-sources can be selected at once
+ BassDevice.SingleIn := RecordInfo.singlein;
+
+ // init list for capture buffers per channel
SetLength(BassDevice.CaptureChannel, BassDevice.AudioFormat.Channels);
- // get input sources
- SourceIndex := 0;
- BassDevice.MicSource := 0;
+ BassDevice.MicSource := -1;
+ BassDevice.SourceRestore := -1;
+
+ // add a virtual default source (will not change mixer-settings)
+ SetLength(BassDevice.Source, 1);
+ BassDevice.Source[0].Name := DEFAULT_SOURCE_NAME;
+
+ // add real input sources
+ SourceIndex := 1;
// 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
+ SourceName := BASS_RecordGetInputName(SourceIndex-1);
+
+ {$IFDEF DARWIN}
+ // Under MacOSX the SingStar Mics have an empty InputName.
+ // So, we have to add a hard coded Workaround for this problem
+ // FIXME: - Do we need this anymore? Doesn't the (new) default source already solve this problem?
+ // - Normally a nil return value of BASS_RecordGetInputName() means end-of-list, so maybe
+ // BASS is not able to detect any mic-sources (the default source will work then).
+ // - Does BASS_RecordGetInfo() return true or false? If it returns true in this case
+ // we could use this value to check if the device exists.
+ // Please check that, eddie.
+ // If it returns false, then the source is not detected and it does not make sense to add a second
+ // fake device here.
+ // What about BASS_RecordGetInput()? Does it return a value <> -1?
+ // - Does it even work at all with this fake source-index, now that input switching works?
+ // This info was not used before (sources were never switched), so it did not matter what source-index was used.
+ // But now BASS_RecordSetInput() will probably fail.
+ if ((SourceName = nil) and (SourceIndex = 1) and (Pos('USBMIC Serial#', Descr) > 0)) then
+ SourceName := 'Microphone'
+ {$ENDIF}
+
+ if (SourceName = nil) then
+ break;
+
+ SetLength(BassDevice.Source, Length(BassDevice.Source)+1);
+ BassDevice.Source[SourceIndex].Name := SourceName;
+
+ // get input-source info
Flags := BASS_RecordGetInput(SourceIndex);
- if ((Flags <> -1) and ((Flags and BASS_INPUT_TYPE_MIC) <> 0)) then
+ if (Flags <> -1) then
begin
- BassDevice.MicSource := SourceIndex;
+ // is the current source a mic-source?
+ if ((Flags and BASS_INPUT_TYPE_MIC) <> 0) then
+ BassDevice.MicSource := SourceIndex;
end;
Inc(SourceIndex);
@@ -250,16 +427,22 @@ begin
Inc(DeviceIndex);
end;
-
+
Inc(BassDeviceID);
end;
result := true;
end;
-destructor TAudioInput_Bass.Destroy;
+function TAudioInput_Bass.InitializeRecord(): boolean;
+begin
+ Result := EnumDevices();
+end;
+
+function TAudioInput_Bass.FinalizeRecord(): boolean;
begin
- inherited;
+ CaptureStop;
+ Result := inherited FinalizeRecord;
end;