DEADSOFTWARE

Sound: OpenAL: Add optional ModPlug loader
authorfgsfds <pvt.fgsfds@gmail.com>
Sun, 1 Sep 2019 00:00:27 +0000 (03:00 +0300)
committerfgsfds <pvt.fgsfds@gmail.com>
Sun, 1 Sep 2019 00:00:27 +0000 (03:00 +0300)
src/engine/e_soundfile_modplug.pas [new file with mode: 0644]
src/lib/modplug/modplug.pas [new file with mode: 0644]

diff --git a/src/engine/e_soundfile_modplug.pas b/src/engine/e_soundfile_modplug.pas
new file mode 100644 (file)
index 0000000..01fd455
--- /dev/null
@@ -0,0 +1,191 @@
+(* 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_modplug;
+
+interface
+
+uses e_soundfile, modplug;
+
+type
+  // a module loader that uses libxmp-lite
+
+  TModPlugLoader = 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
+    FFile: PModPlugFile;
+  end;
+
+  TModPlugLoaderFactory = 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, classes;
+
+var
+  Settings: ModPlug_Settings = (
+    mFlags            : MODPLUG_ENABLE_OVERSAMPLING or MODPLUG_ENABLE_NOISE_REDUCTION;
+    mChannels         : 2;
+    mBits             : 16;
+    mFrequency        : 44100;
+    mResamplingMode   : MODPLUG_RESAMPLE_LINEAR;
+    mStereoSeparation : 128;
+    mMaxMixChannels   : 32;
+    mReverbDepth      : 0;
+    mReverbDelay      : 0;
+    mBassAmount       : 0;
+    mBassRange        : 0;
+    mSurroundDepth    : 0;
+    mSurroundDelay    : 0;
+    mLoopCount        : -1;
+  );
+
+(* TModPlugLoaderFactory *)
+
+function TModPlugLoaderFactory.MatchHeader(Data: Pointer; Len: LongWord): Boolean;
+var
+  Mpf: PModPlugFile;
+begin
+  // HACK: there's no "test" function in modplug, so just try to load that shit
+  Result := False;
+
+  Mpf := ModPlug_Load(Data, Len);
+  if Mpf = nil then Exit;
+  ModPlug_Unload(Mpf);
+
+  Result := True;
+end;
+
+function TModPlugLoaderFactory.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 TModPlugLoaderFactory.GetLoader(): TSoundLoader;
+begin
+  ModPlug_SetSettings(@Settings); // update settings just in case
+  Result := TModPlugLoader.Create();
+end;
+
+(* TModPlugLoader *)
+
+function TModPlugLoader.Load(Data: Pointer; Len: LongWord; SStreaming: Boolean): Boolean;
+begin
+  Result := False;
+
+  FFile := ModPlug_Load(Data, Len);
+  if FFile = nil then
+  begin
+    e_LogWriteln('ModPlug: ERROR: ModPlug_Load failed');
+    Exit;
+  end;
+
+  FFormat.SampleRate := 44100;
+  FFormat.SampleBits := 16;
+  FFormat.Channels := 2;
+  FStreaming := True; // modules are always streaming
+
+  Result := True;
+end;
+
+function TModPlugLoader.Load(FName: string; SStreaming: Boolean): Boolean;
+var
+  S: TStream = nil;
+  Data: Pointer;
+  Len: LongInt;
+begin
+  Result := False;
+
+  try
+    S := openDiskFileRO(FName);
+    // ayy just read the entire file
+    Data := GetMem(S.Size);
+    if Data = nil then
+      raise Exception.Create('out of memory');
+    Len := S.Read(Data^, S.Size);
+    if Len < 0 then
+      raise Exception.Create('what the fuck');
+    Result := Load(Data, Len, SStreaming)
+  except
+    on E: Exception do
+      e_LogWritefln('ModPlug: ERROR: could not read file `%s`: %s', [FName, E.Message]);
+  end;
+
+  if Data <> nil then FreeMem(Data);
+  if S <> nil then S.Free();
+end;
+
+function TModPlugLoader.SetPosition(Pos: LongWord): Boolean;
+begin
+  Result := False;
+  if FFile = nil then Exit;
+  ModPlug_Seek(FFile, Pos);
+  Result := True;
+end;
+
+function TModPlugLoader.FillBuffer(Buf: Pointer; Len: LongWord): LongWord;
+var
+  Cnt: LongInt;
+begin
+  Result := 0;
+  if FFile = nil then Exit;
+
+  Cnt := ModPlug_Read(FFile, Buf, Len);
+  if Cnt < 0 then Exit;
+
+  if FLooping and (Cnt < Len) then
+  begin
+    // assume it just ended and restart, because modplug only loops if the
+    // module tells it to
+    ModPlug_Seek(FFile, 0);
+    // this used to be Result := Cnt + Read(FFile, Buf + Cnt, Len - Cnt)
+    // but the difference appears to be negligible
+    Result := ModPlug_Read(FFile, Buf, Len);
+  end
+  else
+    Result := Len;
+end;
+
+function TModPlugLoader.GetAll(var OutPtr: Pointer): LongWord;
+begin
+  Result := 0; // modules are always streaming, so this don't make sense
+end;
+
+procedure TModPlugLoader.Free();
+begin
+  if FFile <> nil then
+  begin
+    ModPlug_Unload(FFile);
+    FFile := nil;
+  end;
+end;
+
+initialization
+  e_AddSoundLoader(TModPlugLoaderFactory.Create());
+end.
diff --git a/src/lib/modplug/modplug.pas b/src/lib/modplug/modplug.pas
new file mode 100644 (file)
index 0000000..4472895
--- /dev/null
@@ -0,0 +1,134 @@
+{
+  Translation of the libmodplug headers for FreePascal
+  Copyright (C) 2006 by Ivo Steinmann
+}
+
+(*
+ * This source code is public domain.
+ *
+ * Authors: Kenton Varda <temporal@gauge3d.org> (C interface wrapper)
+ *)
+
+unit modplug;
+
+{$MODE OBJFPC}
+{$PACKRECORDS C}
+
+interface
+
+uses
+  ctypes;
+
+{$IFDEF WINDOWS}
+  {$IFNDEF LIBMODPLUG_PINDOZE_STATIC}
+    {$DEFINE MP_DYNAMIC}
+  {$ENDIF}
+{$ENDIF}
+
+{$IFDEF MP_DYNAMIC}
+const
+{$IF DEFINED(WINDOWS)}
+  modpluglib = 'libmodplug.dll';
+{$ELSEIF DEFINED(UNIX)}
+  modpluglib = 'libmodplug.so';
+{$ELSE}
+  {$MESSAGE ERROR 'MP_DYNAMIC not supported'}
+{$IFEND}
+{$ELSE}
+  {$LINKLIB stdc++} // is this necessary?
+  {$LINKLIB modplug}
+{$ENDIF}
+
+
+type
+  PModPlugFile = Pointer;
+  ModPlugFile = record
+  end;
+
+(* Load a mod file.  [data] should point to a block of memory containing the complete
+ * file, and [size] should be the size of that block.
+ * Return the loaded mod file on success, or NULL on failure. *)
+function ModPlug_Load(data: pointer; size: cint): PModPlugFile; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+(* Unload a mod file. *)
+procedure ModPlug_Unload(_file: PModPlugFile); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+(* Read sample data into the buffer.  Returns the number of bytes read.  If the end
+ * of the mod has been reached, zero is returned. *)
+function ModPlug_Read(_file: PModPlugFile; buffer: pointer; size: cint): cint; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+(* Get the name of the mod.  The returned buffer is stored within the ModPlugFile
+ * structure and will remain valid until you unload the file. *)
+function ModPlug_GetName(_file: PModPlugFile): pcchar; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+(* Get the length of the mod, in milliseconds.  Note that this result is not always
+ * accurate, especially in the case of mods with loops. *)
+function ModPlug_GetLength(_file: PModPlugFile): cint; cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+(* Seek to a particular position in the song.  Note that seeking and MODs don't mix very
+ * well.  Some mods will be missing instruments for a short time after a seek, as ModPlug
+ * does not scan the sequence backwards to find out which instruments were supposed to be
+ * playing at that time.  (Doing so would be difficult and not very reliable.)  Also,
+ * note that seeking is not very exact in some mods -- especially those for which
+ * ModPlug_GetLength() does not report the full length. *)
+procedure ModPlug_Seek(_file: PModPlugFile; millisecond: cint); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+
+const
+// _ModPlug_Flags
+  MODPLUG_ENABLE_OVERSAMPLING     = 1 shl 0;  (* Enable oversampling (highly recommended) *)
+  MODPLUG_ENABLE_NOISE_REDUCTION  = 1 shl 1;  (* Enable noise reduction *)
+  MODPLUG_ENABLE_REVERB           = 1 shl 2;  (* Enable reverb *)
+  MODPLUG_ENABLE_MEGABASS         = 1 shl 3;  (* Enable megabass *)
+  MODPLUG_ENABLE_SURROUND         = 1 shl 4;  (* Enable surround sound. *)
+
+// _ModPlug_ResamplingMode
+  MODPLUG_RESAMPLE_NEAREST = 0;  (* No interpolation (very fast, extremely bad sound quality) *)
+  MODPLUG_RESAMPLE_LINEAR  = 1;  (* Linear interpolation (fast, good quality) *)
+  MODPLUG_RESAMPLE_SPLINE  = 2;  (* Cubic spline interpolation (high quality) *)
+  MODPLUG_RESAMPLE_FIR     = 3;  (* 8-tap fir filter (extremely high quality) *)
+
+type
+  PModPlug_Settings = ^ModPlug_Settings;
+  ModPlug_Settings = record
+    mFlags            : cint;  (* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed *)
+    mChannels         : cint;  (* Number of channels - 1 for mono or 2 for stereo *)
+    mBits             : cint;  (* Bits per sample - 8, 16, or 32 *)
+    mFrequency        : cint;  (* Sampling rate - 11025, 22050, or 44100 *)
+    mResamplingMode   : cint;  (* One of MODPLUG_RESAMPLE_*, above *)
+    mStereoSeparation : cint;  (* 1-256 *)
+    mMaxMixChannels   : cint;  (* Maximum number of mixing channels, 32-256 *)
+    mReverbDepth      : cint;  (* Reverb level 0(quiet)-100(loud)      *)
+    mReverbDelay      : cint;  (* Reverb delay in ms, usually 40-200ms *)
+    mBassAmount       : cint;  (* XBass level 0(quiet)-100(loud)       *)
+    mBassRange        : cint;  (* XBass cutoff in Hz 10-100            *)
+    mSurroundDepth    : cint;  (* Surround level 0(quiet)-100(heavy)   *)
+    mSurroundDelay    : cint;  (* Surround delay in ms, usually 5-40ms *)
+    mLoopCount        : cint;  (* Number of times to loop.  Zero prevents looping. -1 loops forever. *)
+  end;
+
+(* Get and set the mod decoder settings.  All options, except for channels, bits-per-sample,
+ * sampling rate, and loop count, will take effect immediately.  Those options which don't
+ * take effect immediately will take effect the next time you load a mod. *)
+procedure ModPlug_GetSettings(settings: PModPlug_Settings); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+procedure ModPlug_SetSettings(const settings: PModPlug_Settings); cdecl; external {$IFDEF MP_DYNAMIC}modpluglib{$ENDIF};
+
+implementation
+
+// TODO: why the fuck does this exist here
+
+(*
+
+function cppNew(s: cint): pointer; cdecl; public; alias : '_Znaj'; alias : '_Znwj';
+begin
+  GetMem(Result, s);
+end;
+
+procedure cppDelete(p: pointer); cdecl; public; alias : '_ZdlPv'; alias : '_ZdaPv';
+begin
+  FreeMem(p);
+end;
+
+*)
+
+end.