DEADSOFTWARE

Sound: OpenAL: libxmp support
authorfgsfds <pvt.fgsfds@gmail.com>
Sat, 31 Aug 2019 18:06:53 +0000 (21:06 +0300)
committerfgsfds <pvt.fgsfds@gmail.com>
Sat, 31 Aug 2019 18:07:08 +0000 (21:07 +0300)
src/engine/e_sound_al.inc
src/engine/e_soundfile.pas
src/engine/e_soundfile_wav.pas
src/engine/e_soundfile_xmp.pas [new file with mode: 0644]
src/game/Doom2DF.lpr
src/lib/openal/al.pas
src/lib/xmp/xmp.pas [new file with mode: 0644]

index d5701298b933e05c529d9ec15835d0b33f94cc66..dfe5919ea8ef52f374a57939a80ecc6ce474ca7e 100644 (file)
@@ -24,9 +24,7 @@ uses
 
 type
   TSoundRec = record
-    Fmt: TSoundFormat;
-    Data: Pointer;
-    DataLen: Integer;
+    Loader: TSoundLoader;
     alBuffer: ALuint;
     isMusic: Boolean;
     Loops: Boolean;
@@ -91,6 +89,7 @@ procedure e_ReleaseSoundSystem();
 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;
@@ -101,17 +100,24 @@ uses
   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
@@ -136,8 +142,8 @@ end;
 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
@@ -145,21 +151,21 @@ var
 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()]);
@@ -170,29 +176,43 @@ begin
 
   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;
 
@@ -203,7 +223,7 @@ var
 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;
@@ -267,8 +288,7 @@ begin
     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;
@@ -276,32 +296,41 @@ begin
 
   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;
@@ -340,32 +369,41 @@ begin
 
   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;
@@ -380,9 +418,11 @@ begin
   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;
@@ -409,12 +449,29 @@ 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;
@@ -480,11 +537,12 @@ begin
     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;
 
@@ -543,6 +601,7 @@ begin
       e_DeleteSound(i);
   SetLength(e_SoundsArray, 0);
   e_SoundsArray := nil;
+  CurStream := NO_SOUND_ID;
 end;
 
 procedure e_ReleaseSoundSystem();
@@ -557,11 +616,56 @@ begin
   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
@@ -570,6 +674,9 @@ begin
       alSources[S] := 0;
       alOwners[S] := nil;
     end;
+
+  // update the stream sources
+  UpdateStreamSource(MUSIC_SOURCE);
 end;
 
 { TBasicSound: }
@@ -646,6 +753,8 @@ end;
 
 procedure TBasicSound.Stop();
 begin
+  if FID = CurStream then
+    CurStream := NO_SOUND_ID;
   if InvalidSource() then
     Exit;
   GetPosition();
index df4aa45dfa2c04a01b4e8b4af2c601b7a032c748..919715c5dea3dc3da6a7a9242748b10e5af7f87a 100644 (file)
@@ -22,38 +22,49 @@ type
   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
@@ -61,27 +72,27 @@ 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)
@@ -22,20 +22,33 @@ uses e_soundfile;
 
 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
@@ -48,20 +61,27 @@ 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);
 
@@ -71,25 +91,26 @@ begin
   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');
 
@@ -102,26 +123,51 @@ begin
   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
new file mode 100644 (file)
index 0000000..aaf1df1
--- /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.
index f972e59a650dd8bee91d380ec6895f26a08f16da..c3c7c3e5a88767a87bcab210667c58ade6299a22 100644 (file)
@@ -61,8 +61,10 @@ uses
 {$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',
index 0d6afba2344b9aeca4fbfb0354cacccd91728d50..2ca6fc12aac8f3ea24c050ab7a54338d809590a5 100644 (file)
@@ -14,7 +14,9 @@ uses
   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
new file mode 100644 (file)
index 0000000..2f48d07
--- /dev/null
@@ -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.