summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 76f1664)
raw | patch | inline | side by side (parent: 76f1664)
author | fgsfds <pvt.fgsfds@gmail.com> | |
Sat, 31 Aug 2019 18:06:53 +0000 (21:06 +0300) | ||
committer | fgsfds <pvt.fgsfds@gmail.com> | |
Sat, 31 Aug 2019 18:07:08 +0000 (21:07 +0300) |
src/engine/e_sound_al.inc | patch | blob | history | |
src/engine/e_soundfile.pas | patch | blob | history | |
src/engine/e_soundfile_wav.pas | patch | blob | history | |
src/engine/e_soundfile_xmp.pas | [new file with mode: 0644] | patch | blob |
src/game/Doom2DF.lpr | patch | blob | history | |
src/lib/openal/al.pas | patch | blob | history | |
src/lib/xmp/xmp.pas | [new file with mode: 0644] | patch | blob |
index d5701298b933e05c529d9ec15835d0b33f94cc66..dfe5919ea8ef52f374a57939a80ecc6ce474ca7e 100644 (file)
type
TSoundRec = record
- Fmt: TSoundFormat;
- Data: Pointer;
- DataLen: Integer;
+ Loader: TSoundLoader;
alBuffer: ALuint;
isMusic: Boolean;
Loops: Boolean;
procedure e_SoundUpdate();
var
+ e_SoundFormat: TSoundFormat; // desired sound format
e_SoundsArray: array of TSoundRec = nil;
e_ZeroPosition: array [0..2] of ALfloat;
e_ALError: ALenum = 0;
g_window, g_options, utils;
const
- NUM_SOURCES = 250;
+ NUM_SOURCES = 255; // + 1 stereo
+ NUM_STREAM_BUFFERS = 8;
+ STREAM_BUFSIZE = 8192;
MUSIC_SOURCE = 0;
var
+ SoundMuted: Boolean = False;
+ CurStream: DWORD = NO_SOUND_ID;
alDevice: PALCdevice = nil;
alContext: PALCcontext = nil;
- // sources for one-shot sounds
- // TBasicSounds have a built in source that lives and dies with them
- alSources: array of ALuint;
- alOwners: array of TBasicSound;
- SoundMuted: Boolean = False;
+ // sources for everything
+ alSources: array [0..NUM_SOURCES] of ALuint;
+ // last TBasicSound that has played on each source
+ alOwners: array [0..NUM_SOURCES] of TBasicSound;
+ // buffers for the music stream
+ alStreamBufs: array [0..NUM_STREAM_BUFFERS-1] of ALuint;
+ alStreamData: array [0..STREAM_BUFSIZE-1] of Byte;
+ alStreamAvail: Integer = NUM_STREAM_BUFFERS;
function CheckALError(): Boolean;
begin
function e_InitSoundSystem(NoOutput: Boolean = False): Boolean;
var
alExt, alRend, alVendor, alVer: string;
- DevName: string = '';
- alAttrs: array [0..4] of ALCint = (
+ WantDev: string = '';
+ WantAttrs: array [0..4] of ALCint = (
ALC_STEREO_SOURCES, 1,
ALC_MONO_SOURCES, NUM_SOURCES,
0
begin
Result := False;
- DevName := alcGetString(nil, ALC_DEVICE_SPECIFIER);
- e_LogWritefln('AL: available devices: %s', [DevName]);
+ WantDev := alcGetString(nil, ALC_DEVICE_SPECIFIER);
+ e_LogWritefln('AL: available devices: %s', [WantDev]);
// TODO: open a dummy device when NoOutput is true or something
- DevName := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER);
- e_LogWritefln('AL: trying to open device %s', [DevName]);
+ WantDev := alcGetString(nil, ALC_DEFAULT_DEVICE_SPECIFIER);
+ e_LogWritefln('AL: trying to open device %s', [WantDev]);
- alDevice := alcOpenDevice(PChar(DevName));
+ alDevice := alcOpenDevice(PChar(WantDev));
if alDevice = nil then
begin
- e_LogWritefln('AL: ERROR: could not open device %s: %s', [DevName, GetALError()]);
+ e_LogWritefln('AL: ERROR: could not open device %s: %s', [WantDev, GetALError()]);
exit;
end;
- alContext := alcCreateContext(alDevice, alAttrs);
+ alContext := alcCreateContext(alDevice, WantAttrs);
if alContext = nil then
begin
e_LogWritefln('AL: ERROR: could not create context: %s', [GetALError()]);
alcMakeContextCurrent(alContext);
+ // TODO: actually parse these from alc attributes or something
+ e_SoundFormat.SampleRate := 48000;
+ e_SoundFormat.SampleBits := 16;
+ e_SoundFormat.Channels := 2;
+
alVendor := alGetString(AL_VENDOR);
alRend := alGetString(AL_RENDERER);
alVer := alGetString(AL_VERSION);
alExt := alGetString(AL_EXTENSIONS);
e_LogWriteln('AL INFO:');
- e_LogWriteln(' Version: ' + alVer);
- e_LogWriteln(' Vendor: ' + alVendor);
- e_LogWriteln(' Renderer: ' + alRend);
- e_LogWriteln(' Device: ' + DevName);
+ e_LogWriteln(' Version: ' + alVer);
+ e_LogWriteln(' Vendor: ' + alVendor);
+ e_LogWriteln(' Renderer: ' + alRend);
+ e_LogWriteln(' Device: ' + WantDev);
+ e_LogWriteln(' Sample rate: ' + IntToStr(e_SoundFormat.SampleRate));
e_LogWriteln(' Extensions:');
e_LogWriteln(' ' + alExt);
- SetLength(alSources, NUM_SOURCES + 1); // 0 is the music source
- SetLength(alOwners, NUM_SOURCES + 1); // to avoid destructive operations on sources
- ZeroMemory(@alSources[0], sizeof(alSources[0]) * Length(alSources));
- ZeroMemory(@alOwners[0], sizeof(alOwners[0]) * Length(alOwners));
+ ZeroMemory(@alSources[0], sizeof(alSources));
+ ZeroMemory(@alOwners[0], sizeof(alOwners));
+ ZeroMemory(@alStreamBufs[0], sizeof(alStreamBufs));
+ ZeroMemory(@alStreamData[0], sizeof(alStreamData));
+ CurStream := NO_SOUND_ID;
alGetError(); // reset the goddamn error state
alGenSources(1, @alSources[0]); // generate the music source
if CheckALError() then
e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError());
+ alStreamAvail := 0;
+ alGenBuffers(NUM_STREAM_BUFFERS, @alStreamBufs[0]); // generate buffers for the music stream
+ if CheckALError() then
+ e_LogWriteln('AL: ERROR: alGenSources() for music failed: ' + GetALError())
+ else
+ alStreamAvail := NUM_STREAM_BUFFERS;
+
Result := True;
end;
begin
if e_SoundsArray <> nil then
for i := 0 to High(e_SoundsArray) do
- if e_SoundsArray[i].alBuffer = 0 then
+ if (e_SoundsArray[i].alBuffer = 0) and (e_SoundsArray[i].Loader = nil) then
begin
Result := i;
Exit;
@@ -248,14 +268,15 @@ function e_LoadSound(FileName: String; var ID: DWORD; isMusic: Boolean; ForceNoL
var
find_id: DWORD;
Loader: TSoundLoader;
+ OutData: Pointer;
+ OutLen: LongWord;
begin
ID := NO_SOUND_ID;
Result := False;
find_id := FindESound();
- e_SoundsArray[find_id].Data := nil;
- e_SoundsArray[find_id].DataLen := 0;
+ e_SoundsArray[find_id].Loader := nil;
e_SoundsArray[find_id].isMusic := isMusic;
e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
e_SoundsArray[find_id].nRefs := 0;
exit;
end;
- e_SoundsArray[find_id].Data := Loader.Load(FileName, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt);
- if e_SoundsArray[find_id].Data = nil then
+ if not Loader.Load(FileName, e_SoundsArray[find_id].isMusic) then
begin
e_LogWritefln('Could not load sound `%s`', [FileName]);
exit;
alGetError(); // reset error state, god damn it
- alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
- if CheckALError() then
+ if not Loader.Streaming then
begin
- e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]);
- Loader.Free(e_SoundsArray[find_id].Data);
- exit;
- end;
+ alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
+ if CheckALError() then
+ begin
+ e_LogWritefln('Could not create AL buffer for `%s`: %s', [FileName, GetALError()]);
+ Loader.Free();
+ exit;
+ end;
- alBufferData(
- e_SoundsArray[find_id].alBuffer,
- GetALSoundFormat(e_SoundsArray[find_id].Fmt),
- e_SoundsArray[find_id].Data,
- e_SoundsArray[find_id].DataLen,
- e_SoundsArray[find_id].Fmt.SampleRate
- );
+ OutLen := Loader.GetAll(OutData);
+ alBufferData(
+ e_SoundsArray[find_id].alBuffer,
+ GetALSoundFormat(Loader.Format),
+ OutData,
+ OutLen,
+ Loader.Format.SampleRate
+ );
- // don't need this anymore
- Loader.Free(e_SoundsArray[find_id].Data);
- e_SoundsArray[find_id].Data := nil;
- e_SoundsArray[find_id].DataLen := 0;
+ // don't need this anymore
+ Loader.Free();
+ Loader := nil;
- if CheckALError() then
+ if CheckALError() then
+ begin
+ e_LogWriteln('AL: what the fuck: ' + GetALError());
+ alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
+ e_SoundsArray[find_id].alBuffer := 0;
+ exit;
+ end;
+ end
+ else
begin
- e_LogWriteln('AL: what the fuck: ' + GetALError());
- alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
- exit;
+ e_SoundsArray[find_id].alBuffer := 0;
+ e_SoundsArray[find_id].Loader := Loader;
end;
ID := find_id;
@@ -312,27 +341,27 @@ function e_LoadSoundMem(pData: Pointer; Length: Integer; var ID: DWORD; isMusic:
var
find_id: DWORD;
Loader: TSoundLoader;
+ OutData: Pointer;
+ OutLen: LongWord;
begin
ID := NO_SOUND_ID;
Result := False;
find_id := FindESound();
- e_SoundsArray[find_id].Data := nil;
- e_SoundsArray[find_id].DataLen := 0;
+ e_SoundsArray[find_id].Loader := nil;
e_SoundsArray[find_id].isMusic := isMusic;
e_SoundsArray[find_id].Loops := isMusic and not ForceNoLoop;
e_SoundsArray[find_id].nRefs := 0;
- Loader := e_GetSoundLoader(pData, Length);
+ Loader := e_GetSoundLoader(pData, LongWord(Length));
if Loader = nil then
begin
e_LogWritefln('Could not find loader for sound `%p`', [pData]);
exit;
end;
- e_SoundsArray[find_id].Data := Loader.Load(pData, Length, e_SoundsArray[find_id].DataLen, e_SoundsArray[find_id].Fmt);
- if e_SoundsArray[find_id].Data = nil then
+ if not Loader.Load(pData, LongWord(Length), e_SoundsArray[find_id].isMusic) then
begin
e_LogWritefln('Could not load sound `%p`', [pData]);
exit;
alGetError(); // reset error state, god damn it
- alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
- if CheckALError() then
+ if not Loader.Streaming then
begin
- e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]);
- Loader.Free(e_SoundsArray[find_id].Data);
- exit;
- end;
+ alGenBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
+ if CheckALError() then
+ begin
+ e_LogWritefln('Could not create AL buffer for `%p`: %s', [pData, GetALError()]);
+ Loader.Free();
+ exit;
+ end;
- alBufferData(
- e_SoundsArray[find_id].alBuffer,
- GetALSoundFormat(e_SoundsArray[find_id].Fmt),
- e_SoundsArray[find_id].Data,
- e_SoundsArray[find_id].DataLen,
- e_SoundsArray[find_id].Fmt.SampleRate
- );
+ OutLen := Loader.GetAll(OutData);
+ alBufferData(
+ e_SoundsArray[find_id].alBuffer,
+ GetALSoundFormat(Loader.Format),
+ OutData,
+ OutLen,
+ Loader.Format.SampleRate
+ );
- // don't need this anymore
- Loader.Free(e_SoundsArray[find_id].Data);
- e_SoundsArray[find_id].Data := nil;
- e_SoundsArray[find_id].DataLen := 0;
+ // don't need this anymore
+ Loader.Free();
+ Loader := nil;
- if CheckALError() then
+ if CheckALError() then
+ begin
+ e_LogWriteln('AL: what the fuck: ' + GetALError());
+ alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
+ e_SoundsArray[find_id].alBuffer := 0;
+ exit;
+ end;
+ end
+ else
begin
- e_LogWriteln('AL: what the fuck: ' + GetALError());
- alDeleteBuffers(1, Addr(e_SoundsArray[find_id].alBuffer));
- exit;
+ e_SoundsArray[find_id].alBuffer := 0;
+ e_SoundsArray[find_id].Loader := Loader;
end;
ID := find_id;
if ID > High(e_SoundsArray) then
exit;
- if e_SoundsArray[ID].isMusic then
+ if e_SoundsArray[ID].Loader <> nil then
begin
- // last source is for music
+ // first source is for streaming sounds
+ // it always exists
+ alOwners[MUSIC_SOURCE] := nil;
Result := MUSIC_SOURCE;
exit;
end;
procedure AssignSound(ID: DWORD; Src: ALuint); inline;
begin
alGetError(); // reset error state
- alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer);
- alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE);
- if (e_SoundsArray[ID].Loops) then
- alSourcei(Src, AL_LOOPING, AL_TRUE)
+
+ if e_SoundsArray[ID].Loader <> nil then
+ begin
+ // this is a stream
+ // reset position
+ e_SoundsArray[ID].Loader.SetPosition(0);
+ if CurStream <> ID then // changing streams, stop the thing just in case
+ alSourceStop(Src);
+ // this shit is playing now
+ CurStream := ID;
+ end
else
- alSourcei(Src, AL_LOOPING, AL_FALSE);
+ begin
+ // this is a full chunk, assign local buffer
+ alSourcei(Src, AL_BUFFER, e_SoundsArray[ID].alBuffer);
+ // these can loop
+ if (e_SoundsArray[ID].Loops) then
+ alSourcei(Src, AL_LOOPING, AL_TRUE)
+ else
+ alSourcei(Src, AL_LOOPING, AL_FALSE);
+ end;
+
+ alSourcei(Src, AL_SOURCE_RELATIVE, AL_TRUE);
end;
function e_PlaySound(ID: DWORD): Integer;
alDeleteBuffers(1, Addr(e_SoundsArray[ID].alBuffer));
e_SoundsArray[ID].alBuffer := 0;
end;
- if (e_SoundsArray[ID].Data <> nil) then
+ if (e_SoundsArray[ID].Loader <> nil) then
begin
- e_SoundsArray[ID].Fmt.Loader.Free(e_SoundsArray[ID].Data);
- e_SoundsArray[ID].Data := nil;
- e_SoundsArray[ID].DataLen := 0;
+ e_SoundsArray[ID].Loader.Free();
+ e_SoundsArray[ID].Loader := nil;
+ if ID = CurStream then
+ CurStream := NO_SOUND_ID;
end;
end;
e_DeleteSound(i);
SetLength(e_SoundsArray, 0);
e_SoundsArray := nil;
+ CurStream := NO_SOUND_ID;
end;
procedure e_ReleaseSoundSystem();
alContext := nil;
end;
+procedure UpdateStreamSource(Src: Integer);
+var
+ OutLen: LongWord;
+ Buf: ALuint;
+ S: Integer;
+begin
+ if alSources[Src] = 0 then Exit;
+
+ alGetError(); // reset error state
+
+ alGetSourcei(alSources[Src], AL_BUFFERS_PROCESSED, S);
+ // unqueue processed buffers
+ if S > 0 then
+ begin
+ alSourceUnqueueBuffers(alSources[Src], S, @alStreamBufs[alStreamAvail]);
+ alStreamAvail := alStreamAvail + S;
+ end;
+
+ alGetError(); // reset error state
+
+ if (alStreamAvail > 0) and (CurStream <> NO_SOUND_ID) then
+ begin
+ // some buffers have freed up, advance stream playback
+ OutLen := e_SoundsArray[CurStream].Loader.FillBuffer(@alStreamData[0], STREAM_BUFSIZE);
+ if OutLen = 0 then Exit; // ran out of stream
+ Buf := alStreamBufs[alStreamAvail-1];
+ Dec(alStreamAvail);
+ // upload
+ alBufferData(
+ Buf,
+ GetALSoundFormat(e_SoundsArray[CurStream].Loader.Format),
+ @alStreamData[0],
+ OutLen,
+ e_SoundsArray[CurStream].Loader.Format.SampleRate
+ );
+ // attach
+ alSourceQueueBuffers(alSources[Src], 1, @Buf);
+ // restart if needed
+ S := GetALSourceState(alSources[Src]);
+ if (S = AL_STOPPED) or (S = AL_INITIAL) then
+ alSourcePlay(alSources[Src]);
+ end;
+end;
+
procedure e_SoundUpdate();
var
S: Integer;
begin
alGetError(); // reset error state
+
// clear out all stopped sources
for S := 1 to High(alSources) do
if (alSources[S] <> 0) and (GetALSourceState(alSources[S]) = AL_STOPPED) then
alSources[S] := 0;
alOwners[S] := nil;
end;
+
+ // update the stream sources
+ UpdateStreamSource(MUSIC_SOURCE);
end;
{ TBasicSound: }
procedure TBasicSound.Stop();
begin
+ if FID = CurStream then
+ CurStream := NO_SOUND_ID;
if InvalidSource() then
Exit;
GetPosition();
index df4aa45dfa2c04a01b4e8b4af2c601b7a032c748..919715c5dea3dc3da6a7a9242748b10e5af7f87a 100644 (file)
TSoundLoader = class;
TSoundFormat = record
- Loader: TSoundLoader;
SampleBits: Integer;
SampleRate: Integer;
Channels: Integer;
end;
- // each sound file format has its own loader
- // TODO: maybe make TBasicSound contain an instance of its loader
- // and add a FetchSamples method or something, for streaming shit
+ // each sound file format has its own loader factory and loader class,
+ // each sound has its own loader instance for streaming purposes
+
TSoundLoader = class
+ protected
+ FFormat: TSoundFormat;
+ FStreaming: Boolean;
+
public
- // can this loader load the sound file in Data?
- function CanLoad(Data: Pointer; Len: Integer): Boolean; virtual; abstract; overload;
- // can this loader load the sound file at FName?
- function CanLoad(FName: string): Boolean; virtual; abstract; overload;
- // load from memory
- function Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; virtual; abstract; overload;
- // load from file
- function Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; virtual; abstract; overload;
- // needed in case memory is allocated in a lib or something
- procedure Free(Data: Pointer); virtual; abstract;
+ function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; virtual; abstract; overload;
+ function Load(FName: string; SStreaming: Boolean): Boolean; virtual; abstract; overload;
+
+ function SetPosition(Pos: LongWord): Boolean; virtual; abstract;
+ function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; virtual; abstract;
+
+ function GetAll(var OutPtr: Pointer): LongWord; virtual; abstract;
+
+ procedure Free(); virtual; abstract;
+
+ property Format: TSoundFormat read FFormat;
+ property Streaming: Boolean read FStreaming;
+ end;
+
+ TSoundLoaderFactory = class
+ function MatchHeader(Data: Pointer; Len: LongWord): Boolean; virtual; abstract;
+ function MatchExtension(FName: string): Boolean; virtual; abstract;
+ function GetLoader(): TSoundLoader; virtual; abstract;
end;
-function e_GetSoundLoader(Data: Pointer; Len: Integer): TSoundLoader; overload;
+function e_GetSoundLoader(Data: Pointer; Len: LongWord): TSoundLoader; overload;
function e_GetSoundLoader(FName: string): TSoundLoader; overload;
-procedure e_AddSoundLoader(Loader: TSoundLoader);
+procedure e_AddSoundLoader(Loader: TSoundLoaderFactory);
implementation
var
- e_SoundLoaders: array of TSoundLoader;
+ e_SoundLoaders: array of TSoundLoaderFactory;
function e_GetSoundLoader(FName: string): TSoundLoader; overload;
var
begin
Result := nil;
for I := Low(e_SoundLoaders) to High(e_SoundLoaders) do
- if e_SoundLoaders[I].CanLoad(FName) then
+ if e_SoundLoaders[I].MatchExtension(FName) then
begin
- Result := e_SoundLoaders[I];
+ Result := e_SoundLoaders[I].GetLoader();
break;
end;
end;
-function e_GetSoundLoader(Data: Pointer; Len: Integer): TSoundLoader; overload;
+function e_GetSoundLoader(Data: Pointer; Len: LongWord): TSoundLoader; overload;
var
I: Integer;
begin
Result := nil;
for I := Low(e_SoundLoaders) to High(e_SoundLoaders) do
- if e_SoundLoaders[I].CanLoad(Data, Len) then
+ if e_SoundLoaders[I].MatchHeader(Data, Len) then
begin
- Result := e_SoundLoaders[I];
+ Result := e_SoundLoaders[I].GetLoader();
break;
end;
end;
-procedure e_AddSoundLoader(Loader: TSoundLoader);
+procedure e_AddSoundLoader(Loader: TSoundLoaderFactory);
begin
SetLength(e_SoundLoaders, Length(e_SoundLoaders) + 1);
e_SoundLoaders[High(e_SoundLoaders)] := Loader;
index b88dd5a8dfc784f70bd4e0255a5e31e8af7b5604..b5b838d4ba224da50e1301ec8db02cfedea0a6e2 100644 (file)
type
// a WAV loader that just uses SDL_LoadWAV
+
TWAVLoader = class (TSoundLoader)
public
- function CanLoad(Data: Pointer; Len: Integer): Boolean; override; overload;
- function CanLoad(FName: string): Boolean; override; overload;
- function Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; override; overload;
- function Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer; override; overload;
- procedure Free(Data: Pointer); override;
+ function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload;
+ function Load(FName: string; SStreaming: Boolean): Boolean; override; overload;
+ function SetPosition(Pos: LongWord): Boolean; override;
+ function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override;
+ function GetAll(var OutPtr: Pointer): LongWord; override;
+ procedure Free(); override;
+ private
+ FData: Pointer;
+ FDataLen: LongWord;
+ end;
+
+ TWAVLoaderFactory = class (TSoundLoaderFactory)
+ function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override;
+ function MatchExtension(FName: string): Boolean; override;
+ function GetLoader(): TSoundLoader; override;
end;
implementation
uses sdl2, utils, e_log;
-function TWAVLoader.CanLoad(Data: Pointer; Len: Integer): Boolean;
+(* TWAVLoaderFactory *)
+
+function TWAVLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;
var
P: PByte;
begin
Result := ((P+0)^ = Ord('R')) and ((P+1)^ = Ord('I')) and ((P+2)^ = Ord('F')) and ((P+3)^ = Ord('F'));
end;
-function TWAVLoader.CanLoad(FName: string): Boolean;
+function TWAVLoaderFactory.MatchExtension(FName: string): Boolean;
begin
- // TODO: actually check for RIFF header
+ // TODO: ehhh
Result := GetFilenameExt(FName) = '.wav';
end;
-function TWAVLoader.Load(Data: Pointer; Len: Integer; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer;
+function TWAVLoaderFactory.GetLoader(): TSoundLoader;
+begin
+ Result := TWAVLoader.Create();
+end;
+
+(* TWAVLoader *)
+
+function TWAVLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
var
Spec: TSDL_AudioSpec;
RW: PSDL_RWops;
TmpLen: UInt32;
TmpBuf: PUInt8;
begin
- Result := nil;
+ Result := False;
RW := SDL_RWFromConstMem(Data, Len);
end
else
begin
- OutFmt.Loader := self;
- OutFmt.SampleRate := Spec.freq;
- OutFmt.SampleBits := SDL_AUDIO_BITSIZE(Spec.format);
- OutFmt.Channels := Spec.channels;
- OutLen := TmpLen;
- Result := TmpBuf;
+ FFormat.SampleRate := Spec.freq;
+ FFormat.SampleBits := SDL_AUDIO_BITSIZE(Spec.format);
+ FFormat.Channels := Spec.channels;
+ FStreaming := False; // never stream wavs
+ FDataLen := TmpLen;
+ FData := TmpBuf;
+ Result := True;
end;
SDL_RWclose(RW);
end;
-function TWAVLoader.Load(FName: string; var OutLen: Integer; var OutFmt: TSoundFormat): Pointer;
+function TWAVLoader.Load(FName: string; SStreaming: Boolean): Boolean;
var
Spec: TSDL_AudioSpec;
RW: PSDL_RWops;
TmpLen: UInt32;
TmpBuf: PUInt8;
begin
- Result := nil;
+ Result := False;
RW := SDL_RWFromFile(PChar(FName), 'rb');
if SDL_LoadWAV_RW(RW, 0, @Spec, @TmpBuf, @TmpLen) = nil then
begin
e_LogWritefln('Could not load WAV file `%s`: %s', [FName, SDL_GetError()]);
- Result := nil;
end
else
begin
- OutFmt.Loader := self;
- OutFmt.SampleRate := Spec.freq;
- OutFmt.SampleBits := SDL_AUDIO_BITSIZE(Spec.format);
- OutFmt.Channels := Spec.channels;
- OutLen := TmpLen;
- Result := TmpBuf;
+ FFormat.SampleRate := Spec.freq;
+ FFormat.SampleBits := SDL_AUDIO_BITSIZE(Spec.format);
+ FFormat.Channels := Spec.channels;
+ FStreaming := False; // never stream wavs
+ FDataLen := TmpLen;
+ FData := TmpBuf;
+ Result := True;
end;
SDL_RWclose(RW);
end;
-procedure TWAVLoader.Free(Data: Pointer);
+function TWAVLoader.SetPosition(Pos: LongWord): Boolean;
+begin
+ Result := False; // makes no sense when not streaming
+end;
+
+function TWAVLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
+begin
+ if FDataLen < Len then
+ Len := FDataLen;
+ if FData <> nil then
+ begin
+ Move(FData^, Buf^, Len);
+ Result := Len;
+ end
+ else
+ Result := 0;
+end;
+
+function TWAVLoader.GetAll(var OutPtr: Pointer): LongWord;
+begin
+ OutPtr := FData;
+ Result := FDataLen;
+end;
+
+procedure TWAVLoader.Free();
begin
- SDL_FreeWAV(Data); // SDL allocates inside the DLL, so we need this
+ if FData <> nil then
+ SDL_FreeWAV(FData); // SDL allocates inside the DLL, so we need this
end;
initialization
- e_AddSoundLoader(TWAVLoader.Create());
+ e_AddSoundLoader(TWAVLoaderFactory.Create());
end.
diff --git a/src/engine/e_soundfile_xmp.pas b/src/engine/e_soundfile_xmp.pas
--- /dev/null
@@ -0,0 +1,197 @@
+(* Copyright (C) Doom 2D: Forever Developers
+ *
+ * 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 3 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. If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
+unit e_soundfile_xmp;
+
+interface
+
+uses e_soundfile, XMP;
+
+type
+ // a module loader that uses libxmp-lite
+
+ TXMPLoader = class (TSoundLoader)
+ public
+ function Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean; override; overload;
+ function Load(FName: string; SStreaming: Boolean): Boolean; override; overload;
+ function SetPosition(Pos: LongWord): Boolean; override;
+ function FillBuffer(Buf: Pointer; Len: LongWord): LongWord; override;
+ function GetAll(var OutPtr: Pointer): LongWord; override;
+ procedure Free(); override;
+ private
+ FXMP: xmp_context;
+ FLoaded: Boolean;
+ end;
+
+ TXMPLoaderFactory = class (TSoundLoaderFactory)
+ function MatchHeader(Data: Pointer; Len: LongWord): Boolean; override;
+ function MatchExtension(FName: string): Boolean; override;
+ function GetLoader(): TSoundLoader; override;
+ end;
+
+implementation
+
+uses sysutils, utils, e_sound, e_log;
+
+(* TXMPLoaderFactory *)
+
+function TXMPLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;
+var
+ Ctx: xmp_context;
+ Err: LongInt;
+begin
+ // HACK: these fine gentlemen didn't provide us with a xmp_test_module_from_memory()
+ // so just load the module and unload it
+
+ Result := False;
+
+ Ctx := xmp_create_context();
+ Err := xmp_load_module_from_memory(Ctx, Data, Len);
+
+ if Err = 0 then
+ begin
+ xmp_release_module(Ctx);
+ Result := True;
+ end;
+
+ xmp_free_context(Ctx);
+end;
+
+function TXMPLoaderFactory.MatchExtension(FName: string): Boolean;
+var
+ Ext: string;
+begin
+ Ext := GetFilenameExt(FName);
+ Result := (Ext = '.it') or (Ext = '.xm') or (Ext = '.mod') or (Ext = '.s3m');
+end;
+
+function TXMPLoaderFactory.GetLoader(): TSoundLoader;
+begin
+ Result := TXMPLoader.Create();
+end;
+
+(* TXMPLoader *)
+
+function TXMPLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
+var
+ Err: LongInt;
+begin
+ Result := False;
+
+ FLoaded := False;
+ FXMP := xmp_create_context();
+ if FXMP = nil then Exit;
+
+ try
+ Err := xmp_load_module_from_memory(FXMP, Data, Len);
+ if Err <> 0 then
+ raise Exception.Create('xmp_load_module_from_memory failed');
+
+ if xmp_start_player(FXMP, e_SoundFormat.SampleRate, 0) <> 0 then
+ raise Exception.Create('xmp_start_player failed');
+
+ FFormat.SampleRate := e_SoundFormat.SampleRate;
+ FFormat.SampleBits := 16;
+ FFormat.Channels := 2;
+
+ FStreaming := True; // modules are always streaming
+ FLoaded := True;
+ Result := True;
+ except
+ on E: Exception do
+ begin
+ e_LogWriteln('TXMPLoader.Load() error: ' + E.Message);
+ if Err = 0 then xmp_release_module(FXMP);
+ xmp_free_context(FXMP);
+ FXMP := nil;
+ end;
+ end;
+end;
+
+function TXMPLoader.Load(FName: string; SStreaming: Boolean): Boolean;
+var
+ Err: LongInt;
+begin
+ Result := False;
+
+ FLoaded := False;
+ FXMP := xmp_create_context();
+ if FXMP = nil then Exit;
+
+ try
+ Err := xmp_load_module(FXMP, PChar(FName));
+ if Err <> 0 then
+ raise Exception.Create('xmp_load_module failed');
+
+ if xmp_start_player(FXMP, e_SoundFormat.SampleRate, 0) <> 0 then
+ raise Exception.Create('xmp_start_player failed');
+
+ FFormat.SampleRate := e_SoundFormat.SampleRate;
+ FFormat.SampleBits := 16;
+ FFormat.Channels := 2;
+
+ FStreaming := True; // modules are always streaming
+ FLoaded := True;
+ Result := True;
+ except
+ on E: Exception do
+ begin
+ e_LogWritefln('TXMPLoader.Load(%s) error: %s', [FName, E.Message]);
+ if Err = 0 then xmp_release_module(FXMP);
+ xmp_free_context(FXMP);
+ FXMP := nil;
+ end;
+ end;
+end;
+
+function TXMPLoader.SetPosition(Pos: LongWord): Boolean;
+begin
+ Result := False;
+ if FXMP = nil then Exit;
+ Result := xmp_set_position(FXMP, Pos) = 0;
+end;
+
+function TXMPLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
+begin
+ Result := 0;
+ if FXMP = nil then Exit;
+ if xmp_play_buffer(FXMP, Buf, Len, 0) = 0 then
+ Result := Len;
+end;
+
+function TXMPLoader.GetAll(var OutPtr: Pointer): LongWord;
+begin
+ Result := 0; // modules are always streaming, so this don't make sense
+end;
+
+procedure TXMPLoader.Free();
+begin
+ if FXMP <> nil then
+ begin
+ if FLoaded then
+ begin
+ xmp_end_player(FXMP);
+ xmp_release_module(FXMP);
+ end;
+ xmp_free_context(FXMP);
+ FXMP := nil;
+ end;
+ FLoaded := False;
+end;
+
+initialization
+ e_AddSoundLoader(TXMPLoaderFactory.Create());
+end.
diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr
index f972e59a650dd8bee91d380ec6895f26a08f16da..c3c7c3e5a88767a87bcab210667c58ade6299a22 100644 (file)
--- a/src/game/Doom2DF.lpr
+++ b/src/game/Doom2DF.lpr
{$ENDIF}
{$IFDEF USE_OPENAL}
AL in '../lib/openal/al.pas',
+ XMP in '../lib/xmp/xmp.pas',
e_soundfile in '../engine/e_soundfile.pas',
e_soundfile_wav in '../engine/e_soundfile_wav.pas',
+ e_soundfile_xmp in '../engine/e_soundfile_xmp.pas',
{$ENDIF}
ENet in '../lib/enet/enet.pp',
e_graphics in '../engine/e_graphics.pas',
diff --git a/src/lib/openal/al.pas b/src/lib/openal/al.pas
index 0d6afba2344b9aeca4fbfb0354cacccd91728d50..2ca6fc12aac8f3ea24c050ab7a54338d809590a5 100644 (file)
--- a/src/lib/openal/al.pas
+++ b/src/lib/openal/al.pas
ctypes;
{$IFDEF WINDOWS}
- {$DEFINE AL_DYNAMIC}
+ {$IFNDEF AL_WINDOZE_STATIC}
+ {$DEFINE AL_DYNAMIC}
+ {$ENDIF}
{$ENDIF}
{$IF DEFINED(AL_DYNAMIC)}
diff --git a/src/lib/xmp/xmp.pas b/src/lib/xmp/xmp.pas
--- /dev/null
+++ b/src/lib/xmp/xmp.pas
@@ -0,0 +1,375 @@
+unit XMP;
+
+interface
+
+{$IFDEF FPC}
+{$PACKRECORDS C}
+{$MODE OBJFPC}
+{$ENDIF}
+
+{$IFDEF WINDOWS}
+ {$IFNDEF LIBXMP_WINDOZE_STATIC}
+ {$DEFINE XMP_DYNAMIC}
+ {$ENDIF}
+{$ENDIF}
+
+{$IF DEFINED(XMP_DYNAMIC)}
+const
+{$IF DEFINED(WINDOWS)}
+ xmplib = 'libxmp-lite.dll';
+{$ELSEIF DEFINED(UNIX)}
+ xmplib = 'libxmp-lite.so';
+{$ELSE}
+ {$MESSAGE ERROR 'XMP_DYNAMIC not supported'}
+{$IFEND}
+{$ELSE}
+ {$LINKLIB xmp-lite}
+{$ENDIF}
+
+const
+ XMP_VER_STRING = '4.5.0';
+ XMP_VER_CODE = $040500;
+ XMP_VER_MAJOR = 4;
+ XMP_VER_MINOR = 5;
+ XMP_VER_RELEASE = 0;
+
+const
+ XMP_NAME_SIZE = 64; (* Size of module name and type *)
+ XMP_KEY_OFF = $81; (* Note number for key off event *)
+ XMP_KEY_CUT = $82; (* Note number for key cut event *)
+ XMP_KEY_FADE = $83; (* Note number for fade event *)
+ (* mixer parameter macros *)
+ (* sample format flags *)
+ XMP_FORMAT_8BIT = (1 shl 0); (* Mix to 8-bit instead of 16 *)
+ XMP_FORMAT_UNSIGNED = (1 shl 1); (* Mix to unsigned samples *)
+ XMP_FORMAT_MONO = (1 shl 2); (* Mix to mono instead of stereo *)
+ (* player parameters *)
+ XMP_PLAYER_AMP = 0; (* Amplification factor *)
+ XMP_PLAYER_MIX = 1; (* Stereo mixing *)
+ XMP_PLAYER_INTERP = 2; (* Interpolation type *)
+ XMP_PLAYER_DSP = 3; (* DSP effect flags *)
+ XMP_PLAYER_FLAGS = 4; (* Player flags *)
+ XMP_PLAYER_CFLAGS = 5; (* Player flags for current module *)
+ XMP_PLAYER_SMPCTL = 6; (* Sample control flags *)
+ XMP_PLAYER_VOLUME = 7; (* Player module volume *)
+ XMP_PLAYER_STATE = 8; (* Internal player state (read only) *)
+ XMP_PLAYER_SMIX_VOLUME = 9; (* SMIX volume *)
+ XMP_PLAYER_DEFPAN = 10; (* Default pan setting *)
+ XMP_PLAYER_MODE = 11; (* Player personality *)
+ XMP_PLAYER_MIXER_TYPE = 12; (* Current mixer (read only) *)
+ XMP_PLAYER_VOICES = 13; (* Maximum number of mixer voices *)
+ (* interpolation types *)
+ XMP_INTERP_NEAREST = 0; (* Nearest neighbor *)
+ XMP_INTERP_LINEAR = 1; (* Linear (default) *)
+ XMP_INTERP_SPLINE = 2; (* Cubic spline *)
+ (* dsp effect types *)
+ XMP_DSP_LOWPASS = (1 shl 0); (* Lowpass filter effect *)
+ XMP_DSP_ALL = (XMP_DSP_LOWPASS);
+ (* player state *)
+ XMP_STATE_UNLOADED = 0; (* Context created *)
+ XMP_STATE_LOADED = 1; (* Module loaded *)
+ XMP_STATE_PLAYING = 2; (* Module playing *)
+ (* player flags *)
+ XMP_FLAGS_VBLANK = (1 shl 0); (* Use vblank timing *)
+ XMP_FLAGS_FX9BUG = (1 shl 1); (* Emulate FX9 bug *)
+ XMP_FLAGS_FIXLOOP = (1 shl 2); (* Emulate sample loop bug *)
+ XMP_FLAGS_A500 = (1 shl 3); (* Use Paula mixer in Amiga modules *)
+ (* player modes *)
+ XMP_MODE_AUTO = 0; (* Autodetect mode (default) *)
+ XMP_MODE_MOD = 1; (* Play as a generic MOD player *)
+ XMP_MODE_NOISETRACKER = 2; (* Play using Noisetracker quirks *)
+ XMP_MODE_PROTRACKER = 3; (* Play using Protracker quirks *)
+ XMP_MODE_S3M = 4; (* Play as a generic S3M player *)
+ XMP_MODE_ST3 = 5; (* Play using ST3 bug emulation *)
+ XMP_MODE_ST3GUS = 6; (* Play using ST3+GUS quirks *)
+ XMP_MODE_XM = 7; (* Play as a generic XM player *)
+ XMP_MODE_FT2 = 8; (* Play using FT2 bug emulation *)
+ XMP_MODE_IT = 9; (* Play using IT quirks *)
+ XMP_MODE_ITSMP = 10; (* Play using IT sample mode quirks *)
+ (* mixer types *)
+ XMP_MIXER_STANDARD = 0; (* Standard mixer *)
+ XMP_MIXER_A500 = 1; (* Amiga 500 *)
+ XMP_MIXER_A500F = 2; (* Amiga 500 with led filter *)
+ (* sample flags *)
+ XMP_SMPCTL_SKIP = (1 shl 0); (* Don't load samples *)
+ (* limits *)
+ XMP_MAX_KEYS = 121; (* Number of valid keys *)
+ XMP_MAX_ENV_POINTS = 32; (* Max number of envelope points *)
+ XMP_MAX_MOD_LENGTH = 256; (* Max number of patterns in module *)
+ XMP_MAX_CHANNELS = 64; (* Max number of channels in module *)
+ XMP_MAX_SRATE = 49170; (* max sampling rate (Hz) *)
+ XMP_MIN_SRATE = 4000; (* min sampling rate (Hz) *)
+ XMP_MIN_BPM = 20; (* min BPM *)
+ (* frame rate = (50 * bpm / 125) Hz *)
+ (* frame size = (sampling rate * channels * size) / frame rate *)
+ XMP_MAX_FRAMESIZE = (5*XMP_MAX_SRATE*2 div XMP_MIN_BPM);
+ (* error codes *)
+ XMP_END = 1;
+ XMP_ERROR_INTERNAL = 2; (* Internal error *)
+ XMP_ERROR_FORMAT = 3; (* Unsupported module format *)
+ XMP_ERROR_LOAD = 4; (* Error loading file *)
+ XMP_ERROR_DEPACK = 5; (* Error depacking file *)
+ XMP_ERROR_SYSTEM = 6; (* System error *)
+ XMP_ERROR_INVALID = 7; (* Invalid parameter *)
+ XMP_ERROR_STATE = 8; (* Invalid player state *)
+
+const
+ XMP_CHANNEL_SYNTH = (1 shl 0); (* Channel is synthesized *)
+ XMP_CHANNEL_MUTED = (1 shl 1); (* Channel is muted *)
+ XMP_CHANNEL_SPLIT = (1 shl 2); (* Split Amiga channel in bits 5-4 *)
+ XMP_CHANNEL_SURROUND = (1 shl 4); (* Surround channel *)
+
+const
+ XMP_ENVELOPE_ON = (1 shl 0); (* Envelope is enabled *)
+ XMP_ENVELOPE_SUS = (1 shl 1); (* Envelope has sustain point *)
+ XMP_ENVELOPE_LOOP = (1 shl 2); (* Envelope has loop *)
+ XMP_ENVELOPE_FLT = (1 shl 3); (* Envelope is used for filter *)
+ XMP_ENVELOPE_SLOOP = (1 shl 4); (* Envelope has sustain loop *)
+ XMP_ENVELOPE_CARRY = (1 shl 5); (* Don't reset envelope position *)
+
+const
+ XMP_INST_NNA_CUT = $00;
+ XMP_INST_NNA_CONT = $01;
+ XMP_INST_NNA_OFF = $02;
+ XMP_INST_NNA_FADE = $03;
+ XMP_INST_DCT_OFF = $00;
+ XMP_INST_DCT_NOTE = $01;
+ XMP_INST_DCT_SMP = $02;
+ XMP_INST_DCT_INST = $03;
+ XMP_INST_DCA_CUT = XMP_INST_NNA_CUT;
+ XMP_INST_DCA_OFF = XMP_INST_NNA_OFF;
+ XMP_INST_DCA_FADE = XMP_INST_NNA_FADE;
+
+const
+ XMP_SAMPLE_16BIT = (1 shl 0); (* 16bit sample *)
+ XMP_SAMPLE_LOOP = (1 shl 1); (* Sample is looped *)
+ XMP_SAMPLE_LOOP_BIDIR = (1 shl 2); (* Bidirectional sample loop *)
+ XMP_SAMPLE_LOOP_REVERSE = (1 shl 3); (* Backwards sample loop *)
+ XMP_SAMPLE_LOOP_FULL = (1 shl 4); (* Play full sample before looping *)
+ XMP_SAMPLE_SLOOP = (1 shl 5); (* Sample has sustain loop *)
+ XMP_SAMPLE_SLOOP_BIDIR = (1 shl 6); (* Bidirectional sustain loop *)
+ XMP_SAMPLE_SYNTH = (1 shl 15); (* Data contains synth patch *)
+
+type
+ xmp_channel = record
+ pan: longint; (* Channel pan (0x80 is center) *)
+ vol: longint; (* Channel volume *)
+ flg: longint; (* Channel flags *)
+ end;
+
+ xmp_pattern = record
+ rows: longint; (* Number of rows *)
+ index: array [0..0] of longint; (* Track index *)
+ end;
+
+ xmp_event = record
+ note: byte; (* Note number (0 means no note) *)
+ ins: byte; (* Patch number *)
+ vol: byte; (* Volume (0 to basevol) *)
+ fxt: byte; (* Effect type *)
+ fxp: byte; (* Effect parameter *)
+ f2t: byte; (* Secondary effect type *)
+ f2p: byte; (* Secondary effect parameter *)
+ _flag: byte; (* Internal (reserved) flags *)
+ end;
+
+ pxmp_event = ^xmp_event;
+
+ xmp_track = record
+ rows: longint; (* Number of rows *)
+ event: array [0..0] of xmp_event; (* Event data *)
+ end;
+
+ xmp_envelope = record
+ flg: longint; (* Flags *)
+ npt: longint; (* Number of envelope points *)
+ scl: longint; (* Envelope scaling *)
+ sus: longint; (* Sustain start point *)
+ sue: longint; (* Sustain end point *)
+ lps: longint; (* Loop start point *)
+ lpe: longint; (* Loop end point *)
+ data: array [0..(XMP_MAX_ENV_POINTS*2)-1] of smallint;
+ end;
+
+ xmp_subinstrument = record
+ vol: longint; (* Default volume *)
+ gvl: longint; (* Global volume *)
+ pan: longint; (* Pan *)
+ xpo: longint; (* Transpose *)
+ fin: longint; (* Finetune *)
+ vwf: longint; (* Vibrato waveform *)
+ vde: longint; (* Vibrato depth *)
+ vra: longint; (* Vibrato rate *)
+ vsw: longint; (* Vibrato sweep *)
+ rvv: longint; (* Random volume/pan variation (IT) *)
+ sid: longint; (* Sample number *)
+ nna: longint; (* New note action *)
+ dct: longint; (* Duplicate check type *)
+ dca: longint; (* Duplicate check action *)
+ ifc: longint; (* Initial filter cutoff *)
+ ifr: longint; (* Initial filter resonance *)
+ end;
+
+ pxmp_subinstrument = ^xmp_subinstrument;
+
+ xmp_instrument = record
+ name: array [0..31] of char; (* Instrument name *)
+ vol: longint; (* Instrument volume *)
+ nsm: longint; (* Number of samples *)
+ rls: longint; (* Release (fadeout) *)
+ aei: xmp_envelope; (* Amplitude envelope info *)
+ pei: xmp_envelope; (* Pan envelope info *)
+ fei: xmp_envelope; (* Frequency envelope info *)
+ map : array[0..(XMP_MAX_KEYS)-1] of record
+ ins : byte;
+ xpo : char;
+ end;
+ sub: pxmp_subinstrument;
+ extra: pointer;
+ end;
+
+ xmp_sample = record
+ name: array [0..31] of char; (* Sample name *)
+ len: longint; (* Sample length *)
+ lps: longint; (* Loop start *)
+ lpe: longint; (* Loop end *)
+ flg: longint; (* Flags *)
+ data: pbyte; (* Sample data *)
+ end;
+
+ xmp_sequence = record
+ entry_point: longint;
+ duration: longint;
+ end;
+
+ pxmp_sequence = ^xmp_sequence;
+
+ xmp_module = record
+ name: array [0..XMP_NAME_SIZE-1] of char; (* Module title *)
+ format: array [0..XMP_NAME_SIZE-1] of char; (* Module format *)
+
+ pat: longint; (* Number of patterns *)
+ trk: longint; (* Number of tracks *)
+ chn: longint; (* Tracks per pattern *)
+ ins: longint; (* Number of instruments *)
+ smp: longint; (* Number of samples *)
+ spd: longint; (* Initial speed *)
+ bpm: longint; (* Initial BPM *)
+ len: longint; (* Module length in patterns *)
+ rst: longint; (* Restart position *)
+ gvl: longint; (* Global volume *)
+
+ xxp: pointer; // xmp_pattern**
+ xxt: pointer; // xmp_track**
+ xxi: pointer; // xmp_instrument*
+ xxs: pointer; // xmp_sample*
+ xxc: array [0..XMP_MAX_CHANNELS-1] of xmp_channel;
+ xxo: array [0..XMP_MAX_MOD_LENGTH-1] of byte;
+ end;
+
+ pxmp_module = ^xmp_module;
+
+ xmp_test_info = record
+ name: array [0..XMP_NAME_SIZE-1] of char; (* Module title *)
+ format: array [0..XMP_NAME_SIZE-1] of char; (* Module format *)
+ end;
+
+ xmp_module_info = record
+ md5: array [0..15] of byte; (* MD5 message digest *)
+ vol_base: longint; (* Volume scale *)
+ module: pxmp_module; (* Pointer to module data *)
+ comment: pchar; (* Comment text, if any *)
+ num_sequences: longint; (* Number of valid sequences *)
+ seq_data: pxmp_sequence; (* Pointer to sequence data *)
+ end;
+
+ xmp_channel_info = record
+ period: longword; (* Sample period (times 4096) *)
+ position: longword; (* Sample position *)
+ pitchbend: word; (* Linear bend from base note*)
+ note: byte; (* Current base note number *)
+ instrument: byte; (* Current instrument number *)
+ usample: byte; (* Current sample number *)
+ volume: byte; (* Current volume *)
+ pan: byte; (* Current stereo pan *)
+ reserved: byte; (* Reserved *)
+ event: xmp_event; (* Current track event *)
+ end;
+
+ (* Current frame information *)
+ xmp_frame_info = record
+ pos: longint; (* Current position *)
+ pattern: longint; (* Current pattern *)
+ row: longint; (* Current row in pattern *)
+ num_rows: longint; (* Number of rows in current pattern *)
+ frame: longint; (* Current frame *)
+ speed: longint; (* Current replay speed *)
+ bpm: longint; (* Current bpm *)
+ time: longint; (* Current module time in ms *)
+ total_time: longint; (* Estimated replay time in ms*)
+ frame_time: longint; (* Frame replay time in us *)
+ buffer: plongint; (* Pointer to sound buffer *)
+ buffer_size: longint; (* Used buffer size *)
+ total_size: longint; (* Total buffer size *)
+ volume: longint; (* Current master volume *)
+ loop_count: longint; (* Loop counter *)
+ virt_channels: longint; (* Number of virtual channels *)
+ virt_used: longint; (* Used virtual channels *)
+ sequence: longint; (* Current sequence *)
+ channel_info: array [0..XMP_MAX_CHANNELS-1] of xmp_channel_info;
+ end;
+
+ xmp_context = pointer;
+
+var
+ xmp_version: pchar; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+ xmp_vercode: longword; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_create_context(): xmp_context; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_free_context(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_get_format_list(): ppchar; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_test_module(fname: pchar; var info: xmp_test_info): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_load_module(ctx: xmp_context; fname: pchar): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_load_module_from_memory(ctx: xmp_context; buf: pointer; bufsiz: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_load_module_from_file(ctx: xmp_context; fstream: pointer; msize: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_scan_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_get_module_info(ctx: xmp_context; var info: xmp_module_info); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_release_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_start_player(ctx: xmp_context; rate, format: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_play_frame(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_play_buffer(ctx: xmp_context; buf: pointer; bufsiz: longint; loop: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_get_frame_info(ctx: xmp_context; var info: xmp_frame_info); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_end_player(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+procedure xmp_inject_event(ctx: xmp_context; ch: longint; ev: pxmp_event); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_next_position(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_prev_position(ctx: xmp_context): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_set_position(ctx: xmp_context; pos: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_seek_time(ctx: xmp_context; ms: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_stop_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+procedure xmp_restart_module(ctx: xmp_context); cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_channel_mute(ctx: xmp_context; ch, mute: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_channel_vol(ctx: xmp_context; ch, vol: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+function xmp_set_player(ctx: xmp_context; param, value: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_get_player(ctx: xmp_context; param: longint): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+function xmp_set_instrument_path(ctx: xmp_context; path: pchar): longint; cdecl; external {$IFDEF XMP_DYNAMIC}xmplib{$ENDIF};
+
+(*
+function xmp_start_smix;
+procedure xmp_end_smix;
+function xmp_smix_play_instrument;
+function xmp_smix_play_sample;
+function xmp_smix_channel_pan;
+function xmp_smix_load_sample;
+function xmp_smix_release_sample;
+*)
+
+implementation
+
+
+end.