unit UAudio_bass;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses Classes,
{$IFDEF win32}
windows,
{$ENDIF}
Messages,
SysUtils,
{$IFNDEF FPC}
Forms,
{$ENDIF}
bass,
ULog,
UMusic;
implementation
uses
{$IFDEF LAZARUS}
lclintf,
{$ENDIF}
URecord,
UIni,
UMain,
UCommon,
UThemes;
type
TMPModes = (mpNotReady, mpStopped, mpPlaying, mpRecording, mpSeeking,
mpPaused, mpOpen);
const
ModeStr: array[TMPModes] of string = ('Not ready', 'Stopped', 'Playing', 'Recording', 'Seeking', 'Paused', 'Open');
type
{$IFDEF UseBASSInput}
TAudio_bass = class( TInterfacedObject, IAudioPlayback, IAudioInput)
{$ELSE}
TAudio_bass = class( TInterfacedObject, IAudioPlayback)
{$ENDIF}
private
BassStart: hStream; // Wait, I've replaced this with BASS
BassBack: hStream; // It has almost all features we need
BassSwoosh: hStream;
BassChange: hStream; // Almost? It aleady has them all :)
BassOption: hStream;
BassClick: hStream;
BassDrum: hStream;
BassHihat: hStream;
BassClap: hStream;
BassShuffle: hStream;
//Custom Sounds
CustomSounds: array of TCustomSoundEntry;
Loaded: boolean;
Loop: boolean;
public
Bass: hStream;
function GetName: String;
{IAudioOutput interface}
procedure InitializePlayback;
procedure SetVolume(Volume: integer);
procedure SetMusicVolume(Volume: integer);
procedure SetLoop(Enabled: boolean);
function Open(Name: string): boolean; // true if succeed
procedure Rewind;
procedure MoveTo(Time: real);
procedure Play;
procedure Pause; //Pause Mod
procedure Stop;
procedure Close;
function Finished: boolean;
function Length: real;
function getPosition: real;
procedure PlayStart;
procedure PlayBack;
procedure PlaySwoosh;
procedure PlayChange;
procedure PlayOption;
procedure PlayClick;
procedure PlayDrum;
procedure PlayHihat;
procedure PlayClap;
procedure PlayShuffle;
procedure StopShuffle;
function LoadSoundFromFile(var hStream: hStream; Name: string): boolean;
//Equalizer
function GetFFTData: TFFTData;
// Interface for Visualizer
function GetPCMData(var data: TPCMData): Cardinal;
//Custom Sounds
function LoadCustomSound(const Filename: String): Cardinal;
procedure PlayCustomSound(const Index: Cardinal );
{IAudioInput interface}
{$IFDEF UseBASSInput}
procedure InitializeRecord;
procedure CaptureStart;
procedure CaptureStop;
procedure CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
procedure StopCard(Card: byte);
{$ENDIF}
end;
{$IFDEF UseBASSInput}
TBassSoundCard = class(TGenericSoundCard)
RecordStream: HSTREAM;
end;
{$ENDIF}
var
singleton_MusicBass : IAudioPlayback;
function TAudio_bass.GetName: String;
begin
result := 'BASS';
end;
procedure TAudio_bass.InitializePlayback;
var
Pet: integer;
S: integer;
begin
writeln( 'TAudio_bass.InitializePlayback' );
// Log.BenchmarkStart(4);
// Log.LogStatus('Initializing Playback Subsystem', 'Music Initialize');
Loaded := false;
Loop := false;
writeln( 'TAudio_bass BASS_Init' );
if not BASS_Init(1, 44100, 0, 0, nil) then
begin
Log.LogError('Could not initialize BASS', 'Error');
Exit;
end;
// Log.BenchmarkEnd(4); Log.LogBenchmark('--> Bass Init', 4);
// config playing buffer
// BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
// BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
// Log.LogStatus('Loading Sounds', 'Music Initialize');
writeln( 'TAudio_bass LoadSoundFromFile' );
// Log.BenchmarkStart(4);
LoadSoundFromFile(BassStart, SoundPath + 'Common Start.mp3');
LoadSoundFromFile(BassBack, SoundPath + 'Common Back.mp3');
LoadSoundFromFile(BassSwoosh, SoundPath + 'menu swoosh.mp3');
LoadSoundFromFile(BassChange, SoundPath + 'select music change music 50.mp3');
LoadSoundFromFile(BassOption, SoundPath + 'option change col.mp3');
LoadSoundFromFile(BassClick, SoundPath + 'rimshot022b.mp3');
// LoadSoundFromFile(BassDrum, SoundPath + 'bassdrumhard076b.mp3');
// LoadSoundFromFile(BassHihat, SoundPath + 'hihatclosed068b.mp3');
// LoadSoundFromFile(BassClap, SoundPath + 'claps050b.mp3');
// LoadSoundFromFile(BassShuffle, SoundPath + 'Shuffle.mp3');
// Log.BenchmarkEnd(4);
// Log.LogBenchmark('--> Loading Sounds', 4);
end;
procedure TAudio_bass.SetVolume(Volume: integer);
begin
//Old Sets Wave Volume
//BASS_SetVolume(Volume);
//New: Sets Volume only for this Application
BASS_SetConfig(BASS_CONFIG_GVOL_SAMPLE, Volume);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
BASS_SetConfig(BASS_CONFIG_GVOL_MUSIC, Volume);
end;
procedure TAudio_bass.SetMusicVolume(Volume: Integer);
begin
//Max Volume Prevention
if Volume > 100 then
Volume := 100;
if Volume < 0 then
Volume := 0;
//Set Volume
// TODO : jb_linux replace with something other than bass
BASS_ChannelSetAttributes (Bass, -1, Volume, -101);
end;
procedure TAudio_bass.SetLoop(Enabled: boolean);
begin
Loop := Enabled;
end;
function TAudio_bass.Open(Name: string): boolean;
begin
Loaded := false;
if FileExists(Name) then
begin
Bass := Bass_StreamCreateFile(false, pchar(Name), 0, 0, 0);
Loaded := true;
//Set Max Volume
SetMusicVolume (100);
end;
Result := Loaded;
end;
procedure TAudio_bass.Rewind;
begin
if Loaded then begin
end;
end;
procedure TAudio_bass.MoveTo(Time: real);
var
bytes: integer;
begin
bytes := BASS_ChannelSeconds2Bytes(Bass, Time);
BASS_ChannelSetPosition(Bass, bytes);
end;
procedure TAudio_bass.Play;
begin
if Loaded then
begin
if Loop then
BASS_ChannelPlay(Bass, True); // start from beginning... actually bass itself does not loop, nor does this TAudio_bass Class
BASS_ChannelPlay(Bass, False); // for setting position before playing
end;
end;
procedure TAudio_bass.Pause; //Pause Mod
begin
if Loaded then begin
BASS_ChannelPause(Bass); // Pauses Song
end;
end;
procedure TAudio_bass.Stop;
begin
Bass_ChannelStop(Bass);
end;
procedure TAudio_bass.Close;
begin
Bass_StreamFree(Bass);
end;
function TAudio_bass.Length: real;
var
bytes: integer;
begin
Result := 60;
bytes := BASS_ChannelGetLength(Bass);
Result := BASS_ChannelBytes2Seconds(Bass, bytes);
end;
function TAudio_bass.getPosition: real;
var
bytes: integer;
begin
Result := 0;
bytes := BASS_ChannelGetPosition(BASS);
Result := BASS_ChannelBytes2Seconds(BASS, bytes);
end;
function TAudio_bass.Finished: boolean;
begin
Result := false;
if BASS_ChannelIsActive(BASS) = BASS_ACTIVE_STOPPED then
begin
Result := true;
end;
end;
procedure TAudio_bass.PlayStart;
begin
BASS_ChannelPlay(BassStart, True);
end;
procedure TAudio_bass.PlayBack;
begin
BASS_ChannelPlay(BassBack, True);// then
end;
procedure TAudio_bass.PlaySwoosh;
begin
BASS_ChannelPlay(BassSwoosh, True);
end;
procedure TAudio_bass.PlayChange;
begin
BASS_ChannelPlay(BassChange, True);
end;
procedure TAudio_bass.PlayOption;
begin
BASS_ChannelPlay(BassOption, True);
end;
procedure TAudio_bass.PlayClick;
begin
BASS_ChannelPlay(BassClick, True);
end;
procedure TAudio_bass.PlayDrum;
begin
BASS_ChannelPlay(BassDrum, True);
end;
procedure TAudio_bass.PlayHihat;
begin
BASS_ChannelPlay(BassHihat, True);
end;
procedure TAudio_bass.PlayClap;
begin
BASS_ChannelPlay(BassClap, True);
end;
procedure TAudio_bass.PlayShuffle;
begin
BASS_ChannelPlay(BassShuffle, True);
end;
procedure TAudio_bass.StopShuffle;
begin
BASS_ChannelStop(BassShuffle);
end;
function TAudio_bass.LoadSoundFromFile(var hStream: hStream; Name: string): boolean;
var
L: Integer;
begin
if FileExists(Name) then
begin
Log.LogStatus('Loading Sound: "' + Name + '"', 'LoadSoundFromFile');
try
hStream := BASS_StreamCreateFile(False, pchar(Name), 0, 0, 0);
//Add CustomSound
L := High(CustomSounds) + 1;
SetLength (CustomSounds, L + 1);
CustomSounds[L].Filename := Name;
CustomSounds[L].Handle := hStream;
except
Log.LogError('Failed to open using BASS', 'LoadSoundFromFile');
end;
end
else
begin
Log.LogError('Sound not found: "' + Name + '"', 'LoadSoundFromFile');
exit;
end;
end;
//Equalizer
function TAudio_bass.GetFFTData: TFFTData;
var
Data: TFFTData;
begin
//Get Channel Data Mono and 256 Values
BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
end;
{*
* Copies interleaved PCM 16bit uint (maybe fake) stereo samples into data.
* Returns the number of frames (= stereo/mono sample)
*}
function TAudio_bass.GetPCMData(var data: TPCMData): Cardinal;
var
info: BASS_CHANNELINFO;
nBytes: DWORD;
begin
//Get Channel Data Mono and 256 Values
BASS_ChannelGetInfo(Bass, info);
ZeroMemory(@data, sizeof(TPCMData));
if (info.chans = 1) then
begin
// mono file -> add stereo channel
{
nBytes := BASS_ChannelGetData(Bass, @data[0], samples*sizeof(Smallint));
// interleave data
//CopyMemory(@data[1], @data[0], samples*sizeof(Smallint));
}
result := 0;
end
else
begin
// stereo file
nBytes := BASS_ChannelGetData(Bass, @data, sizeof(TPCMData));
end;
if(nBytes <= 0) then
result := 0
else
result := nBytes div sizeof(TPCMStereoSample);
end;
function TAudio_bass.LoadCustomSound(const Filename: String): Cardinal;
var
S: hStream;
I: Integer;
F: String;
begin
//Search for Sound in already loaded Sounds
F := UpperCase(SoundPath + FileName);
For I := 0 to High(CustomSounds) do
begin
if (UpperCase(CustomSounds[I].Filename) = F) then
begin
Result := I;
Exit;
end;
end;
if LoadSoundFromFile(S, SoundPath + Filename) then
Result := High(CustomSounds)
else
Result := 0;
end;
procedure TAudio_bass.PlayCustomSound(const Index: Cardinal );
begin
if Index <= High(CustomSounds) then
BASS_ChannelPlay(CustomSounds[Index].Handle, True);
end;
{$IFDEF UseBASSInput}
procedure TAudio_bass.InitializeRecord;
var
device: integer;
Descr: string;
input: integer;
input2: integer;
InputName: PChar;
Flags: integer;
mic: array[0..15] of integer;
SC: integer; // soundcard
SCI: integer; // soundcard input
No: integer;
function isDuplicate(Desc: String): Boolean;
var
I: Integer;
begin
Result := False;
//Check for Soundcard with same Description
For I := 0 to SC-1 do
begin
if (Recording.SoundCard[I].Description = Desc) then
begin
Result := True;
Break;
end;
end;
end;
begin
with Recording do
begin
// checks for recording devices and puts them into an array
SetLength(SoundCard, 0);
SC := 0;
Descr := BASS_RecordGetDeviceDescription(SC);
while (Descr <> '') do
begin
//If there is another SoundCard with the Same ID, Search an available Name
if (IsDuplicate(Descr)) then
begin
No:= 1; //Count of SoundCards with same Name
Repeat
Inc(No)
Until not IsDuplicate(Descr + ' (' + InttoStr(No) + ')');
//Set Description
Descr := Descr + ' (' + InttoStr(No) + ')';
end;
SetLength(SoundCard, SC+1);
// TODO: free object on termination
SoundCard[SC] := TBassSoundCard.Create();
SoundCard[SC].Description := Descr;
//Get Recording Inputs
SCI := 0;
BASS_RecordInit(SC);
InputName := BASS_RecordGetInputName(SCI);
{$IFDEF DARWIN}
// Under MacOSX the SingStar Mics have an empty
// InputName. So, we have to add a hard coded
// Workaround for this problem
if (InputName = nil) and (Pos( 'USBMIC Serial#', Descr) > 0) then
begin
InputName := 'Microphone';
end;
{$ENDIF}
SetLength(SoundCard[SC].Input, 1);
SoundCard[SC].Input[SCI].Name := InputName;
// process each input
while (InputName <> nil) do
begin
Flags := BASS_RecordGetInput(SCI);
if (SCI >= 1) {AND (Flags AND BASS_INPUT_OFF = 0)} then
begin
SetLength(SoundCard[SC].Input, SCI+1);
SoundCard[SC].Input[SCI].Name := InputName;
end;
//Set Mic Index
if ((Flags and BASS_INPUT_TYPE_MIC) = 1) then
SoundCard[SC].MicInput := SCI;
Inc(SCI);
InputName := BASS_RecordGetInputName(SCI);
end;
BASS_RecordFree;
Inc(SC);
Descr := BASS_RecordGetDeviceDescription(SC);
end; // while
end; // with Recording
end;
// TODO: code is used by all IAudioInput implementors
// -> move to a common superclass (TAudioInput_Generic?)
procedure TAudio_bass.CaptureStart;
var
S: integer;
SC: integer;
PlayerLeft, PlayerRight: integer;
CaptureSoundLeft, CaptureSoundRight: TSound;
begin
for S := 0 to High(Recording.Sound) do
Recording.Sound[S].BufferLong[0].Clear;
for SC := 0 to High(Ini.CardList) do begin
PlayerLeft := Ini.CardList[SC].ChannelL-1;
PlayerRight := Ini.CardList[SC].ChannelR-1;
if PlayerLeft >= PlayersPlay then PlayerLeft := -1;
if PlayerRight >= PlayersPlay then PlayerRight := -1;
if (PlayerLeft > -1) or (PlayerRight > -1) then begin
if (PlayerLeft > -1) then
CaptureSoundLeft := Recording.Sound[PlayerLeft]
else
CaptureSoundLeft := nil;
if (PlayerRight > -1) then
CaptureSoundRight := Recording.Sound[PlayerRight]
else
CaptureSoundRight := nil;
CaptureCard(SC, CaptureSoundLeft, CaptureSoundRight);
end;
end;
end;
// TODO: code is used by all IAudioInput implementors
// -> move to a common superclass (TAudioInput_Generic?)
procedure TAudio_bass.CaptureStop;
var
SC: integer;
PlayerLeft: integer;
PlayerRight: integer;
begin
for SC := 0 to High(Ini.CardList) do begin
PlayerLeft := Ini.CardList[SC].ChannelL-1;
PlayerRight := Ini.CardList[SC].ChannelR-1;
if PlayerLeft >= PlayersPlay then PlayerLeft := -1;
if PlayerRight >= PlayersPlay then PlayerRight := -1;
if (PlayerLeft > -1) or (PlayerRight > -1) then
StopCard(SC);
end;
end;
{*
* 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
Recording.HandleMicrophoneData(buffer, len, Recording.SoundCard[Card]);
Result := true;
end;
{*
* Start input-capturing on Soundcard specified by Card.
* Params:
* Card - soundcard index in Recording.SoundCard array
* CaptureSoundLeft - sound(-buffer) used for left channel capture data
* CaptureSoundRight - sound(-buffer) used for right channel capture data
*}
procedure TAudio_bass.CaptureCard(Card: byte; CaptureSoundLeft, CaptureSoundRight: TSound);
var
Error: integer;
ErrorMsg: string;
bassSoundCard: TBassSoundCard;
begin
if not BASS_RecordInit(Card) then
begin
Error := BASS_ErrorGetCode;
ErrorMsg := IntToStr(Error);
if Error = BASS_ERROR_DX then ErrorMsg := 'No DX5';
if Error = BASS_ERROR_ALREADY then ErrorMsg := 'The device has already been initialized';
if Error = BASS_ERROR_DEVICE then ErrorMsg := 'The device number specified is invalid';
if Error = BASS_ERROR_DRIVER then ErrorMsg := 'There is no available device driver';
Log.LogError('Error initializing record [' + IntToStr(Card) + ']');
Log.LogError('TAudio_bass.CaptureCard: Error initializing record: ' + ErrorMsg);
end
else
begin
bassSoundCard := TBassSoundCard(Recording.SoundCard[Card]);
bassSoundCard.CaptureSoundLeft := CaptureSoundLeft;
bassSoundCard.CaptureSoundRight := CaptureSoundRight;
// capture in 44.1kHz/stereo/16bit and a 20ms callback period
bassSoundCard.RecordStream :=
BASS_RecordStart(44100, 2, MakeLong(0, 20) , @MicrophoneCallback, Card);
end;
end;
{*
* Stop input-capturing on Soundcard specified by Card.
* Params:
* Card - soundcard index in Recording.SoundCard array
*}
procedure TAudio_bass.StopCard(Card: byte);
begin
BASS_RecordSetDevice(Card);
BASS_RecordFree;
end;
{$ENDIF}
initialization
singleton_MusicBass := TAudio_bass.create();
writeln( 'UAudio_Bass - Register' );
AudioManager.add( singleton_MusicBass );
finalization
writeln( 'UAudio_Bass - UnRegister' );
AudioManager.Remove( singleton_MusicBass );
end.