{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UMusic;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
SysUtils,
Classes,
UTime,
UBeatTimer,
UPath;
type
TNoteType = (ntFreestyle, ntNormal, ntGolden);
{**
* acoStretch: Stretch to screen width and height
* - ignores aspect
* + no borders
* + no image data loss
* acoCrop: Stretch to screen width or height, crop the other dimension
* + keeps aspect
* + no borders
* - frame borders are cropped (image data loss)
* acoLetterBox: Stretch to screen width, add bars at or crop top and bottom
* + keeps aspect
* - borders at top and bottom
* o top/bottom is cropped if width < height (unusual)
*}
TAspectCorrection = (acoStretch, acoCrop, acoLetterBox);
TRectCoords = record
Left, Right: double;
Upper, Lower: double;
end;
const
// ScoreFactor defines how a notehit of a specified notetype is
// measured in comparison to the other types
// 0 means this notetype is not rated at all
// 2 means a hit of this notetype will be rated w/ twice as much
// points as a hit of a notetype w/ ScoreFactor 1
ScoreFactor: array[TNoteType] of integer = (0, 1, 2);
type
(**
* 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: UTF8String; // 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: UTF8String;
//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;
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)
asfS32, // signed 32 bits (endianness: System)
asfFloat, // float
asfDouble // double
);
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: PByteArray; BufSize: integer); virtual; abstract;
end;
TVoiceRemoval = class(TSoundEffect)
public
procedure Callback(Buffer: PByteArray; BufSize: integer); override;
end;
type
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: PByteArray; 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
AvgSyncDiff: double; //** average difference between stream and sync clock
SyncSource: TSyncSource;
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: PByteArray; BufferSize: integer; Frame: PByteArray; 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 Fade(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: PByteArray; 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: UTF8String; // soundcard name
end;
TAudioOutputDeviceList = array of TAudioOutputDevice;
type
IGenericPlayback = Interface
['{63A5EBC3-3F4D-4F23-8DFB-B5165FCE33DD}']
function GetName: String;
end;
IVideo = interface
['{58DFC674-9168-41EA-B59D-A61307242B80}']
procedure Play;
procedure Pause;
procedure Stop;
procedure SetLoop(Enable: boolean);
function GetLoop(): boolean;
procedure SetPosition(Time: real);
function GetPosition: real;
procedure SetScreen(Screen: integer);
function GetScreen(): integer;
procedure SetScreenPosition(X, Y: double; Z: double = 0.0);
procedure GetScreenPosition(var X, Y, Z: double);
procedure SetWidth(Width: double);
function GetWidth(): double;
procedure SetHeight(Height: double);
function GetHeight(): double;
{**
* Sub-image of the video frame to draw.
* This can be used for zooming or similar purposes.
*}
procedure SetFrameRange(Range: TRectCoords);
function GetFrameRange(): TRectCoords;
function GetFrameAspect(): real;
procedure SetAspectCorrection(AspectCorrection: TAspectCorrection);
function GetAspectCorrection(): TAspectCorrection;
procedure SetAlpha(Alpha: double);
function GetAlpha(): double;
procedure SetReflectionSpacing(Spacing: double);
function GetReflectionSpacing(): double;
procedure GetFrame(Time: Extended);
procedure Draw();
procedure DrawReflection();
property Screen: integer read GetScreen;
property Width: double read GetWidth write SetWidth;
property Height: double read GetHeight write SetHeight;
property Alpha: double read GetAlpha write SetAlpha;
property ReflectionSpacing: double read GetReflectionSpacing write SetReflectionSpacing;
property FrameAspect: real read GetFrameAspect;
property AspectCorrection: TAspectCorrection read GetAspectCorrection write SetAspectCorrection;
property Loop: boolean read GetLoop write SetLoop;
property Position: real read GetPosition write SetPosition;
end;
IVideoPlayback = Interface( IGenericPlayback )
['{3574C40C-28AE-4201-B3D1-3D1F0759B131}']
function Init(): boolean;
function Finalize: boolean;
function Open(const FileName : IPath): IVideo;
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 Fade(Time: real; TargetVolume: single);
procedure SetSyncSource(SyncSource: TSyncSource);
procedure Rewind;
function Finished: boolean;
function Length: real;
function Open(const Filename: IPath): 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;
// 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: IPath): 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: IPath): TVideoDecodeStream;
procedure SetPosition(Time: real);
function GetPosition: real;
procedure UpdateTexture(Texture: glUint);
property Loop: boolean read GetLoop write SetLoop;
property Position: real read GetPosition write SetPosition;
end;
*)
IAudioDecoder = Interface( IGenericDecoder )
['{AB47B1B6-2AA9-4410-BF8C-EC79561B5478}']
function Open(const Filename: IPath): 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: PByteArray; OutputBuffer: PByteArray; 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 IPath = (
'%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;
Applause:TAudioPlaybackStream;
BGMusic: TAudioPlaybackStream;
constructor Create();
destructor Destroy(); override;
procedure LoadSounds();
procedure UnloadSounds();
procedure StartBgMusic();
procedure PauseBgMusic();
// TODO
//function AddSound(Filename: IPath): 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
math,
UIni,
UNote,
UCommandLine,
URecord,
ULog,
UPathUtils;
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 := InterfaceList[i] as IAudioDecoder;
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 := InterfaceList[i] as IAudioPlayback;
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 := InterfaceList[i] as IAudioInput;
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 := InterfaceList[i] as IVideoPlayback;
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 := InterfaceList[i] as IVideoVisualization;
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
(InterfaceList[i] as IAudioPlayback).FinalizePlayback();
// finalize audio input interfaces
FilterInterfaceList(IAudioInput, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
(InterfaceList[i] as IAudioInput).FinalizeRecord();
// finalize audio decoder interfaces
FilterInterfaceList(IAudioDecoder, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
(InterfaceList[i] as IAudioDecoder).FinalizeDecoder();
// finalize video interfaces
FilterInterfaceList(IVideoPlayback, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
(InterfaceList[i] as IVideoPlayback).Finalize();
// finalize audio decoder interfaces
FilterInterfaceList(IVideoVisualization, MediaManager, InterfaceList);
for i := 0 to InterfaceList.Count-1 do
(InterfaceList[i] as IVideoVisualization).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;
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.Append('Common start.mp3'));
Back := AudioPlayback.OpenSound(SoundPath.Append('Common back.mp3'));
Swoosh := AudioPlayback.OpenSound(SoundPath.Append('menu swoosh.mp3'));
Change := AudioPlayback.OpenSound(SoundPath.Append('select music change music 50.mp3'));
Option := AudioPlayback.OpenSound(SoundPath.Append('option change col.mp3'));
Click := AudioPlayback.OpenSound(SoundPath.Append('rimshot022b.mp3'));
Applause:= AudioPlayback.OpenSound(SoundPath.Append('Applause.mp3'));
BGMusic := AudioPlayback.OpenSound(SoundPath.Append('background track.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(Applause);
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: PByteArray; 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(PByte(Buffer), FrameSize);
end;
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;
{.$DEFINE LOG_SYNC}
(*
* 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;
FrameDiff: double;
FrameSkip: integer;
ReqFrames: integer;
MasterClock: real;
CurPosition: real;
const
AVG_HISTORY_FACTOR = 0.7;
SYNC_REPOS_THRESHOLD = 5.000;
SYNC_SOFT_THRESHOLD = 0.010;
begin
Result := BufferSize;
if (not assigned(SyncSource)) then
Exit;
if (BufferSize <= 0) then
Exit;
CurPosition := Position;
MasterClock := SyncSource.GetClock();
// difference between sync-source and stream position
// (negative if the music-stream's position is ahead of the master clock)
TimeDiff := MasterClock - CurPosition;
// 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;
{$IFDEF LOG_SYNC}
//Log.LogError(Format('c:%.3f | p:%.3f | d:%.3f | a:%.3f',
// [MasterClock, CurPosition, TimeDiff, AvgSyncDiff]), 'Synch');
{$ENDIF}
// check if we are out of sync
if (Abs(AvgSyncDiff) >= SYNC_REPOS_THRESHOLD) then
begin
{$IFDEF LOG_SYNC}
Log.LogError(Format('ReposSynch: %.3f > %.3f',
[Abs(AvgSyncDiff), SYNC_REPOS_THRESHOLD]), 'Synch');
{$ENDIF}
// diff far is too large -> reposition stream
// (resulting position might still be out of sync)
SetPosition(CurPosition + AvgSyncDiff);
// reset sync info
AvgSyncDiff := -1;
end
else if (Abs(AvgSyncDiff) >= SYNC_SOFT_THRESHOLD) then
begin
{$IFDEF LOG_SYNC}
Log.LogError(Format('SoftSynch: %.3f > %.3f',
[Abs(AvgSyncDiff), SYNC_SOFT_THRESHOLD]), 'Synch');
{$ENDIF}
// hard sync: directly jump to the current position
FrameSkip := Round(AvgSyncDiff * FormatInfo.SampleRate);
Result := BufferSize + FrameSkip * FormatInfo.FrameSize;
if (Result < 0) then
Result := 0;
// reset sync info
AvgSyncDiff := -1;
end;
end;
(*
* Fills a buffer with copies of the given Frame or with 0 if Frame is nil.
*)
procedure TAudioPlaybackStream.FillBufferWithFrame(Buffer: PByteArray; BufferSize: integer; Frame: PByteArray; 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.