unit UMusic;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
UTime,
Classes;
type
TNoteType = (ntFreestyle, ntNormal, ntGolden);
(**
* TLineFragment represents a fragment of a lyrics line.
* This is a text-fragment (e.g. a syllable) assigned to a note pitch,
* represented by a bar in the sing-screen.
*)
PLineFragment = ^TLineFragment;
TLineFragment = record
Color: integer;
Start: integer; // beat the fragment starts at
Length: integer; // length in beats
Tone: integer; // full range tone
Text: string; // text assigned to this fragment (a syllable, word, etc.)
NoteType: TNoteType; // note-type: golden-note/freestyle etc.
end;
(**
* TLine represents one lyrics line and consists of multiple
* notes.
*)
PLine = ^TLine;
TLine = record
Start: integer; // the start beat of this line (<> start beat of the first note of this line)
Lyric: string;
LyricWidth: real; // @deprecated: width of the line in pixels.
// Do not use this as the width is not correct.
// Use TLyricsEngine.GetUpperLine().Width instead.
End_: integer;
BaseNote: integer;
HighNote: integer; // index of last note in line (= High(Note)?)
TotalNotes: integer; // value of all notes in the line
LastLine: boolean;
Note: array of TLineFragment;
end;
(**
* TLines stores sets of lyric lines and information on them.
* Normally just one set is defined but in duet mode it might for example
* contain two sets.
*)
TLines = record
Current: integer; // for drawing of current line
High: integer; // (= High(Line)?)
Number: integer;
Resolution: integer;
NotesGAP: integer;
ScoreValue: integer;
Line: array of TLine;
end;
(**
* TLyricsState contains all information concerning the
* state of the lyrics, e.g. the current beat or duration of the lyrics.
*)
TLyricsState = class
private
Timer: TRelativeTimer; // keeps track of the current time
public
OldBeat: integer; // previous discovered beat
CurrentBeat: integer; // current beat (rounded)
MidBeat: real; // current beat (float)
// now we use this for super synchronization!
// only used when analyzing voice
// TODO: change ...D to ...Detect(ed)
OldBeatD: integer; // previous discovered beat
CurrentBeatD: integer; // current discovered beat (rounded)
MidBeatD: real; // current discovered beat (float)
// we use this for audible clicks
// TODO: Change ...C to ...Click
OldBeatC: integer; // previous discovered beat
CurrentBeatC: integer;
MidBeatC: real; // like CurrentBeatC
OldLine: integer; // previous displayed sentence
StartTime: real; // time till start of lyrics (= Gap)
TotalTime: real; // total song time
constructor Create();
procedure Pause();
procedure Resume();
procedure Reset();
procedure UpdateBeats();
(**
* current song time (in seconds) used as base-timer for lyrics etc.
*)
function GetCurrentTime(): real;
procedure SetCurrentTime(Time: real);
end;
const
FFTSize = 512; // size of FFT data (output: FFTSize/2 values)
type
TFFTData = array[0..(FFTSize div 2)-1] of Single;
type
PPCMStereoSample = ^TPCMStereoSample;
TPCMStereoSample = array[0..1] of SmallInt;
TPCMData = array[0..511] of TPCMStereoSample;
type
TStreamStatus = (ssStopped, ssPlaying, ssPaused);
const
StreamStatusStr: array[TStreamStatus] of string =
('Stopped', 'Playing', 'Paused');
type
TAudioSampleFormat = (
asfU8, asfS8, // unsigned/signed 8 bits
asfU16LSB, asfS16LSB, // unsigned/signed 16 bits (endianness: LSB)
asfU16MSB, asfS16MSB, // unsigned/signed 16 bits (endianness: MSB)
asfU16, asfS16, // unsigned/signed 16 bits (endianness: System)
asfS24, // signed 24 bits (endianness: System)
asfS32, // signed 32 bits (endianness: System)
asfFloat // float
);
const
// Size of one sample (one channel only) in bytes
AudioSampleSize: array[TAudioSampleFormat] of integer = (
1, 1, // asfU8, asfS8
2, 2, // asfU16LSB, asfS16LSB
2, 2, // asfU16MSB, asfS16MSB
2, 2, // asfU16, asfS16
3, // asfS24
4, // asfS32
4 // asfFloat
);
const
CHANNELMAP_LEFT = 1;
CHANNELMAP_RIGHT = 2;
CHANNELMAP_FRONT = CHANNELMAP_LEFT or CHANNELMAP_RIGHT;
type
TAudioFormatInfo = class
private
fSampleRate : double;
fChannels : byte;
fFormat : TAudioSampleFormat;
fFrameSize : integer;
procedure SetChannels(Channels: byte);
procedure SetFormat(Format: TAudioSampleFormat);
procedure UpdateFrameSize();
function GetBytesPerSec(): double;
public
constructor Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat);
function Copy(): TAudioFormatInfo;
(**
* Returns the inverse ratio of the size of data in this format to its
* size in a given target format.
* Example: SrcSize*SrcInfo.GetRatio(TgtInfo) = TgtSize
*)
function GetRatio(TargetInfo: TAudioFormatInfo): double;
property SampleRate: double read fSampleRate write fSampleRate;
property Channels: byte read fChannels write SetChannels;
property Format: TAudioSampleFormat read fFormat write SetFormat;
property FrameSize: integer read fFrameSize;
property BytesPerSec: double read GetBytesPerSec;
end;
type
TSoundEffect = class
public
EngineData: Pointer; // can be used for engine-specific data
procedure Callback(Buffer: PChar; BufSize: integer); virtual; abstract;
end;
TVoiceRemoval = class(TSoundEffect)
public
procedure Callback(Buffer: PChar; BufSize: integer); override;
end;
type
TSyncSource = class
function GetClock(): real; virtual; abstract;
end;
TAudioProcessingStream = class;
TOnCloseHandler = procedure(Stream: TAudioProcessingStream);
TAudioProcessingStream = class
protected
OnCloseHandlers: array of TOnCloseHandler;
function GetLength(): real; virtual; abstract;
function GetPosition(): real; virtual; abstract;
procedure SetPosition(Time: real); virtual; abstract;
function GetLoop(): boolean; virtual; abstract;
procedure SetLoop(Enabled: boolean); virtual; abstract;
procedure PerformOnClose();
public
function GetAudioFormatInfo(): TAudioFormatInfo; virtual; abstract;
procedure Close(); virtual; abstract;
(**
* Adds a new OnClose action handler.
* The handlers are performed in the order they were added.
* If not stated explicitely, member-variables might have been invalidated
* already. So do not use any member (variable/method/...) if you are not
* sure it is valid.
*)
procedure AddOnCloseHandler(Handler: TOnCloseHandler);
property Length: real read GetLength;
property Position: real read GetPosition write SetPosition;
property Loop: boolean read GetLoop write SetLoop;
end;
TAudioSourceStream = class(TAudioProcessingStream)
protected
function IsEOF(): boolean; virtual; abstract;
function IsError(): boolean; virtual; abstract;
public
function ReadData(Buffer: PChar; BufferSize: integer): integer; virtual; abstract;
property EOF: boolean read IsEOF;
property Error: boolean read IsError;
end;
(*
* State-Chart for playback-stream state transitions
* []: Transition, (): State
*
* /---[Play/FadeIn]--->-\ /-------[Pause]----->-\
* -[Create]->(Stop) (Play) (Pause)
* \\-<-[Stop/EOF*/Error]-/ \-<---[Play/FadeIn]--//
* \-<------------[Stop/EOF*/Error]--------------/
*
* *: if not looped, otherwise stream is repeated
* Note: SetPosition() does not change the state.
*)
TAudioPlaybackStream = class(TAudioProcessingStream)
protected
SyncSource: TSyncSource;
AvgSyncDiff: double;
SourceStream: TAudioSourceStream;
function GetLatency(): double; virtual; abstract;
function GetStatus(): TStreamStatus; virtual; abstract;
function GetVolume(): single; virtual; abstract;
procedure SetVolume(Volume: single); virtual; abstract;
function Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
procedure FillBufferWithFrame(Buffer: PChar; BufferSize: integer; Frame: PChar; FrameSize: integer);
public
(**
* Opens a SourceStream for playback.
* Note that the caller (not the TAudioPlaybackStream) is responsible to
* free the SourceStream after the Playback-Stream is closed.
* You may use an OnClose-handler to achieve this. GetSourceStream()
* guarantees to deliver this method's SourceStream parameter to
* the OnClose-handler. Freeing SourceStream at OnClose is allowed.
*)
function Open(SourceStream: TAudioSourceStream): boolean; virtual; abstract;
procedure Play(); virtual; abstract;
procedure Pause(); virtual; abstract;
procedure Stop(); virtual; abstract;
procedure FadeIn(Time: real; TargetVolume: single); virtual; abstract;
procedure GetFFTData(var data: TFFTData); virtual; abstract;
function GetPCMData(var data: TPCMData): Cardinal; virtual; abstract;
procedure AddSoundEffect(Effect: TSoundEffect); virtual; abstract;
procedure RemoveSoundEffect(Effect: TSoundEffect); virtual; abstract;
procedure SetSyncSource(SyncSource: TSyncSource);
function GetSourceStream(): TAudioSourceStream;
property Status: TStreamStatus read GetStatus;
property Volume: single read GetVolume write SetVolume;
end;
TAudioDecodeStream = class(TAudioSourceStream)
end;
TAudioVoiceStream = class(TAudioSourceStream)
protected
FormatInfo: TAudioFormatInfo;
ChannelMap: integer;
public
destructor Destroy; override;
function Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean; virtual;
procedure Close(); override;
procedure WriteData(Buffer: PChar; BufferSize: integer); virtual; abstract;
function GetAudioFormatInfo(): TAudioFormatInfo; override;
function GetLength(): real; override;
function GetPosition(): real; override;
procedure SetPosition(Time: real); override;
function GetLoop(): boolean; override;
procedure SetLoop(Enabled: boolean); override;
end;
type
// soundcard output-devices information
TAudioOutputDevice = class
public
Name: string; // soundcard name
end;
TAudioOutputDeviceList = array of TAudioOutputDevice;
type
IGenericPlayback = Interface
['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
function GetName: String;
function Open(const Filename: string): boolean; // true if succeed
procedure Close;
procedure Play;
procedure Pause;
procedure Stop;
procedure SetPosition(Time: real);
function GetPosition: real;
property Position: real read GetPosition write SetPosition;
end;
IVideoPlayback = Interface( IGenericPlayback )
['{3574C40C-28AE-4201-B3D1-3D1F0759B131}']
function Init(): boolean;
function Finalize: boolean;
procedure GetFrame(Time: Extended); // WANT TO RENAME THESE TO BE MORE GENERIC
procedure DrawGL(Screen: integer); // WANT TO RENAME THESE TO BE MORE GENERIC
end;
IVideoVisualization = Interface( IVideoPlayback )
['{5AC17D60-B34D-478D-B632-EB00D4078017}']
end;
IAudioPlayback = Interface( IGenericPlayback )
['{E4AE0B40-3C21-4DC5-847C-20A87E0DFB96}']
function InitializePlayback: boolean;
function FinalizePlayback: boolean;
function GetOutputDeviceList(): TAudioOutputDeviceList;
procedure SetAppVolume(Volume: single);
procedure SetVolume(Volume: single);
procedure SetLoop(Enabled: boolean);
procedure FadeIn(Time: real; TargetVolume: single);
procedure SetSyncSource(SyncSource: TSyncSource);
procedure Rewind;
function Finished: boolean;
function Length: real;
// Sounds
// TODO:
// add a TMediaDummyPlaybackStream implementation that will
// be used by the TSoundLib whenever OpenSound() fails, so checking for
// nil-pointers is not neccessary anymore.
// PlaySound/StopSound will be removed then, OpenSound will be renamed to
// CreateSound.
function OpenSound(const Filename: String): TAudioPlaybackStream;
procedure PlaySound(Stream: TAudioPlaybackStream);
procedure StopSound(Stream: TAudioPlaybackStream);
// Equalizer
procedure GetFFTData(var Data: TFFTData);
// Interface for Visualizer
function GetPCMData(var Data: TPCMData): Cardinal;
function CreateVoiceStream(ChannelMap: integer; FormatInfo: TAudioFormatInfo): TAudioVoiceStream;
end;
IGenericDecoder = Interface
['{557B0E9A-604D-47E4-B826-13769F3E10B7}']
function GetName(): String;
function InitializeDecoder(): boolean;
function FinalizeDecoder(): boolean;
//function IsSupported(const Filename: string): boolean;
end;
(*
IVideoDecoder = Interface( IGenericDecoder )
['{2F184B2B-FE69-44D5-9031-0A2462391DCA}']
function Open(const Filename: string): TVideoDecodeStream;
end;
*)
IAudioDecoder = Interface( IGenericDecoder )
['{AB47B1B6-2AA9-4410-BF8C-EC79561B5478}']
function Open(const Filename: string): TAudioDecodeStream;
end;
IAudioInput = Interface
['{A5C8DA92-2A0C-4AB2-849B-2F7448C6003A}']
function GetName: String;
function InitializeRecord: boolean;
function FinalizeRecord(): boolean;
procedure CaptureStart;
procedure CaptureStop;
end;
type
TAudioConverter = class
protected
fSrcFormatInfo: TAudioFormatInfo;
fDstFormatInfo: TAudioFormatInfo;
public
function Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean; virtual;
destructor Destroy(); override;
(**
* Converts the InputBuffer and stores the result in OutputBuffer.
* If the result is not -1, InputSize will be set to the actual number of
* input-buffer bytes used.
* Returns the number of bytes written to the output-buffer or -1 if an error occured.
*)
function Convert(InputBuffer: PChar; OutputBuffer: PChar; var InputSize: integer): integer; virtual; abstract;
(**
* Destination/Source size ratio
*)
function GetRatio(): double; virtual; abstract;
function GetOutputBufferSize(InputSize: integer): integer; virtual; abstract;
property SrcFormatInfo: TAudioFormatInfo read fSrcFormatInfo;
property DstFormatInfo: TAudioFormatInfo read fDstFormatInfo;
end;
(* TODO
const
SOUNDID_START = 0;
SOUNDID_BACK = 1;
SOUNDID_SWOOSH = 2;
SOUNDID_CHANGE = 3;
SOUNDID_OPTION = 4;
SOUNDID_CLICK = 5;
LAST_SOUNDID = SOUNDID_CLICK;
BaseSoundFilenames: array[0..LAST_SOUNDID] of string = (
'%SOUNDPATH%/Common start.mp3', // Start
'%SOUNDPATH%/Common back.mp3', // Back
'%SOUNDPATH%/menu swoosh.mp3', // Swoosh
'%SOUNDPATH%/select music change music 50.mp3', // Change
'%SOUNDPATH%/option change col.mp3', // Option
'%SOUNDPATH%/rimshot022b.mp3' // Click
{
'%SOUNDPATH%/bassdrumhard076b.mp3', // Drum (unused)
'%SOUNDPATH%/hihatclosed068b.mp3', // Hihat (unused)
'%SOUNDPATH%/claps050b.mp3', // Clap (unused)
'%SOUNDPATH%/Shuffle.mp3' // Shuffle (unused)
}
);
*)
type
TSoundLibrary = class
private
// TODO
//Sounds: array of TAudioPlaybackStream;
public
// TODO: move sounds to the private section
// and provide IDs instead.
Start: TAudioPlaybackStream;
Back: TAudioPlaybackStream;
Swoosh: TAudioPlaybackStream;
Change: TAudioPlaybackStream;
Option: TAudioPlaybackStream;
Click: TAudioPlaybackStream;
BGMusic: TAudioPlaybackStream;
constructor Create();
destructor Destroy(); override;
procedure LoadSounds();
procedure UnloadSounds();
procedure StartBgMusic();
procedure PauseBgMusic();
// TODO
//function AddSound(Filename: string): integer;
//procedure RemoveSound(ID: integer);
//function GetSound(ID: integer): TAudioPlaybackStream;
//property Sound[ID: integer]: TAudioPlaybackStream read GetSound; default;
end;
var
// TODO: JB --- THESE SHOULD NOT BE GLOBAL
Lines: array of TLines;
LyricsState: TLyricsState;
SoundLib: TSoundLibrary;
procedure InitializeSound;
procedure InitializeVideo;
procedure FinalizeMedia;
function Visualization(): IVideoPlayback;
function VideoPlayback(): IVideoPlayback;
function AudioPlayback(): IAudioPlayback;
function AudioInput(): IAudioInput;
function AudioDecoders(): TInterfaceList;
function MediaManager: TInterfaceList;
procedure DumpMediaInterfaces();
implementation
uses
sysutils,
math,
UIni,
UMain,
UCommandLine,
URecord,
ULog;
var
DefaultVideoPlayback : IVideoPlayback;
DefaultVisualization : IVideoPlayback;
DefaultAudioPlayback : IAudioPlayback;
DefaultAudioInput : IAudioInput;
AudioDecoderList : TInterfaceList;
MediaInterfaceList : TInterfaceList;
constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat);
begin
inherited Create();
fChannels := Channels;
fSampleRate := SampleRate;
fFormat := Format;
UpdateFrameSize();
end;
procedure TAudioFormatInfo.SetChannels(Channels: byte);
begin
fChannels := Channels;
UpdateFrameSize();
end;
procedure TAudioFormatInfo.SetFormat(Format: TAudioSampleFormat);
begin
fFormat := Format;
UpdateFrameSize();
end;
function TAudioFormatInfo.GetBytesPerSec(): double;
begin
Result := FrameSize * SampleRate;
end;
procedure TAudioFormatInfo.UpdateFrameSize();
begin
fFrameSize := AudioSampleSize[fFormat] * fChannels;
end;
function TAudioFormatInfo.Copy(): TAudioFormatInfo;
begin
Result := TAudioFormatInfo.Create(Self.Channels, Self.SampleRate, Self.Format);
end;
function TAudioFormatInfo.GetRatio(TargetInfo: TAudioFormatInfo): double;
begin
Result := (TargetInfo.FrameSize / Self.FrameSize) *
(TargetInfo.SampleRate / Self.SampleRate)
end;
function MediaManager: TInterfaceList;
begin
if (not assigned(MediaInterfaceList)) then
MediaInterfaceList := TInterfaceList.Create();
Result := MediaInterfaceList;
end;
function VideoPlayback(): IVideoPlayback;
begin
Result := DefaultVideoPlayback;
end;
function Visualization(): IVideoPlayback;
begin
Result := DefaultVisualization;
end;
function AudioPlayback(): IAudioPlayback;
begin
Result := DefaultAudioPlayback;
end;
function AudioInput(): IAudioInput;
begin
Result := DefaultAudioInput;
end;
function AudioDecoders(): TInterfaceList;
begin
Result := AudioDecoderList;
end;
procedure FilterInterfaceList(const IID: TGUID; InList, OutList: TInterfaceList);
var
i: integer;
obj: IInterface;
begin
if (not assigned(OutList)) then
Exit;
OutList.Clear;
for i := 0 to InList.Count-1 do
begin
if assigned(InList[i]) then
begin
// add object to list if it implements the interface searched for
if (InList[i].QueryInterface(IID, obj) = 0) then
OutList.Add(obj);
end;
end;
end;
procedure InitializeSound;
var
i: integer;
InterfaceList: TInterfaceList;
CurrentAudioDecoder: IAudioDecoder;
CurrentAudioPlayback: IAudioPlayback;
CurrentAudioInput: IAudioInput;
begin
// create a temporary list for interface enumeration
InterfaceList := TInterfaceList.Create();
// initialize all audio-decoders first
FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
begin
CurrentAudioDecoder := IAudioDecoder(InterfaceList[i]);
if (not CurrentAudioDecoder.InitializeDecoder()) then
begin
Log.LogError('Initialize failed, Removing - '+ CurrentAudioDecoder.GetName);
MediaManager.Remove(CurrentAudioDecoder);
end;
end;
// create and setup decoder-list (see AudioDecoders())
AudioDecoderList := TInterfaceList.Create;
FilterInterfaceList(IAudioDecoder, MediaManager, AudioDecoders);
// find and initialize playback interface
DefaultAudioPlayback := nil;
FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
begin
CurrentAudioPlayback := IAudioPlayback(InterfaceList[i]);
if (CurrentAudioPlayback.InitializePlayback()) then
begin
DefaultAudioPlayback := CurrentAudioPlayback;
break;
end;
Log.LogError('Initialize failed, Removing - '+ CurrentAudioPlayback.GetName);
MediaManager.Remove(CurrentAudioPlayback);
end;
// find and initialize input interface
DefaultAudioInput := nil;
FilterInterfaceList(IAudioInput, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
begin
CurrentAudioInput := IAudioInput(InterfaceList[i]);
if (CurrentAudioInput.InitializeRecord()) then
begin
DefaultAudioInput := CurrentAudioInput;
break;
end;
Log.LogError('Initialize failed, Removing - '+ CurrentAudioInput.GetName);
MediaManager.Remove(CurrentAudioInput);
end;
InterfaceList.Free;
// Update input-device list with registered devices
AudioInputProcessor.UpdateInputDeviceConfig();
// Load in-game sounds
SoundLib := TSoundLibrary.Create;
end;
procedure InitializeVideo();
var
i: integer;
InterfaceList: TInterfaceList;
VideoInterface: IVideoPlayback;
VisualInterface: IVideoVisualization;
begin
InterfaceList := TInterfaceList.Create;
// initialize and set video-playback singleton
DefaultVideoPlayback := nil;
FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
begin
VideoInterface := IVideoPlayback(InterfaceList[i]);
if (VideoInterface.Init()) then
begin
DefaultVideoPlayback := VideoInterface;
break;
end;
Log.LogError('Initialize failed, Removing - '+ VideoInterface.GetName);
MediaManager.Remove(VideoInterface);
end;
// initialize and set visualization singleton
DefaultVisualization := nil;
FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
begin
VisualInterface := IVideoVisualization(InterfaceList[i]);
if (VisualInterface.Init()) then
begin
DefaultVisualization := VisualInterface;
break;
end;
Log.LogError('Initialize failed, Removing - '+ VisualInterface.GetName);
MediaManager.Remove(VisualInterface);
end;
InterfaceList.Free;
// now that we have all interfaces, we can dump them
// TODO: move this to another place
if FindCmdLineSwitch( cMediaInterfaces ) then
begin
DumpMediaInterfaces();
halt;
end;
end;
procedure UnloadMediaModules;
var
i: integer;
InterfaceList: TInterfaceList;
begin
FreeAndNil(AudioDecoderList);
DefaultAudioPlayback := nil;
DefaultAudioInput := nil;
DefaultVideoPlayback := nil;
DefaultVisualization := nil;
// create temporary interface list
InterfaceList := TInterfaceList.Create();
// finalize audio playback interfaces (should be done before the decoders)
FilterInterfaceList(IAudioPlayback, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
IAudioPlayback(InterfaceList[i]).FinalizePlayback();
// finalize audio input interfaces
FilterInterfaceList(IAudioInput, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
IAudioInput(InterfaceList[i]).FinalizeRecord();
// finalize audio decoder interfaces
FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
IAudioDecoder(InterfaceList[i]).FinalizeDecoder();
// finalize video interfaces
FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
IVideoPlayback(InterfaceList[i]).Finalize();
// finalize audio decoder interfaces
FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
IVideoVisualization(InterfaceList[i]).Finalize();
InterfaceList.Free;
// finally free interfaces (by removing all references to them)
FreeAndNil(MediaInterfaceList);
end;
procedure FinalizeMedia;
begin
// stop, close and free sounds
SoundLib.Free;
// stop and close music stream
if (AudioPlayback <> nil) then
AudioPlayback.Close;
// stop any active captures
if (AudioInput <> nil) then
AudioInput.CaptureStop;
if (VideoPlayback <> nil) then
VideoPlayback.Close;
if (Visualization <> nil) then
Visualization.Close;
UnloadMediaModules();
end;
procedure DumpMediaInterfaces();
begin
writeln( '' );
writeln( '--------------------------------------------------------------' );
writeln( ' In-use Media Interfaces ' );
writeln( '--------------------------------------------------------------' );
writeln( 'Registered Audio Playback Interface : ' + AudioPlayback.GetName );
writeln( 'Registered Audio Input Interface : ' + AudioInput.GetName );
writeln( 'Registered Video Playback Interface : ' + VideoPlayback.GetName );
writeln( 'Registered Visualization Interface : ' + Visualization.GetName );
writeln( '--------------------------------------------------------------' );
writeln( '' );
end;
{ TSoundLibrary }
constructor TSoundLibrary.Create();
begin
inherited;
LoadSounds();
end;
destructor TSoundLibrary.Destroy();
begin
UnloadSounds();
inherited;
end;
procedure TSoundLibrary.LoadSounds();
begin
UnloadSounds();
Start := AudioPlayback.OpenSound(SoundPath + 'Common start.mp3');
Back := AudioPlayback.OpenSound(SoundPath + 'Common back.mp3');
Swoosh := AudioPlayback.OpenSound(SoundPath + 'menu swoosh.mp3');
Change := AudioPlayback.OpenSound(SoundPath + 'select music change music 50.mp3');
Option := AudioPlayback.OpenSound(SoundPath + 'option change col.mp3');
Click := AudioPlayback.OpenSound(SoundPath + 'rimshot022b.mp3');
BGMusic := AudioPlayback.OpenSound(SoundPath + 'Bebeto_-_Loop010.mp3');
if (BGMusic <> nil) then
BGMusic.Loop := True;
end;
procedure TSoundLibrary.UnloadSounds();
begin
FreeAndNil(Start);
FreeAndNil(Back);
FreeAndNil(Swoosh);
FreeAndNil(Change);
FreeAndNil(Option);
FreeAndNil(Click);
FreeAndNil(BGMusic);
end;
(* TODO
function TSoundLibrary.GetSound(ID: integer): TAudioPlaybackStream;
begin
if ((ID >= 0) and (ID < Length(Sounds))) then
Result := Sounds[ID]
else
Result := nil;
end;
*)
procedure TSoundLibrary.StartBgMusic();
begin
if (TBackgroundMusicOption(Ini.BackgroundMusicOption) = bmoOn) and
(Soundlib.BGMusic <> nil) and not (Soundlib.BGMusic.Status = ssPlaying) then
begin
AudioPlayback.PlaySound(Soundlib.BGMusic);
end;
end;
procedure TSoundLibrary.PauseBgMusic();
begin
If (Soundlib.BGMusic <> nil) then
begin
Soundlib.BGMusic.Pause;
end;
end;
{ TVoiceRemoval }
procedure TVoiceRemoval.Callback(Buffer: PChar; BufSize: integer);
var
FrameIndex, FrameSize: integer;
Value: integer;
Sample: PPCMStereoSample;
begin
FrameSize := 2 * SizeOf(SmallInt);
for FrameIndex := 0 to (BufSize div FrameSize)-1 do
begin
Sample := PPCMStereoSample(Buffer);
// channel difference
Value := Sample[0] - Sample[1];
// clip
if (Value > High(SmallInt)) then
Value := High(SmallInt)
else if (Value < Low(SmallInt)) then
Value := Low(SmallInt);
// assign result
Sample[0] := Value;
Sample[1] := Value;
// increase to next frame
Inc(Buffer, FrameSize);
end;
end;
{ TVoiceRemoval }
constructor TLyricsState.Create();
begin
// create a triggered timer, so we can Pause() it, set the time
// and Resume() it afterwards for better synching.
Timer := TRelativeTimer.Create(true);
// reset state
Reset();
end;
procedure TLyricsState.Pause();
begin
Timer.Pause();
end;
procedure TLyricsState.Resume();
begin
Timer.Resume();
end;
procedure TLyricsState.SetCurrentTime(Time: real);
begin
// do not start the timer (if not started already),
// after setting the current time
Timer.SetTime(Time, false);
end;
function TLyricsState.GetCurrentTime(): real;
begin
Result := Timer.GetTime();
end;
(**
* Resets the timer and state of the lyrics.
* The timer will be stopped afterwards so you have to call Resume()
* to start the lyrics timer.
*)
procedure TLyricsState.Reset();
begin
Pause();
SetCurrentTime(0);
StartTime := 0;
TotalTime := 0;
OldBeat := -1;
MidBeat := -1;
CurrentBeat := -1;
OldBeatC := -1;
MidBeatC := -1;
CurrentBeatC := -1;
OldBeatD := -1;
MidBeatD := -1;
CurrentBeatD := -1;
end;
(**
* Updates the beat information (CurrentBeat/MidBeat/...) according to the
* current lyric time.
*)
procedure TLyricsState.UpdateBeats();
var
CurLyricsTime: real;
begin
CurLyricsTime := GetCurrentTime();
OldBeat := CurrentBeat;
MidBeat := GetMidBeat(CurLyricsTime - StartTime / 1000);
CurrentBeat := Floor(MidBeat);
OldBeatC := CurrentBeatC;
MidBeatC := GetMidBeat(CurLyricsTime - StartTime / 1000);
CurrentBeatC := Floor(MidBeatC);
OldBeatD := CurrentBeatD;
// MidBeatD = MidBeat with additional GAP
MidBeatD := -0.5 + GetMidBeat(CurLyricsTime - (StartTime + 120 + 20) / 1000);
CurrentBeatD := Floor(MidBeatD);
end;
{ TAudioConverter }
function TAudioConverter.Init(SrcFormatInfo: TAudioFormatInfo; DstFormatInfo: TAudioFormatInfo): boolean;
begin
fSrcFormatInfo := SrcFormatInfo.Copy();
fDstFormatInfo := DstFormatInfo.Copy();
Result := true;
end;
destructor TAudioConverter.Destroy();
begin
FreeAndNil(fSrcFormatInfo);
FreeAndNil(fDstFormatInfo);
end;
{ TAudioProcessingStream }
procedure TAudioProcessingStream.AddOnCloseHandler(Handler: TOnCloseHandler);
begin
if (@Handler <> nil) then
begin
SetLength(OnCloseHandlers, System.Length(OnCloseHandlers)+1);
OnCloseHandlers[High(OnCloseHandlers)] := @Handler;
end;
end;
procedure TAudioProcessingStream.PerformOnClose();
var i: integer;
begin
for i := 0 to High(OnCloseHandlers) do
begin
OnCloseHandlers[i](Self);
end;
end;
{ TAudioPlaybackStream }
function TAudioPlaybackStream.GetSourceStream(): TAudioSourceStream;
begin
Result := SourceStream;
end;
procedure TAudioPlaybackStream.SetSyncSource(SyncSource: TSyncSource);
begin
Self.SyncSource := SyncSource;
AvgSyncDiff := -1;
end;
(*
* Results an adjusted size of the input buffer size to keep the stream in sync
* with the SyncSource. If no SyncSource was assigned to this stream, the
* input buffer size will be returned, so this method will have no effect.
*
* These are the possible cases:
* - Result > BufferSize: stream is behind the sync-source (stream is too slow),
* (Result-BufferSize) bytes of the buffer must be skipped.
* - Result = BufferSize: stream is in sync,
* there is nothing to do.
* - Result < BufferSize: stream is ahead of the sync-source (stream is too fast),
* (BufferSize-Result) bytes of the buffer must be padded.
*)
function TAudioPlaybackStream.Synchronize(BufferSize: integer; FormatInfo: TAudioFormatInfo): integer;
var
TimeDiff: double;
TimeCorrectionFactor: double;
const
AVG_HISTORY_FACTOR = 0.9;
SYNC_THRESHOLD = 0.045;
MAX_SYNC_DIFF_TIME = 0.002;
begin
Result := BufferSize;
if (not assigned(SyncSource)) then
Exit;
if (BufferSize <= 0) then
Exit;
// difference between sync-source and stream position
// (negative if the music-stream's position is ahead of the master clock)
TimeDiff := SyncSource.GetClock() - (Position - GetLatency());
// calculate average time difference (some sort of weighted mean).
// The bigger AVG_HISTORY_FACTOR is, the smoother is the average diff.
// This means that older diffs are weighted more with a higher history factor
// than with a lower. Do not use a too low history factor. FFMpeg produces
// very instable timestamps (pts) for ogg due to some bugs. They may differ
// +-50ms from the real stream position. Without filtering those glitches we
// would synch without any need, resulting in ugly plopping sounds.
if (AvgSyncDiff = -1) then
AvgSyncDiff := TimeDiff
else
AvgSyncDiff := TimeDiff * (1-AVG_HISTORY_FACTOR) +
AvgSyncDiff * AVG_HISTORY_FACTOR;
// check if sync needed
if (Abs(AvgSyncDiff) >= SYNC_THRESHOLD) then
begin
// TODO: use SetPosition if diff is too large (>5s)
if (TimeDiff < 1) then
TimeCorrectionFactor := Sign(TimeDiff)*TimeDiff*TimeDiff
else
TimeCorrectionFactor := TimeDiff;
// calculate adapted buffer size
// reduce size of data to fetch if music is ahead, increase otherwise
Result := BufferSize + Round(TimeCorrectionFactor * FormatInfo.SampleRate) * FormatInfo.FrameSize;
if (Result < 0) then
Result := 0;
// reset average
AvgSyncDiff := -1;
end;
(*
DebugWriteln('Diff: ' + floattostrf(TimeDiff, ffFixed, 15, 3) +
'| SyS: ' + floattostrf(SyncSource.GetClock(), ffFixed, 15, 3) +
'| Pos: ' + floattostrf(Position, ffFixed, 15, 3) +
'| Avg: ' + floattostrf(AvgSyncDiff, ffFixed, 15, 3));
*)
end;
(*
* Fills a buffer with copies of the given frame or with 0 if frame.
*)
procedure TAudioPlaybackStream.FillBufferWithFrame(Buffer: PChar; BufferSize: integer; Frame: PChar; FrameSize: integer);
var
i: integer;
FrameCopyCount: integer;
begin
// the buffer must at least contain place for one copy of the frame.
if ((Buffer = nil) or (BufferSize <= 0) or (BufferSize < FrameSize)) then
Exit;
// no valid frame -> fill with 0
if ((Frame = nil) or (FrameSize <= 0)) then
begin
FillChar(Buffer[0], BufferSize, 0);
Exit;
end;
// number of frames to copy
FrameCopyCount := BufferSize div FrameSize;
// insert as many copies of frame into the buffer as possible
for i := 0 to FrameCopyCount-1 do
Move(Frame[0], Buffer[i*FrameSize], FrameSize);
end;
{ TAudioVoiceStream }
function TAudioVoiceStream.Open(ChannelMap: integer; FormatInfo: TAudioFormatInfo): boolean;
begin
Self.ChannelMap := ChannelMap;
Self.FormatInfo := FormatInfo.Copy();
// a voice stream is always mono, reassure the the format is correct
Self.FormatInfo.Channels := 1;
Result := true;
end;
destructor TAudioVoiceStream.Destroy;
begin
Close();
inherited;
end;
procedure TAudioVoiceStream.Close();
begin
PerformOnClose();
FreeAndNil(FormatInfo);
end;
function TAudioVoiceStream.GetAudioFormatInfo(): TAudioFormatInfo;
begin
Result := FormatInfo;
end;
function TAudioVoiceStream.GetLength(): real;
begin
Result := -1;
end;
function TAudioVoiceStream.GetPosition(): real;
begin
Result := -1;
end;
procedure TAudioVoiceStream.SetPosition(Time: real);
begin
end;
function TAudioVoiceStream.GetLoop(): boolean;
begin
Result := false;
end;
procedure TAudioVoiceStream.SetLoop(Enabled: boolean);
begin
end;
end.